From e4d810c39cfb5b64af1d92a3221b2da6903d5078 Mon Sep 17 00:00:00 2001 From: Jacob Bare Date: Wed, 19 Jan 2022 12:38:30 -0600 Subject: [PATCH 1/7] Add identityx fragment types to client --- packages/marko-web-identity-x/api/fragment-types.json | 1 + packages/marko-web-identity-x/utils/create-client.js | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 packages/marko-web-identity-x/api/fragment-types.json diff --git a/packages/marko-web-identity-x/api/fragment-types.json b/packages/marko-web-identity-x/api/fragment-types.json new file mode 100644 index 000000000..89ff64791 --- /dev/null +++ b/packages/marko-web-identity-x/api/fragment-types.json @@ -0,0 +1 @@ +{"__schema":{"types":[{"kind":"INTERFACE","name":"FieldInterface","possibleTypes":[{"name":"SelectField"},{"name":"BooleanField"}]}]}} \ No newline at end of file diff --git a/packages/marko-web-identity-x/utils/create-client.js b/packages/marko-web-identity-x/utils/create-client.js index 8cb022cdd..40fcf801d 100644 --- a/packages/marko-web-identity-x/utils/create-client.js +++ b/packages/marko-web-identity-x/utils/create-client.js @@ -1,8 +1,9 @@ const fetch = require('node-fetch'); const { ApolloClient } = require('apollo-client'); -const { InMemoryCache } = require('apollo-cache-inmemory'); +const { InMemoryCache, IntrospectionFragmentMatcher } = require('apollo-cache-inmemory'); const { createHttpLink } = require('apollo-link-http'); const { setContext } = require('apollo-link-context'); +const introspectionQueryResultData = require('../api/fragment-types.json'); const rootConfig = { connectToDevTools: false, @@ -40,6 +41,8 @@ module.exports = ({ ...config, ...rootConfig, link: contextLink.concat(httpLink), - cache: new InMemoryCache(), + cache: new InMemoryCache({ + fragmentMatcher: new IntrospectionFragmentMatcher({ introspectionQueryResultData }), + }), }); }; From ee221a8825dfad0b29ec7ef4d652c7226ab97982 Mon Sep 17 00:00:00 2001 From: Jacob Bare Date: Wed, 19 Jan 2022 12:39:04 -0600 Subject: [PATCH 2/7] Ensure boolean values are handled as booleans And use `answer` field for value --- packages/marko-web-identity-x/api/fragments/active-user.js | 4 +++- .../browser/form/fields/custom-boolean.vue | 4 ++-- packages/marko-web-identity-x/browser/profile.vue | 6 +++--- packages/marko-web-identity-x/routes/profile.js | 4 +++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/marko-web-identity-x/api/fragments/active-user.js b/packages/marko-web-identity-x/api/fragments/active-user.js index 53f2a279c..50bbb9a10 100644 --- a/packages/marko-web-identity-x/api/fragments/active-user.js +++ b/packages/marko-web-identity-x/api/fragments/active-user.js @@ -27,6 +27,9 @@ fragment ActiveUserFragment on AppUser { sort: { field: name, order: asc } }) { id + hasAnswered + answer + value field { id label @@ -38,7 +41,6 @@ fragment ActiveUserFragment on AppUser { identifier { value type } } } - value } customSelectFieldAnswers(input: { onlyActive: true diff --git a/packages/marko-web-identity-x/browser/form/fields/custom-boolean.vue b/packages/marko-web-identity-x/browser/form/fields/custom-boolean.vue index 6acb3226d..867984bbb 100644 --- a/packages/marko-web-identity-x/browser/form/fields/custom-boolean.vue +++ b/packages/marko-web-identity-x/browser/form/fields/custom-boolean.vue @@ -47,10 +47,10 @@ export default { computed: { given: { get() { - return this.value; + return Boolean(this.value); }, set(given) { - this.$emit('input', given); + this.$emit('input', Boolean(given)); }, }, }, diff --git a/packages/marko-web-identity-x/browser/profile.vue b/packages/marko-web-identity-x/browser/profile.vue index 1f14e47a1..43238498a 100644 --- a/packages/marko-web-identity-x/browser/profile.vue +++ b/packages/marko-web-identity-x/browser/profile.vue @@ -83,7 +83,7 @@ :id="fieldAnswer.id" :message="fieldAnswer.field.label" :required="fieldAnswer.field.required" - :value="fieldAnswer.value" + :value="fieldAnswer.answer" @input="onCustomBooleanChange(fieldAnswer.id)" /> @@ -373,8 +373,8 @@ export default { onCustomBooleanChange(id) { const objIndex = this.customBooleanFieldAnswers.findIndex((obj => obj.id === id)); - const value = !this.customBooleanFieldAnswers[objIndex].value; - this.customBooleanFieldAnswers[objIndex].value = value; + const answer = !this.customBooleanFieldAnswers[objIndex].answer; + this.customBooleanFieldAnswers[objIndex].answer = answer; this.user.customBooleanFieldAnswers = this.customBooleanFieldAnswers; }, diff --git a/packages/marko-web-identity-x/routes/profile.js b/packages/marko-web-identity-x/routes/profile.js index b1c744b60..caa9267ba 100644 --- a/packages/marko-web-identity-x/routes/profile.js +++ b/packages/marko-web-identity-x/routes/profile.js @@ -74,7 +74,9 @@ module.exports = asyncRoute(async (req, res) => { // only update custom questions when there some :) const customBooleanFieldsInput = customBooleanFieldAnswers.map(fieldAnswer => ({ fieldId: fieldAnswer.field.id, - value: fieldAnswer.value, + // can either be true, false or null. convert null to false. + // the form submit is effectively answers the question. + value: Boolean(fieldAnswer.answer), })); await identityX.client.mutate({ mutation: customBooleanFieldsMutation, From 65aa493489033351e3074615a9e3e264d94d2188 Mon Sep 17 00:00:00 2001 From: Jacob Bare Date: Wed, 19 Jan 2022 12:39:27 -0600 Subject: [PATCH 3/7] Create `isDeploymentTypeId` external ID util --- .../external-id/is-deployment-type-id.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 packages/marko-web-omeda-identity-x/external-id/is-deployment-type-id.js diff --git a/packages/marko-web-omeda-identity-x/external-id/is-deployment-type-id.js b/packages/marko-web-omeda-identity-x/external-id/is-deployment-type-id.js new file mode 100644 index 000000000..4d7bf2d0c --- /dev/null +++ b/packages/marko-web-omeda-identity-x/external-id/is-deployment-type-id.js @@ -0,0 +1,8 @@ +const isOmedaNamespace = require('./is-omeda-namespace'); + +module.exports = ({ externalId, brandKey } = {}) => isOmedaNamespace({ + externalId, + brandKey, + type: 'deploymentType', + valueMatcher: id => parseInt(id, 10), +}); From 4ca9cb7eeb28d44b6fc004103304e09c95af4ef6 Mon Sep 17 00:00:00 2001 From: Jacob Bare Date: Wed, 19 Jan 2022 12:40:22 -0600 Subject: [PATCH 4/7] Prime idx user boolean fields with omeda demos --- .../integration-hooks/on-login-link-sent.js | 128 +++++++++++++----- 1 file changed, 95 insertions(+), 33 deletions(-) diff --git a/packages/marko-web-omeda-identity-x/integration-hooks/on-login-link-sent.js b/packages/marko-web-omeda-identity-x/integration-hooks/on-login-link-sent.js index 081de6709..108b06b27 100644 --- a/packages/marko-web-omeda-identity-x/integration-hooks/on-login-link-sent.js +++ b/packages/marko-web-omeda-identity-x/integration-hooks/on-login-link-sent.js @@ -1,5 +1,6 @@ const gql = require('graphql-tag'); const { get, getAsArray } = require('@parameter1/base-cms-object-path'); +const isOmedaDeploymentTypeId = require('../external-id/is-deployment-type-id'); const isOmedaDemographicId = require('../external-id/is-demographic-id'); const FIELD_QUERY = gql` @@ -9,6 +10,7 @@ const FIELD_QUERY = gql` node { id name + type active externalId { id @@ -22,6 +24,11 @@ const FIELD_QUERY = gql` externalIdentifier } } + + ... on BooleanField { + whenTrue { type value } + whenFalse { type value } + } } } } @@ -41,6 +48,9 @@ const CUSTOMER_QUERY = gql` demographic { id description } value { id description } } + primaryEmailAddress { + optInStatus { deploymentTypeId status { id } } + } } } `; @@ -51,8 +61,14 @@ const SET_OMEDA_DATA = gql` } `; -const SET_OMEDA_DEMOGRAPHIC_DATA = gql` - mutation SetOmedaDemographicData($input: UpdateAppUserCustomSelectAnswersMutationInput!) { +const SET_OMEDA_BOOLEAN_DEMOGRAPHICS = gql` + mutation SetOmedaDemographicBooleanData($input: UpdateAppUserCustomBooleanAnswersMutationInput!) { + updateAppUserCustomBooleanAnswers(input: $input) { id } + } +`; + +const SET_OMEDA_SELECT_DEMOGRAPHICS = gql` + mutation SetOmedaDemographicSelectData($input: UpdateAppUserCustomSelectAnswersMutationInput!) { updateAppUserCustomSelectAnswers(input: $input) { id } } `; @@ -98,31 +114,61 @@ const setOmedaDemographics = async ({ return map; }, new Map()); - const answerMap = new Map(); + const booleanAnswerMap = new Map(); + const selectAnswerMap = new Map(); omedaLinkedFields.forEach((field) => { if (answeredQuestionMap.has(field.id)) return; const { value: demoId } = field.externalId.identifier; const valueIdSet = omedaCustomerDemoValuesMap.get(demoId); if (!valueIdSet) return; - field.options.forEach((option) => { - const { externalIdentifier } = option; - if (!externalIdentifier || !valueIdSet.has(externalIdentifier)) return; - if (!answerMap.has(field.id)) answerMap.set(field.id, new Set()); - answerMap.get(field.id).add(option.id); - }); + + if (field.type === 'select') { + field.options.forEach((option) => { + const { externalIdentifier } = option; + if (!externalIdentifier || !valueIdSet.has(externalIdentifier)) return; + if (!selectAnswerMap.has(field.id)) selectAnswerMap.set(field.id, new Set()); + selectAnswerMap.get(field.id).add(option.id); + }); + } + + if (field.type === 'boolean') { + const { whenTrue, whenFalse } = field; + if (whenTrue.type === 'INTEGER' && valueIdSet.has(`${whenTrue.value}`)) { + booleanAnswerMap.set(field.id, true); + return; + } + if (whenFalse.type === 'INTEGER' && valueIdSet.has(`${whenFalse.value}`)) { + booleanAnswerMap.set(field.id, false); + } + } }); - if (answerMap.size) { - const answers = []; - answerMap.forEach((optionIdSet, fieldId) => { - answers.push({ fieldId, optionIds: [...optionIdSet] }); - }); - await identityX.client.mutate({ - mutation: SET_OMEDA_DEMOGRAPHIC_DATA, - variables: { input: { id: user.id, answers } }, - context: { apiToken: identityX.config.getApiToken() }, - }); - } + await Promise.all([ + (async () => { + if (!selectAnswerMap.size) return; + const answers = []; + selectAnswerMap.forEach((optionIdSet, fieldId) => { + answers.push({ fieldId, optionIds: [...optionIdSet] }); + }); + await identityX.client.mutate({ + mutation: SET_OMEDA_SELECT_DEMOGRAPHICS, + variables: { input: { id: user.id, answers } }, + context: { apiToken: identityX.config.getApiToken() }, + }); + })(), + (async () => { + if (!booleanAnswerMap.size) return; + const answers = []; + booleanAnswerMap.forEach((value, fieldId) => { + answers.push({ fieldId, value }); + }); + await identityX.client.mutate({ + mutation: SET_OMEDA_BOOLEAN_DEMOGRAPHICS, + variables: { input: { id: user.id, answers } }, + context: { apiToken: identityX.config.getApiToken() }, + }); + })(), + ]); }; module.exports = async ({ @@ -147,21 +193,37 @@ module.exports = async ({ }), ]); - const omedaLinkedFields = getAsArray(data, 'fields.edges') - .map(edge => edge.node) - .filter((field) => { - if (!field.active || !field.externalId) return false; - return isOmedaDemographicId({ externalId: field.externalId, brandKey }); - }); + const omedaLinkedFields = { + demographic: [], + deploymentType: [], + }; + getAsArray(data, 'fields.edges').forEach((edge) => { + const { node: field } = edge; + const { externalId } = field; + if (!field.active || !externalId) return; + if (isOmedaDemographicId({ externalId, brandKey })) { + omedaLinkedFields.demographic.push(field); + } + if (field.type === 'boolean' && isOmedaDeploymentTypeId({ externalId, brandKey })) { + omedaLinkedFields.deploymentType.push(field); + } + }); - const answeredQuestionMap = user.customSelectFieldAnswers.reduce((map, select) => { - if (!select.hasAnswered) return map; - map.set(select.field.id, true); - return map; - }, new Map()); + const answeredQuestionMap = new Map(); + user.customSelectFieldAnswers.forEach((select) => { + if (!select.hasAnswered) return; + answeredQuestionMap.set(select.field.id, true); + }); + user.customBooleanFieldAnswers.forEach((boolean) => { + if (!boolean.hasAnswered) return; + answeredQuestionMap.set(boolean.field.id, true); + }); const hasAnsweredAllOmedaQuestions = omedaLinkedFields - .every(field => answeredQuestionMap.has(field.id)); + .demographic.every(field => answeredQuestionMap.has(field.id)) + && omedaLinkedFields + .deploymentType.every(field => answeredQuestionMap.has(field.id)); + if (user.verified && user.hasAnsweredAllOmedaQuestions) { return; } @@ -175,7 +237,7 @@ module.exports = async ({ identityX, user, omedaCustomer, - omedaLinkedFields, + omedaLinkedFields: omedaLinkedFields.demographic, answeredQuestionMap, })); } From f6945326f935a026f2a0193c544a4d1442d24bdd Mon Sep 17 00:00:00 2001 From: Jacob Bare Date: Wed, 19 Jan 2022 13:18:37 -0600 Subject: [PATCH 5/7] Set deployment type questions on login --- .../integration-hooks/on-login-link-sent.js | 69 +++++++++++++++---- 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/packages/marko-web-omeda-identity-x/integration-hooks/on-login-link-sent.js b/packages/marko-web-omeda-identity-x/integration-hooks/on-login-link-sent.js index 108b06b27..66a9f622b 100644 --- a/packages/marko-web-omeda-identity-x/integration-hooks/on-login-link-sent.js +++ b/packages/marko-web-omeda-identity-x/integration-hooks/on-login-link-sent.js @@ -61,14 +61,14 @@ const SET_OMEDA_DATA = gql` } `; -const SET_OMEDA_BOOLEAN_DEMOGRAPHICS = gql` - mutation SetOmedaDemographicBooleanData($input: UpdateAppUserCustomBooleanAnswersMutationInput!) { +const SET_OMEDA_BOOLEAN_FIELD_ANSWERS = gql` + mutation SetOmedaBooleanFieldAnswers($input: UpdateAppUserCustomBooleanAnswersMutationInput!) { updateAppUserCustomBooleanAnswers(input: $input) { id } } `; -const SET_OMEDA_SELECT_DEMOGRAPHICS = gql` - mutation SetOmedaDemographicSelectData($input: UpdateAppUserCustomSelectAnswersMutationInput!) { +const SET_OMEDA_SELECT_FIELD_ANSWERS = gql` + mutation SetOmedaSelectFieldAnswers($input: UpdateAppUserCustomSelectAnswersMutationInput!) { updateAppUserCustomSelectAnswers(input: $input) { id } } `; @@ -151,7 +151,7 @@ const setOmedaDemographics = async ({ answers.push({ fieldId, optionIds: [...optionIdSet] }); }); await identityX.client.mutate({ - mutation: SET_OMEDA_SELECT_DEMOGRAPHICS, + mutation: SET_OMEDA_SELECT_FIELD_ANSWERS, variables: { input: { id: user.id, answers } }, context: { apiToken: identityX.config.getApiToken() }, }); @@ -163,7 +163,7 @@ const setOmedaDemographics = async ({ answers.push({ fieldId, value }); }); await identityX.client.mutate({ - mutation: SET_OMEDA_BOOLEAN_DEMOGRAPHICS, + mutation: SET_OMEDA_BOOLEAN_FIELD_ANSWERS, variables: { input: { id: user.id, answers } }, context: { apiToken: identityX.config.getApiToken() }, }); @@ -171,6 +171,40 @@ const setOmedaDemographics = async ({ ]); }; +const setOmedaDeploymentTypes = async ({ + identityX, + user, + omedaCustomer, + omedaLinkedFields, + answeredQuestionMap, +}) => { + const omedaDeploymentOptInMap = getAsArray(omedaCustomer, 'primaryEmailAddress.optInStatus').reduce((map, { deploymentTypeId, status }) => { + const optedIn = status.id === 'IN'; + map.set(`${deploymentTypeId}`, optedIn); + return map; + }, new Map()); + + const answerMap = new Map(); + omedaLinkedFields.forEach((field) => { + if (answeredQuestionMap.has(field.id)) return; + const { value: deploymentTypeId } = field.externalId.identifier; + const optedIn = omedaDeploymentOptInMap.get(deploymentTypeId); + if (optedIn == null) return; + answerMap.set(field.id, optedIn); + }); + if (!answerMap.size) return; + + const answers = []; + answerMap.forEach((value, fieldId) => { + answers.push({ fieldId, value }); + }); + await identityX.client.mutate({ + mutation: SET_OMEDA_BOOLEAN_FIELD_ANSWERS, + variables: { input: { id: user.id, answers } }, + context: { apiToken: identityX.config.getApiToken() }, + }); +}; + module.exports = async ({ brandKey, omedaGraphQLProp = '$omedaGraphQLClient', @@ -233,13 +267,22 @@ module.exports = async ({ const promises = []; if (!user.verified) promises.push(setOmedaData({ identityX, user, omedaCustomer })); if (!hasAnsweredAllOmedaQuestions) { - promises.push(setOmedaDemographics({ - identityX, - user, - omedaCustomer, - omedaLinkedFields: omedaLinkedFields.demographic, - answeredQuestionMap, - })); + promises.push((async () => { + await setOmedaDemographics({ + identityX, + user, + omedaCustomer, + omedaLinkedFields: omedaLinkedFields.demographic, + answeredQuestionMap, + }); + await setOmedaDeploymentTypes({ + identityX, + user, + omedaCustomer, + omedaLinkedFields: omedaLinkedFields.deploymentType, + answeredQuestionMap, + }); + })()); } await Promise.all(promises); }; From 77b8321517cc26ae0ffc4f654f7aaa92cb5e1fe1 Mon Sep 17 00:00:00 2001 From: Jacob Bare Date: Wed, 19 Jan 2022 14:00:51 -0600 Subject: [PATCH 6/7] Pass omeda client key --- docker-compose.yml | 2 ++ services/example-website/config/omeda.js | 1 + services/example-website/index.js | 1 + 3 files changed, 4 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index ca171deeb..2fb22bd57 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -342,6 +342,8 @@ services: IDENTITYX_APP_ID: ${EXAMPLE_IDENTITYX_APP_ID-60774dbe3dac5936323ae121} IDENTITYX_API_TOKEN: ${EXAMPLE_IDENTITYX_API_TOKEN-} + OMEDA_CLIENT_KEY: ${EXAMPLE_OMEDA_CLIENT_KEY-client_allu} + OMEDA_GRAPHQL_URI: ${EXAMPLE_OMEDA_GRAPHQL_URI-} OMEDA_BRAND_KEY: ${EXAMPLE_OMEDA_BRAND_KEY-allucd} OMEDA_APP_ID: ${EXAMPLE_OMEDA_APP_ID} OMEDA_INPUT_ID: ${EXAMPLE_OMEDA_INPUT_ID} diff --git a/services/example-website/config/omeda.js b/services/example-website/config/omeda.js index 77f2178af..a45b7d21d 100644 --- a/services/example-website/config/omeda.js +++ b/services/example-website/config/omeda.js @@ -1,5 +1,6 @@ module.exports = { brandKey: process.env.OMEDA_BRAND_KEY, + clientKey: process.env.OMEDA_CLIENT_KEY, appId: process.env.OMEDA_APP_ID, inputId: process.env.OMEDA_INPUT_ID, graphqlUri: 'https://graphql.omeda.parameter1.com/', diff --git a/services/example-website/index.js b/services/example-website/index.js index de70a15ff..ce859e355 100644 --- a/services/example-website/index.js +++ b/services/example-website/index.js @@ -23,6 +23,7 @@ module.exports = startServer({ const omedaConfig = getAsObject(siteConfig, 'omeda'); const idxConfig = getAsObject(siteConfig, 'identityX'); omedaIdentityX(app, { + clientKey: omedaConfig.clientKey, brandKey: omedaConfig.brandKey, appId: omedaConfig.appId, inputId: omedaConfig.inputId, From 8bd00d48e75582305b763f32126d5ebe421817ca Mon Sep 17 00:00:00 2001 From: Jacob Bare Date: Wed, 19 Jan 2022 14:01:25 -0600 Subject: [PATCH 7/7] Push boolean questions (deployment/demo) to Omeda --- .../rapid-identify.js | 20 +++++++++++++++++++ packages/marko-web-omeda/rapid-identify.js | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/packages/marko-web-omeda-identity-x/rapid-identify.js b/packages/marko-web-omeda-identity-x/rapid-identify.js index 72c7d6497..53b08e531 100644 --- a/packages/marko-web-omeda-identity-x/rapid-identify.js +++ b/packages/marko-web-omeda-identity-x/rapid-identify.js @@ -1,6 +1,7 @@ const gql = require('graphql-tag'); const { get, getAsArray } = require('@parameter1/base-cms-object-path'); const isOmedaDemographicId = require('./external-id/is-demographic-id'); +const isDeploymentTypeId = require('./external-id/is-deployment-type-id'); const ALPHA3_CODE = gql` query GetAlpha3Code($alpha2: String!) { @@ -67,6 +68,24 @@ module.exports = async ({ }; }); + const deploymentTypes = []; + getAsArray(appUser, 'customBooleanFieldAnswers').forEach((boolean) => { + const { field, hasAnswered } = boolean; + const { externalId } = field; + if (!field.active || !externalId || !hasAnswered) return; + + const { identifier } = field.externalId; + const id = parseInt(identifier.value, 10); + + if (isOmedaDemographicId({ externalId, brandKey })) { + demographics.push({ id, values: [`${boolean.value}`] }); + } + + if (isDeploymentTypeId({ externalId, brandKey })) { + deploymentTypes.push({ id, optedIn: boolean.answer }); + } + }); + const { id, encryptedCustomerId } = await omedaRapidIdentify({ email: appUser.email, productId, @@ -78,6 +97,7 @@ module.exports = async ({ ...(regionCode && { regionCode }), ...(postalCode && { postalCode }), ...(demographics.length && { demographics }), + ...(deploymentTypes.length && { deploymentTypes }), ...(promoCode && { promoCode }), }); diff --git a/packages/marko-web-omeda/rapid-identify.js b/packages/marko-web-omeda/rapid-identify.js index 994e6c330..38e278242 100644 --- a/packages/marko-web-omeda/rapid-identify.js +++ b/packages/marko-web-omeda/rapid-identify.js @@ -21,7 +21,10 @@ module.exports = async (omedaGraphQLClient, { countryCode, postalCode, + // deprecated, use `deploymentTypes` instead deploymentTypeIds, + + deploymentTypes, demographics, promoCode, @@ -39,6 +42,7 @@ module.exports = async (omedaGraphQLClient, { ...(postalCode && { postalCode }), ...(isArray(deploymentTypeIds) && deploymentTypeIds.length && { deploymentTypeIds }), + ...(isArray(deploymentTypes) && deploymentTypes.length && { deploymentTypes }), ...(isArray(demographics) && demographics.length && { demographics }), ...(promoCode && { promoCode }),