mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
feat: add more events in integrations (#4815)
https://linear.app/unleash/issue/2-1253/add-support-for-more-events-in-the-slack-app-integration Adds support for a lot more events in our integrations. Here is how the full list looks like: - 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 - FEATURE_ARCHIVED - FEATURE_CREATED - FEATURE_DELETED - FEATURE_ENVIRONMENT_DISABLED - FEATURE_ENVIRONMENT_ENABLED - FEATURE_ENVIRONMENT_VARIANTS_UPDATED - FEATURE_METADATA_UPDATED - FEATURE_POTENTIALLY_STALE_ON - FEATURE_PROJECT_CHANGE - FEATURE_REVIVED - FEATURE_STALE_OFF - FEATURE_STALE_ON - FEATURE_STRATEGY_ADD - FEATURE_STRATEGY_REMOVE - FEATURE_STRATEGY_UPDATE - FEATURE_TAGGED - FEATURE_UNTAGGED - GROUP_CREATED - GROUP_DELETED - GROUP_UPDATED - 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 I added the events that I thought were relevant based on my own discretion. Know of any event we should add? Let me know and I'll add it 🙂 For now I only added these events to the new Slack App integration, but we can add them to the other integrations as well since they are now supported. The event formatter was refactored and changed quite a bit in order to make it easier to maintain and add new events in the future. As a result, events are now posted with different text. Do we consider this a breaking change? If so, I can keep the old event formatter around, create a new one and only use it for the new Slack App integration. I noticed we don't have good 404 behaviors in the UI for things that are deleted in the meantime, that's why I avoided some links to specific resources (like feature strategies, integration configurations, etc), but we could add them later if we improve this. This PR also tries to add some consistency to the the way we log events.
This commit is contained in:
parent
a0571ce022
commit
521cc24a22
@ -71,10 +71,12 @@ export const IntegrationForm: VFC<IntegrationFormProps> = ({
|
||||
value: environment.name,
|
||||
label: environment.name,
|
||||
}));
|
||||
const selectableEvents = provider?.events?.map(event => ({
|
||||
value: event,
|
||||
label: event,
|
||||
}));
|
||||
const selectableEvents = provider?.events
|
||||
?.map(event => ({
|
||||
value: event,
|
||||
label: event,
|
||||
}))
|
||||
.sort((a, b) => a.label.localeCompare(b.label));
|
||||
const { uiConfig } = useUiConfig();
|
||||
const [formValues, setFormValues] = useState(initialValues);
|
||||
const [errors, setErrors] = useState<{
|
||||
|
@ -1,16 +1,16 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Should call datadog webhook for archived toggle 1`] = `"{"text":"%%% \\n some@user.com just archived feature toggle *[some-toggle](http://some-url.com/archive)* \\n %%% ","title":"Unleash notification update"}"`;
|
||||
exports[`Should call datadog webhook for archived toggle 1`] = `"{"text":"%%% \\n *some@user.com* archived *some-toggle* in project ** \\n %%% ","title":"Unleash notification update"}"`;
|
||||
|
||||
exports[`Should call datadog webhook for archived toggle with project info 1`] = `"{"text":"%%% \\n some@user.com just archived feature toggle *[some-toggle](http://some-url.com/projects/some-project/archive)* \\n %%% ","title":"Unleash notification update"}"`;
|
||||
exports[`Should call datadog webhook for archived toggle with project info 1`] = `"{"text":"%%% \\n *some@user.com* archived *some-toggle* in project *[some-project](http://some-url.com/projects/some-project)* \\n %%% ","title":"Unleash notification update"}"`;
|
||||
|
||||
exports[`Should call datadog webhook 1`] = `"{"text":"%%% \\n some@user.com created feature toggle [some-toggle](http://some-url.com/projects//features/some-toggle) in project *undefined* \\n %%% ","title":"Unleash notification update"}"`;
|
||||
exports[`Should call datadog webhook 1`] = `"{"text":"%%% \\n *some@user.com* created *[some-toggle](http://some-url.com/projects//features/some-toggle)* in project ** \\n %%% ","title":"Unleash notification update"}"`;
|
||||
|
||||
exports[`Should call datadog webhook for toggled environment 1`] = `"{"text":"%%% \\n some@user.com *disabled* [some-toggle](http://some-url.com/projects/default/features/some-toggle) in *development* environment in project *default* \\n %%% ","title":"Unleash notification update"}"`;
|
||||
exports[`Should call datadog webhook for toggled environment 1`] = `"{"text":"%%% \\n *some@user.com* disabled *[some-toggle](http://some-url.com/projects/default/features/some-toggle)* for the *development* environment in project *[default](http://some-url.com/projects/default)* \\n %%% ","title":"Unleash notification update"}"`;
|
||||
|
||||
exports[`Should call datadog webhook with JSON when template set 1`] = `"{"text":"{\\n \\"event\\": \\"feature-created\\",\\n \\"createdBy\\": \\"some@user.com\\"\\n}","title":"Unleash notification update"}"`;
|
||||
|
||||
exports[`Should include customHeaders in headers when calling service 1`] = `"{"text":"%%% \\n some@user.com *disabled* [some-toggle](http://some-url.com/projects/default/features/some-toggle) in *development* environment in project *default* \\n %%% ","title":"Unleash notification update"}"`;
|
||||
exports[`Should include customHeaders in headers when calling service 1`] = `"{"text":"%%% \\n *some@user.com* disabled *[some-toggle](http://some-url.com/projects/default/features/some-toggle)* for the *development* environment in project *[default](http://some-url.com/projects/default)* \\n %%% ","title":"Unleash notification update"}"`;
|
||||
|
||||
exports[`Should include customHeaders in headers when calling service 2`] = `
|
||||
{
|
||||
@ -20,7 +20,7 @@ exports[`Should include customHeaders in headers when calling service 2`] = `
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should not include source_type_name when included in the config 1`] = `"{"text":"%%% \\n some@user.com *disabled* [some-toggle](http://some-url.com/projects/default/features/some-toggle) in *development* environment in project *default* \\n %%% ","title":"Unleash notification update","source_type_name":"my-custom-source-type"}"`;
|
||||
exports[`Should not include source_type_name when included in the config 1`] = `"{"text":"%%% \\n *some@user.com* disabled *[some-toggle](http://some-url.com/projects/default/features/some-toggle)* for the *development* environment in project *[default](http://some-url.com/projects/default)* \\n %%% ","title":"Unleash notification update","source_type_name":"my-custom-source-type"}"`;
|
||||
|
||||
exports[`Should not include source_type_name when included in the config 2`] = `
|
||||
{
|
||||
|
@ -0,0 +1,183 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Should format specialised text for events when IPs changed 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *remoteAddress* in *production* IPs from empty set of IPs to [127.0.0.1]; constraints from empty set of constraints to [appName is one of (x,y)]",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when constraints and rollout percentage and stickiness changed 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *flexibleRollout* in *production* stickiness from default to random; rollout from 67% to 32%; constraints from empty set of constraints to [appName is one of (x,y)]",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from empty set of constraints to [appName is one of (x,y), appName not is one of (x)]",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated 2`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from empty set of constraints to [appName is not one of (x,y), appName not is not one of (x)]",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated 3`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from empty set of constraints to [appName is a string that contains (x,y), appName not is a string that contains (x)]",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated 4`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from empty set of constraints to [appName is a string that starts with (x,y), appName not is a string that starts with (x)]",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated 5`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from empty set of constraints to [appName is a string that ends with (x,y), appName not is a string that ends with (x)]",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint DATE_AFTER 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a date after 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint DATE_BEFORE 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a date before 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint NUM_EQ 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a number equal to 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint NUM_GT 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a number greater than 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint NUM_GTE 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a number greater than or equal to 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint NUM_LT 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a number less than 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint NUM_LTE 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a number less than or equal to 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint SEMVER_EQ 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a SemVer equal to 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint SEMVER_GT 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a SemVer greater than 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when default strategy updated with numeric constraint SEMVER_LT 1`] = `
|
||||
{
|
||||
"text": "*admin* updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *[default](unleashUrl/projects/default)* by updating strategy *default* in *production* constraints from [appName is a SemVer less than 4] to empty set of constraints",
|
||||
"url": "unleashUrl/projects/default/features/aaa",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when groupId changed 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *flexibleRollout* in *production* groupId from new-feature to different-feature",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when host names changed 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *applicationHostname* in *production* hostNames from empty set of hostNames to [unleash.com]; constraints from empty set of constraints to [appName is one of (x,y)]",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when neither rollout percentage nor stickiness changed 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *flexibleRollout* in *production*",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when no specific text for strategy exists yet 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *newStrategy* in *production*",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when rollout percentage changed 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *flexibleRollout* in *production* rollout from 67% to 32%",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when stickiness changed 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *flexibleRollout* in *production* stickiness from default to random",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when strategy added 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* added strategy *flexibleRollout* to *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* for the *production* environment in project *[my-other-project](unleashUrl/projects/my-other-project)*",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when strategy removed 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* removed strategy *default* from *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* for the *production* environment in project *[my-other-project](unleashUrl/projects/my-other-project)*",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`Should format specialised text for events when userIds changed 1`] = `
|
||||
{
|
||||
"text": "*user@company.com* updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *[my-other-project](unleashUrl/projects/my-other-project)* by updating strategy *userWithId* in *production* userIds from empty set of userIds to [a,b]; constraints from empty set of constraints to [appName is one of (x,y)]",
|
||||
"url": "unleashUrl/projects/my-other-project/features/new-feature",
|
||||
}
|
||||
`;
|
@ -1,14 +1,14 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Should call slack webhook 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":"some@user.com created feature toggle <http://some-url.com/projects/default/features/some-toggle|some-toggle> in project *default*","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
exports[`Should call slack webhook 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":"*some@user.com* created *<http://some-url.com/projects/default/features/some-toggle|some-toggle>* in project *<http://some-url.com/projects/default|default>*","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
|
||||
exports[`Should call slack webhook for archived toggle 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":" some@user.com just archived feature toggle *<http://some-url.com/archive|some-toggle>*","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/archive"}]}]}"`;
|
||||
exports[`Should call slack webhook for archived toggle 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":"*some@user.com* archived *some-toggle* in project **","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/projects//archive"}]}]}"`;
|
||||
|
||||
exports[`Should call slack webhook for archived toggle with project info 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":" some@user.com just archived feature toggle *<http://some-url.com/projects/some-project/archive|some-toggle>*","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/projects/some-project/archive"}]}]}"`;
|
||||
exports[`Should call slack webhook for archived toggle with project info 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":"*some@user.com* archived *some-toggle* in project *<http://some-url.com/projects/some-project|some-project>*","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/projects/some-project/archive"}]}]}"`;
|
||||
|
||||
exports[`Should call webhook for toggled environment 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":"some@user.com *disabled* <http://some-url.com/projects/default/features/some-toggle|some-toggle> in *development* environment in project *default*","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
exports[`Should call webhook for toggled environment 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":"*some@user.com* disabled *<http://some-url.com/projects/default/features/some-toggle|some-toggle>* for the *development* environment in project *<http://some-url.com/projects/default|default>*","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
|
||||
exports[`Should include custom headers from parameters in call to service 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":"some@user.com *disabled* <http://some-url.com/projects/default/features/some-toggle|some-toggle> in *development* environment in project *default*","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
exports[`Should include custom headers from parameters in call to service 1`] = `"{"username":"Unleash","icon_emoji":":unleash:","text":"*some@user.com* disabled *<http://some-url.com/projects/default/features/some-toggle|some-toggle>* for the *development* environment in project *<http://some-url.com/projects/default|default>*","channel":"#general","attachments":[{"actions":[{"name":"featureToggle","text":"Open in Unleash","type":"button","value":"featureToggle","style":"primary","url":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
|
||||
exports[`Should include custom headers from parameters in call to service 2`] = `
|
||||
{
|
||||
|
@ -1,14 +1,14 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Should call teams webhook 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":"some@user.com created feature toggle [some-toggle](http://some-url.com/projects//features/some-toggle) in project *undefined*","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-created"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/projects//features/some-toggle"}]}]}"`;
|
||||
exports[`Should call teams webhook 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":"*some@user.com* created *[some-toggle](http://some-url.com/projects//features/some-toggle)* in project **","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-created"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/projects//features/some-toggle"}]}]}"`;
|
||||
|
||||
exports[`Should call teams webhook for archived toggle 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":" some@user.com just archived feature toggle *[some-toggle](http://some-url.com/archive)*","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-archived"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/archive"}]}]}"`;
|
||||
exports[`Should call teams webhook for archived toggle 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":"*some@user.com* archived *some-toggle* in project **","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-archived"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/projects//archive"}]}]}"`;
|
||||
|
||||
exports[`Should call teams webhook for archived toggle with project info 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":" some@user.com just archived feature toggle *[some-toggle](http://some-url.com/projects/some-project/archive)*","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-archived"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/projects/some-project/archive"}]}]}"`;
|
||||
exports[`Should call teams webhook for archived toggle with project info 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":"*some@user.com* archived *some-toggle* in project *[some-project](http://some-url.com/projects/some-project)*","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-archived"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/projects/some-project/archive"}]}]}"`;
|
||||
|
||||
exports[`Should call teams webhook for toggled environment 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":"some@user.com *disabled* [some-toggle](http://some-url.com/projects/default/features/some-toggle) in *development* environment in project *default*","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-environment-disabled"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
exports[`Should call teams webhook for toggled environment 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":"*some@user.com* disabled *[some-toggle](http://some-url.com/projects/default/features/some-toggle)* for the *development* environment in project *[default](http://some-url.com/projects/default)*","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-environment-disabled"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
|
||||
exports[`Should include custom headers in call to teams 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":"some@user.com *disabled* [some-toggle](http://some-url.com/projects/default/features/some-toggle) in *development* environment in project *default*","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-environment-disabled"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
exports[`Should include custom headers in call to teams 1`] = `"{"themeColor":"0076D7","summary":"Message","sections":[{"activityTitle":"*some@user.com* disabled *[some-toggle](http://some-url.com/projects/default/features/some-toggle)* for the *development* environment in project *[default](http://some-url.com/projects/default)*","activitySubtitle":"Unleash notification update","facts":[{"name":"User","value":"some@user.com"},{"name":"Action","value":"feature-environment-disabled"}]}],"potentialAction":[{"@type":"OpenUri","name":"Go to feature","targets":[{"os":"default","uri":"http://some-url.com/projects/default/features/some-toggle"}]}]}"`;
|
||||
|
||||
exports[`Should include custom headers in call to teams 2`] = `
|
||||
{
|
||||
|
@ -68,7 +68,7 @@ export default class DatadogAddon extends Addon {
|
||||
) {
|
||||
text = Mustache.render(bodyTemplate, context);
|
||||
} else {
|
||||
text = `%%% \n ${this.msgFormatter.format(event)} \n %%% `;
|
||||
text = `%%% \n ${this.msgFormatter.format(event).text} \n %%% `;
|
||||
}
|
||||
|
||||
const { tags: eventTags } = event;
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
STR_STARTS_WITH,
|
||||
} from '../util';
|
||||
|
||||
const testCases: [string, IEvent, string][] = [
|
||||
const testCases: [string, IEvent][] = [
|
||||
[
|
||||
'when groupId changed',
|
||||
{
|
||||
@ -57,7 +57,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by updating strategy flexibleRollout in *production* groupId from new-feature to different-feature',
|
||||
],
|
||||
[
|
||||
'when rollout percentage changed',
|
||||
@ -91,7 +90,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by updating strategy flexibleRollout in *production* rollout from 67% to 32%',
|
||||
],
|
||||
[
|
||||
'when stickiness changed',
|
||||
@ -125,7 +123,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by updating strategy flexibleRollout in *production* stickiness from default to random',
|
||||
],
|
||||
[
|
||||
'when constraints and rollout percentage and stickiness changed',
|
||||
@ -167,7 +164,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by updating strategy flexibleRollout in *production* stickiness from default to random; rollout from 67% to 32%; constraints from empty set of constraints to [appName is one of (x,y)]',
|
||||
],
|
||||
[
|
||||
'when neither rollout percentage nor stickiness changed',
|
||||
@ -201,7 +197,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by updating strategy flexibleRollout in *production*',
|
||||
],
|
||||
[
|
||||
'when strategy added',
|
||||
@ -226,7 +221,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by adding strategy flexibleRollout in *production*',
|
||||
],
|
||||
[
|
||||
'when strategy removed',
|
||||
@ -247,17 +241,10 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by removing strategy default in *production*',
|
||||
],
|
||||
...[
|
||||
[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'],
|
||||
].map(
|
||||
([operator, display]) =>
|
||||
<[string, IEvent, string]>[
|
||||
...[IN, NOT_IN, STR_CONTAINS, STR_STARTS_WITH, STR_ENDS_WITH].map(
|
||||
(operator) =>
|
||||
<[string, IEvent]>[
|
||||
'when default strategy updated',
|
||||
{
|
||||
id: 39,
|
||||
@ -298,23 +285,22 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'default',
|
||||
environment: 'production',
|
||||
},
|
||||
`admin updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *default* by updating strategy default in *production* constraints from empty set of constraints to [appName ${display} (x,y), appName not ${display} (x)]`,
|
||||
],
|
||||
),
|
||||
...[
|
||||
[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'],
|
||||
NUM_EQ,
|
||||
NUM_GT,
|
||||
NUM_GTE,
|
||||
NUM_LT,
|
||||
NUM_LTE,
|
||||
DATE_BEFORE,
|
||||
DATE_AFTER,
|
||||
SEMVER_EQ,
|
||||
SEMVER_GT,
|
||||
SEMVER_LT,
|
||||
].map(
|
||||
([operator, display]) =>
|
||||
<[string, IEvent, string]>[
|
||||
(operator) =>
|
||||
<[string, IEvent]>[
|
||||
`when default strategy updated with numeric constraint ${operator}`,
|
||||
{
|
||||
id: 39,
|
||||
@ -349,7 +335,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'default',
|
||||
environment: 'production',
|
||||
},
|
||||
`admin updated *[aaa](unleashUrl/projects/default/features/aaa)* in project *default* by updating strategy default in *production* constraints from [appName ${display} 4] to empty set of constraints`,
|
||||
],
|
||||
),
|
||||
[
|
||||
@ -390,7 +375,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by updating strategy userWithId in *production* userIds from empty set of userIds to [a,b]; constraints from empty set of constraints to [appName is one of (x,y)]',
|
||||
],
|
||||
[
|
||||
'when IPs changed',
|
||||
@ -426,7 +410,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by updating strategy remoteAddress in *production* IPs from empty set of IPs to [127.0.0.1]; constraints from empty set of constraints to [appName is one of (x,y)]',
|
||||
],
|
||||
[
|
||||
'when host names changed',
|
||||
@ -462,7 +445,6 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by updating strategy applicationHostname in *production* hostNames from empty set of hostNames to [unleash.com]; constraints from empty set of constraints to [appName is one of (x,y)]',
|
||||
],
|
||||
[
|
||||
'when no specific text for strategy exists yet',
|
||||
@ -498,14 +480,13 @@ const testCases: [string, IEvent, string][] = [
|
||||
project: 'my-other-project',
|
||||
environment: 'production',
|
||||
},
|
||||
'user@company.com updated *[new-feature](unleashUrl/projects/my-other-project/features/new-feature)* in project *my-other-project* by updating strategy newStrategy in *production*',
|
||||
],
|
||||
];
|
||||
|
||||
testCases.forEach(([description, event, expected]) =>
|
||||
testCases.forEach(([description, event]) =>
|
||||
test(`Should format specialised text for events ${description}`, () => {
|
||||
const formatter = new FeatureEventFormatterMd('unleashUrl');
|
||||
const actual = formatter.format(event);
|
||||
expect(actual).toBe(expected);
|
||||
const formattedEvent = formatter.format(event);
|
||||
expect(formattedEvent).toMatchSnapshot();
|
||||
}),
|
||||
);
|
||||
|
@ -1,8 +1,30 @@
|
||||
import Mustache from 'mustache';
|
||||
import {
|
||||
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,
|
||||
FEATURE_ARCHIVED,
|
||||
FEATURE_CREATED,
|
||||
FEATURE_DELETED,
|
||||
FEATURE_ENVIRONMENT_DISABLED,
|
||||
FEATURE_ENVIRONMENT_ENABLED,
|
||||
FEATURE_ENVIRONMENT_VARIANTS_UPDATED,
|
||||
FEATURE_METADATA_UPDATED,
|
||||
FEATURE_POTENTIALLY_STALE_ON,
|
||||
FEATURE_PROJECT_CHANGE,
|
||||
@ -12,21 +34,247 @@ import {
|
||||
FEATURE_STRATEGY_ADD,
|
||||
FEATURE_STRATEGY_REMOVE,
|
||||
FEATURE_STRATEGY_UPDATE,
|
||||
FEATURE_UPDATED,
|
||||
FEATURE_VARIANTS_UPDATED,
|
||||
FEATURE_TAGGED,
|
||||
FEATURE_UNTAGGED,
|
||||
GROUP_CREATED,
|
||||
GROUP_DELETED,
|
||||
GROUP_UPDATED,
|
||||
IConstraint,
|
||||
IEvent,
|
||||
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,
|
||||
} from '../types';
|
||||
|
||||
interface IEventData {
|
||||
action: string;
|
||||
path?: string;
|
||||
}
|
||||
|
||||
interface IFormattedEventData {
|
||||
text: string;
|
||||
url?: string;
|
||||
}
|
||||
|
||||
export interface FeatureEventFormatter {
|
||||
format: (event: IEvent) => string;
|
||||
featureLink: (event: IEvent) => string;
|
||||
format: (event: IEvent) => IFormattedEventData;
|
||||
}
|
||||
export enum LinkStyle {
|
||||
SLACK = 0,
|
||||
MD = 1,
|
||||
}
|
||||
|
||||
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]: {
|
||||
action: '*{{user}}* disabled *{{feature}}* for the *{{event.environment}}* environment in project *{{project}}*',
|
||||
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',
|
||||
},
|
||||
[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',
|
||||
},
|
||||
};
|
||||
|
||||
export class FeatureEventFormatterMd implements FeatureEventFormatter {
|
||||
private readonly unleashUrl: string;
|
||||
|
||||
@ -37,174 +285,173 @@ export class FeatureEventFormatterMd implements FeatureEventFormatter {
|
||||
this.linkStyle = linkStyle;
|
||||
}
|
||||
|
||||
generateArchivedText(event: IEvent): string {
|
||||
const { createdBy, type } = event;
|
||||
const action = type === FEATURE_ARCHIVED ? 'archived' : 'revived';
|
||||
const feature = this.generateFeatureLink(event);
|
||||
return ` ${createdBy} just ${action} feature toggle *${feature}*`;
|
||||
}
|
||||
|
||||
generateFeatureLink(event: IEvent): string {
|
||||
if (this.linkStyle === LinkStyle.SLACK) {
|
||||
return `<${this.featureLink(event)}|${event.featureName}>`;
|
||||
} else {
|
||||
return `[${event.featureName}](${this.featureLink(event)})`;
|
||||
}
|
||||
}
|
||||
|
||||
generateStaleText(event: IEvent): string {
|
||||
const { createdBy, type } = event;
|
||||
const isStale = type === FEATURE_STALE_ON;
|
||||
const feature = this.generateFeatureLink(event);
|
||||
|
||||
if (isStale) {
|
||||
return `${createdBy} marked ${feature} as stale and this feature toggle is now *ready to be removed* from the code.`;
|
||||
}
|
||||
return `${createdBy} removed the stale marking on *${feature}*.`;
|
||||
}
|
||||
|
||||
generateEnvironmentToggleText(event: IEvent): string {
|
||||
const { createdBy, environment, type, project } = event;
|
||||
const toggleStatus =
|
||||
type === FEATURE_ENVIRONMENT_ENABLED ? 'enabled' : 'disabled';
|
||||
const feature = this.generateFeatureLink(event);
|
||||
return `${createdBy} *${toggleStatus}* ${feature} in *${environment}* environment in project *${project}*`;
|
||||
}
|
||||
|
||||
generateStrategyChangeText(event: IEvent): string {
|
||||
const { createdBy, environment, project, data, preData } = event;
|
||||
const feature = this.generateFeatureLink(event);
|
||||
const strategyText = () => {
|
||||
switch (data.name) {
|
||||
case 'flexibleRollout':
|
||||
return this.flexibleRolloutStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment,
|
||||
);
|
||||
case 'default':
|
||||
return this.defaultStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment,
|
||||
);
|
||||
case 'userWithId':
|
||||
return this.userWithIdStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment,
|
||||
);
|
||||
case 'remoteAddress':
|
||||
return this.remoteAddressStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment,
|
||||
);
|
||||
case 'applicationHostname':
|
||||
return this.applicationHostnameStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment,
|
||||
);
|
||||
default:
|
||||
return `by updating strategy ${data?.name} in *${environment}*`;
|
||||
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}`;
|
||||
}
|
||||
};
|
||||
|
||||
return `${createdBy} updated *${feature}* in project *${project}* ${strategyText()}`;
|
||||
}
|
||||
}
|
||||
|
||||
private applicationHostnameStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment: string | undefined,
|
||||
) {
|
||||
return this.listOfValuesStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment,
|
||||
'hostNames',
|
||||
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}`;
|
||||
}
|
||||
}
|
||||
|
||||
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)})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getStrategyTitle(event: IEvent): string | undefined {
|
||||
return (
|
||||
event.preData?.title ||
|
||||
event.data?.title ||
|
||||
event.preData?.name ||
|
||||
event.data?.name
|
||||
);
|
||||
}
|
||||
|
||||
private remoteAddressStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment: string | undefined,
|
||||
) {
|
||||
return this.listOfValuesStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment,
|
||||
'IPs',
|
||||
);
|
||||
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}*`;
|
||||
}
|
||||
};
|
||||
|
||||
return strategyText();
|
||||
}
|
||||
}
|
||||
|
||||
private userWithIdStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment: string | undefined,
|
||||
) {
|
||||
return this.listOfValuesStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment,
|
||||
'userIds',
|
||||
);
|
||||
private applicationHostnameStrategyChangeText(event: IEvent) {
|
||||
return this.listOfValuesStrategyChangeText(event, 'hostNames');
|
||||
}
|
||||
|
||||
private remoteAddressStrategyChangeText(event: IEvent) {
|
||||
return this.listOfValuesStrategyChangeText(event, 'IPs');
|
||||
}
|
||||
|
||||
private userWithIdStrategyChangeText(event: IEvent) {
|
||||
return this.listOfValuesStrategyChangeText(event, 'userIds');
|
||||
}
|
||||
|
||||
private listOfValuesStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment: string | undefined,
|
||||
event: IEvent,
|
||||
propertyName: string,
|
||||
) {
|
||||
const { preData, data, environment } = event;
|
||||
const userIdText = (values) =>
|
||||
values.length === 0
|
||||
? `empty set of ${propertyName}`
|
||||
: `[${values}]`;
|
||||
const usersText =
|
||||
preData.parameters[propertyName] === data.parameters[propertyName]
|
||||
preData?.parameters[propertyName] === data?.parameters[propertyName]
|
||||
? ''
|
||||
: !preData
|
||||
? ` ${propertyName} to ${userIdText(
|
||||
data?.parameters[propertyName],
|
||||
)}`
|
||||
: ` ${propertyName} from ${userIdText(
|
||||
preData.parameters[propertyName],
|
||||
)} to ${userIdText(data.parameters[propertyName])}`;
|
||||
)} to ${userIdText(data?.parameters[propertyName])}`;
|
||||
const constraintText = this.constraintChangeText(
|
||||
preData.constraints,
|
||||
data.constraints,
|
||||
preData?.constraints,
|
||||
data?.constraints,
|
||||
);
|
||||
const strategySpecificText = [usersText, constraintText]
|
||||
.filter((x) => x.length)
|
||||
.join(';');
|
||||
return `by updating strategy ${data?.name} in *${environment}*${strategySpecificText}`;
|
||||
return `by updating strategy *${this.getStrategyTitle(
|
||||
event,
|
||||
)}* in *${environment}*${strategySpecificText}`;
|
||||
}
|
||||
|
||||
private flexibleRolloutStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment: string | undefined,
|
||||
) {
|
||||
private flexibleRolloutStrategyChangeText(event: IEvent) {
|
||||
const { preData, data, environment } = event;
|
||||
const {
|
||||
rollout: oldRollout,
|
||||
stickiness: oldStickiness,
|
||||
groupId: oldGroupId,
|
||||
} = preData.parameters;
|
||||
const { rollout, stickiness, groupId } = data.parameters;
|
||||
} = preData?.parameters || {};
|
||||
const { rollout, stickiness, groupId } = data?.parameters || {};
|
||||
const stickinessText =
|
||||
oldStickiness === stickiness
|
||||
? ''
|
||||
: !oldStickiness
|
||||
? ` stickiness to ${stickiness}`
|
||||
: ` stickiness from ${oldStickiness} to ${stickiness}`;
|
||||
const rolloutText =
|
||||
oldRollout === rollout
|
||||
? ''
|
||||
: !oldRollout
|
||||
? ` rollout to ${rollout}%`
|
||||
: ` rollout from ${oldRollout}% to ${rollout}%`;
|
||||
const groupIdText =
|
||||
oldGroupId === groupId
|
||||
? ''
|
||||
: !oldGroupId
|
||||
? ` groupId to ${groupId}`
|
||||
: ` groupId from ${oldGroupId} to ${groupId}`;
|
||||
const constraintText = this.constraintChangeText(
|
||||
preData.constraints,
|
||||
data.constraints,
|
||||
preData?.constraints,
|
||||
data?.constraints,
|
||||
);
|
||||
const strategySpecificText = [
|
||||
stickinessText,
|
||||
@ -214,25 +461,24 @@ export class FeatureEventFormatterMd implements FeatureEventFormatter {
|
||||
]
|
||||
.filter((txt) => txt.length)
|
||||
.join(';');
|
||||
return `by updating strategy ${data?.name} in *${environment}*${strategySpecificText}`;
|
||||
return `by updating strategy *${this.getStrategyTitle(
|
||||
event,
|
||||
)}* in *${environment}*${strategySpecificText}`;
|
||||
}
|
||||
|
||||
private defaultStrategyChangeText(
|
||||
preData,
|
||||
data,
|
||||
environment: string | undefined,
|
||||
) {
|
||||
return `by updating strategy ${
|
||||
data?.name
|
||||
} in *${environment}*${this.constraintChangeText(
|
||||
preData.constraints,
|
||||
data.constraints,
|
||||
private defaultStrategyChangeText(event: IEvent) {
|
||||
const { preData, data, environment } = event;
|
||||
return `by updating strategy *${this.getStrategyTitle(
|
||||
event,
|
||||
)}* in *${environment}*${this.constraintChangeText(
|
||||
preData?.constraints,
|
||||
data?.constraints,
|
||||
)}`;
|
||||
}
|
||||
|
||||
private constraintChangeText(
|
||||
oldConstraints: IConstraint[],
|
||||
newConstraints: IConstraint[],
|
||||
oldConstraints: IConstraint[] = [],
|
||||
newConstraints: IConstraint[] = [],
|
||||
) {
|
||||
const formatConstraints = (constraints: IConstraint[]) => {
|
||||
const constraintOperatorDescriptions = {
|
||||
@ -255,7 +501,7 @@ export class FeatureEventFormatterMd implements FeatureEventFormatter {
|
||||
const formatConstraint = (constraint: IConstraint) => {
|
||||
const val = Object.hasOwn(constraint, 'value')
|
||||
? constraint.value
|
||||
: `(${constraint.values.join(',')})`;
|
||||
: `(${constraint.values?.join(',')})`;
|
||||
const operator = Object.hasOwn(
|
||||
constraintOperatorDescriptions,
|
||||
constraint.operator,
|
||||
@ -279,93 +525,35 @@ export class FeatureEventFormatterMd implements FeatureEventFormatter {
|
||||
: ` constraints from ${oldConstraintText} to ${newConstraintText}`;
|
||||
}
|
||||
|
||||
generateStrategyRemoveText(event: IEvent): string {
|
||||
const { createdBy, environment, project, preData } = event;
|
||||
const feature = this.generateFeatureLink(event);
|
||||
return `${createdBy} updated *${feature}* in project *${project}* by removing strategy ${preData?.name} in *${environment}*`;
|
||||
}
|
||||
format(event: IEvent): {
|
||||
text: string;
|
||||
url?: string;
|
||||
} {
|
||||
const { createdBy, type } = event;
|
||||
const { action, path } = EVENT_MAP[type] || {
|
||||
action: `triggered *${type}*`,
|
||||
};
|
||||
|
||||
generateStrategyAddText(event: IEvent): string {
|
||||
const { createdBy, environment, project, data } = event;
|
||||
const feature = this.generateFeatureLink(event);
|
||||
return `${createdBy} updated *${feature}* in project *${project}* by adding strategy ${data?.name} in *${environment}*`;
|
||||
}
|
||||
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),
|
||||
};
|
||||
|
||||
generateMetadataText(event: IEvent): string {
|
||||
const { createdBy, project } = event;
|
||||
const feature = this.generateFeatureLink(event);
|
||||
return `${createdBy} updated the metadata for ${feature} in project *${project}*`;
|
||||
}
|
||||
Mustache.escape = (text) => text;
|
||||
|
||||
generateProjectChangeText(event: IEvent): string {
|
||||
const { createdBy, project, featureName } = event;
|
||||
return `${createdBy} moved ${featureName} to ${project}`;
|
||||
}
|
||||
const text = Mustache.render(action, context);
|
||||
const url = path
|
||||
? `${this.unleashUrl}${Mustache.render(path, context)}`
|
||||
: undefined;
|
||||
|
||||
generateFeaturePotentiallyStaleOnText(event: IEvent): string {
|
||||
const { project, createdBy } = event;
|
||||
const feature = this.generateFeatureLink(event);
|
||||
|
||||
return `${createdBy} marked feature toggle *${feature}* (in project *${project}*) as *potentially stale*.`;
|
||||
}
|
||||
|
||||
featureLink(event: IEvent): string {
|
||||
const { type, project = '', featureName } = event;
|
||||
if (type === FEATURE_ARCHIVED) {
|
||||
if (project) {
|
||||
return `${this.unleashUrl}/projects/${project}/archive`;
|
||||
}
|
||||
return `${this.unleashUrl}/archive`;
|
||||
}
|
||||
|
||||
return `${this.unleashUrl}/projects/${project}/features/${featureName}`;
|
||||
}
|
||||
|
||||
getAction(type: string): string {
|
||||
switch (type) {
|
||||
case FEATURE_CREATED:
|
||||
return 'created';
|
||||
case FEATURE_UPDATED:
|
||||
return 'updated';
|
||||
case FEATURE_VARIANTS_UPDATED:
|
||||
return 'updated variants for';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
defaultText(event: IEvent): string {
|
||||
const { createdBy, project, type } = event;
|
||||
const action = this.getAction(type);
|
||||
const feature = this.generateFeatureLink(event);
|
||||
return `${createdBy} ${action} feature toggle ${feature} in project *${project}*`;
|
||||
}
|
||||
|
||||
format(event: IEvent): string {
|
||||
switch (event.type) {
|
||||
case FEATURE_ARCHIVED:
|
||||
case FEATURE_REVIVED:
|
||||
return this.generateArchivedText(event);
|
||||
case FEATURE_STALE_ON:
|
||||
case FEATURE_STALE_OFF:
|
||||
return this.generateStaleText(event);
|
||||
case FEATURE_ENVIRONMENT_DISABLED:
|
||||
case FEATURE_ENVIRONMENT_ENABLED:
|
||||
return this.generateEnvironmentToggleText(event);
|
||||
case FEATURE_STRATEGY_REMOVE:
|
||||
return this.generateStrategyRemoveText(event);
|
||||
case FEATURE_STRATEGY_ADD:
|
||||
return this.generateStrategyAddText(event);
|
||||
case FEATURE_STRATEGY_UPDATE:
|
||||
return this.generateStrategyChangeText(event);
|
||||
case FEATURE_METADATA_UPDATED:
|
||||
return this.generateMetadataText(event);
|
||||
case FEATURE_PROJECT_CHANGE:
|
||||
return this.generateProjectChangeText(event);
|
||||
case FEATURE_POTENTIALLY_STALE_ON:
|
||||
return this.generateFeaturePotentiallyStaleOnText(event);
|
||||
default:
|
||||
return this.defaultText(event);
|
||||
}
|
||||
return {
|
||||
text,
|
||||
url,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,42 @@ import {
|
||||
FEATURE_PROJECT_CHANGE,
|
||||
FEATURE_POTENTIALLY_STALE_ON,
|
||||
FEATURE_ENVIRONMENT_VARIANTS_UPDATED,
|
||||
FEATURE_DELETED,
|
||||
FEATURE_TAGGED,
|
||||
FEATURE_UNTAGGED,
|
||||
CONTEXT_FIELD_CREATED,
|
||||
CONTEXT_FIELD_UPDATED,
|
||||
CONTEXT_FIELD_DELETED,
|
||||
PROJECT_CREATED,
|
||||
PROJECT_DELETED,
|
||||
ADDON_CONFIG_CREATED,
|
||||
ADDON_CONFIG_UPDATED,
|
||||
ADDON_CONFIG_DELETED,
|
||||
USER_CREATED,
|
||||
USER_UPDATED,
|
||||
USER_DELETED,
|
||||
SEGMENT_CREATED,
|
||||
SEGMENT_UPDATED,
|
||||
SEGMENT_DELETED,
|
||||
GROUP_CREATED,
|
||||
GROUP_UPDATED,
|
||||
CHANGE_REQUEST_CREATED,
|
||||
CHANGE_REQUEST_DISCARDED,
|
||||
CHANGE_ADDED,
|
||||
CHANGE_DISCARDED,
|
||||
CHANGE_EDITED,
|
||||
CHANGE_REQUEST_REJECTED,
|
||||
CHANGE_REQUEST_APPROVED,
|
||||
CHANGE_REQUEST_APPROVAL_ADDED,
|
||||
CHANGE_REQUEST_CANCELLED,
|
||||
CHANGE_REQUEST_SENT_TO_REVIEW,
|
||||
CHANGE_REQUEST_APPLIED,
|
||||
API_TOKEN_CREATED,
|
||||
API_TOKEN_DELETED,
|
||||
SERVICE_ACCOUNT_CREATED,
|
||||
SERVICE_ACCOUNT_DELETED,
|
||||
SERVICE_ACCOUNT_UPDATED,
|
||||
GROUP_DELETED,
|
||||
} from '../types/events';
|
||||
import { IAddonDefinition } from '../types/model';
|
||||
|
||||
@ -49,20 +85,56 @@ const slackAppDefinition: IAddonDefinition = {
|
||||
},
|
||||
],
|
||||
events: [
|
||||
FEATURE_CREATED,
|
||||
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,
|
||||
FEATURE_ARCHIVED,
|
||||
FEATURE_REVIVED,
|
||||
FEATURE_STALE_ON,
|
||||
FEATURE_STALE_OFF,
|
||||
FEATURE_ENVIRONMENT_ENABLED,
|
||||
FEATURE_CREATED,
|
||||
FEATURE_DELETED,
|
||||
FEATURE_ENVIRONMENT_DISABLED,
|
||||
FEATURE_ENVIRONMENT_ENABLED,
|
||||
FEATURE_ENVIRONMENT_VARIANTS_UPDATED,
|
||||
FEATURE_METADATA_UPDATED,
|
||||
FEATURE_POTENTIALLY_STALE_ON,
|
||||
FEATURE_PROJECT_CHANGE,
|
||||
FEATURE_REVIVED,
|
||||
FEATURE_STALE_OFF,
|
||||
FEATURE_STALE_ON,
|
||||
FEATURE_STRATEGY_ADD,
|
||||
FEATURE_STRATEGY_REMOVE,
|
||||
FEATURE_STRATEGY_UPDATE,
|
||||
FEATURE_STRATEGY_ADD,
|
||||
FEATURE_METADATA_UPDATED,
|
||||
FEATURE_PROJECT_CHANGE,
|
||||
FEATURE_POTENTIALLY_STALE_ON,
|
||||
FEATURE_TAGGED,
|
||||
FEATURE_UNTAGGED,
|
||||
GROUP_CREATED,
|
||||
GROUP_DELETED,
|
||||
GROUP_UPDATED,
|
||||
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,
|
||||
],
|
||||
tagTypes: [
|
||||
{
|
||||
|
@ -7,6 +7,8 @@ import {
|
||||
WebAPIRequestError,
|
||||
WebAPIRateLimitedError,
|
||||
WebAPIHTTPError,
|
||||
KnownBlock,
|
||||
Block,
|
||||
} from '@slack/web-api';
|
||||
import Addon from './addon';
|
||||
|
||||
@ -78,36 +80,41 @@ export default class SlackAppAddon extends Addon {
|
||||
this.accessToken = accessToken;
|
||||
}
|
||||
|
||||
const text = this.msgFormatter.format(event);
|
||||
const url = this.msgFormatter.featureLink(event);
|
||||
const { text, url } = this.msgFormatter.format(event);
|
||||
|
||||
const blocks: (Block | KnownBlock)[] = [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (url) {
|
||||
blocks.push({
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
url,
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Open in Unleash',
|
||||
},
|
||||
value: 'featureToggle',
|
||||
style: 'primary',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
const requests = eventChannels.map((name) => {
|
||||
return this.slackClient!.chat.postMessage({
|
||||
channel: name,
|
||||
text,
|
||||
blocks: [
|
||||
{
|
||||
type: 'section',
|
||||
text: {
|
||||
type: 'mrkdwn',
|
||||
text,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'actions',
|
||||
elements: [
|
||||
{
|
||||
type: 'button',
|
||||
url,
|
||||
text: {
|
||||
type: 'plain_text',
|
||||
text: 'Open in Unleash',
|
||||
},
|
||||
value: 'featureToggle',
|
||||
style: 'primary',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
blocks,
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -47,8 +47,7 @@ export default class SlackAddon extends Addon {
|
||||
slackChannels.push(defaultChannel);
|
||||
}
|
||||
|
||||
const text = this.msgFormatter.format(event);
|
||||
const featureLink = this.msgFormatter.featureLink(event);
|
||||
const { text, url: featureLink } = this.msgFormatter.format(event);
|
||||
|
||||
const requests = slackChannels.map((channel) => {
|
||||
const body = {
|
||||
|
@ -27,8 +27,7 @@ export default class TeamsAddon extends Addon {
|
||||
): Promise<void> {
|
||||
const { url, customHeaders } = parameters;
|
||||
const { createdBy } = event;
|
||||
const text = this.msgFormatter.format(event);
|
||||
const featureLink = this.msgFormatter.featureLink(event);
|
||||
const { text, url: featureLink } = this.msgFormatter.format(event);
|
||||
|
||||
const body = {
|
||||
themeColor: '0076D7',
|
||||
|
@ -634,7 +634,7 @@ test('should store ADDON_CONFIG_REMOVE event', async () => {
|
||||
|
||||
expect(events.length).toBe(3);
|
||||
expect(events[2].type).toBe(ADDON_CONFIG_DELETED);
|
||||
expect(events[2].data.id).toBe(addonConfig.id);
|
||||
expect(events[2].preData.id).toBe(addonConfig.id);
|
||||
});
|
||||
|
||||
test('should hide sensitive fields when fetching', async () => {
|
||||
|
@ -12,6 +12,7 @@ import { IUnleashStores, IUnleashConfig } from '../types';
|
||||
import { IAddonDefinition } from '../types/model';
|
||||
import { minutesToMilliseconds } from 'date-fns';
|
||||
import EventService from './event-service';
|
||||
import { omitKeys } from '../util';
|
||||
|
||||
const SUPPORTED_EVENTS = Object.keys(events).map((k) => events[k]);
|
||||
|
||||
@ -205,7 +206,7 @@ export default class AddonService {
|
||||
await this.eventService.storeEvent({
|
||||
type: events.ADDON_CONFIG_CREATED,
|
||||
createdBy: userName,
|
||||
data: { provider: addonConfig.provider },
|
||||
data: omitKeys(createdAddon, 'parameters'),
|
||||
});
|
||||
|
||||
return createdAddon;
|
||||
@ -238,18 +239,20 @@ export default class AddonService {
|
||||
await this.eventService.storeEvent({
|
||||
type: events.ADDON_CONFIG_UPDATED,
|
||||
createdBy: userName,
|
||||
data: { id, provider: addonConfig.provider },
|
||||
preData: omitKeys(existingConfig, 'parameters'),
|
||||
data: omitKeys(result, 'parameters'),
|
||||
});
|
||||
this.logger.info(`User ${userName} updated addon ${id}`);
|
||||
return result;
|
||||
}
|
||||
|
||||
async removeAddon(id: number, userName: string): Promise<void> {
|
||||
const existingConfig = await this.addonStore.get(id);
|
||||
await this.addonStore.delete(id);
|
||||
await this.eventService.storeEvent({
|
||||
type: events.ADDON_CONFIG_DELETED,
|
||||
createdBy: userName,
|
||||
data: { id },
|
||||
preData: omitKeys(existingConfig, 'parameters'),
|
||||
});
|
||||
this.logger.info(`User ${userName} removed addon ${id}`);
|
||||
}
|
||||
|
@ -130,8 +130,9 @@ class ContextService {
|
||||
updatedContextField: IContextFieldDto,
|
||||
userName: string,
|
||||
): Promise<void> {
|
||||
// validations
|
||||
await this.contextFieldStore.get(updatedContextField.name);
|
||||
const contextField = await this.contextFieldStore.get(
|
||||
updatedContextField.name,
|
||||
);
|
||||
const value = await contextSchema.validateAsync(updatedContextField);
|
||||
|
||||
// update
|
||||
@ -139,20 +140,20 @@ class ContextService {
|
||||
await this.eventService.storeEvent({
|
||||
type: CONTEXT_FIELD_UPDATED,
|
||||
createdBy: userName,
|
||||
preData: contextField,
|
||||
data: value,
|
||||
});
|
||||
}
|
||||
|
||||
async deleteContextField(name: string, userName: string): Promise<void> {
|
||||
// validate existence
|
||||
await this.contextFieldStore.get(name);
|
||||
const contextField = await this.contextFieldStore.get(name);
|
||||
|
||||
// delete
|
||||
await this.contextFieldStore.delete(name);
|
||||
await this.eventService.storeEvent({
|
||||
type: CONTEXT_FIELD_DELETED,
|
||||
createdBy: userName,
|
||||
data: { name },
|
||||
preData: contextField,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -122,7 +122,7 @@ class FeatureTagService {
|
||||
createdBy: userName,
|
||||
featureName: featureToggle.name,
|
||||
project: featureToggle.project,
|
||||
data: removedTag,
|
||||
preData: removedTag,
|
||||
})),
|
||||
);
|
||||
|
||||
@ -171,7 +171,7 @@ class FeatureTagService {
|
||||
createdBy: userName,
|
||||
featureName,
|
||||
project: featureToggle.project,
|
||||
data: tag,
|
||||
preData: tag,
|
||||
tags,
|
||||
});
|
||||
}
|
||||
|
@ -176,7 +176,7 @@ export class GroupService {
|
||||
await this.eventService.storeEvent({
|
||||
type: GROUP_DELETED,
|
||||
createdBy: userName,
|
||||
data: group,
|
||||
preData: group,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -161,7 +161,7 @@ export class SegmentService implements ISegmentService {
|
||||
await this.eventService.storeEvent({
|
||||
type: SEGMENT_DELETED,
|
||||
createdBy: user.email || user.username,
|
||||
data: segment,
|
||||
preData: segment,
|
||||
});
|
||||
}
|
||||
|
||||
@ -171,7 +171,7 @@ export class SegmentService implements ISegmentService {
|
||||
await this.eventService.storeEvent({
|
||||
type: SEGMENT_DELETED,
|
||||
createdBy: user.email || user.username,
|
||||
data: segment,
|
||||
preData: segment,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -293,7 +293,7 @@ test('should store segment-created and segment-deleted events', async () => {
|
||||
const events = await db.stores.eventStore.getEvents();
|
||||
|
||||
expect(events[0].type).toEqual('segment-deleted');
|
||||
expect(events[0].data.id).toEqual(segment1.id);
|
||||
expect(events[0].preData.id).toEqual(segment1.id);
|
||||
expect(events[1].type).toEqual('segment-created');
|
||||
expect(events[1].data.id).toEqual(segment1.id);
|
||||
});
|
||||
|
@ -32,20 +32,56 @@ The configuration settings allow you to choose the events you're interested in a
|
||||
|
||||
You can choose to trigger updates for the following events:
|
||||
|
||||
- feature-created
|
||||
- feature-metadata-updated
|
||||
- feature-project-change
|
||||
- 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
|
||||
- feature-archived
|
||||
- feature-created
|
||||
- feature-deleted
|
||||
- feature-environment-disabled
|
||||
- feature-environment-enabled
|
||||
- feature-environment-variants-updated
|
||||
- feature-metadata-updated
|
||||
- feature-potentially-stale-on
|
||||
- feature-project-change
|
||||
- feature-revived
|
||||
- feature-strategy-update
|
||||
- feature-stale-off
|
||||
- feature-stale-on
|
||||
- feature-strategy-add
|
||||
- feature-strategy-remove
|
||||
- feature-stale-on
|
||||
- feature-stale-off
|
||||
- feature-environment-enabled
|
||||
- feature-environment-disabled
|
||||
- feature-environment-variants-updated
|
||||
- feature-potentially-stale-on
|
||||
- feature-strategy-update
|
||||
- feature-tagged
|
||||
- feature-untagged
|
||||
- group-created
|
||||
- group-deleted
|
||||
- group-updated
|
||||
- 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
|
||||
|
||||
#### Parameters {#parameters}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user