2023-09-29 17:11:59 +02:00
|
|
|
import Mustache from 'mustache';
|
2021-10-28 14:09:11 +02:00
|
|
|
import {
|
2023-09-29 17:11:59 +02:00
|
|
|
ADDON_CONFIG_CREATED,
|
|
|
|
ADDON_CONFIG_DELETED,
|
|
|
|
ADDON_CONFIG_UPDATED,
|
|
|
|
API_TOKEN_CREATED,
|
|
|
|
API_TOKEN_DELETED,
|
|
|
|
CHANGE_ADDED,
|
|
|
|
CHANGE_DISCARDED,
|
|
|
|
CHANGE_EDITED,
|
|
|
|
CHANGE_REQUEST_APPLIED,
|
|
|
|
CHANGE_REQUEST_APPROVAL_ADDED,
|
|
|
|
CHANGE_REQUEST_APPROVED,
|
|
|
|
CHANGE_REQUEST_CANCELLED,
|
|
|
|
CHANGE_REQUEST_CREATED,
|
|
|
|
CHANGE_REQUEST_DISCARDED,
|
|
|
|
CHANGE_REQUEST_REJECTED,
|
|
|
|
CHANGE_REQUEST_SENT_TO_REVIEW,
|
|
|
|
CONTEXT_FIELD_CREATED,
|
|
|
|
CONTEXT_FIELD_DELETED,
|
|
|
|
CONTEXT_FIELD_UPDATED,
|
2021-10-28 14:09:11 +02:00
|
|
|
FEATURE_ARCHIVED,
|
2023-02-27 10:56:56 +01:00
|
|
|
FEATURE_CREATED,
|
2023-09-29 17:11:59 +02:00
|
|
|
FEATURE_DELETED,
|
2023-02-27 10:56:56 +01:00
|
|
|
FEATURE_ENVIRONMENT_DISABLED,
|
2021-10-28 14:09:11 +02:00
|
|
|
FEATURE_ENVIRONMENT_ENABLED,
|
2023-09-29 17:11:59 +02:00
|
|
|
FEATURE_ENVIRONMENT_VARIANTS_UPDATED,
|
2023-02-27 10:56:56 +01:00
|
|
|
FEATURE_METADATA_UPDATED,
|
2023-07-19 15:52:19 +02:00
|
|
|
FEATURE_POTENTIALLY_STALE_ON,
|
2023-02-27 10:56:56 +01:00
|
|
|
FEATURE_PROJECT_CHANGE,
|
2021-10-28 14:09:11 +02:00
|
|
|
FEATURE_REVIVED,
|
|
|
|
FEATURE_STALE_OFF,
|
2023-02-27 10:56:56 +01:00
|
|
|
FEATURE_STALE_ON,
|
|
|
|
FEATURE_STRATEGY_ADD,
|
2021-10-28 14:09:11 +02:00
|
|
|
FEATURE_STRATEGY_REMOVE,
|
2023-02-27 10:56:56 +01:00
|
|
|
FEATURE_STRATEGY_UPDATE,
|
2023-09-29 17:11:59 +02:00
|
|
|
FEATURE_TAGGED,
|
|
|
|
FEATURE_UNTAGGED,
|
|
|
|
GROUP_CREATED,
|
|
|
|
GROUP_DELETED,
|
|
|
|
GROUP_UPDATED,
|
2023-02-27 10:56:56 +01:00
|
|
|
IConstraint,
|
|
|
|
IEvent,
|
2023-10-16 15:30:40 +02:00
|
|
|
MESSAGE_BANNER_CREATED,
|
|
|
|
MESSAGE_BANNER_DELETED,
|
|
|
|
MESSAGE_BANNER_UPDATED,
|
2023-09-29 17:11:59 +02:00
|
|
|
PROJECT_CREATED,
|
|
|
|
PROJECT_DELETED,
|
|
|
|
SEGMENT_CREATED,
|
|
|
|
SEGMENT_DELETED,
|
|
|
|
SEGMENT_UPDATED,
|
|
|
|
SERVICE_ACCOUNT_CREATED,
|
|
|
|
SERVICE_ACCOUNT_DELETED,
|
|
|
|
SERVICE_ACCOUNT_UPDATED,
|
|
|
|
USER_CREATED,
|
|
|
|
USER_DELETED,
|
|
|
|
USER_UPDATED,
|
2023-02-27 10:56:56 +01:00
|
|
|
} from '../types';
|
2021-10-28 14:09:11 +02:00
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
interface IEventData {
|
|
|
|
action: string;
|
|
|
|
path?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface IFormattedEventData {
|
|
|
|
text: string;
|
|
|
|
url?: string;
|
|
|
|
}
|
|
|
|
|
2021-10-28 14:09:11 +02:00
|
|
|
export interface FeatureEventFormatter {
|
2023-09-29 17:11:59 +02:00
|
|
|
format: (event: IEvent) => IFormattedEventData;
|
2021-10-28 14:09:11 +02:00
|
|
|
}
|
|
|
|
export enum LinkStyle {
|
2023-09-29 14:18:21 +02:00
|
|
|
SLACK = 0,
|
|
|
|
MD = 1,
|
2021-10-28 14:09:11 +02:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
const EVENT_MAP: Record<string, IEventData> = {
|
|
|
|
[ADDON_CONFIG_CREATED]: {
|
|
|
|
action: '*{{user}}* created a new *{{event.data.provider}}* integration configuration',
|
|
|
|
path: '/integrations',
|
|
|
|
},
|
|
|
|
[ADDON_CONFIG_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted a *{{event.preData.provider}}* integration configuration',
|
|
|
|
path: '/integrations',
|
|
|
|
},
|
|
|
|
[ADDON_CONFIG_UPDATED]: {
|
|
|
|
action: '*{{user}}* updated a *{{event.preData.provider}}* integration configuration',
|
|
|
|
path: '/integrations',
|
|
|
|
},
|
|
|
|
[API_TOKEN_CREATED]: {
|
|
|
|
action: '*{{user}}* created API token *{{event.data.username}}*',
|
|
|
|
path: '/admin/api',
|
|
|
|
},
|
|
|
|
[API_TOKEN_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted API token *{{event.preData.username}}*',
|
|
|
|
path: '/admin/api',
|
|
|
|
},
|
|
|
|
[CHANGE_ADDED]: {
|
|
|
|
action: '*{{user}}* added a change to change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_DISCARDED]: {
|
|
|
|
action: '*{{user}}* discarded a change in change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_EDITED]: {
|
|
|
|
action: '*{{user}}* edited a change in change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_REQUEST_APPLIED]: {
|
|
|
|
action: '*{{user}}* applied change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_REQUEST_APPROVAL_ADDED]: {
|
|
|
|
action: '*{{user}}* added an approval to change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_REQUEST_APPROVED]: {
|
|
|
|
action: '*{{user}}* approved change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_REQUEST_CANCELLED]: {
|
|
|
|
action: '*{{user}}* cancelled change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_REQUEST_CREATED]: {
|
|
|
|
action: '*{{user}}* created change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_REQUEST_DISCARDED]: {
|
|
|
|
action: '*{{user}}* discarded change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_REQUEST_REJECTED]: {
|
|
|
|
action: '*{{user}}* rejected change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CHANGE_REQUEST_SENT_TO_REVIEW]: {
|
|
|
|
action: '*{{user}}* sent to review change request {{changeRequest}}',
|
|
|
|
path: '/projects/{{event.project}}/change-requests/{{event.data.changeRequestId}}',
|
|
|
|
},
|
|
|
|
[CONTEXT_FIELD_CREATED]: {
|
|
|
|
action: '*{{user}}* created context field *{{event.data.name}}*',
|
|
|
|
path: '/context',
|
|
|
|
},
|
|
|
|
[CONTEXT_FIELD_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted context field *{{event.preData.name}}*',
|
|
|
|
path: '/context',
|
|
|
|
},
|
|
|
|
[CONTEXT_FIELD_UPDATED]: {
|
|
|
|
action: '*{{user}}* updated context field *{{event.preData.name}}*',
|
|
|
|
path: '/context',
|
|
|
|
},
|
|
|
|
[FEATURE_ARCHIVED]: {
|
|
|
|
action: '*{{user}}* archived *{{event.featureName}}* in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/archive',
|
|
|
|
},
|
|
|
|
[FEATURE_CREATED]: {
|
|
|
|
action: '*{{user}}* created *{{feature}}* in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted *{{event.featureName}}* in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}',
|
|
|
|
},
|
|
|
|
[FEATURE_ENVIRONMENT_DISABLED]: {
|
|
|
|
action: '*{{user}}* disabled *{{feature}}* for the *{{event.environment}}* environment in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_ENVIRONMENT_ENABLED]: {
|
2023-10-08 19:22:12 +02:00
|
|
|
action: '*{{user}}* enabled *{{feature}}* for the *{{event.environment}}* environment in project *{{project}}*',
|
2023-09-29 17:11:59 +02:00
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_ENVIRONMENT_VARIANTS_UPDATED]: {
|
|
|
|
action: '*{{user}}* updated variants for *{{feature}}* for the *{{event.environment}}* environment in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}/variants',
|
|
|
|
},
|
|
|
|
[FEATURE_METADATA_UPDATED]: {
|
|
|
|
action: '*{{user}}* updated *{{feature}}* metadata in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_POTENTIALLY_STALE_ON]: {
|
|
|
|
action: '*{{feature}}* was marked as potentially stale in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_PROJECT_CHANGE]: {
|
|
|
|
action: '*{{user}}* moved *{{feature}}* from *{{event.data.oldProject}}* to *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_REVIVED]: {
|
|
|
|
action: '*{{user}}* revived *{{feature}}* in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_STALE_OFF]: {
|
|
|
|
action: '*{{user}}* removed the stale marking on *{{feature}}* in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_STALE_ON]: {
|
|
|
|
action: '*{{user}}* marked *{{feature}}* as stale in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_STRATEGY_ADD]: {
|
|
|
|
action: '*{{user}}* added strategy *{{strategyTitle}}* to *{{feature}}* for the *{{event.environment}}* environment in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_STRATEGY_REMOVE]: {
|
|
|
|
action: '*{{user}}* removed strategy *{{strategyTitle}}* from *{{feature}}* for the *{{event.environment}}* environment in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_STRATEGY_UPDATE]: {
|
|
|
|
action: '*{{user}}* updated *{{feature}}* in project *{{project}}* {{strategyChangeText}}',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_TAGGED]: {
|
|
|
|
action: '*{{user}}* tagged *{{feature}}* with *{{event.data.type}}:{{event.data.value}}* in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[FEATURE_UNTAGGED]: {
|
|
|
|
action: '*{{user}}* untagged *{{feature}}* with *{{event.preData.type}}:{{event.preData.value}}* in project *{{project}}*',
|
|
|
|
path: '/projects/{{event.project}}/features/{{event.featureName}}',
|
|
|
|
},
|
|
|
|
[GROUP_CREATED]: {
|
|
|
|
action: '*{{user}}* created group *{{event.data.name}}*',
|
|
|
|
path: '/admin/groups',
|
|
|
|
},
|
|
|
|
[GROUP_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted group *{{event.preData.name}}*',
|
|
|
|
path: '/admin/groups',
|
|
|
|
},
|
|
|
|
[GROUP_UPDATED]: {
|
|
|
|
action: '*{{user}}* updated group *{{event.preData.name}}*',
|
|
|
|
path: '/admin/groups',
|
|
|
|
},
|
2023-10-16 15:30:40 +02:00
|
|
|
[MESSAGE_BANNER_CREATED]: {
|
|
|
|
action: '*{{user}}* created message banner *{{event.data.message}}*',
|
|
|
|
path: '/admin/message-banners',
|
|
|
|
},
|
|
|
|
[MESSAGE_BANNER_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted message banner *{{event.preData.message}}*',
|
|
|
|
path: '/admin/message-banners',
|
|
|
|
},
|
|
|
|
[MESSAGE_BANNER_UPDATED]: {
|
|
|
|
action: '*{{user}}* updated message banner *{{event.preData.message}}*',
|
|
|
|
path: '/admin/message-banners',
|
|
|
|
},
|
2023-09-29 17:11:59 +02:00
|
|
|
[PROJECT_CREATED]: {
|
|
|
|
action: '*{{user}}* created project *{{project}}*',
|
|
|
|
path: '/projects',
|
|
|
|
},
|
|
|
|
[PROJECT_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted project *{{event.project}}*',
|
|
|
|
path: '/projects',
|
|
|
|
},
|
|
|
|
[SEGMENT_CREATED]: {
|
|
|
|
action: '*{{user}}* created segment *{{event.data.name}}*',
|
|
|
|
path: '/segments',
|
|
|
|
},
|
|
|
|
[SEGMENT_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted segment *{{event.preData.name}}*',
|
|
|
|
path: '/segments',
|
|
|
|
},
|
|
|
|
[SEGMENT_UPDATED]: {
|
|
|
|
action: '*{{user}}* updated segment *{{event.preData.name}}*',
|
|
|
|
path: '/segments',
|
|
|
|
},
|
|
|
|
[SERVICE_ACCOUNT_CREATED]: {
|
|
|
|
action: '*{{user}}* created service account *{{event.data.name}}*',
|
|
|
|
path: '/admin/service-accounts',
|
|
|
|
},
|
|
|
|
[SERVICE_ACCOUNT_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted service account *{{event.preData.name}}*',
|
|
|
|
path: '/admin/service-accounts',
|
|
|
|
},
|
|
|
|
[SERVICE_ACCOUNT_UPDATED]: {
|
|
|
|
action: '*{{user}}* updated service account *{{event.preData.name}}*',
|
|
|
|
path: '/admin/service-accounts',
|
|
|
|
},
|
|
|
|
[USER_CREATED]: {
|
|
|
|
action: '*{{user}}* created user *{{event.data.name}}*',
|
|
|
|
path: '/admin/users',
|
|
|
|
},
|
|
|
|
[USER_DELETED]: {
|
|
|
|
action: '*{{user}}* deleted user *{{event.preData.name}}*',
|
|
|
|
path: '/admin/users',
|
|
|
|
},
|
|
|
|
[USER_UPDATED]: {
|
|
|
|
action: '*{{user}}* updated user *{{event.preData.name}}*',
|
|
|
|
path: '/admin/users',
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2021-10-28 14:09:11 +02:00
|
|
|
export class FeatureEventFormatterMd implements FeatureEventFormatter {
|
2023-02-27 10:56:56 +01:00
|
|
|
private readonly unleashUrl: string;
|
2021-10-28 14:09:11 +02:00
|
|
|
|
2023-02-27 10:56:56 +01:00
|
|
|
private readonly linkStyle: LinkStyle;
|
2021-10-28 14:09:11 +02:00
|
|
|
|
|
|
|
constructor(unleashUrl: string, linkStyle: LinkStyle = LinkStyle.MD) {
|
|
|
|
this.unleashUrl = unleashUrl;
|
|
|
|
this.linkStyle = linkStyle;
|
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
generateChangeRequestLink(event: IEvent): string | undefined {
|
|
|
|
const { preData, data, project, environment } = event;
|
|
|
|
const changeRequestId =
|
|
|
|
data?.changeRequestId || preData?.changeRequestId;
|
|
|
|
if (project && changeRequestId) {
|
|
|
|
const url = `${this.unleashUrl}/projects/${project}/change-requests/${changeRequestId}`;
|
|
|
|
const text = `#${changeRequestId}`;
|
|
|
|
const featureLink = this.generateFeatureLink(event);
|
|
|
|
const featureText = featureLink
|
|
|
|
? ` for feature toggle *${featureLink}*`
|
|
|
|
: '';
|
|
|
|
const environmentText = environment
|
|
|
|
? ` in *${environment}* environment`
|
|
|
|
: '';
|
|
|
|
const projectLink = this.generateProjectLink(event);
|
|
|
|
const projectText = project ? ` in project *${projectLink}*` : '';
|
|
|
|
if (this.linkStyle === LinkStyle.SLACK) {
|
|
|
|
return `*<${url}|${text}>*${featureText}${environmentText}${projectText}`;
|
|
|
|
} else {
|
|
|
|
return `*[${text}](${url})*${featureText}${environmentText}${projectText}`;
|
|
|
|
}
|
|
|
|
}
|
2021-10-28 14:09:11 +02:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
featureLink(event: IEvent): string | undefined {
|
|
|
|
const { type, project = '', featureName } = event;
|
|
|
|
if (type === FEATURE_ARCHIVED) {
|
|
|
|
if (project) {
|
|
|
|
return `${this.unleashUrl}/projects/${project}/archive`;
|
|
|
|
}
|
|
|
|
return `${this.unleashUrl}/archive`;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (featureName) {
|
|
|
|
return `${this.unleashUrl}/projects/${project}/features/${featureName}`;
|
2021-10-28 14:09:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
generateFeatureLink(event: IEvent): string | undefined {
|
|
|
|
if (event.featureName) {
|
|
|
|
if (this.linkStyle === LinkStyle.SLACK) {
|
|
|
|
return `<${this.featureLink(event)}|${event.featureName}>`;
|
|
|
|
} else {
|
|
|
|
return `[${event.featureName}](${this.featureLink(event)})`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-10-28 14:09:11 +02:00
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
generateProjectLink(event: IEvent): string | undefined {
|
|
|
|
if (event.project) {
|
|
|
|
if (this.linkStyle === LinkStyle.SLACK) {
|
|
|
|
return `<${this.unleashUrl}/projects/${event.project}|${event.project}>`;
|
|
|
|
} else {
|
|
|
|
return `[${event.project}](${this.unleashUrl}/projects/${event.project})`;
|
|
|
|
}
|
2021-10-28 14:09:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
getStrategyTitle(event: IEvent): string | undefined {
|
|
|
|
return (
|
|
|
|
event.preData?.title ||
|
|
|
|
event.data?.title ||
|
|
|
|
event.preData?.name ||
|
|
|
|
event.data?.name
|
|
|
|
);
|
2021-10-28 14:09:11 +02:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
generateFeatureStrategyChangeText(event: IEvent): string | undefined {
|
|
|
|
const { environment, data, preData, type } = event;
|
|
|
|
if (type === FEATURE_STRATEGY_UPDATE && (data || preData)) {
|
|
|
|
const strategyText = () => {
|
|
|
|
switch ((data || preData).name) {
|
|
|
|
case 'flexibleRollout':
|
|
|
|
return this.flexibleRolloutStrategyChangeText(event);
|
|
|
|
case 'default':
|
|
|
|
return this.defaultStrategyChangeText(event);
|
|
|
|
case 'userWithId':
|
|
|
|
return this.userWithIdStrategyChangeText(event);
|
|
|
|
case 'remoteAddress':
|
|
|
|
return this.remoteAddressStrategyChangeText(event);
|
|
|
|
case 'applicationHostname':
|
|
|
|
return this.applicationHostnameStrategyChangeText(
|
|
|
|
event,
|
|
|
|
);
|
|
|
|
default:
|
|
|
|
return `by updating strategy *${this.getStrategyTitle(
|
|
|
|
event,
|
|
|
|
)}* in *${environment}*`;
|
|
|
|
}
|
|
|
|
};
|
2023-02-27 10:56:56 +01:00
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
return strategyText();
|
|
|
|
}
|
2023-02-27 10:56:56 +01:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
private applicationHostnameStrategyChangeText(event: IEvent) {
|
|
|
|
return this.listOfValuesStrategyChangeText(event, 'hostNames');
|
2023-02-27 10:56:56 +01:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
private remoteAddressStrategyChangeText(event: IEvent) {
|
|
|
|
return this.listOfValuesStrategyChangeText(event, 'IPs');
|
2023-02-27 10:56:56 +01:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
private userWithIdStrategyChangeText(event: IEvent) {
|
|
|
|
return this.listOfValuesStrategyChangeText(event, 'userIds');
|
2023-02-27 10:56:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private listOfValuesStrategyChangeText(
|
2023-09-29 17:11:59 +02:00
|
|
|
event: IEvent,
|
2023-02-27 10:56:56 +01:00
|
|
|
propertyName: string,
|
|
|
|
) {
|
2023-09-29 17:11:59 +02:00
|
|
|
const { preData, data, environment } = event;
|
2023-02-27 10:56:56 +01:00
|
|
|
const userIdText = (values) =>
|
|
|
|
values.length === 0
|
|
|
|
? `empty set of ${propertyName}`
|
|
|
|
: `[${values}]`;
|
|
|
|
const usersText =
|
2023-09-29 17:11:59 +02:00
|
|
|
preData?.parameters[propertyName] === data?.parameters[propertyName]
|
2023-02-27 10:56:56 +01:00
|
|
|
? ''
|
2023-09-29 17:11:59 +02:00
|
|
|
: !preData
|
|
|
|
? ` ${propertyName} to ${userIdText(
|
|
|
|
data?.parameters[propertyName],
|
|
|
|
)}`
|
2023-02-27 10:56:56 +01:00
|
|
|
: ` ${propertyName} from ${userIdText(
|
|
|
|
preData.parameters[propertyName],
|
2023-09-29 17:11:59 +02:00
|
|
|
)} to ${userIdText(data?.parameters[propertyName])}`;
|
2023-02-27 10:56:56 +01:00
|
|
|
const constraintText = this.constraintChangeText(
|
2023-09-29 17:11:59 +02:00
|
|
|
preData?.constraints,
|
|
|
|
data?.constraints,
|
2023-02-27 10:56:56 +01:00
|
|
|
);
|
2023-10-09 09:11:39 +02:00
|
|
|
const segmentsText = this.segmentsChangeText(
|
|
|
|
preData?.segments,
|
|
|
|
data?.segments,
|
|
|
|
);
|
|
|
|
const strategySpecificText = [usersText, constraintText, segmentsText]
|
2023-02-27 10:56:56 +01:00
|
|
|
.filter((x) => x.length)
|
|
|
|
.join(';');
|
2023-09-29 17:11:59 +02:00
|
|
|
return `by updating strategy *${this.getStrategyTitle(
|
|
|
|
event,
|
|
|
|
)}* in *${environment}*${strategySpecificText}`;
|
2023-02-27 10:56:56 +01:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
private flexibleRolloutStrategyChangeText(event: IEvent) {
|
|
|
|
const { preData, data, environment } = event;
|
2023-02-27 10:56:56 +01:00
|
|
|
const {
|
|
|
|
rollout: oldRollout,
|
|
|
|
stickiness: oldStickiness,
|
|
|
|
groupId: oldGroupId,
|
2023-09-29 17:11:59 +02:00
|
|
|
} = preData?.parameters || {};
|
|
|
|
const { rollout, stickiness, groupId } = data?.parameters || {};
|
2023-02-27 10:56:56 +01:00
|
|
|
const stickinessText =
|
|
|
|
oldStickiness === stickiness
|
|
|
|
? ''
|
2023-09-29 17:11:59 +02:00
|
|
|
: !oldStickiness
|
|
|
|
? ` stickiness to ${stickiness}`
|
2023-02-27 10:56:56 +01:00
|
|
|
: ` stickiness from ${oldStickiness} to ${stickiness}`;
|
|
|
|
const rolloutText =
|
|
|
|
oldRollout === rollout
|
|
|
|
? ''
|
2023-09-29 17:11:59 +02:00
|
|
|
: !oldRollout
|
|
|
|
? ` rollout to ${rollout}%`
|
2023-02-27 10:56:56 +01:00
|
|
|
: ` rollout from ${oldRollout}% to ${rollout}%`;
|
|
|
|
const groupIdText =
|
|
|
|
oldGroupId === groupId
|
|
|
|
? ''
|
2023-09-29 17:11:59 +02:00
|
|
|
: !oldGroupId
|
|
|
|
? ` groupId to ${groupId}`
|
2023-02-27 10:56:56 +01:00
|
|
|
: ` groupId from ${oldGroupId} to ${groupId}`;
|
|
|
|
const constraintText = this.constraintChangeText(
|
2023-09-29 17:11:59 +02:00
|
|
|
preData?.constraints,
|
|
|
|
data?.constraints,
|
2023-02-27 10:56:56 +01:00
|
|
|
);
|
2023-10-09 09:11:39 +02:00
|
|
|
const segmentsText = this.segmentsChangeText(
|
|
|
|
preData?.segments,
|
|
|
|
data?.segments,
|
|
|
|
);
|
2023-02-27 10:56:56 +01:00
|
|
|
const strategySpecificText = [
|
|
|
|
stickinessText,
|
|
|
|
rolloutText,
|
|
|
|
groupIdText,
|
|
|
|
constraintText,
|
2023-10-09 09:11:39 +02:00
|
|
|
segmentsText,
|
2023-02-27 10:56:56 +01:00
|
|
|
]
|
|
|
|
.filter((txt) => txt.length)
|
|
|
|
.join(';');
|
2023-09-29 17:11:59 +02:00
|
|
|
return `by updating strategy *${this.getStrategyTitle(
|
|
|
|
event,
|
|
|
|
)}* in *${environment}*${strategySpecificText}`;
|
2023-02-27 10:56:56 +01:00
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
private defaultStrategyChangeText(event: IEvent) {
|
|
|
|
const { preData, data, environment } = event;
|
2023-10-09 09:11:39 +02:00
|
|
|
const constraintText = this.constraintChangeText(
|
2023-09-29 17:11:59 +02:00
|
|
|
preData?.constraints,
|
|
|
|
data?.constraints,
|
2023-10-09 09:11:39 +02:00
|
|
|
);
|
|
|
|
const segmentsText = this.segmentsChangeText(
|
|
|
|
preData?.segments,
|
|
|
|
data?.segments,
|
|
|
|
);
|
|
|
|
const strategySpecificText = [constraintText, segmentsText]
|
|
|
|
.filter((txt) => txt.length)
|
|
|
|
.join(';');
|
|
|
|
return `by updating strategy *${this.getStrategyTitle(
|
|
|
|
event,
|
|
|
|
)}* in *${environment}*${strategySpecificText}`;
|
2023-02-27 10:56:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private constraintChangeText(
|
2023-09-29 17:11:59 +02:00
|
|
|
oldConstraints: IConstraint[] = [],
|
|
|
|
newConstraints: IConstraint[] = [],
|
2023-02-27 10:56:56 +01:00
|
|
|
) {
|
|
|
|
const formatConstraints = (constraints: IConstraint[]) => {
|
|
|
|
const constraintOperatorDescriptions = {
|
|
|
|
IN: 'is one of',
|
|
|
|
NOT_IN: 'is not one of',
|
|
|
|
STR_CONTAINS: 'is a string that contains',
|
|
|
|
STR_STARTS_WITH: 'is a string that starts with',
|
|
|
|
STR_ENDS_WITH: 'is a string that ends with',
|
|
|
|
NUM_EQ: 'is a number equal to',
|
|
|
|
NUM_GT: 'is a number greater than',
|
|
|
|
NUM_GTE: 'is a number greater than or equal to',
|
|
|
|
NUM_LT: 'is a number less than',
|
|
|
|
NUM_LTE: 'is a number less than or equal to',
|
|
|
|
DATE_BEFORE: 'is a date before',
|
|
|
|
DATE_AFTER: 'is a date after',
|
|
|
|
SEMVER_EQ: 'is a SemVer equal to',
|
|
|
|
SEMVER_GT: 'is a SemVer greater than',
|
|
|
|
SEMVER_LT: 'is a SemVer less than',
|
|
|
|
};
|
|
|
|
const formatConstraint = (constraint: IConstraint) => {
|
2023-09-29 14:18:21 +02:00
|
|
|
const val = Object.hasOwn(constraint, 'value')
|
2023-02-27 10:56:56 +01:00
|
|
|
? constraint.value
|
2023-09-29 17:11:59 +02:00
|
|
|
: `(${constraint.values?.join(',')})`;
|
2023-09-29 14:18:21 +02:00
|
|
|
const operator = Object.hasOwn(
|
|
|
|
constraintOperatorDescriptions,
|
2023-02-27 10:56:56 +01:00
|
|
|
constraint.operator,
|
|
|
|
)
|
|
|
|
? constraintOperatorDescriptions[constraint.operator]
|
|
|
|
: constraint.operator;
|
|
|
|
|
|
|
|
return `${constraint.contextName} ${
|
|
|
|
constraint.inverted ? 'not ' : ''
|
|
|
|
}${operator} ${val}`;
|
|
|
|
};
|
|
|
|
|
|
|
|
return constraints.length === 0
|
|
|
|
? 'empty set of constraints'
|
|
|
|
: `[${constraints.map(formatConstraint).join(', ')}]`;
|
|
|
|
};
|
|
|
|
const oldConstraintText = formatConstraints(oldConstraints);
|
|
|
|
const newConstraintText = formatConstraints(newConstraints);
|
|
|
|
return oldConstraintText === newConstraintText
|
|
|
|
? ''
|
|
|
|
: ` constraints from ${oldConstraintText} to ${newConstraintText}`;
|
|
|
|
}
|
|
|
|
|
2023-10-09 09:11:39 +02:00
|
|
|
private segmentsChangeText(
|
|
|
|
oldSegments: string[] = [],
|
|
|
|
newSegments: string[] = [],
|
|
|
|
) {
|
|
|
|
const formatSegments = (segments: string[]) => {
|
|
|
|
return segments.length === 0
|
|
|
|
? 'empty set of segments'
|
|
|
|
: `(${segments.join(',')})`;
|
|
|
|
};
|
|
|
|
const oldSegmentsText = formatSegments(oldSegments);
|
|
|
|
const newSegmentsText = formatSegments(newSegments);
|
|
|
|
|
|
|
|
return oldSegmentsText === newSegmentsText
|
|
|
|
? ''
|
|
|
|
: ` segments from ${oldSegmentsText} to ${newSegmentsText}`;
|
|
|
|
}
|
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
format(event: IEvent): {
|
|
|
|
text: string;
|
|
|
|
url?: string;
|
|
|
|
} {
|
|
|
|
const { createdBy, type } = event;
|
|
|
|
const { action, path } = EVENT_MAP[type] || {
|
|
|
|
action: `triggered *${type}*`,
|
|
|
|
};
|
2022-10-05 11:30:51 +02:00
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
const context = {
|
|
|
|
user: createdBy,
|
|
|
|
event,
|
|
|
|
strategyTitle: this.getStrategyTitle(event),
|
|
|
|
strategyChangeText: this.generateFeatureStrategyChangeText(event),
|
|
|
|
changeRequest: this.generateChangeRequestLink(event),
|
|
|
|
feature: this.generateFeatureLink(event),
|
|
|
|
project: this.generateProjectLink(event),
|
|
|
|
};
|
2021-10-28 14:09:11 +02:00
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
Mustache.escape = (text) => text;
|
2021-10-28 14:09:11 +02:00
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
const text = Mustache.render(action, context);
|
|
|
|
const url = path
|
|
|
|
? `${this.unleashUrl}${Mustache.render(path, context)}`
|
|
|
|
: undefined;
|
2021-10-28 14:09:11 +02:00
|
|
|
|
2023-09-29 17:11:59 +02:00
|
|
|
return {
|
|
|
|
text,
|
|
|
|
url,
|
|
|
|
};
|
2021-10-28 14:09:11 +02:00
|
|
|
}
|
|
|
|
}
|