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

feat: migrates notifications category to support in app messaging channel notifications #11170

Merged
merged 41 commits into from
Oct 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
b09ce19
feat: in app messaging initial commit
lazpavel Sep 22, 2022
4142ebf
feat: add in app messaging e2e test
lazpavel Sep 26, 2022
074cc98
Delete channel-SMS.ts
lazpavel Sep 26, 2022
35bdd26
Delete channel-FCM.ts
lazpavel Sep 26, 2022
adcf2c8
Delete channel-APNS.ts
lazpavel Sep 26, 2022
480d21e
Delete channel-Email.ts
lazpavel Sep 26, 2022
acb9876
feat: convert errors to amplify errors
lazpavel Sep 27, 2022
60f83ae
Merge branch 'dev' into in-app-messaging
lazpavel Sep 27, 2022
f291641
feat: address PR feedback
lazpavel Sep 29, 2022
633f4b1
Merge branch 'dev' into in-app-messaging
lazpavel Sep 29, 2022
675826b
Merge branch 'dev' into in-app-messaging
lazpavel Sep 29, 2022
44d6cd1
feat: address PR feedback
lazpavel Sep 29, 2022
8d59f51
feat: removing unused code
lazpavel Oct 3, 2022
6219ab9
feat: narrowing down the scope for lambda execution role policy
lazpavel Oct 3, 2022
645ca43
feat: remove arm build from tagged release
lazpavel Oct 4, 2022
758bead
In-app-messaging (#11103)
lazpavel Oct 4, 2022
5dc4646
Merge branch 'dev' into in-app-messaging
lazpavel Oct 4, 2022
b09659c
Merge branch 'dev' into in-app-messaging
Oct 4, 2022
1626e01
Merge branch 'tagged-release-without-e2e-tests/in-app-messaging' into…
lazpavel Oct 4, 2022
f07e5e1
fix: prevent adding in app messaging to projects with legacy analytics
Oct 5, 2022
6cf7029
fix: update error handling for notification channels commands
Oct 5, 2022
00185b4
Merge branch 'dev' into in-app-messaging
Oct 6, 2022
95c47e3
fix: test that pulled project has correct aws-exports content
Oct 6, 2022
a6b7800
fix: incorrect status for notifications after pull
Oct 7, 2022
a22d9ed
fix: fixed notifications unit test
Oct 7, 2022
5d8807c
chore: remove arm target from tagged releases
Oct 8, 2022
3f61866
Merge branch 'remove-arm-64-tagged-release' into in-app-messaging
Oct 8, 2022
99a2b16
chore: self-review PR updates
Oct 10, 2022
75e5ee8
Merge branch 'dev' into in-app-messaging
lazpavel Oct 10, 2022
922cfdf
feat: migrates analytics category to support in app messaging channel…
Oct 11, 2022
d6256e6
test: migrates analytics category to support in app messaging channel…
Oct 11, 2022
223d223
chore: merged dev
Oct 13, 2022
8475eaf
chore: PR feedback
Oct 13, 2022
8803dda
chore: PR feedback
Oct 14, 2022
9fb0c27
feat: minor bump no-op
Oct 14, 2022
cd1115c
feat: migrates notifications category to support in app messaging cha…
Oct 12, 2022
05b8f8b
chore: dev merge
Oct 14, 2022
d43d80a
chore: PR feedback
Oct 14, 2022
6677565
Update packages/amplify-category-analytics/src/migrations/in-app-mess…
lazpavel Oct 14, 2022
a1122af
Update pinpoint-helper.ts
lazpavel Oct 14, 2022
fcbfadc
chore: PR feedback
Oct 15, 2022
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
2 changes: 1 addition & 1 deletion packages/amplify-category-analytics/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export const getPermissionPolicies = async (context: $TSContext, resourceOpsMapp
*/
export const executeAmplifyCommand = async (context: $TSContext) : Promise<$TSAny> => {
context.exeInfo = context.amplify.getProjectDetails();
migrationCheck(context);
await migrationCheck(context);

let commandPath = path.normalize(path.join(__dirname, 'commands'));
commandPath = context.input.command === 'help'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,83 @@
import {
$TSAny,
$TSContext, AmplifyCategories, JSONUtilities, pathManager, stateManager,
$TSContext,
AmplifyCategories,
AmplifySupportedService,
JSONUtilities,
pathManager,
stateManager,
readCFNTemplate, writeCFNTemplate,
} from 'amplify-cli-core';
import fs from 'fs-extra';
import * as path from 'path';
import { pinpointHasInAppMessagingPolicy, pinpointInAppMessagingPolicyName } from '../utils/pinpoint-helper';
import { analyticsPush } from '../commands/analytics';
import { invokeAuthPush } from '../plugin-client-api-auth';
import { getAllDefaults } from '../provider-utils/awscloudformation/default-values/pinpoint-defaults';
import { getAnalyticsResources } from '../utils/analytics-helper';
import {
getNotificationsCategoryHasPinpointIfExists,
getPinpointRegionMappings,
pinpointHasInAppMessagingPolicy,
pinpointInAppMessagingPolicyName,
} from '../utils/pinpoint-helper';

/**
* checks if the project has been migrated to the latest version of in-app messaging
*/
export const inAppMessagingMigrationCheck = async (context: $TSContext): Promise<void> => {
if (['add', 'update', 'push'].includes(context.input.command) && !pinpointHasInAppMessagingPolicy(context)) {
const projectBackendDirPath = pathManager.getBackendDirPath();
const projectBackendDirPath = pathManager.getBackendDirPath();
const resources = getAnalyticsResources(context);

if (resources.length > 0 && !pinpointHasInAppMessagingPolicy(context)) {
lazpavel marked this conversation as resolved.
Show resolved Hide resolved
const amplifyMeta = stateManager.getMeta();
const analytics = amplifyMeta[AmplifyCategories.ANALYTICS] || {};
Object.keys(analytics).forEach(resourceName => {
const resourcePath = path.join(projectBackendDirPath, AmplifyCategories.ANALYTICS, resourceName);
const templateFilePath = path.join(resourcePath, 'pinpoint-cloudformation-template.json');
const analyticsResourcePath = path.join(projectBackendDirPath, AmplifyCategories.ANALYTICS, resourceName);
const templateFilePath = path.join(analyticsResourcePath, 'pinpoint-cloudformation-template.json');
const cfn = JSONUtilities.readJson(templateFilePath);
const updatedCfn = migratePinpointCFN(cfn);
fs.ensureDirSync(resourcePath);
fs.ensureDirSync(analyticsResourcePath);
JSONUtilities.writeJson(templateFilePath, updatedCfn);
});
}

const pinpointApp = getNotificationsCategoryHasPinpointIfExists();
if (resources.length === 0 && pinpointApp) {
const resourceParameters = getAllDefaults(context.amplify.getProjectDetails());
const notificationsInfo = {
appName: pinpointApp.appName,
resourceName: pinpointApp.appName,
};

Object.assign(resourceParameters, notificationsInfo);

const resource = resourceParameters.resourceName;
delete resourceParameters.resourceName;
const analyticsResourcePath = path.join(projectBackendDirPath, AmplifyCategories.ANALYTICS, resource);
stateManager.setResourceParametersJson(undefined, AmplifyCategories.ANALYTICS, resource, resourceParameters);

const templateFileName = 'pinpoint-cloudformation-template.json';
const templateFilePath = path.join(analyticsResourcePath, templateFileName);
if (!fs.existsSync(templateFilePath)) {
const templateSourceFilePath = path.join(__dirname, '..', 'provider-utils', 'awscloudformation', 'cloudformation-templates', templateFileName);
const { cfnTemplate } = readCFNTemplate(templateSourceFilePath);
cfnTemplate.Mappings = await getPinpointRegionMappings(context);
await writeCFNTemplate(cfnTemplate, templateFilePath);
}

const options = {
service: AmplifySupportedService.PINPOINT,
providerPlugin: 'awscloudformation',
};
context.amplify.updateamplifyMetaAfterResourceAdd(AmplifyCategories.ANALYTICS, resource, options);

context.parameters.options.yes = true;
context.exeInfo.inputParams = (context.exeInfo.inputParams) || {};
context.exeInfo.inputParams.yes = true;

await invokeAuthPush(context);
await analyticsPush(context);
}
};

const migratePinpointCFN = (cfn: $TSAny): $TSAny => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ export const analyticsMigrations = async (context: $TSContext): Promise<void> =>
* checks if the project has been migrated to the latest version
*/
export const migrationCheck = async (context: $TSContext): Promise<void> => {
await analyticsMigrations(context);
if (['add', 'update', 'push'].includes(context.input.command)) {
await analyticsMigrations(context);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ import os from 'os';
import {
$TSContext, ResourceAlreadyExistsError, exitOnNextTick, AmplifyCategories,
$TSAny,
JSONUtilities,
} from 'amplify-cli-core';
import { printer } from 'amplify-prompts';
import { getNotificationsCategoryHasPinpointIfExists, getPinpointRegionMappings } from '../../../utils/pinpoint-helper';

const providerName = 'awscloudformation';
// FIXME: may be removed from here, since addResource can pass category to addWalkthrough
const category = AmplifyCategories.ANALYTICS;
const parametersFileName = 'parameters.json';
Expand Down Expand Up @@ -63,7 +64,7 @@ const configure = (
Object.assign(defaultValues, parameters);
}

const pinpointApp = checkIfNotificationsCategoryExists(context);
const pinpointApp = getNotificationsCategoryHasPinpointIfExists();

if (pinpointApp) {
Object.assign(defaultValues, pinpointApp);
Expand Down Expand Up @@ -172,30 +173,11 @@ const configure = (
const resourceDirPath = path.join(projectBackendDirPath, category, resource);
delete defaultValues.resourceName;
writeParams(resourceDirPath, defaultValues);
writeCfnFile(context, resourceDirPath);
await writeCfnFile(context, resourceDirPath);
return resource;
});
};

const checkIfNotificationsCategoryExists = (context: $TSContext): $TSAny => {
lazpavel marked this conversation as resolved.
Show resolved Hide resolved
const { amplify } = context;
const { amplifyMeta } = amplify.getProjectDetails();
let pinpointApp: $TSAny;

if (amplifyMeta.notifications) {
const categoryResources = amplifyMeta.notifications;
Object.keys(categoryResources).forEach(resource => {
if (categoryResources[resource].service === serviceName && categoryResources[resource].output.Id) {
pinpointApp = {};
pinpointApp.appId = categoryResources[resource].output.Id;
pinpointApp.appName = resource;
}
});
}

return pinpointApp;
};

const resourceAlreadyExists = (context: $TSContext): string | undefined => {
const { amplify } = context;
const { amplifyMeta } = amplify.getProjectDetails();
Expand All @@ -213,33 +195,17 @@ const resourceAlreadyExists = (context: $TSContext): string | undefined => {
return resourceName;
};

const writeCfnFile = (context: $TSContext, resourceDirPath: string, force = false): void => {
const writeCfnFile = async (context: $TSContext, resourceDirPath: string, force = false): Promise<void> => {
fs.ensureDirSync(resourceDirPath);
const templateFilePath = path.join(resourceDirPath, templateFileName);
if (!fs.existsSync(templateFilePath) || force) {
const templateSourceFilePath = `${__dirname}/../cloudformation-templates/${templateFileName}`;
const templateSourceFilePath = path.join(__dirname, '..', 'cloudformation-templates', templateFileName);
const templateSource = context.amplify.readJsonFile(templateSourceFilePath);
templateSource.Mappings = getTemplateMappings(context);
const jsonString = JSON.stringify(templateSource, null, 4);
fs.writeFileSync(templateFilePath, jsonString, 'utf8');
templateSource.Mappings = await getPinpointRegionMappings(context);
JSONUtilities.writeJson(templateFilePath, templateSource);
}
};

const getTemplateMappings = (context:$TSContext):Record<string, $TSAny> => {
lazpavel marked this conversation as resolved.
Show resolved Hide resolved
const Mappings: Record<string, $TSAny> = {
RegionMapping: {},
};
const providerPlugins = context.amplify.getProviderPlugins(context);
const provider = require(providerPlugins[providerName]);
const regionMapping = provider.getPinpointRegionMapping();
Object.keys(regionMapping).forEach(region => {
Mappings.RegionMapping[region] = {
pinpointRegion: regionMapping[region],
};
});
return Mappings;
};

/**
* Save the params.json file for analytics category
* @param {*} resourceDirPath Path to params.json
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import {
AmplifySupportedService,
pathManager, readCFNTemplate,
open, $TSAny, $TSContext, $TSMeta, AmplifyCategories,
open, $TSAny, $TSContext, $TSMeta, AmplifyCategories, stateManager,
} from 'amplify-cli-core';
import { printer } from 'amplify-prompts';
import * as path from 'path';
import { getAnalyticsResources } from './analytics-helper';

/**
* Pinpoint app type definition
*/
export type PinpointApp = {
appId: string;
appName: string;
};

export const pinpointInAppMessagingPolicyName = 'pinpointInAppMessagingPolicyName';

/**
Expand Down Expand Up @@ -84,3 +92,42 @@ export const pinpointHasInAppMessagingPolicy = (context: $TSContext): boolean =>
const { cfnTemplate } = readCFNTemplate(pinpointCloudFormationTemplatePath, { throwIfNotExist: false }) || {};
return !!cfnTemplate?.Parameters?.[pinpointInAppMessagingPolicyName];
};

/**
* checks if notifications category has a pinpoint resource - legacy projects
*/
export const getNotificationsCategoryHasPinpointIfExists = (): PinpointApp | undefined => {
const amplifyMeta = stateManager.getMeta();
if (amplifyMeta.notifications) {
const categoryResources = amplifyMeta.notifications;
const pinpointServiceResource = Object.keys(categoryResources).find(
(resource: string) => categoryResources[resource].service === AmplifySupportedService.PINPOINT
&& categoryResources[resource].output.Id,
);

if (pinpointServiceResource) {
return {
appId: categoryResources[pinpointServiceResource].output.Id,
appName: pinpointServiceResource,
};
}
}

return undefined;
};

/**
* returns provider pinpoint region mapping
*/
export const getPinpointRegionMappings = async (context: $TSContext): Promise<Record<string, $TSAny>> => {
const Mappings: Record<string, $TSAny> = {
RegionMapping: {},
};
const regionMapping: $TSAny = await context.amplify.invokePluginMethod(context, 'awscloudformation', undefined, 'getPinpointRegionMapping', []);
Object.keys(regionMapping).forEach(region => {
Mappings.RegionMapping[region] = {
pinpointRegion: regionMapping[region],
};
});
return Mappings;
};
2 changes: 1 addition & 1 deletion packages/amplify-category-notifications/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export const migrate = async (context: $TSContext): Promise<void> => {
*/
export const executeAmplifyCommand = async (context: $TSContext): Promise<void> => {
context.exeInfo = context.amplify.getProjectDetails();
migrationCheck(context);
await migrationCheck(context);

let commandPath = path.normalize(path.join(__dirname, 'commands'));
commandPath = context.input.command === 'help' ? path.join(commandPath, category) : path.join(commandPath, category, context.input.command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ import { invokeAnalyticsMigrations } from '../plugin-client-api-analytics';
* checks if the project has been migrated to the latest version
*/
export const migrationCheck = async (context: $TSContext): Promise<void> => {
await invokeAnalyticsMigrations(context);
if (['add', 'update', 'push'].includes(context.input.command)) {
await invokeAnalyticsMigrations(context);
}
};
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,8 @@ export const getPinpointAppStatus = async (context: $TSContext, amplifyMeta: $TS
if (resources.length > 0) {
// eslint-disable-next-line prefer-destructuring
resultPinpointApp.app = resources[0];
resultPinpointApp.status = (resultPinpointApp.app.id) ? IPinpointDeploymentStatus.APP_IS_DEPLOYED
resultPinpointApp.status = resultPinpointApp.app.id
? IPinpointDeploymentStatus.APP_IS_DEPLOYED
: IPinpointDeploymentStatus.APP_IS_CREATED_NOT_DEPLOYED;
}
// Check if Notifications is using an App but different from Analytics - Legacy behavior
Expand Down
4 changes: 2 additions & 2 deletions packages/amplify-e2e-core/src/categories/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export type AddAuthIdentityPoolAndUserPoolWithOAuthSettings = AddAuthUserPoolOnl
idpAppleAppId: string;
};

export function addAuthWithDefault(cwd: string, settings: any = {}): Promise<void> {
export function addAuthWithDefault(cwd: string, settings: any = {}, testingWithLatestCodebase = false): Promise<void> {
return new Promise((resolve, reject) => {
spawn(getCLIPath(), ['add', 'auth'], { cwd, stripColors: true })
spawn(getCLIPath(testingWithLatestCodebase), ['add', 'auth'], { cwd, stripColors: true })
.wait('Do you want to use the default authentication')
.sendCarriageReturn()
.wait('How do you want users to be able to sign in')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {
addNotificationChannel,
amplifyPushAuth,
createNewProjectDir,
deleteProject,
deleteProjectDir,
} from '@aws-amplify/amplify-e2e-core';
import { initJSProjectWithProfile, versionCheck } from '../../../migration-helpers';
import { addLegacySmsNotificationChannel } from '../../../migration-helpers/notifications-helpers';
import { getShortId } from '../../../migration-helpers/utils';

describe('amplify add notifications', () => {
let projectRoot: string;
const migrateFromVersion = { v: '10.0.0' };
const migrateToVersion = { v: 'uninitialized' };

beforeEach(async () => {
projectRoot = await createNewProjectDir('notification-migration-2');
});

afterEach(async () => {
await deleteProject(projectRoot, undefined, true);
deleteProjectDir(projectRoot);
});

beforeAll(async () => {
await versionCheck(process.cwd(), false, migrateFromVersion);
await versionCheck(process.cwd(), true, migrateToVersion);
});

it('should add in app notifications if another notification channel added with an older version', async () => {
expect(migrateFromVersion.v).not.toEqual(migrateToVersion.v);
const settings = { resourceName: `notification${getShortId()}` };

await initJSProjectWithProfile(projectRoot, {}, false);
await addLegacySmsNotificationChannel(projectRoot, settings.resourceName);
await addNotificationChannel(projectRoot, settings, 'In-App Messaging', true, true, true);
await amplifyPushAuth(projectRoot, true);
});

it('should add in app notifications if another notification channel added and pushed with an older version', async () => {
expect(migrateFromVersion.v).not.toEqual(migrateToVersion.v);
const settings = { resourceName: `notification${getShortId()}` };

await initJSProjectWithProfile(projectRoot, {}, false);
await addLegacySmsNotificationChannel(projectRoot, settings.resourceName);
await amplifyPushAuth(projectRoot, false);
await addNotificationChannel(projectRoot, settings, 'In-App Messaging', true, true, true);
await amplifyPushAuth(projectRoot, true);
});
});
Loading