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

[Client Profiles] HRM Migrations and save automation #469

Closed
wants to merge 22 commits into from

Conversation

GPaoloni
Copy link
Collaborator

@GPaoloni GPaoloni commented Oct 2, 2023

Description

This PR:

  • Creates the tables needed for Client Profiles feature, and populates them accordingly.
  • Adds logic to contact creation to relate client profiles:
    • Create a new profile and identifier if there is no existing record for a given number.
    • Reuse the existing ones if the number is already saved.

The tables structure is as in the following diagram.
Screenshot from 2023-10-05 13-09-29

For further details is refer to the below linked ticket.

Checklist

Verification steps

  • Start hrm server from master branch, and save a few "live" contacts and a few "offline contacts".
  • Stop server and start again in this branch.
  • Confirm that the migration is successful:
    • Tables are created.
    • Identifiers and Profiles tables are populated with the present numbers in the Contacts table.
    • Contacts table is updated: profileId and identifierId are populated ONLY for those records with number (no offline contacts).
  • Save new contacts and confirm the existing identifiers are re-used.
  • Use a new identifier (or delete one of the existing ones) and save a new contact, confirm that the new profile and identifier are saved before saving the contact.

@GPaoloni GPaoloni marked this pull request as draft October 4, 2023 14:16
@GPaoloni GPaoloni marked this pull request as ready for review October 5, 2023 16:10
@GPaoloni GPaoloni changed the title Gian chi 1908 [Client Profiles] HRM Migrations and save automation Oct 5, 2023
@stephenhand
Copy link
Collaborator

Is the intention for ProfileFlags to always be a predefined list managed from the HRM codebase, or is it the intention to have user definable flags in the future?

I ask because if a lookup table is a predefined set of values that only changes if the application is updated, I prefer to key them on a human readable string 'code' rather than an integer or UUID, it can save lots of time debugging in the future!

Copy link
Collaborator

@stephenhand stephenhand left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, couple of Qs - thanks for the comprehensive service tests!

console.log("Table 'Identifiers' ownership altered.");
await queryInterface.sequelize.query(`
CREATE INDEX IF NOT EXISTS "Identifiers_identifier_accountSid" ON public."Identifiers" USING btree
("identifier" ASC NULLS LAST, "accountSid" COLLATE pg_catalog."default" ASC NULLS LAST)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want this table to permit duplicate identifiers for the same account or should we add a unique constraint to this index?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely not. good catch! @GPaoloni I think we are replacing this PR/branch with #481 at this point. Could you fix the unique constraint there, please?

accountSid: string,
): Promise<TResult<{ identifier: Identifier; profile: Profile }>> => {
try {
if (!idx) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we care about the get / create logic being atomic? At the moment, with no unique constraint being enforced there's a small chance we could duplicate identifiers here

@@ -267,6 +268,18 @@ export const createContact = async (
conversationMediaPayload,
} = getNewContactPayload(newContact);

const profileResult = await getOrCreateProfileWithIdentifier(conn)(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely fine for now but I wonder in the future if profile creation should be tied to contact creation? We've separated transcript creation now, and in the future profiles might be auto-generated on the back end?

There is also the case of offline contacts, presumably those will have their identifier added manually by the counsellor after they are created, so will need to be linked up after the fact.

I know this is all out of scope for this PR and this totally makes sense for now, just thinking aloud

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The initial work is basically to get to feature parity with the current "previous contacts" banner, but using robust backend systems that will allow for expansion. Profile creation will definitely not be tied one to one like this with contact creation in the future... exactly what it will look like is a bit up in the air still. ;)

FOR identifier IN (SELECT * FROM "Identifiers") LOOP
-- Create a "blank" Profile
INSERT INTO "Profiles"("name", "accountSid", "createdAt", "updatedAt")
VALUES (NULL, identifier."accountSid", current_timestamp, current_timestamp)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would some sort of name not be preferable if only to make debugging easier? Or does your logic require these autogenerated profiles to have a null name?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We haven't decided on how to populate the name field yet. It is null temporarily until those choices are made. We know it will be needed, but for now there are still discussions around what to populate it with.

DO $$
DECLARE tmp RECORD;
BEGIN
FOR tmp IN (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The FROM clause in the UPDATE statement can be used to update tables with data from other tables (or the same table with a self join): https://www.postgresql.org/docs/current/sql-update.html

In this case, instead of using a loop you could do this update in a single query, something like:

          UPDATE "Contacts" c SET "profileId" = tmp."profileId", "identifierId" = tmp."identifierId"
          FROM "ProfilesToIdentifiers" p2i
          LEFT JOIN "Identifiers" ids ON p2i."identifierId" = ids.id
          WHERE c."number" = ids."identifier" AND c."accountSid" = ids."accountSid";

Obvs loops are fine for migrations, but it's good practice to try to avoid them anyway, so you know how to tackle similar issues in places where you should be avoiding SQL loops if at all possible (i.e. production code).

Don't change this code if you have it working, just FYI for the future

@GPaoloni
Copy link
Collaborator Author

Closed in favor of #471 and #481. All changes are contained there, plus the feedback left in the review here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants