Skip to content

Commit

Permalink
feat: migrates analytics category to support in app messaging channel…
Browse files Browse the repository at this point in the history
… notifications (#11158)

* feat: migrates analytics category to support in app messaging channel notifications
  • Loading branch information
lazpavel committed Oct 14, 2022
1 parent 015187a commit 9dfbf6c
Show file tree
Hide file tree
Showing 34 changed files with 403 additions and 151 deletions.
1 change: 1 addition & 0 deletions packages/amplify-category-analytics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"watch": "tsc --watch"
},
"dependencies": {
"@aws-amplify/amplify-environment-parameters": "1.1.3",
"amplify-cli-core": "3.2.2",
"amplify-prompts": "2.6.0",
"fs-extra": "^8.1.0",
Expand Down
54 changes: 29 additions & 25 deletions packages/amplify-category-analytics/src/analytics-resource-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,19 @@ import { addResource } from './provider-utils/awscloudformation/index';
import { analyticsPush } from './commands/analytics';
import { invokeAuthPush } from './plugin-client-api-auth';
import { invokeNotificationsAPIGetAvailableChannelNames } from './plugin-client-api-notifications';
import { pinpointHasInAppMessagingPolicy } from './utils/pinpoint-helper';
import { getAnalyticsResources } from './utils/analytics-helper';
import { analyticsMigrations } from './migrations';

/**
* Get all analytics resources. If resourceProviderService name is provided,
* then only return resources matching the service.
* @returns Array of resources in Analytics category (IAmplifyResource type)
*/
export const analyticsPluginAPIGetResources = (resourceProviderServiceName?: string, context?: $TSContext): Array<IAnalyticsResource> => {
const resourceList: Array<IAnalyticsResource> = [];
const amplifyMeta = (context) ? context.exeInfo.amplifyMeta : stateManager.getMeta();
if (amplifyMeta?.[AmplifyCategories.ANALYTICS]) {
const categoryResources = amplifyMeta[AmplifyCategories.ANALYTICS];
Object.keys(categoryResources).forEach(resource => {
// if resourceProviderService is provided, then only return resources provided by that service
// else return all resources. e.g. Pinpoint, Kinesis
if (!resourceProviderServiceName || categoryResources[resource].service === resourceProviderServiceName) {
resourceList.push({
category: AmplifyCategories.ANALYTICS,
resourceName: resource,
service: categoryResources[resource].service,
region: categoryResources[resource]?.output?.Region,
id: categoryResources[resource]?.output?.Id,
output: categoryResources[resource]?.output,
});
}
});
}
return resourceList;
};
export const analyticsPluginAPIGetResources = (
resourceProviderServiceName?: string,
context?: $TSContext,
): Array<IAnalyticsResource> => getAnalyticsResources(context, resourceProviderServiceName);

/**
* Create an Analytics resource of the given provider type. e.g Pinpoint or Kinesis
Expand Down Expand Up @@ -129,8 +114,10 @@ export const analyticsPluginAPIToggleNotificationChannel = async (
* @param resourceProviderServiceName - Pinpoint or Kinesis
* @returns analytics push status
*/
export const analyticsPluginAPIPush = async (context: $TSContext, resourceProviderServiceName: string)
: Promise<IPluginCapabilityAPIResponse> => {
export const analyticsPluginAPIPush = async (
context: $TSContext,
resourceProviderServiceName: string,
): Promise<IPluginCapabilityAPIResponse> => {
const pushResponse: IPluginCapabilityAPIResponse = {
pluginName: AmplifyCategories.ANALYTICS,
resourceProviderServiceName,
Expand Down Expand Up @@ -273,7 +260,10 @@ const pinpointAPIEnableNotificationChannel = (
break;
}
default: {
throw Error(`Channel ${notificationChannel} is not supported on Analytics resource`);
throw new AmplifyError('ConfigurationError', {
message: `Channel ${notificationChannel} is not supported on Analytics resource`,
resolution: 'Use one of the supported channels',
});
}
}
return pinPointCFNInputParams;
Expand Down Expand Up @@ -302,3 +292,17 @@ const pinpointAPIDisableNotificationChannel = (
}
return pinPointCFNInputParams;
};

/**
* Checks if analytics pinpoint resource has in-app messaging policy
*/
export const analyticsPluginAPIPinpointHasInAppMessagingPolicy = async (
context: $TSContext,
): Promise<boolean> => pinpointHasInAppMessagingPolicy(context);

/**
* Exposes the analytics migration API
*/
export const analyticsPluginAPIMigrations = (
context: $TSContext,
): Promise<void> => analyticsMigrations(context);
35 changes: 12 additions & 23 deletions packages/amplify-category-analytics/src/commands/analytics.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,44 @@
/* eslint-disable @typescript-eslint/no-var-requires */
/* eslint-disable global-require */
/* eslint-disable import/no-dynamic-require */
import { $TSAny, $TSContext } from 'amplify-cli-core';
import { printer } from 'amplify-prompts';

const featureName = 'analytics';
export { run as analyticsPush } from './analytics/push';
export const name = 'analytics';

/**
* Analytics category command router. Invokes functionality for all CLI calls
* @param context amplify cli context
*/
const analyticsRun = async (context:$TSContext): Promise<$TSAny> => {
export const run = async (context: $TSContext): Promise<$TSAny> => {
if (/^win/.test(process.platform)) {
try {
const { run } = require(`./${featureName}/${context.parameters.first}`);
return run(context);
} catch (e) {
printer.error('Command not found');
}
const { run: runCommand } = await import(`./${name}/${context.parameters.first}`);
return runCommand(context);
}
const header = `amplify ${featureName} <subcommand>`;
const header = `amplify ${name} <subcommand>`;

const commands = [
{
name: 'add',
description: `Takes you through a CLI flow to add an ${featureName} resource to your local backend`,
description: `Takes you through a CLI flow to add an ${name} resource to your local backend`,
},
{
name: 'update',
description: `Takes you through steps in the CLI to update an ${featureName} resource`,
description: `Takes you through steps in the CLI to update an ${name} resource`,
},
{
name: 'push',
description: `Provisions only ${featureName} cloud resources with the latest local developments`,
description: `Provisions only ${name} cloud resources with the latest local developments`,
},
{
name: 'remove',
description: `Removes ${featureName} resource from your local backend. The resource is removed from the cloud on the next push command.`,
description: `Removes ${name} resource from your local backend. The resource is removed from the cloud on the next push command.`,
},
{
name: 'console',
description: `Opens the web console for the ${featureName} category`,
description: `Opens the web console for the ${name} category`,
},
];

context.amplify.showHelp(header, commands);

printer.info('');
printer.blankLine();
return context;
};

export { run as analyticsPush } from './analytics/push';
export const name = featureName;
export const run = analyticsRun;
15 changes: 12 additions & 3 deletions packages/amplify-category-analytics/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { $TSContext, $TSAny } from 'amplify-cli-core';
import { $TSContext, $TSAny, amplifyFaultWithTroubleshootingLink } from 'amplify-cli-core';
import * as path from 'path';
import inquirer, { QuestionCollection } from 'inquirer';
import { printer } from 'amplify-prompts';
import * as pinpointHelper from './utils/pinpoint-helper';
import * as kinesisHelper from './utils/kinesis-helper';
import { migrationCheck } from './migrations';

export { migrate } from './provider-utils/awscloudformation/service-walkthroughs/pinpoint-walkthrough';

export {
analyticsPluginAPIGetResources,
analyticsPluginAPICreateResource,
analyticsPluginAPIToggleNotificationChannel,
analyticsPluginAPIPinpointHasInAppMessagingPolicy,
analyticsPluginAPIMigrations,
analyticsPluginAPIPostPush,
analyticsPluginAPIPush,
} from './analytics-resource-api';
Expand Down Expand Up @@ -85,8 +88,11 @@ export const getPermissionPolicies = async (context: $TSContext, resourceOpsMapp
printer.error(`Provider not configured for ${category}: ${resourceName}`);
}
} catch (e) {
printer.warn(`Could not get policies for ${category}: ${resourceName}`);
throw e;
throw amplifyFaultWithTroubleshootingLink('AnalyticsCategoryFault', {
message: `Could not get policies for ${category}: ${resourceName}`,
stack: e.stack,
details: e.message,
});
}
}
return { permissionPolicies, resourceAttributes };
Expand All @@ -97,6 +103,9 @@ export const getPermissionPolicies = async (context: $TSContext, resourceOpsMapp
* @param context - Amplify CLI context
*/
export const executeAmplifyCommand = async (context: $TSContext) : Promise<$TSAny> => {
context.exeInfo = context.amplify.getProjectDetails();
migrationCheck(context);

let commandPath = path.normalize(path.join(__dirname, 'commands'));
commandPath = context.input.command === 'help'
? path.join(commandPath, category)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import {
$TSAny,
$TSContext, AmplifyCategories, JSONUtilities, pathManager, stateManager,
} from 'amplify-cli-core';
import fs from 'fs-extra';
import * as path from 'path';
import { 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 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 cfn = JSONUtilities.readJson(templateFilePath);
const updatedCfn = migratePinpointCFN(cfn);
fs.ensureDirSync(resourcePath);
JSONUtilities.writeJson(templateFilePath, updatedCfn);
});
}
};

const migratePinpointCFN = (cfn: $TSAny): $TSAny => {
const { Parameters, Conditions, Resources } = cfn;

Parameters[pinpointInAppMessagingPolicyName] = {
Type: 'String',
Default: 'NONE',
};

Conditions.ShouldEnablePinpointInAppMessaging = {
'Fn::Not': [
{
'Fn::Equals': [
{
Ref: 'pinpointInAppMessagingPolicyName',
},
'NONE',
],
},
],
};

Resources.PinpointInAppMessagingPolicy = {
Condition: 'ShouldEnablePinpointInAppMessaging',
Type: 'AWS::IAM::Policy',
Properties: {
PolicyName: {
Ref: 'pinpointInAppMessagingPolicyName',
},
Roles: [
{
Ref: 'unauthRoleName',
},
{
Ref: 'authRoleName',
},
],
PolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: [
'mobiletargeting:GetInAppMessages',
],
Resource: [
{
'Fn::Join': [
'',
[
'arn:aws:mobiletargeting:',
{
'Fn::FindInMap': [
'RegionMapping',
{
Ref: 'AWS::Region',
},
'pinpointRegion',
],
},
':',
{
Ref: 'AWS::AccountId',
},
':apps/',
{
'Fn::GetAtt': [
'PinpointFunctionOutputs',
'Id',
],
},
'*',
],
],
},
],
},
],
},
},
};

return cfn;
};
17 changes: 17 additions & 0 deletions packages/amplify-category-analytics/src/migrations/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { $TSContext } from 'amplify-cli-core';
import { inAppMessagingMigrationCheck } from './in-app-messaging-migration';

/**
* Analytics plugin migrations
* @param context amplify CLI context
*/
export const analyticsMigrations = async (context: $TSContext): Promise<void> => {
await inAppMessagingMigrationCheck(context);
};

/**
* checks if the project has been migrated to the latest version
*/
export const migrationCheck = async (context: $TSContext): Promise<void> => {
await analyticsMigrations(context);
};
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ const migrateCFN = (cfn: $TSAny): $TSAny => {
],
};
Resources.PinpointFunctionOutputs.Properties.appName = newAppName;

// replace all IAMPrefix refs
replaceRef(Resources, 'IAMPrefix', {
'Fn::Select': ['4', { 'Fn::Split': [':', { Ref: 'authRoleArn' }] }],
Expand Down
31 changes: 31 additions & 0 deletions packages/amplify-category-analytics/src/utils/analytics-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
$TSContext, AmplifyCategories, IAnalyticsResource, stateManager,
} from 'amplify-cli-core';

/**
* Get all analytics resources. If resourceProviderService name is provided,
* then only return resources matching the service.
* @returns Array of resources in Analytics category (IAmplifyResource type)
*/
export const getAnalyticsResources = (context?: $TSContext, resourceProviderServiceName?: string): IAnalyticsResource[] => {
const resourceList: Array<IAnalyticsResource> = [];
const amplifyMeta = (context) ? context.exeInfo.amplifyMeta : stateManager.getMeta();
if (amplifyMeta?.[AmplifyCategories.ANALYTICS]) {
const categoryResources = amplifyMeta[AmplifyCategories.ANALYTICS];
Object.keys(categoryResources).forEach(resource => {
// if resourceProviderService is provided, then only return resources provided by that service
// else return all resources. e.g. Pinpoint, Kinesis
if (!resourceProviderServiceName || categoryResources[resource].service === resourceProviderServiceName) {
resourceList.push({
category: AmplifyCategories.ANALYTICS,
resourceName: resource,
service: categoryResources[resource].service,
region: categoryResources[resource]?.output?.Region,
id: categoryResources[resource]?.output?.Id,
output: categoryResources[resource]?.output,
});
}
});
}
return resourceList;
};

0 comments on commit 9dfbf6c

Please sign in to comment.