Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: attempt to mock SQS in hrmservice #654

Draft
wants to merge 60 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
7e0a00f
refactor: move contacts and cases types to HrmTypes module
GPaoloni May 3, 2024
0c62562
chore: license header
GPaoloni May 3, 2024
03d3a21
chore: moved hrm types to own package hrm-types
GPaoloni May 6, 2024
02e996f
chore: pull hrm-types from correct package
GPaoloni May 6, 2024
9e44933
chore: added dependencies to search-index-consumer
GPaoloni May 6, 2024
b3a140c
chore: move types around to ease lambda implementation
GPaoloni May 7, 2024
e3d37ca
chore: implement search-index-consumer indexing
GPaoloni May 7, 2024
bd80def
Merge remote-tracking branch 'origin/master' into gian_CHI-2717-2
GPaoloni May 7, 2024
d5ec170
chore: fix TS complaints
GPaoloni May 7, 2024
43456e2
fix: add emtpy parent if missing in contact document
GPaoloni May 8, 2024
83c1777
fix: wrong import
GPaoloni May 8, 2024
9417567
chore: added logic to update documents via elasticsearch client
GPaoloni May 10, 2024
2bc2d8e
chore: update hrm-search-config package to generate case update scrip…
GPaoloni May 10, 2024
340bdd2
chore: search-index-consumer rework
GPaoloni May 10, 2024
7cf998c
fix: small TS errors
GPaoloni May 10, 2024
f5b3bef
debug
GPaoloni May 10, 2024
c0cabb1
fix: contemplate case where case index payloads is undefined
GPaoloni May 10, 2024
8097a42
chore: fix unit tests
GPaoloni May 13, 2024
a10662d
debug logs
GPaoloni May 13, 2024
08e28c9
debug
GPaoloni May 13, 2024
c59db2a
chore: factor out convertToScriptUpdate logic
GPaoloni May 14, 2024
4101c0c
chore: refactor search-index-consumer to improve redability
GPaoloni May 14, 2024
7df50bd
chore: lint
GPaoloni May 14, 2024
06f0254
chore: removed comments
GPaoloni May 14, 2024
b208991
chore: moved channelTypes constants to hrm-types module
GPaoloni May 16, 2024
acb1245
chore fetch transcripts from S3
GPaoloni May 16, 2024
0982e55
debug
GPaoloni May 16, 2024
9ef239b
chore: add packages/s3-client to tsconfig.build
GPaoloni May 16, 2024
c0a62d1
chore: remove debug logs
GPaoloni May 17, 2024
8387804
chore: add support for parameterized ES config
GPaoloni May 21, 2024
d6e187b
chore: provide ssm parameter name from env vars to ES config
GPaoloni May 21, 2024
9a2703f
Merge remote-tracking branch 'origin/gian_CHI-2717-2' into gian_CHI-2…
GPaoloni May 21, 2024
7c51980
chore: rename env var for ES config
GPaoloni May 21, 2024
2178928
chore: added hrm-search-config package to hrm-core
GPaoloni May 22, 2024
0b8307e
chore: added wrapper to publish index-search messages to correspondin…
GPaoloni May 22, 2024
7913bbf
Merge remote-tracking branch 'origin/master' into gian_CHI-2723
GPaoloni May 22, 2024
0d4f1ec
chore: added logic to publish updates on contacts into corresponding …
GPaoloni May 23, 2024
b165e3f
chore: debug logs
GPaoloni May 23, 2024
5e4c468
chore: copy hrm packages at build time
GPaoloni May 23, 2024
029654c
chore: add message group id to sqs calls
GPaoloni May 27, 2024
7262aaf
chore: convertToIndexDocument only filters out undefined values
GPaoloni May 27, 2024
06bfd60
chore: trigger re-index on conversation media updates
GPaoloni May 27, 2024
f8b494f
chore: fix unit tests
GPaoloni May 29, 2024
de33345
chore: added unit tests
GPaoloni May 29, 2024
e655b60
chore: fix service tests
GPaoloni May 29, 2024
8ca4f42
chore: added logic to publish updates on cases into index-search queue
GPaoloni May 30, 2024
50d068e
chore: added logic to publish updates on case sections into index-sea…
GPaoloni May 30, 2024
beb8680
chore: fix unit tests
GPaoloni May 31, 2024
42536fb
Merge remote-tracking branch 'origin/gian_CHI-2724' into gian_CHI-2725
GPaoloni May 31, 2024
28a47dc
chore: naming nit
GPaoloni May 31, 2024
c7ba6c5
chore: PR feedback (naming nit)
GPaoloni May 31, 2024
b7ef6ee
chore: add support for deleteDocument OP in elasticsearch client package
GPaoloni May 31, 2024
f553d18
chore: add logic to handle remove case op in search-index-consumer
GPaoloni May 31, 2024
d0c1f4a
chore: trigger 'remove case from index' op when a case is deleted in HRM
GPaoloni May 31, 2024
51ffc2d
chore: handle errors, avoid publishing if there's no record
GPaoloni Jun 3, 2024
c228b35
chore: fix unit tests
GPaoloni Jun 4, 2024
9f33296
chore: contemplate possible undefined deleted case
GPaoloni Jun 4, 2024
ea0dcd4
chore: add FF to avoid introducing breaking code
GPaoloni Jun 4, 2024
2af5817
Merge remote-tracking branch 'origin/gian_CHI-2724' into gian_CHI-2725
GPaoloni Jun 4, 2024
0b90edc
chore: attempt to mock SQS in hrmservice
GPaoloni Jun 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions cdk/hrm-search-index-stack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/**
* Copyright (C) 2021-2023 Technology Matters
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/

import * as cdk from 'aws-cdk-lib';
import * as lambdaNode from 'aws-cdk-lib/aws-lambda-nodejs';
import { SqsEventSource } from 'aws-cdk-lib/aws-lambda-event-sources';
import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as s3 from 'aws-cdk-lib/aws-s3';

export default class ContactRetrieveStack extends cdk.Stack {
constructor({
scope,
id,
params = {
skipLambda: false,
},
props,
}: {
scope: cdk.Construct;
id: string;
params: {
completeQueue: sqs.Queue;
docsBucket: s3.Bucket;
skipLambda?: boolean;
};
props?: cdk.StackProps;
}) {
super(scope, id, props);

const queue = new cdk.aws_sqs.Queue(this, id, {
deadLetterQueue: { maxReceiveCount: 1, queue: params.completeQueue },
});

new cdk.CfnOutput(this, `queueUrl`, {
value: queue.queueUrl,
description: `The url of the ${id} queue`,
});

new cdk.aws_ssm.StringParameter(this, `queue-url-consumer`, {
parameterName: `/local/us-east-1/sqs/jobs/hrm-search-index/queue-url-consumer`,
stringValue: queue.queueUrl,
});

// duplicated for test env
new cdk.aws_ssm.StringParameter(this, `queue-url-consumer-test`, {
parameterName: `/local/us-east-1/sqs/jobs/hrm-search-index/queue-url-consumer-test`,
stringValue: queue.queueUrl,
});

if (params.skipLambda) return;

/*
Here, there be dragons.

To use the queue urls from inside of a lambda, we have to replace
'localhost' with 'localstack' so that container to container dns
lookups resolve correctly on the docker network.

This is WAY more complicated than it should be and took way too long
to figure out. But here goes:

CDK passes around tokens that are used inside of the final CloudFormation
template that is generated and deployed, not the actual string values for
things like queue urls that aren't known until the deployment is partially
complete.

Tokens are unresolvable until they are applied in the produced CF template.
So, you can't just do normal string replace operations. You have to use
native cloudformation functions to manipulate the string.

BUUUT. There is no "replace" function in cloudformation. So you have
to use split/join to do a janky replace.

Also... for some reason we can't use "Fn::split" inside of a "Fn::Join"
function directly. We have to use the select to iterate over items in the
split or else we just get a string of "Fn::split" as our url. I have no idea
why, but i discovered this working pattern by trial and error mixed with reviewing
generate cloudformation templates from the CDK that used the join/split replace
pattern.

This is pretty fragile since we can't arbitrarily split/join if there are
multiple instances of the needle in the haystack. But it works for this
simple case.
(rbd 08/10/22)
*/
const splitCompleteQueueUrl = cdk.Fn.split(
'localhost',
params.completeQueue.queueUrl,
);
const completedQueueUrl = cdk.Fn.join('localstack', [
cdk.Fn.select(0, splitCompleteQueueUrl),
cdk.Fn.select(1, splitCompleteQueueUrl),
]);

