|
@@ -13,7 +13,9 @@ import {NOTIFICATION_SETTING_FIELDS} from 'app/views/settings/account/notificati
|
|
|
import ParentLabel from 'app/views/settings/account/notifications/parentLabel';
|
|
|
import {FieldObject} from 'app/views/settings/components/forms/type';
|
|
|
|
|
|
-// Which fine-tuning parts are grouped by project
|
|
|
+/**
|
|
|
+ * Which fine-tuning parts are grouped by project
|
|
|
+ */
|
|
|
export const isGroupedByProject = (notificationType: string): boolean =>
|
|
|
['alerts', 'email', 'workflow'].includes(notificationType);
|
|
|
|
|
@@ -69,20 +71,19 @@ export const getChoiceString = (choices: string[][], key: string): string => {
|
|
|
return found[1];
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Transform `data`, a mapping of providers to values, so that all providers in
|
|
|
+ * `providerList` are "on" in the resulting object. The "on" value is
|
|
|
+ * determined by checking `data` for non-"never" values and falling back to the
|
|
|
+ * value `fallbackValue`. The "off" value is either "default" or "never"
|
|
|
+ * depending on whether `scopeType` is "parent" or "user" respectively.
|
|
|
+ */
|
|
|
export const backfillMissingProvidersWithFallback = (
|
|
|
data: {[key: string]: string},
|
|
|
providerList: string[],
|
|
|
fallbackValue: string,
|
|
|
scopeType: string
|
|
|
): NotificationSettingsByProviderObject => {
|
|
|
- /**
|
|
|
- * Transform `data`, a mapping of providers to values, so that all providers
|
|
|
- * in `providerList` are "on" in the resulting object. The "on" value is
|
|
|
- * determined by checking `data` for non-"never" values and falling back to
|
|
|
- * the value `fallbackValue`. The "off" value is either "default" or "never"
|
|
|
- * depending on whether `scopeType` is "parent" or "user" respectively.
|
|
|
- */
|
|
|
-
|
|
|
// First pass: determine the fallback value.
|
|
|
const fallback = Object.values(data).reduce(
|
|
|
(previousValue, currentValue) =>
|
|
@@ -102,10 +103,12 @@ export const backfillMissingProvidersWithFallback = (
|
|
|
);
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Deeply merge N notification settings objects (usually just 2).
|
|
|
+ */
|
|
|
export const mergeNotificationSettings = (
|
|
|
...objects: NotificationSettingsObject[]
|
|
|
): NotificationSettingsObject => {
|
|
|
- /** Deeply merge N notification settings objects (usually just 2). */
|
|
|
const output = {};
|
|
|
objects.map(settingsByType =>
|
|
|
Object.entries(settingsByType).map(([type, settingsByScopeType]) =>
|
|
@@ -120,15 +123,15 @@ export const mergeNotificationSettings = (
|
|
|
return output;
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Get the mapping of providers to values that describe a user's parent-
|
|
|
+ * independent notification preferences. The data from the API uses the user ID
|
|
|
+ * rather than "me" so we assume the first ID is the user's.
|
|
|
+ */
|
|
|
export const getUserDefaultValues = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject
|
|
|
): NotificationSettingsByProviderObject => {
|
|
|
- /**
|
|
|
- * Get the mapping of providers to values that describe a user's parent-
|
|
|
- * independent notification preferences. The data from the API uses the user
|
|
|
- * ID rather than "me" so we assume the first ID is the user's.
|
|
|
- */
|
|
|
return (
|
|
|
Object.values(notificationSettings[notificationType]?.user || {}).pop() ||
|
|
|
Object.fromEntries(
|
|
@@ -140,11 +143,13 @@ export const getUserDefaultValues = (
|
|
|
);
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Get the list of providers currently active on this page. Note: this can be empty.
|
|
|
+ */
|
|
|
export const getCurrentProviders = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject
|
|
|
): string[] => {
|
|
|
- /** Get the list of providers currently active on this page. Note: this can be empty. */
|
|
|
const userData = getUserDefaultValues(notificationType, notificationSettings);
|
|
|
|
|
|
return Object.entries(userData)
|
|
@@ -152,28 +157,28 @@ export const getCurrentProviders = (
|
|
|
.map(([provider, _]) => provider);
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Calculate the currently selected provider.
|
|
|
+ */
|
|
|
export const getCurrentDefault = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject
|
|
|
): string => {
|
|
|
- /** Calculate the currently selected provider. */
|
|
|
-
|
|
|
const providersList = getCurrentProviders(notificationType, notificationSettings);
|
|
|
return providersList.length
|
|
|
? getUserDefaultValues(notificationType, notificationSettings)[providersList[0]]
|
|
|
: 'never';
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * For a given notificationType, are the parent-independent setting "never" for
|
|
|
+ * all providers and are the parent-specific settings "default" or "never". If
|
|
|
+ * so, the API is telling us that the user has opted out of all notifications.
|
|
|
+ */
|
|
|
export const decideDefault = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject
|
|
|
): string => {
|
|
|
- /**
|
|
|
- * For a given notificationType, are the parent-independent setting "never"
|
|
|
- * for all providers and are the parent-specific settings "default" or
|
|
|
- * "never". If so, the API is telling us that the user has opted out of
|
|
|
- * all notifications.
|
|
|
- */
|
|
|
const compare = (a: string, b: string): number => VALUE_MAPPING[a] - VALUE_MAPPING[b];
|
|
|
|
|
|
const parentIndependentSetting =
|
|
@@ -196,55 +201,50 @@ export const decideDefault = (
|
|
|
return parentSpecificSetting === 'default' ? 'never' : parentSpecificSetting;
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * For a given notificationType, are the parent-independent setting "never" for
|
|
|
+ * all providers and are the parent-specific settings "default" or "never"? If
|
|
|
+ * so, the API is telling us that the user has opted out of all notifications.
|
|
|
+ */
|
|
|
export const isEverythingDisabled = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject
|
|
|
-): boolean => {
|
|
|
- /**
|
|
|
- * For a given notificationType, are the parent-independent setting "never"
|
|
|
- * for all providers and are the parent-specific settings "default" or
|
|
|
- * "never"? If so, the API is telling us that the user has opted out of
|
|
|
- * all notifications.
|
|
|
- */
|
|
|
- return ['never', 'default'].includes(
|
|
|
- decideDefault(notificationType, notificationSettings)
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
+): boolean =>
|
|
|
+ ['never', 'default'].includes(decideDefault(notificationType, notificationSettings));
|
|
|
+
|
|
|
+/**
|
|
|
+ * Extract either the list of project or organization IDs from the notification
|
|
|
+ * settings in state. This assumes that the notification settings object is
|
|
|
+ * fully backfilled with settings for every parent.
|
|
|
+ */
|
|
|
export const getParentIds = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject
|
|
|
-): string[] => {
|
|
|
- /**
|
|
|
- * Extract either the list of project or organization IDs from the
|
|
|
- * notification settings in state. This assumes that the notification settings
|
|
|
- * object is fully backfilled with settings for every parent.
|
|
|
- */
|
|
|
- return Object.keys(
|
|
|
+): string[] =>
|
|
|
+ Object.keys(
|
|
|
notificationSettings[notificationType]?.[getParentKey(notificationType)] || {}
|
|
|
);
|
|
|
-};
|
|
|
|
|
|
export const getParentValues = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject,
|
|
|
parentId: string
|
|
|
-): NotificationSettingsByProviderObject => {
|
|
|
- return (
|
|
|
- notificationSettings[notificationType]?.[getParentKey(notificationType)]?.[
|
|
|
- parentId
|
|
|
- ] || {
|
|
|
- email: 'default',
|
|
|
- }
|
|
|
- );
|
|
|
-};
|
|
|
+): NotificationSettingsByProviderObject =>
|
|
|
+ notificationSettings[notificationType]?.[getParentKey(notificationType)]?.[
|
|
|
+ parentId
|
|
|
+ ] || {
|
|
|
+ email: 'default',
|
|
|
+ };
|
|
|
|
|
|
+/**
|
|
|
+ * Get a mapping of all parent IDs to the notification setting for the current
|
|
|
+ * providers.
|
|
|
+ */
|
|
|
export const getParentData = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject,
|
|
|
parents: OrganizationSummary[] | Project[]
|
|
|
): NotificationSettingsByProviderObject => {
|
|
|
- /** Get a mapping of all parent IDs to the notification setting for the current providers. */
|
|
|
const provider = getCurrentProviders(notificationType, notificationSettings)[0];
|
|
|
|
|
|
return Object.fromEntries(
|
|
@@ -255,54 +255,31 @@ export const getParentData = (
|
|
|
);
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Are there are more than N project or organization settings?
|
|
|
+ */
|
|
|
export const isSufficientlyComplex = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject
|
|
|
-): boolean => {
|
|
|
- /** Are there are more than N project or organization settings? */
|
|
|
- return (
|
|
|
- getParentIds(notificationType, notificationSettings).length >
|
|
|
- MIN_PROJECTS_FOR_CONFIRMATION
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
+): boolean =>
|
|
|
+ getParentIds(notificationType, notificationSettings).length >
|
|
|
+ MIN_PROJECTS_FOR_CONFIRMATION;
|
|
|
+
|
|
|
+/**
|
|
|
+ * I don't need to update the provider for EVERY once of the user's projects
|
|
|
+ * and organizations, just the user and parents that have explicit settings.
|
|
|
+ */
|
|
|
export const getStateToPutForProvider = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject,
|
|
|
changedData: NotificationSettingsByProviderObject
|
|
|
): NotificationSettingsObject => {
|
|
|
- /**
|
|
|
- * I don't need to update the provider for EVERY once of the user's projects
|
|
|
- * and organizations, just the user and parents that have explicit settings.
|
|
|
- */
|
|
|
const providerList: string[] = changedData.provider.split('+');
|
|
|
const fallbackValue = getFallBackValue(notificationType);
|
|
|
|
|
|
- let updatedNotificationSettings;
|
|
|
- if (Object.keys(notificationSettings).length) {
|
|
|
- updatedNotificationSettings = {
|
|
|
- [notificationType]: Object.fromEntries(
|
|
|
- Object.entries(notificationSettings[notificationType]).map(
|
|
|
- ([scopeType, scopeTypeData]) => [
|
|
|
- scopeType,
|
|
|
- Object.fromEntries(
|
|
|
- Object.entries(scopeTypeData).map(([scopeId, scopeIdData]) => [
|
|
|
- scopeId,
|
|
|
- backfillMissingProvidersWithFallback(
|
|
|
- scopeIdData,
|
|
|
- providerList,
|
|
|
- fallbackValue,
|
|
|
- scopeType
|
|
|
- ),
|
|
|
- ])
|
|
|
- ),
|
|
|
- ]
|
|
|
- )
|
|
|
- ),
|
|
|
- };
|
|
|
- } else {
|
|
|
- // If the user has no settings, we need to create them.
|
|
|
- updatedNotificationSettings = {
|
|
|
+ // If the user has no settings, we need to create them.
|
|
|
+ if (!Object.keys(notificationSettings).length) {
|
|
|
+ return {
|
|
|
[notificationType]: {
|
|
|
user: {
|
|
|
me: Object.fromEntries(providerList.map(provider => [provider, fallbackValue])),
|
|
@@ -311,22 +288,40 @@ export const getStateToPutForProvider = (
|
|
|
};
|
|
|
}
|
|
|
|
|
|
- return updatedNotificationSettings;
|
|
|
+ return {
|
|
|
+ [notificationType]: Object.fromEntries(
|
|
|
+ Object.entries(notificationSettings[notificationType]).map(
|
|
|
+ ([scopeType, scopeTypeData]) => [
|
|
|
+ scopeType,
|
|
|
+ Object.fromEntries(
|
|
|
+ Object.entries(scopeTypeData).map(([scopeId, scopeIdData]) => [
|
|
|
+ scopeId,
|
|
|
+ backfillMissingProvidersWithFallback(
|
|
|
+ scopeIdData,
|
|
|
+ providerList,
|
|
|
+ fallbackValue,
|
|
|
+ scopeType
|
|
|
+ ),
|
|
|
+ ])
|
|
|
+ ),
|
|
|
+ ]
|
|
|
+ )
|
|
|
+ ),
|
|
|
+ };
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Update the current providers' parent-independent notification settings with
|
|
|
+ * the new value. If the new value is "never", then also update all
|
|
|
+ * parent-specific notification settings to "default". If the previous value
|
|
|
+ * was "never", then assume providerList should be "email" only.
|
|
|
+ */
|
|
|
export const getStateToPutForDefault = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject,
|
|
|
changedData: NotificationSettingsByProviderObject,
|
|
|
parentIds: string[]
|
|
|
): NotificationSettingsObject => {
|
|
|
- /**
|
|
|
- * Update the current providers' parent-independent notification settings
|
|
|
- * with the new value. If the new value is "never", then also update all
|
|
|
- * parent-specific notification settings to "default". If the previous value
|
|
|
- * was "never", then assume providerList should be "email" only.
|
|
|
- */
|
|
|
-
|
|
|
const newValue = Object.values(changedData)[0];
|
|
|
let providerList = getCurrentProviders(notificationType, notificationSettings);
|
|
|
if (!providerList.length) {
|
|
@@ -354,14 +349,15 @@ export const getStateToPutForDefault = (
|
|
|
return updatedNotificationSettings;
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Get the diff of the Notification Settings for this parent ID.
|
|
|
+ */
|
|
|
export const getStateToPutForParent = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject,
|
|
|
changedData: NotificationSettingsByProviderObject,
|
|
|
parentId: string
|
|
|
): NotificationSettingsObject => {
|
|
|
- /** Get the diff of the Notification Settings for this parent ID. */
|
|
|
-
|
|
|
const providerList = getCurrentProviders(notificationType, notificationSettings);
|
|
|
const newValue = Object.values(changedData)[0];
|
|
|
|
|
@@ -376,6 +372,9 @@ export const getStateToPutForParent = (
|
|
|
};
|
|
|
};
|
|
|
|
|
|
+/**
|
|
|
+ * Render each parent and add a default option to the the field choices.
|
|
|
+ */
|
|
|
export const getParentField = (
|
|
|
notificationType: string,
|
|
|
notificationSettings: NotificationSettingsObject,
|
|
@@ -385,8 +384,6 @@ export const getParentField = (
|
|
|
parentId: string
|
|
|
) => NotificationSettingsObject
|
|
|
): FieldObject => {
|
|
|
- /** Render each parent and add a default option to the the field choices. */
|
|
|
-
|
|
|
const defaultFields = NOTIFICATION_SETTING_FIELDS[notificationType];
|
|
|
|
|
|
return Object.assign({}, defaultFields, {
|