const fn = new lambdaNode.NodejsFunction(this, 'search-index-consumer', {
// TODO: change this back to 18 once it isn't broken upstream
runtime: cdk.aws_lambda.Runtime.NODEJS_16_X,
memorySize: 512,
timeout: cdk.Duration.seconds(10),
handler: 'handler',
entry: `./hrm-domain/lambdas/search-index-consumer/index.ts`,
environment: {
NODE_OPTIONS: '--enable-source-maps',
S3_ENDPOINT: 'http://localstack:4566',
S3_FORCE_PATH_STYLE: 'true',
S3_REGION: 'us-east-1',
SSM_ENDPOINT: 'http://localstack:4566',
SQS_ENDPOINT: 'http://localstack:4566',
NODE_ENV: 'local',
completed_sqs_queue_url: completedQueueUrl,
},
bundling: { sourceMap: true },
deadLetterQueueEnabled: true,
deadLetterQueue: params.completeQueue,
});

fn.addEventSource(
new SqsEventSource(queue, { batchSize: 10, reportBatchItemFailures: true }),
);

fn.addToRolePolicy(
new cdk.aws_iam.PolicyStatement({
actions: ['ssm:GetParametersByPath'],
resources: [`arn:aws:ssm:${this.region}:*:parameter/local/*`],
}),
);

fn.addToRolePolicy(
new cdk.aws_iam.PolicyStatement({
actions: [
's3:PutObject',
's3:PutObjectAcl',
's3:GetObject',
's3:GetObjectAcl',
's3:DeleteObject',
],
resources: [params.docsBucket.bucketArn],
}),
);
}
}
13 changes: 13 additions & 0 deletions cdk/init-stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import * as dotenv from 'dotenv';
import ContactCompleteStack from './contact-complete-stack';
import ContactCoreStack from './contact-core-stack';
import ContactRetrieveStack from './contact-retrieve-stack';
import HrmSearchIndexStack from './hrm-search-index-stack';
import HrmMicoservicesStack from './hrm-micoroservices-stack';
import LocalCoreStack from './local-core-stack';
import ResourcesCoreStack from './resources-core-stack';
Expand Down Expand Up @@ -100,6 +101,18 @@ async function main() {
},
});

new HrmSearchIndexStack({
scope: app,
id: 'hrm-search-index',
params: {
completeQueue: contactComplete.completeQueue,
docsBucket: localCore.docsBucket,
},
props: {
env: { region: app.node.tryGetContext('region') },
},
});

new ResourcesCoreStack({
scope: app,
id: 'resources-core',
Expand Down
7 changes: 4 additions & 3 deletions hrm-domain/hrm-core/case/caseDataAccess.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@ import { Contact } from '../contact/contactDataAccess';
import { DateFilter, OrderByDirectionType } from '../sql';
import { TKConditionsSets } from '../permissions/rulesMap';
import { TwilioUser } from '@tech-matters/twilio-worker-auth';
import { TwilioUserIdentifier } from '@tech-matters/types';
import { AccountSID, TwilioUserIdentifier } from '@tech-matters/types';
import {
PrecalculatedCasePermissionConditions,
CaseRecordCommon,
CaseService,
} from '@tech-matters/hrm-types';
import { CaseSectionRecord } from './caseSection/types';
import { pick } from 'lodash';
Expand Down Expand Up @@ -249,8 +250,8 @@ export const searchByProfileId = generalizedSearchQueryFunction<{
}),
);

export const deleteById = async (id, accountSid) => {
return db.oneOrNone(DELETE_BY_ID, [accountSid, id]);
export const deleteById = async (id: CaseService['id'], accountSid: AccountSID) => {
return db.oneOrNone<CaseRecord>(DELETE_BY_ID, [accountSid, id]);
};

export const updateStatus = async (
Expand Down
3 changes: 1 addition & 2 deletions hrm-domain/hrm-core/case/caseRoutesV0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/

import createError from 'http-errors';
import * as casesDb from './caseDataAccess';
import * as caseApi from './caseService';
import { publicEndpoint, SafeRouter } from '../permissions';
import {
Expand Down Expand Up @@ -93,7 +92,7 @@ casesRouter.expressRouter.use('/:caseId/sections', caseSectionRoutesV0);
casesRouter.delete('/:id', publicEndpoint, async (req, res) => {
const { hrmAccountId } = req;
const { id } = req.params;
const deleted = await casesDb.deleteById(id, hrmAccountId);
const deleted = await caseApi.deleteCaseById({ accountSid: hrmAccountId, caseId: id });
if (!deleted) {
throw createError(404);
}
Expand Down
48 changes: 31 additions & 17 deletions hrm-domain/hrm-core/case/caseSection/caseSectionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { TwilioUser } from '@tech-matters/twilio-worker-auth';
import { RulesFile, TKConditionsSets } from '../../permissions/rulesMap';
import { ListConfiguration } from '../caseDataAccess';
import { HrmAccountId } from '@tech-matters/types';
import { indexCaseInSearchIndex } from '../caseService';

const sectionRecordToSection = (
sectionRecord: CaseSectionRecord | undefined,
Expand Down Expand Up @@ -65,7 +66,13 @@ export const createCaseSection = async (
createdAt: nowISO,
accountSid,
};
return sectionRecordToSection(await create()(record));

const created = await create()(record);

// trigger index operation but don't await for it
indexCaseInSearchIndex({ accountSid, caseId: parseInt(caseId, 10) });

return sectionRecordToSection(created);
};

export const replaceCaseSection = async (
Expand All @@ -83,15 +90,19 @@ export const replaceCaseSection = async (
updatedBy: workerSid,
updatedAt: nowISO,
};
return sectionRecordToSection(
await updateById()(
accountSid,
Number.parseInt(caseId),
sectionType,
sectionId,
record,
),

const updated = await updateById()(
accountSid,
Number.parseInt(caseId),
sectionType,
sectionId,
record,
);

// trigger index operation but don't await for it
indexCaseInSearchIndex({ accountSid, caseId: parseInt(caseId, 10) });

return sectionRecordToSection(updated);
};

export const getCaseSection = async (
Expand Down Expand Up @@ -155,13 +166,16 @@ export const deleteCaseSection = async (
sectionId: string,
{ user }: { user: TwilioUser },
): Promise<CaseSection | undefined> => {
return sectionRecordToSection(
await deleteById()(
accountSid,
Number.parseInt(caseId),
sectionType,
sectionId,
user.workerSid,
),
const deleted = await deleteById()(
accountSid,
Number.parseInt(caseId),
sectionType,
sectionId,
user.workerSid,
);

// trigger index operation but don't await for it
indexCaseInSearchIndex({ accountSid, caseId: parseInt(caseId, 10) });

return sectionRecordToSection(deleted);
};
Loading
Loading