diff --git a/Makefile b/Makefile index c47248a2a..2972f37d2 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,12 @@ gen-log-api: .PHONY: lint lint: npm run "lint" + npm run "lint-ts" + + +.PHONY: lint-strict +lint-strict: + npm run "lint-strict" .PHONY: run run: diff --git a/package-lock.json b/package-lock.json index 43166b16f..ca07bfd08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,6 @@ "clsx": "^2.0.0", "date-fns": "^3.0.5", "http-status-codes": "^2.3.0", - "immutability-helper": "^3.1.1", "jdenticon": "^3.2.0", "lodash": "^4.17.21", "nanoid": "^5.0.4", @@ -5457,11 +5456,6 @@ "url": "https://opencollective.com/immer" } }, - "node_modules/immutability-helper": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/immutability-helper/-/immutability-helper-3.1.1.tgz", - "integrity": "sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ==" - }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", diff --git a/package.json b/package.json index 4bb568720..532915feb 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,10 @@ "test:no-watch": "EXTEND_ESLINT=true vitest run", "test:coverage": "EXTEND_ESLINT=true vitest --coverage", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint-ts": "tsc --noEmit --skipLibCheck", "lint:watch": "eslint -w src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint-ts:watch": "tsc --noEmit --skipLibCheck --watch", + "lint-strict": "tsc --noEmit --skipLibCheck --strictNullChecks", "deps": "npm run deps:license && npm run deps:stale", "deps:license": "node scripts/deps-license-check.js", "deps:stale": "node scripts/deps-stale-check.js", @@ -32,7 +35,6 @@ "clsx": "^2.0.0", "date-fns": "^3.0.5", "http-status-codes": "^2.3.0", - "immutability-helper": "^3.1.1", "jdenticon": "^3.2.0", "lodash": "^4.17.21", "nanoid": "^5.0.4", diff --git a/src/api/application-alerting.ts b/src/api/application-alerting.ts deleted file mode 100644 index 7669a75a3..000000000 --- a/src/api/application-alerting.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { createRadixApiUrl } from './api-config'; -import { postJson, putJson } from './api-helpers'; -import { apiResources } from './resources'; - -import { RawModel } from '../models/model-types'; -import { AlertingConfigModel } from '../models/radix-api/alerting/alerting-config'; -import { UpdateAlertingConfigModel } from '../models/radix-api/alerting/update-alerting-config'; - -export const api = { - enableAlerting: async ({ - appName, - }: { - appName: string; - }): Promise> => { - const encAppName = encodeURIComponent(appName); - - return await postJson, never>( - createRadixApiUrl( - `${apiResources.APPLICATION_ALERTING.makeUrl(encAppName)}/enable` - ) - ); - }, - - disableAlerting: async ({ - appName, - }: { - appName: string; - }): Promise> => { - const encAppName = encodeURIComponent(appName); - - return await postJson, never>( - createRadixApiUrl( - `${apiResources.APPLICATION_ALERTING.makeUrl(encAppName)}/disable` - ) - ); - }, - - updateAlerting: async ({ - appName, - request, - }: { - appName: string; - request: UpdateAlertingConfigModel; - }): Promise> => { - const encAppName = encodeURIComponent(appName); - - return await putJson>( - createRadixApiUrl( - `${apiResources.APPLICATION_ALERTING.makeUrl(encAppName)}` - ), - null, - JSON.stringify(request) - ); - }, -}; diff --git a/src/api/environment-alerting.ts b/src/api/environment-alerting.ts deleted file mode 100644 index 58eda9f84..000000000 --- a/src/api/environment-alerting.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { createRadixApiUrl } from './api-config'; -import { postJson, putJson } from './api-helpers'; -import { apiResources } from './resources'; - -import { RawModel } from '../models/model-types'; -import { AlertingConfigModel } from '../models/radix-api/alerting/alerting-config'; -import { UpdateAlertingConfigModel } from '../models/radix-api/alerting/update-alerting-config'; - -export const api = { - enableAlerting: async ({ - appName, - envName, - }: { - appName: string; - envName: string; - }): Promise> => { - const encAppName = encodeURIComponent(appName); - const encEnvName = encodeURIComponent(envName); - - return await postJson, never>( - createRadixApiUrl( - `${apiResources.ENVIRONMENT_ALERTING.makeUrl( - encAppName, - encEnvName - )}/enable` - ) - ); - }, - - disableAlerting: async ({ - appName, - envName, - }: { - appName: string; - envName: string; - }): Promise> => { - const encAppName = encodeURIComponent(appName); - const encEnvName = encodeURIComponent(envName); - - return await postJson, never>( - createRadixApiUrl( - `${apiResources.ENVIRONMENT_ALERTING.makeUrl( - encAppName, - encEnvName - )}/disable` - ) - ); - }, - - updateAlerting: async ({ - appName, - envName, - request, - }: { - appName: string; - envName: string; - request: UpdateAlertingConfigModel; - }): Promise> => { - const encAppName = encodeURIComponent(appName); - const encEnvName = encodeURIComponent(envName); - - return await putJson>( - createRadixApiUrl( - `${apiResources.ENVIRONMENT_ALERTING.makeUrl(encAppName, encEnvName)}` - ), - null, - JSON.stringify(request) - ); - }, -}; diff --git a/src/components/alerting/alerting-actions.tsx b/src/components/alerting/alerting-actions.tsx index 30de4a9d0..bec79c4fb 100644 --- a/src/components/alerting/alerting-actions.tsx +++ b/src/components/alerting/alerting-actions.tsx @@ -1,111 +1,80 @@ import { Button } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { FunctionComponent } from 'react'; - -import { - AlertingConfigModel, - AlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/alerting-config'; -import { - UpdateAlertingConfigModel, - UpdateAlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/update-alerting-config'; import './style.css'; +import { AlertingConfig } from '../../store/radix-api'; -export const AlertingActions: FunctionComponent<{ - config: AlertingConfigModel; - editConfig?: UpdateAlertingConfigModel; - enableAlertingCallback: () => void; - disableAlertingCallback: () => void; - editAlertingEnableCallback: () => void; - editAlertingDisableCallback: () => void; - saveAlertingCallback: () => void; +type Props = { + config: AlertingConfig; isSaving: boolean; - isAlertingEditEnabled: boolean; - isAlertingEditDirty: boolean; -}> = ({ - config, + isEdit: boolean; + onEdit: () => void; + onSave: () => void; + onCancel: () => void; + onEnable: () => void; + onDisable: () => void; +}; + +export const AlertingActions = ({ + isEdit, isSaving, - enableAlertingCallback, - disableAlertingCallback, - editAlertingEnableCallback, - editAlertingDisableCallback, - saveAlertingCallback, - isAlertingEditEnabled, - isAlertingEditDirty, -}) => ( -
-
- {config.enabled ? ( - <> - {isAlertingEditEnabled ? ( - <> - - + + + ) : ( + - - ) : ( - - )} - - ) : ( - - )} -
+ )} + + ) : ( + + )} +
-
- {config.enabled && ( - - )} +
+ {config.enabled && ( + + )} +
- -); + ); +}; AlertingActions.propTypes = { - config: PropTypes.shape(AlertingConfigModelValidationMap) - .isRequired as PropTypes.Validator, - editConfig: PropTypes.shape( - UpdateAlertingConfigModelValidationMap - ) as PropTypes.Validator, - enableAlertingCallback: PropTypes.func.isRequired, - disableAlertingCallback: PropTypes.func.isRequired, - editAlertingEnableCallback: PropTypes.func.isRequired, - editAlertingDisableCallback: PropTypes.func.isRequired, - saveAlertingCallback: PropTypes.func.isRequired, isSaving: PropTypes.bool.isRequired, - isAlertingEditEnabled: PropTypes.bool.isRequired, - isAlertingEditDirty: PropTypes.bool.isRequired, + isEdit: PropTypes.bool.isRequired, + config: PropTypes.object.isRequired as PropTypes.Validator, + + onSave: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + onEdit: PropTypes.func.isRequired, + onEnable: PropTypes.func.isRequired, + onDisable: PropTypes.func.isRequired, }; diff --git a/src/components/alerting/alerting-overview.tsx b/src/components/alerting/alerting-overview.tsx index 9199e1030..1b5745549 100644 --- a/src/components/alerting/alerting-overview.tsx +++ b/src/components/alerting/alerting-overview.tsx @@ -2,43 +2,39 @@ import { Icon, Typography } from '@equinor/eds-core-react'; import { warning_outlined, check_circle_outlined } from '@equinor/eds-icons'; import * as PropTypes from 'prop-types'; import { Fragment, FunctionComponent } from 'react'; - -import { - AlertingConfigModel, - AlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/alerting-config'; - -const AlertingSlackStatus: FunctionComponent<{ - config: AlertingConfigModel; -}> = ({ config }) => ( - <> - {Object.entries(config.receiverSecretStatus ?? {}).map((v) => ( - - {v[1].slackConfig.webhookUrlConfigured ? ( -
- - Slack webhook URL is configured. -
- ) : ( -
- - - Missing required Slack webhook URL. Radix cannot send alerts until - the webhook is configured. - -
- )} -
- ))} - -); +import { AlertingConfig } from '../../store/radix-api'; export const AlertingConfigStatus: FunctionComponent<{ - config: AlertingConfigModel; -}> = ({ config }) => - config.enabled && config.ready && ; + config: AlertingConfig; +}> = ({ config }) => { + if (!config.enabled || !config.ready) { + return null; + } + + return ( + <> + {Object.entries(config.receiverSecretStatus ?? {}).map(([key, value]) => ( + + {value.slackConfig?.webhookUrlConfigured ? ( +
+ + Slack webhook URL is configured. +
+ ) : ( +
+ + + Missing required Slack webhook URL. Radix cannot send alerts + until the webhook is configured. + +
+ )} +
+ ))} + + ); +}; AlertingConfigStatus.propTypes = { - config: PropTypes.shape(AlertingConfigModelValidationMap) - .isRequired as PropTypes.Validator, + config: PropTypes.object.isRequired as PropTypes.Validator, }; diff --git a/src/components/alerting/buildEditConfig.ts b/src/components/alerting/buildEditConfig.ts new file mode 100644 index 000000000..8d3348fa5 --- /dev/null +++ b/src/components/alerting/buildEditConfig.ts @@ -0,0 +1,32 @@ +import { + AlertingConfig, + ReceiverConfigMap, + UpdateAlertingConfig, +} from '../../store/radix-api'; +import { cloneDeep } from 'lodash'; + +const buildReceiverSecrets = (receviers: ReceiverConfigMap) => { + const secretsConfig = {}; + if (!receviers) { + return secretsConfig; + } + + for (const [receiverName, receiver] of Object.entries(receviers)) { + secretsConfig[receiverName] = {}; + if (receiver.slackConfig) { + secretsConfig[receiverName]['slackConfig'] = { webhookUrl: undefined }; + } + } + + return secretsConfig; +}; + +export const buildEditConfig = ( + config: AlertingConfig +): UpdateAlertingConfig => { + return { + alerts: cloneDeep(config.alerts), + receivers: cloneDeep(config.receivers), + receiverSecrets: buildReceiverSecrets(config.receivers), + }; +}; diff --git a/src/components/alerting/dev.tsx b/src/components/alerting/dev.tsx index bc57039e3..a6e67b627 100644 --- a/src/components/alerting/dev.tsx +++ b/src/components/alerting/dev.tsx @@ -1,35 +1,26 @@ -import { Fragment } from 'react'; +import { ComponentProps, Fragment } from 'react'; -import { Alerting, AlertingProps } from '.'; - -import { RequestState } from '../../state/state-utils/request-states'; - -const noopFunc = () => void 0; +import { Alerting } from '.'; +const noopFunc = async () => {}; +type AlertingProps = ComponentProps; const testData: Array = [ { + isSaving: false, alertingConfig: { enabled: false, ready: false }, - isAlertingEditEnabled: false, - isAlertingEditDirty: false, disableAlerting: noopFunc, - editAlertingDisable: noopFunc, - editAlertingEnable: noopFunc, - editAlertingSetSlackUrl: noopFunc, enableAlerting: noopFunc, updateAlerting: noopFunc, }, { + isSaving: false, alertingConfig: { enabled: true, ready: false }, - isAlertingEditEnabled: false, - isAlertingEditDirty: false, disableAlerting: noopFunc, - editAlertingDisable: noopFunc, - editAlertingEnable: noopFunc, - editAlertingSetSlackUrl: noopFunc, enableAlerting: noopFunc, updateAlerting: noopFunc, }, { + isSaving: false, alertingConfig: { enabled: true, ready: true, @@ -37,16 +28,12 @@ const testData: Array = [ slack1: { slackConfig: { webhookUrlConfigured: false } }, }, }, - isAlertingEditEnabled: false, - isAlertingEditDirty: false, disableAlerting: noopFunc, - editAlertingDisable: noopFunc, - editAlertingEnable: noopFunc, - editAlertingSetSlackUrl: noopFunc, enableAlerting: noopFunc, updateAlerting: noopFunc, }, { + isSaving: false, alertingConfig: { enabled: true, ready: true, @@ -54,29 +41,19 @@ const testData: Array = [ slack1: { slackConfig: { webhookUrlConfigured: true } }, }, }, - isAlertingEditEnabled: false, - isAlertingEditDirty: false, disableAlerting: noopFunc, - editAlertingDisable: noopFunc, - editAlertingEnable: noopFunc, - editAlertingSetSlackUrl: noopFunc, enableAlerting: noopFunc, updateAlerting: noopFunc, }, { + isSaving: false, alertingConfig: { enabled: false, ready: false }, - isAlertingEditEnabled: false, - isAlertingEditDirty: false, - enableAlertingRequestState: RequestState.FAILURE, - enableAlertingLastError: 'enable error', disableAlerting: noopFunc, - editAlertingDisable: noopFunc, - editAlertingEnable: noopFunc, - editAlertingSetSlackUrl: noopFunc, enableAlerting: noopFunc, updateAlerting: noopFunc, }, { + isSaving: false, alertingConfig: { enabled: true, ready: true, @@ -84,14 +61,7 @@ const testData: Array = [ slack1: { slackConfig: { webhookUrlConfigured: true } }, }, }, - isAlertingEditEnabled: false, - isAlertingEditDirty: false, - disableAlertingRequestState: RequestState.FAILURE, - disableAlertingLastError: 'disable error', disableAlerting: noopFunc, - editAlertingDisable: noopFunc, - editAlertingEnable: noopFunc, - editAlertingSetSlackUrl: noopFunc, enableAlerting: noopFunc, updateAlerting: noopFunc, }, diff --git a/src/components/alerting/edit-alerting.tsx b/src/components/alerting/edit-alerting.tsx index ab900948a..121b1cfe8 100644 --- a/src/components/alerting/edit-alerting.tsx +++ b/src/components/alerting/edit-alerting.tsx @@ -1,66 +1,47 @@ import { TextField } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { useState, useEffect, FunctionComponent, ChangeEvent } from 'react'; - import { - UpdateAlertingConfigModel, - UpdateAlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/update-alerting-config'; + FunctionComponent, + ChangeEvent, + Dispatch, + SetStateAction, +} from 'react'; + +import { AlertingConfig } from '../../store/radix-api'; -function buildSlackReceiverNamesFromConfig( - config: UpdateAlertingConfigModel -): Array { - return Object.entries(config?.receivers || {}) - .filter((e) => e[1].slackConfig) - .map((e) => e[0]); -} +export type ChangedReceivers = Record; -const UpdateSlackReceivers: FunctionComponent<{ - receivers: Array; - slackUrlChangeCallback: (receiver: string, slackUrl: string) => void; -}> = ({ receivers, slackUrlChangeCallback }) => ( +export const UpdateSlackReceivers: FunctionComponent<{ + alertingConfig: AlertingConfig; + changedReceivers: ChangedReceivers; + setChangedReceivers: Dispatch>; +}> = ({ alertingConfig, changedReceivers, setChangedReceivers }) => ( <> - {receivers?.map((receiver) => ( - ) => - slackUrlChangeCallback(receiver, ev.target.value) - } - /> - ))} + {Object.entries(alertingConfig.receivers ?? {}) + .filter(([, value]) => value.slackConfig.enabled) + .map(([receiver]) => ( + ) => + setChangedReceivers((old: ChangedReceivers) => ({ + ...old, + [receiver]: ev.target.value, + })) + } + /> + ))} ); UpdateSlackReceivers.propTypes = { - receivers: PropTypes.arrayOf(PropTypes.string).isRequired, - slackUrlChangeCallback: PropTypes.func.isRequired, -}; - -export const EditAlerting: FunctionComponent<{ - editConfig: UpdateAlertingConfigModel; - editAlertingSetSlackUrl: (receiver: string, slackUrl: string) => void; -}> = ({ editConfig, editAlertingSetSlackUrl }) => { - const [slackReceivers, setSlackReceivers] = useState>([]); - useEffect(() => { - setSlackReceivers(buildSlackReceiverNamesFromConfig(editConfig)); - }, [editConfig]); - - return editConfig ? ( - - ) : ( - <> - ); -}; - -EditAlerting.propTypes = { - editConfig: PropTypes.shape(UpdateAlertingConfigModelValidationMap) - .isRequired as PropTypes.Validator, - editAlertingSetSlackUrl: PropTypes.func.isRequired, + alertingConfig: PropTypes.object + .isRequired as PropTypes.Validator, + changedReceivers: PropTypes.object + .isRequired as PropTypes.Validator, + setChangedReceivers: PropTypes.func.isRequired, }; diff --git a/src/components/alerting/index.tsx b/src/components/alerting/index.tsx index fcaad1314..55bfcff0f 100644 --- a/src/components/alerting/index.tsx +++ b/src/components/alerting/index.tsx @@ -1,150 +1,47 @@ import { Icon, Typography } from '@equinor/eds-core-react'; import { info_circle } from '@equinor/eds-icons'; import * as PropTypes from 'prop-types'; -import { FunctionComponent, useEffect, useState } from 'react'; +import { useState } from 'react'; import { AlertingActions } from './alerting-actions'; import { AlertingConfigStatus } from './alerting-overview'; -import { EditAlerting } from './edit-alerting'; +import { UpdateSlackReceivers, ChangedReceivers } from './edit-alerting'; +import { buildEditConfig } from './buildEditConfig'; -import { Alert, AlertType } from '../alert'; +import { Alert } from '../alert'; import { externalUrls } from '../../externalUrls'; -import { - AlertingConfigModel, - AlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/alerting-config'; -import { - UpdateAlertingConfigModel, - UpdateAlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/update-alerting-config'; -import { RequestState } from '../../state/state-utils/request-states'; - -export interface AlertingProps { - alertingConfig: AlertingConfigModel; - alertingEditConfig?: UpdateAlertingConfigModel; - editAlertingEnable: (config: AlertingConfigModel) => void; - editAlertingDisable: () => void; - editAlertingSetSlackUrl: (receiver: string, slackUrl: string) => void; - enableAlerting: () => void; - updateAlerting: (config: UpdateAlertingConfigModel) => void; - disableAlerting: () => void; - enableAlertingRequestState?: RequestState; - disableAlertingRequestState?: RequestState; - updateAlertingRequestState?: RequestState; - enableAlertingLastError?: string; - disableAlertingLastError?: string; - updateAlertingLastError?: string; - isAlertingEditEnabled: boolean; - isAlertingEditDirty: boolean; -} - -function useIsSaving( - ...states: [RequestState, RequestState, RequestState] -): boolean { - const [isSaving, setIsSaving] = useState(false); - useEffect( - () => - setIsSaving(states.some((state) => state === RequestState.IN_PROGRESS)), - [states, setIsSaving] - ); - - return isSaving; +import { AlertingConfig, UpdateAlertingConfig } from '../../store/radix-api'; + +interface Props { + isSaving: boolean; + alertingConfig: AlertingConfig; + enableAlerting: () => Promise; + updateAlerting: (config: UpdateAlertingConfig) => Promise; + disableAlerting: () => Promise; } -export const Alerting: FunctionComponent = ({ +export const Alerting = ({ + isSaving, alertingConfig, enableAlerting, disableAlerting, updateAlerting, - enableAlertingRequestState, - disableAlertingRequestState, - updateAlertingRequestState, - enableAlertingLastError, - disableAlertingLastError, - updateAlertingLastError, - alertingEditConfig, - editAlertingEnable, - editAlertingDisable, - editAlertingSetSlackUrl, - isAlertingEditEnabled, - isAlertingEditDirty, -}) => { - const [lastError, setLastError] = useState<{ - type?: AlertType; - message: string; - }>(undefined); - const [isNotReady, setIsNotReady] = useState(false); - - useEffect( - () => setIsNotReady(alertingConfig.enabled && !alertingConfig.ready), - [alertingConfig] - ); - - useEffect(() => { - if (isNotReady) { - setLastError({ - type: 'warning', - message: - 'Alert is not ready to be configured yet. Please wait a few minutes. If the problem persists, get in touch on our Slack support channel.', - }); - } else { - setLastError(undefined); - } - }, [isNotReady]); - - useEffect(() => { - if (enableAlertingRequestState === RequestState.FAILURE) { - setLastError({ message: enableAlertingLastError }); - } - }, [enableAlertingRequestState, enableAlertingLastError]); - - useEffect(() => { - if (disableAlertingRequestState === RequestState.FAILURE) { - setLastError({ message: disableAlertingLastError }); - } - }, [disableAlertingRequestState, disableAlertingLastError]); - - useEffect(() => { - if (updateAlertingRequestState === RequestState.FAILURE) { - setLastError({ message: updateAlertingLastError }); - } - }, [updateAlertingRequestState, updateAlertingLastError]); - - // Disable editing on unmount - useEffect(() => { - return () => { - editAlertingDisable(); - }; - }, [editAlertingDisable]); - - const onSaveAlerting = () => { - setLastError(undefined); - updateAlerting(alertingEditConfig); - }; - - // Handle isSaving state - const isSaving = useIsSaving( - enableAlertingRequestState, - disableAlertingRequestState, - updateAlertingRequestState +}: Props) => { + const [edit, setEdit] = useState(false); + const [changedReceivers, setChangedReceivers] = useState( + {} ); + const onSave = async () => { + const config: UpdateAlertingConfig = buildEditConfig(alertingConfig); + Object.entries(changedReceivers).forEach(([receiver, url]) => { + config.receiverSecrets[receiver] = { slackConfig: { webhookUrl: url } }; + }); - const onEnableAlerting = () => { - setLastError(undefined); - enableAlerting(); - }; - - const onDisableAlerting = () => { - setLastError(undefined); - disableAlerting(); - }; - - const onEditAlertingEnable = () => { - editAlertingEnable(alertingConfig); + await updateAlerting(config); }; - - const onEditAlertingDisable = () => { - editAlertingDisable(); + const onCancel = () => { + setEdit(false); + setChangedReceivers({}); }; return ( @@ -168,50 +65,32 @@ export const Alerting: FunctionComponent = ({ - {isAlertingEditEnabled && ( - )} - {lastError && ( - {lastError.message} - )} - setEdit(true)} + onCancel={onCancel} + onSave={onSave} + config={alertingConfig} + onEnable={enableAlerting} + onDisable={disableAlerting} /> ); }; Alerting.propTypes = { - alertingConfig: PropTypes.shape(AlertingConfigModelValidationMap) - .isRequired as PropTypes.Validator, - alertingEditConfig: PropTypes.shape( - UpdateAlertingConfigModelValidationMap - ) as PropTypes.Validator, - editAlertingEnable: PropTypes.func.isRequired, - editAlertingDisable: PropTypes.func.isRequired, - editAlertingSetSlackUrl: PropTypes.func.isRequired, + isSaving: PropTypes.bool.isRequired, + alertingConfig: PropTypes.object.isRequired, enableAlerting: PropTypes.func.isRequired, updateAlerting: PropTypes.func.isRequired, disableAlerting: PropTypes.func.isRequired, - enableAlertingRequestState: PropTypes.oneOf(Object.values(RequestState)), - disableAlertingRequestState: PropTypes.oneOf(Object.values(RequestState)), - updateAlertingRequestState: PropTypes.oneOf(Object.values(RequestState)), - enableAlertingLastError: PropTypes.string, - disableAlertingLastError: PropTypes.string, - updateAlertingLastError: PropTypes.string, - isAlertingEditEnabled: PropTypes.bool.isRequired, - isAlertingEditDirty: PropTypes.bool.isRequired, }; diff --git a/src/components/app-list/dev.tsx b/src/components/app-list/dev.tsx index 82309a33c..c1ad87498 100644 --- a/src/components/app-list/dev.tsx +++ b/src/components/app-list/dev.tsx @@ -1,7 +1,7 @@ import { Typography } from '@equinor/eds-core-react'; import { Server } from 'miragejs'; -import { AppList, AppListProps } from '.'; +import AppList from '.'; import { GetSearchApplicationsApiArg, @@ -12,6 +12,7 @@ import { GetApplicationVulnerabilitySummariesApiArg, GetApplicationVulnerabilitySummariesApiResponse, } from '../../store/scan-api'; +import { ComponentProps } from 'react'; const testApps: ShowApplicationsApiResponse = [ { @@ -79,8 +80,6 @@ const testVulns: GetApplicationVulnerabilitySummariesApiResponse = [ }, ]; -const noop = () => void 0; - // Mock API response new Server({ routes() { @@ -130,21 +129,20 @@ new Server({ }, }); -const testData: Array<{ description: string } & AppListProps> = [ +const testData: Array< + { description: string } & ComponentProps +> = [ { description: 'With applications, without favourites', - toggleFavouriteApplication: noop, - favouriteAppNames: [], + // favouriteAppNames: [], }, { description: 'With applications, with favourite', - toggleFavouriteApplication: noop, - favouriteAppNames: ['app'], + // favouriteAppNames: ['app'], }, { description: 'With applications, with favourites', - toggleFavouriteApplication: noop, - favouriteAppNames: testApps.map(({ name }) => name), + // favouriteAppNames: testApps.map(({ name }) => name), }, ]; diff --git a/src/components/app-list/index.test.tsx b/src/components/app-list/index.test.tsx index 770ce5954..600d1dc44 100644 --- a/src/components/app-list/index.test.tsx +++ b/src/components/app-list/index.test.tsx @@ -3,13 +3,12 @@ import { render } from '@testing-library/react'; import { Provider } from 'react-redux'; import { MemoryRouter } from 'react-router-dom'; -import { AppList } from '.'; +import AppList from '.'; import store from '../../init/store'; import * as radixApi from '../../store/radix-api'; import { FetchQueryHookResult } from '../../store/types'; -const noApps: Array = []; const noop = () => void 0; describe('AppList component', () => { @@ -53,10 +52,7 @@ describe('AppList component', () => { render( - + ); diff --git a/src/components/app-list/index.tsx b/src/components/app-list/index.tsx index bb3279609..d534f50e5 100644 --- a/src/components/app-list/index.tsx +++ b/src/components/app-list/index.tsx @@ -1,15 +1,10 @@ import { Typography } from '@equinor/eds-core-react'; -import { FunctionComponent, useCallback, useState } from 'react'; -import { connect } from 'react-redux'; +import { FunctionComponent, useState } from 'react'; +import { uniq } from 'lodash'; -import { AppListItem, FavouriteClickedHandler } from '../app-list-item'; +import { AppListItem } from '../app-list-item'; import AsyncResource from '../async-resource/another-async-resource'; import PageCreateApplication from '../page-create-application'; -import { RootState } from '../../init/store'; -import { - getMemoizedFavouriteApplications, - toggleFavouriteApp, -} from '../../state/applications-favourite'; import { useGetSearchApplicationsQuery, useShowApplicationsQuery, @@ -17,18 +12,9 @@ import { import { dataSorter, sortCompareString } from '../../utils/sort-utils'; import './style.css'; +import useLocalStorage from '../../effects/use-local-storage'; -interface AppListState { - favouriteAppNames: Readonly>; -} - -interface AppListDispatch { - toggleFavouriteApplication: (name: string) => void; -} - -export interface AppListProps extends AppListState, AppListDispatch {} - -const pollAppsInterval = 15000; +const pollingInterval = 15000; const LoadingCards: FunctionComponent<{ amount: number }> = ({ amount }) => (
@@ -43,41 +29,35 @@ const LoadingCards: FunctionComponent<{ amount: number }> = ({ amount }) => (
); -export const AppList: FunctionComponent = ({ - toggleFavouriteApplication, - favouriteAppNames, -}) => { +export default function AppList() { const [randomPlaceholderCount] = useState(Math.floor(Math.random() * 5) + 3); - const [favourites, setFavourites] = useState(favouriteAppNames); - - const favouriteToggle = useCallback( - (event, name) => { - event.preventDefault(); - toggleFavouriteApplication(name); - }, - [toggleFavouriteApplication] + const [favourites, setFacourites] = useLocalStorage>( + 'favouriteApplications', + [] ); - if ( - favourites.length !== favouriteAppNames.length || - favourites.some((x) => !favouriteAppNames.includes(x)) - ) { - setFavourites(favouriteAppNames); - } - - const { data: appsData, ...appsState } = useShowApplicationsQuery( - {}, - { pollingInterval: pollAppsInterval } - ); + const { + data: appsData, + refetch, + ...appsState + } = useShowApplicationsQuery({}, { pollingInterval }); const { data: favsData, ...favsState } = useGetSearchApplicationsQuery( { apps: favourites?.join(','), includeEnvironmentActiveComponents: 'true', includeLatestJobSummary: 'true', }, - { skip: !(favourites?.length > 0), pollingInterval: pollAppsInterval } + { skip: favourites.length === 0, pollingInterval } ); + const changeFavouriteApplication = (app: string, isFavourite: boolean) => { + if (isFavourite) { + setFacourites((old) => uniq([...old, app])); + } else { + setFacourites((old) => old.filter((a) => a !== app)); + } + }; + const apps = dataSorter(appsData, [ (x, y) => sortCompareString(x.name, y.name), ]).map((app) => ({ app, isFavourite: favourites.includes(app.name) })); @@ -85,7 +65,7 @@ export const AppList: FunctionComponent = ({ [ ...(favsData ?? []) .filter(({ name }) => favourites.includes(name)) - .map((app) => ({ app, isFavourite: true })), + .map((app) => ({ app, isFavourite: true }) as const), ...apps, ].filter( ({ app, isFavourite }, i, arr) => @@ -99,7 +79,7 @@ export const AppList: FunctionComponent = ({
Favourites
- +
@@ -122,7 +102,10 @@ export const AppList: FunctionComponent = ({ { + changeFavouriteApplication(app.name, false); + e.preventDefault(); + }} isFavourite showStatus /> @@ -149,7 +132,10 @@ export const AppList: FunctionComponent = ({ { + changeFavouriteApplication(app.name, !isFavourite); + e.preventDefault(); + }} isFavourite={isFavourite} /> ))} @@ -171,13 +157,4 @@ export const AppList: FunctionComponent = ({
); -}; - -export default connect( - (state) => ({ - favouriteAppNames: [...getMemoizedFavouriteApplications(state)], - }), - (dispatch) => ({ - toggleFavouriteApplication: (name) => dispatch(toggleFavouriteApp(name)), - }) -)(AppList); +} diff --git a/src/components/app-navbar/index.tsx b/src/components/app-navbar/index.tsx index 6161c2f7e..a40de5346 100644 --- a/src/components/app-navbar/index.tsx +++ b/src/components/app-navbar/index.tsx @@ -13,23 +13,10 @@ import { world, } from '@equinor/eds-icons'; import { clsx } from 'clsx'; -import { - FunctionComponent, - Dispatch as ReactDispatch, - forwardRef, - useEffect, - useState, -} from 'react'; -import { connect } from 'react-redux'; +import { FunctionComponent, forwardRef } from 'react'; import { NavLink } from 'react-router-dom'; -import { Dispatch } from 'redux'; import { AppBadge } from '../app-badge'; -import { RootState } from '../../init/store'; -import { - getMemoizedFavouriteApplications, - toggleFavouriteApp, -} from '../../state/applications-favourite'; import { configVariables } from '../../utils/config'; import { urlToAppMonitoring } from '../../utils/monitoring'; import { @@ -41,40 +28,24 @@ import { } from '../../utils/routing'; import './style.css'; - -type NavbarLinkItem = { label: string; to: string; icon: IconData }; +import { uniq } from 'lodash'; +import useLocalStorage from '../../effects/use-local-storage'; + +type NavbarLinkItem = { + label: string; + to: string; + icon: IconData; + collapsed?: boolean; +}; type NavbarProps = { appName: string; links: Array }; -interface AppNavbarDispatch { - toggleFavouriteState?: (appName: string) => void; -} - -interface AppNavbarState { - isFavourite?: boolean; -} - -export interface AppNavbarProps extends AppNavbarState, AppNavbarDispatch { +export interface AppNavbarProps { appName: string; } const radixClusterType = configVariables.RADIX_CLUSTER_TYPE; -function usePersistedState( - key: string, - defaultValue: T -): [T, ReactDispatch] { - const [state, setState] = useState( - () => JSON.parse(localStorage.getItem(key)) || defaultValue - ); - useEffect(() => { - localStorage.setItem(key, JSON.stringify(state)); - }, [key, state]); - return [state, setState]; -} - -const NavbarLink: FunctionComponent< - NavbarLinkItem & { collapsed?: boolean } -> = ({ collapsed, ...link }) => { +const NavbarLink = ({ collapsed, ...link }: NavbarLinkItem) => { const NavbarLinkElement = forwardRef< HTMLAnchorElement, Parameters[0] & NavbarLinkItem & { collapsed?: boolean } @@ -106,67 +77,72 @@ const NavbarLink: FunctionComponent< ); }; -const NavbarExpanded: FunctionComponent< - NavbarProps & Pick -> = ({ - appName, - links, - isFavourite, - toggleFavouriteState: toggleFavouriteApp = () => void 0, -}) => ( - -); + + ); +}; -const NavbarMinimized: FunctionComponent = ({ - appName, - links, -}) => ( +const NavbarMinimized = ({ appName, links }: NavbarProps) => ( ); -export const AppNavbar: FunctionComponent = ({ - appName, - ...rest -}) => { - const [toggle, setToggle] = usePersistedState('app-nav', false); +export const AppNavbar: FunctionComponent = ({ appName }) => { + const [toggle, setToggle] = useLocalStorage('app-nav', false); const links: Array = [ { label: 'Environments', to: getEnvsUrl(appName), icon: world }, @@ -219,22 +192,13 @@ export const AppNavbar: FunctionComponent = ({ - {(toggle ? NavbarExpanded : NavbarMinimized)({ ...rest, appName, links })} + {toggle ? ( + + ) : ( + + )} ); }; -function mapStateToProps( - state: RootState, - { appName }: AppNavbarProps -): AppNavbarState { - return { - isFavourite: !!getMemoizedFavouriteApplications(state).includes(appName), - }; -} - -function mapDispatchToProps(dispatch: Dispatch): AppNavbarDispatch { - return { toggleFavouriteState: (name) => dispatch(toggleFavouriteApp(name)) }; -} - -export default connect(mapStateToProps, mapDispatchToProps)(AppNavbar); +export default AppNavbar; diff --git a/src/components/configure-application-github/dev.tsx b/src/components/configure-application-github/dev.tsx index fc206f6d8..a2d3d8c13 100644 --- a/src/components/configure-application-github/dev.tsx +++ b/src/components/configure-application-github/dev.tsx @@ -15,6 +15,7 @@ export default ( radixConfigFullName: 'radixconfig.yaml', }} onDeployKeyChange={() => void 0} + refetch={() => void 0} initialSecretPollInterval={5000} /> diff --git a/src/components/configure-application-github/index.tsx b/src/components/configure-application-github/index.tsx index d8df3b05b..306f695a2 100644 --- a/src/components/configure-application-github/index.tsx +++ b/src/components/configure-application-github/index.tsx @@ -7,31 +7,32 @@ import { Typography, } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { FunctionComponent, useEffect, useState } from 'react'; +import { useState } from 'react'; +import { nanoid } from 'nanoid'; import imageDeployKey from './deploy-key02.png'; import imageWebhook from './webhook02.png'; -import { usePollDeployKeyAndSecret } from './use-poll-deploy-key-and-secrets'; -import { useRegenerateDeployKeyAndSecret } from './use-regenerate-deploy-key-and-secret'; - import { Alert } from '../alert'; import { Code } from '../code'; import { CompactCopyButton } from '../compact-copy-button'; import { externalUrls } from '../../externalUrls'; -import { - ApplicationRegistrationModel, - ApplicationRegistrationModelValidationMap, -} from '../../models/radix-api/applications/application-registration'; -import { RequestState } from '../../state/state-utils/request-states'; import { configVariables } from '../../utils/config'; import './style.css'; +import { + ApplicationRegistration, + useGetDeployKeyAndSecretQuery, + useRegenerateDeployKeyMutation, +} from '../../store/radix-api'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; +import { getFetchErrorMessage } from '../../store/utils'; const radixZoneDNS = configVariables.RADIX_CLUSTER_BASE; -export interface ConfigureApplicationGithubProps { - app: ApplicationRegistrationModel; +interface Props { + app: ApplicationRegistration; + refetch: Function; onDeployKeyChange: (appName: string) => void; startVisible?: boolean; useOtherCiToolOptionVisible?: boolean; @@ -40,64 +41,34 @@ export interface ConfigureApplicationGithubProps { initialSecretPollInterval: number; } -export const ConfigureApplicationGithub: FunctionComponent< - ConfigureApplicationGithubProps -> = ({ +export const ConfigureApplicationGithub = ({ app, - onDeployKeyChange, + refetch, startVisible, useOtherCiToolOptionVisible = false, deployKeyTitle = 'Add deploy key', webhookTitle = 'Add webhook', - initialSecretPollInterval, -}) => { +}: Props) => { const isExpanded = !!startVisible; const webhookURL = `https://webhook.${radixZoneDNS}/events/github?appName=${app.name}`; - - const [secretPollInterval, setSecretPollInterval] = useState( - initialSecretPollInterval - ); const [useOtherCiTool, setUseOtherCiTool] = useState(false); - const [savedDeployKey, setSavedDeployKey] = useState(); - const [savedSharedSecret, setSavedSharedSecret] = useState(); - const [regenerateState, regenerateStateFunc, resetRegenerateState] = - useRegenerateDeployKeyAndSecret(app.name); - const [deployKeyAndSecretState] = usePollDeployKeyAndSecret( - app.name, - secretPollInterval - ); - - useEffect(() => { - if (regenerateState.status !== RequestState.SUCCESS) { - return; - } - resetRegenerateState(); - setSecretPollInterval(1000); - }, [regenerateState.status, resetRegenerateState]); + const [regenerateSecrets, { isLoading, error }] = + useRegenerateDeployKeyMutation(); + const { data: secrets, refetch: refetchSecrets } = + useGetDeployKeyAndSecretQuery( + { appName: app.name }, + { pollingInterval: 15000, skip: useOtherCiTool } + ); - useEffect(() => { - if (deployKeyAndSecretState.status !== RequestState.SUCCESS) { - return; - } - if ( - deployKeyAndSecretState.data.publicDeployKey && - deployKeyAndSecretState.data.publicDeployKey !== savedDeployKey && - deployKeyAndSecretState.data.sharedSecret !== savedSharedSecret - ) { - setSavedDeployKey(deployKeyAndSecretState.data.publicDeployKey); - setSavedSharedSecret(deployKeyAndSecretState.data.sharedSecret); - setSecretPollInterval(0); - onDeployKeyChange(app.name); - } - }, [ - app.name, - deployKeyAndSecretState.data, - deployKeyAndSecretState.status, - onDeployKeyChange, - savedDeployKey, - savedSharedSecret, - ]); + const onRegenerate = handlePromiseWithToast(async () => { + await regenerateSecrets({ + appName: app.name, + regenerateDeployKeyAndSecretData: { sharedSecret: nanoid() }, + }).unwrap(); + await refetchSecrets(); + await refetch(); + }); return (
@@ -139,7 +110,7 @@ export const ConfigureApplicationGithub: FunctionComponent<
Copy and paste this key: - {savedDeployKey} + {secrets?.publicDeployKey}
Press "Add key" @@ -147,18 +118,18 @@ export const ConfigureApplicationGithub: FunctionComponent<
- {regenerateState.status === RequestState.FAILURE && ( + {error && ( Failed to regenerate deploy key and webhook secret. - {regenerateState.error} + {getFetchErrorMessage(error)} )} - {regenerateState.status === RequestState.IN_PROGRESS ? ( + {isLoading ? ( <> Regenerating… ) : ( - )} @@ -250,8 +221,8 @@ export const ConfigureApplicationGithub: FunctionComponent< The Shared Secret for this application is{' '} - {savedSharedSecret}{' '} - + {secrets?.sharedSecret}{' '} + Press "Add webhook" @@ -267,8 +238,8 @@ export const ConfigureApplicationGithub: FunctionComponent< }; ConfigureApplicationGithub.propTypes = { - app: PropTypes.shape(ApplicationRegistrationModelValidationMap) - .isRequired as PropTypes.Validator, + app: PropTypes.object + .isRequired as PropTypes.Validator, onDeployKeyChange: PropTypes.func.isRequired, startVisible: PropTypes.bool, useOtherCiToolOptionVisible: PropTypes.bool, diff --git a/src/components/configure-application-github/use-poll-deploy-key-and-secrets.ts b/src/components/configure-application-github/use-poll-deploy-key-and-secrets.ts deleted file mode 100644 index 909d357c6..000000000 --- a/src/components/configure-application-github/use-poll-deploy-key-and-secrets.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { usePollingJson } from '../../effects'; -import { AsyncPollingResult } from '../../effects/use-async-polling'; -import { DeployKeyAndSecretModel } from '../../models/radix-api/applications/deploy-key-and-secret'; -import { DeployKeyAndSecretModelNormalizer } from '../../models/radix-api/applications/deploy-key-and-secret/normalizer'; - -export function usePollDeployKeyAndSecret( - appName: string, - interval = 0 -): AsyncPollingResult> { - const encAppName = encodeURIComponent(appName); - - return usePollingJson( - `/applications/${encAppName}/deploy-key-and-secret`, - interval, - DeployKeyAndSecretModelNormalizer - ); -} diff --git a/src/components/configure-application-github/use-regenerate-deploy-key-and-secret.ts b/src/components/configure-application-github/use-regenerate-deploy-key-and-secret.ts deleted file mode 100644 index 6bd94b2e3..000000000 --- a/src/components/configure-application-github/use-regenerate-deploy-key-and-secret.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { nanoid } from 'nanoid'; - -import { usePostJson } from '../../effects'; -import { AsyncRequestResult } from '../../effects/use-async-request'; - -export function useRegenerateDeployKeyAndSecret( - appName: string -): AsyncRequestResult { - const encAppName = encodeURIComponent(appName); - - return usePostJson( - `/applications/${encAppName}/regenerate-deploy-key`, - () => ({ sharedSecret: nanoid() }) - ); -} diff --git a/src/components/create-application-form/index.tsx b/src/components/create-application-form/index.tsx index ac78fa6b7..0a81245b8 100644 --- a/src/components/create-application-form/index.tsx +++ b/src/components/create-application-form/index.tsx @@ -8,10 +8,7 @@ import { Typography, } from '@equinor/eds-core-react'; import { info_circle } from '@equinor/eds-icons'; -import * as PropTypes from 'prop-types'; -import { ChangeEvent, Component, FormEvent } from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; +import { ChangeEvent, FormEvent, useState } from 'react'; import { Alert } from '../alert'; import { AppConfigAdGroups } from '../app-config-ad-groups'; @@ -20,288 +17,218 @@ import { OnConfigurationItemChangeCallback, } from '../app-config-ci'; import { HandleAdGroupsChangeCB } from '../graph/adGroups'; -import { AppCreateProps } from '../../api/apps'; import { externalUrls } from '../../externalUrls'; -import { RootState } from '../../init/store'; import { ApplicationRegistrationModel } from '../../models/radix-api/applications/application-registration'; import { - ApplicationRegistrationUpsertResponseModelValidationMap, - ApplicationRegistrationUpsertResponseModel, -} from '../../models/radix-api/applications/application-registration-upsert-response'; + ApplicationRegistration, + ApplicationRegistrationUpsertResponse, + useRegisterApplicationMutation, +} from '../../store/radix-api'; import { - getCreationError, - getCreationState, - getCreationResult, -} from '../../state/application-creation'; -import { actions as appsActions } from '../../state/application-creation/action-creators'; -import { RequestState } from '../../state/state-utils/request-states'; - -interface CreateApplicationFormState { - creationState: RequestState; - creationError?: string; - creationResponse?: ApplicationRegistrationUpsertResponseModel; -} - -interface CreateApplicationFormDispatch { - requestCreate: (form: AppCreateProps) => void; -} - -export interface CreateApplicationFormProps - extends CreateApplicationFormState, - CreateApplicationFormDispatch {} + errorToast, + successToast, + warningToast, +} from '../global-top-nav/styled-toaster'; +import { getFetchErrorMessage } from '../../store/utils'; function sanitizeName(name: string): string { // force name to lowercase, no spaces return name?.toLowerCase().replace(/[^a-z0-9]/g, '-') ?? ''; } -export class CreateApplicationForm extends Component< - CreateApplicationFormProps, - AppCreateProps -> { - static readonly propTypes: PropTypes.ValidationMap = - { - creationState: PropTypes.oneOf(Object.values(RequestState)).isRequired, - requestCreate: PropTypes.func.isRequired, - creationError: PropTypes.string, - creationResponse: PropTypes.shape( - ApplicationRegistrationUpsertResponseModelValidationMap - ), - }; - - constructor(props: CreateApplicationFormProps) { - super(props); - this.state = { - appRegistrationRequest: { - applicationRegistration: { - name: '', - repository: '', - sharedSecret: '', - adGroups: [], - owner: '', - creator: '', - wbs: '', - configBranch: '', - radixConfigFullName: 'radixconfig.yaml', - }, - acknowledgeWarnings: false, - }, - }; - - this.handleAdGroupsChange = this.handleAdGroupsChange.bind(this); - this.handleConfigurationItemChange = - this.handleConfigurationItemChange.bind(this); - this.handleAppRegistrationChange = - this.handleAppRegistrationChange.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.handleAcknowledgeWarnings = this.handleAcknowledgeWarnings.bind(this); - } +type Props = { + onCreated: (application: ApplicationRegistration) => Promise; +}; +export default function CreateApplicationForm({ onCreated }: Props) { + const [acknowledgeWarnings, setAcknowledgeWarnings] = useState(false); + const [createApp, creationState] = useRegisterApplicationMutation(); + const [applicationRegistration, setAppRegistration] = + useState({ + name: '', + repository: '', + sharedSecret: '', + adGroups: [], + owner: '', + creator: '', + wbs: '', + configBranch: '', + radixConfigFullName: 'radixconfig.yaml', + configurationItem: '', + readerAdGroups: [], + }); - private handleAdGroupsChange( - ...[value]: Parameters - ): ReturnType { - this.setState(({ appRegistrationRequest }) => ({ - appRegistrationRequest: { - ...appRegistrationRequest, - applicationRegistration: { - ...appRegistrationRequest.applicationRegistration, - adGroups: value.map(({ id }) => id), - }, - }, + const handleAdGroupsChange: HandleAdGroupsChangeCB = (value) => { + setAppRegistration((current) => ({ + ...current, + adGroups: value.map((x) => x.id), })); - } + }; - private handleConfigurationItemChange( - ...[value]: Parameters - ): ReturnType { - this.setState(({ appRegistrationRequest }) => ({ - appRegistrationRequest: { - ...appRegistrationRequest, - applicationRegistration: { - ...appRegistrationRequest.applicationRegistration, - configurationItem: value?.id, - }, - }, + const handleConfigurationItemChange: OnConfigurationItemChangeCallback = ( + value + ) => { + setAppRegistration((current) => ({ + ...current, + configurationItem: value?.id, })); - } + }; - private handleAppRegistrationChange({ + const handleAppRegistrationChange = ({ target: { name, value }, - }: ChangeEvent): void { + }: ChangeEvent) => { const key = name as keyof ApplicationRegistrationModel; - this.setState(({ appRegistrationRequest }) => ({ - appRegistrationRequest: { - ...appRegistrationRequest, - applicationRegistration: { - ...appRegistrationRequest.applicationRegistration, - [key]: key === 'name' ? sanitizeName(value) : value, - }, - }, - })); - } - - private handleSubmit(ev: FormEvent): void { - ev.preventDefault(); - const { appRegistrationRequest } = this.state; - this.props.requestCreate({ - appRegistrationRequest: appRegistrationRequest, - }); - } + if (key === 'name') value = sanitizeName(value); - private handleAcknowledgeWarnings(): void { - this.setState(({ appRegistrationRequest }) => ({ - appRegistrationRequest: { - ...appRegistrationRequest, - acknowledgeWarnings: !appRegistrationRequest.acknowledgeWarnings, - }, - })); - } + setAppRegistration((current) => ({ ...current, [key]: value })); + }; - override render() { - const { acknowledgeWarnings, applicationRegistration } = - this.state.appRegistrationRequest; + const handleSubmit = async (ev: FormEvent) => { + try { + ev.preventDefault(); + const response: ApplicationRegistrationUpsertResponse = await createApp({ + applicationRegistrationRequest: { + applicationRegistration, + acknowledgeWarnings, + }, + }).unwrap(); + + //Only call onCreated when created without warnings, or created with ack warnings + if (response.warnings?.length === 0 || acknowledgeWarnings) { + await onCreated(response.applicationRegistration); + successToast('Saved'); + } else { + warningToast('Registration had warnings'); + } + } catch (e) { + errorToast(`Error while saving. ${getFetchErrorMessage(e)}`); + } + }; - return ( -
- - -
- - Your application needs a GitHub repository with a radixconfig.yaml - file and a Dockerfile. + return ( + + + +
+ + Your application needs a GitHub repository with a radixconfig.yaml + file and a Dockerfile. + + + You can read about{' '} + + radixconfig.yaml + {' '} + and{' '} + + Dockerfile best practices - - You can read about{' '} - - radixconfig.yaml - {' '} - and{' '} - - Dockerfile best practices - - . + . + + + Need help? Get in touch on our{' '} + + Slack support channel + +
+
+
+ + + + + + + {creationState.isError && ( + + Failed to create application.{' '} + {getFetchErrorMessage(creationState.error)} + + )} +
+ {creationState.isLoading && ( - Need help? Get in touch on our{' '} - - Slack support channel - + Creating… -
- -
- - - - - - - {this.props.creationState === RequestState.FAILURE && ( - - Failed to create application. {this.props.creationError} - )} -
- {this.props.creationState === RequestState.IN_PROGRESS && ( - - Creating… - - )} - {this.props.creationState === RequestState.SUCCESS && - this.props.creationResponse.warnings && ( -
- - {this.props.creationResponse.warnings?.map((message, i) => ( - - {message} - - ))} - - -
- )} -
- + {creationState.isSuccess && creationState.data?.warnings && ( +
+ + {creationState.data.warnings.map((message, i) => ( + + {message} + + ))} + + ) => + setAcknowledgeWarnings(e.target.checked) + } + />
+ )} +
+
-
- - ); - } -} - -function mapStateToProps(state: RootState): CreateApplicationFormState { - return { - creationState: getCreationState(state), - creationError: getCreationError(state), - creationResponse: getCreationResult(state), - }; -} - -function mapDispatchToProps(dispatch: Dispatch): CreateApplicationFormDispatch { - return { requestCreate: (form) => dispatch(appsActions.addAppRequest(form)) }; +
+ + + ); } - -export default connect( - mapStateToProps, - mapDispatchToProps -)(CreateApplicationForm); diff --git a/src/components/global-top-nav/styled-toaster.tsx b/src/components/global-top-nav/styled-toaster.tsx index b3ebebc14..c61387737 100644 --- a/src/components/global-top-nav/styled-toaster.tsx +++ b/src/components/global-top-nav/styled-toaster.tsx @@ -17,6 +17,7 @@ import { import 'react-toastify/dist/ReactToastify.css'; import './style.css'; +import { getFetchErrorMessage } from '../../store/utils'; export const StyledToastContainer: FunctionComponent = () => (
@@ -79,3 +80,19 @@ export function successToast( icon: , }); } + +export function handlePromiseWithToast, TReturn>( + fn: (...args: TArgs) => TReturn, + successContent = 'Saved' +) { + return async (...args: TArgs): Promise | undefined> => { + try { + const ret = await fn(...args); + successToast(successContent); + return ret; + } catch (e) { + errorToast(`Error while saving. ${getFetchErrorMessage(e)}`); + return undefined; + } + }; +} diff --git a/src/components/page-about/index.tsx b/src/components/page-about/index.tsx index 259e1cb38..5eb53c8c8 100644 --- a/src/components/page-about/index.tsx +++ b/src/components/page-about/index.tsx @@ -1,19 +1,27 @@ import { Typography } from '@equinor/eds-core-react'; -import { FunctionComponent } from 'react'; import { ConfigList } from '../config-list'; import { AvailabilityCharts } from '../data-chart'; +import { DocumentTitle } from '../document-title'; -export const PageAbout: FunctionComponent = () => ( -
- - Radix Web Console [{import.meta.env.PACKAGE_NAME}@ - {import.meta.env.PACKAGE_VERSION}] - - - Configuration - -
-); - -export { PageAbout as Component }; +export default function PageAbout() { + return ( +
+
+ + About +
+
+
+ + Radix Web Console [{import.meta.env.PACKAGE_NAME}@ + {import.meta.env.PACKAGE_VERSION}] + + + Configuration + +
+
+
+ ); +} diff --git a/src/components/page-configuration/build-secrets-accordion.tsx b/src/components/page-configuration/build-secrets-accordion.tsx index b27f36a78..ed4461cf3 100644 --- a/src/components/page-configuration/build-secrets-accordion.tsx +++ b/src/components/page-configuration/build-secrets-accordion.tsx @@ -3,16 +3,15 @@ import * as PropTypes from 'prop-types'; import { FunctionComponent, ReactNode, useState } from 'react'; import AsyncResource from '../async-resource/another-async-resource'; -import { errorToast, successToast } from '../global-top-nav/styled-toaster'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; import { ScrimPopup } from '../scrim-popup'; import { SecretForm } from '../secret-form'; import { BuildSecretStatusBadge } from '../status-badges/build-secret-status-badge'; import { BuildSecret, - radixApi, useGetBuildSecretsQuery, + useUpdateBuildSecretsSecretValueMutation, } from '../../store/radix-api'; -import { getFetchErrorMessage } from '../../store/utils'; import { dataSorter, sortCompareString } from '../../utils/sort-utils'; import './style.css'; @@ -22,8 +21,18 @@ const BuildSecretForm: FunctionComponent<{ secret: BuildSecret; fetchSecret: () => void; }> = ({ appName, secret, fetchSecret }) => { - const [trigger, { isLoading }] = - radixApi.endpoints.updateBuildSecretsSecretValue.useMutation(); + const [mutate, { isLoading }] = useUpdateBuildSecretsSecretValueMutation(); + + const onSave = handlePromiseWithToast(async (secretValue: string) => { + await mutate({ + appName, + secretName: secret.name, + secretParameters: { secretValue }, + }).unwrap(); + + fetchSecret(); + return true; + }); return ( { - try { - await trigger({ - appName, - secretName: secret.name, - secretParameters: { secretValue: value?.toString() || null }, - }).unwrap(); - - fetchSecret(); - successToast('Saved'); - } catch (error) { - errorToast(`Error while saving. ${getFetchErrorMessage(error)}`); - return false; - } - - return true; - }} + onSave={onSave} /> ); }; diff --git a/src/components/page-configuration/change-admin-form.tsx b/src/components/page-configuration/change-admin-form.tsx index a5fed7a25..dec977ebf 100644 --- a/src/components/page-configuration/change-admin-form.tsx +++ b/src/components/page-configuration/change-admin-form.tsx @@ -5,208 +5,89 @@ import { Typography, } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { Component, FormEvent } from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; +import { FormEvent, useState } from 'react'; -import { Alert } from '../alert'; import { AppConfigAdGroups } from '../app-config-ad-groups'; -import { HandleAdGroupsChangeCB } from '../graph/adGroups'; -import { AppModifyProps } from '../../api/apps'; -import { RootState } from '../../init/store'; import { - getModifyRequestError, - getModifyRequestState, -} from '../../state/application'; -import { actions as appActions } from '../../state/application/action-creators'; -import { RequestState } from '../../state/state-utils/request-states'; - -interface ChangeAdminFormState { - modifyState: RequestState; - modifyError?: string; -} - -interface ChangeAdminFormDispatch { - changeAppAdmin: (appName: string, form: AppModifyProps) => void; - modifyAppReset: (appName: string) => void; -} - -export interface ChangeAdminFormProps - extends ChangeAdminFormState, - ChangeAdminFormDispatch { - appName: string; - adGroups?: Array; - readerAdGroups?: Array; -} - -function deriveStateFromProps(props: ChangeAdminFormProps): AppModifyProps { - return { - appRegistrationPatchRequest: { - applicationRegistrationPatch: { - adGroups: props.adGroups ?? [], - readerAdGroups: props.readerAdGroups ?? [], - }, - }, - }; + ApplicationRegistration, + useModifyRegistrationDetailsMutation, +} from '../../store/radix-api'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; + +interface Props { + registration: ApplicationRegistration; + refetch: Function; } - -export class ChangeAdminForm extends Component< - ChangeAdminFormProps, - AppModifyProps -> { - static readonly propTypes: PropTypes.ValidationMap = { - appName: PropTypes.string.isRequired, - adGroups: PropTypes.arrayOf(PropTypes.string), - readerAdGroups: PropTypes.arrayOf(PropTypes.string), - modifyError: PropTypes.string, - modifyState: PropTypes.oneOf(Object.values(RequestState)).isRequired, - changeAppAdmin: PropTypes.func.isRequired, - modifyAppReset: PropTypes.func.isRequired, - }; - - constructor(props: ChangeAdminFormProps) { - super(props); - this.state = deriveStateFromProps(props); - - this.handleAdGroupsChange = this.handleAdGroupsChange.bind(this); - this.handleReaderAdGroupsChange = - this.handleReaderAdGroupsChange.bind(this); - this.handleFormChanged = this.handleFormChanged.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - } - - private handleAdGroupsChange( - ...[value]: Parameters - ): ReturnType { - this.handleFormChanged(); - this.setState(({ appRegistrationPatchRequest }) => ({ - appRegistrationPatchRequest: { - ...appRegistrationPatchRequest, - applicationRegistrationPatch: { - ...appRegistrationPatchRequest.applicationRegistrationPatch, - adGroups: value.map(({ id }) => id), - }, - }, - })); - } - - private handleReaderAdGroupsChange( - ...[value]: Parameters - ): ReturnType { - this.handleFormChanged(); - this.setState(({ appRegistrationPatchRequest }) => ({ - appRegistrationPatchRequest: { - ...appRegistrationPatchRequest, - applicationRegistrationPatch: { - ...appRegistrationPatchRequest.applicationRegistrationPatch, - readerAdGroups: value.map(({ id }) => id), +export default function ChangeAdminForm({ registration, refetch }: Props) { + const [adminAdGroup, setAdminAdGroup] = useState>(); + const [readerAdGroup, setReaderAdGroup] = useState>(); + const [mutate, { isLoading }] = useModifyRegistrationDetailsMutation(); + + const handleSubmit = handlePromiseWithToast( + async (ev: FormEvent) => { + ev.preventDefault(); + + await mutate({ + appName: registration.name, + applicationRegistrationPatchRequest: { + acknowledgeWarnings: true, + applicationRegistrationPatch: { + adGroups: adminAdGroup || registration.adGroups, + readerAdGroups: readerAdGroup || registration.readerAdGroups, + }, }, - }, - })); - } - - private handleFormChanged(): void { - // if there is a creation error then we will reset the creation request to clear the error - if (this.props.modifyError) { - this.props.modifyAppReset(this.props.appName); - } - } - - private handleSubmit(ev: FormEvent): void { - ev.preventDefault(); - const { appRegistrationPatchRequest } = this.state; - this.props.changeAppAdmin(this.props.appName, { - appRegistrationPatchRequest: appRegistrationPatchRequest, - }); - } - - override componentDidUpdate(prevProps: Readonly) { - // Reset the form if the app data changes (e.g. after the refresh once the update is successful) - const adGroupsUnequal = - this.props.adGroups?.length !== prevProps.adGroups?.length || - !!this.props.adGroups?.find((val, i) => val !== prevProps.adGroups[i]); - const readerAdGroupsUnequal = - this.props.readerAdGroups?.length !== prevProps.readerAdGroups?.length || - !!this.props.readerAdGroups?.find( - (val, i) => val !== prevProps.readerAdGroups[i] - ); - - if (adGroupsUnequal || readerAdGroupsUnequal) { - this.setState({ ...deriveStateFromProps(this.props) }); + }).unwrap(); + await refetch(); + setAdminAdGroup(undefined); + setReaderAdGroup(undefined); } - } - - override render() { - return ( - - - - - Access control - - - -
- {this.props.modifyState === RequestState.FAILURE && ( -
- - Failed to change access control. {this.props.modifyError} - -
- )} -
- - + ); + + return ( + + + + + Access control + + + + +
+ + setAdminAdGroup(value.map((v) => v.id)) + } + /> + + setReaderAdGroup(value.map((v) => v.id)) + } + /> +
+ {isLoading ? ( +
+ Updating…
- {this.props.modifyState === RequestState.IN_PROGRESS ? ( -
- Updating… -
- ) : ( -
- -
- )} - -
-
-
- ); - } -} - -function mapStateToProps(state: RootState): ChangeAdminFormState { - return { - modifyError: getModifyRequestError(state), - modifyState: getModifyRequestState(state), - }; -} - -function mapDispatchToProps(dispatch: Dispatch): ChangeAdminFormDispatch { - return { - changeAppAdmin: (appName, form) => - dispatch(appActions.changeAppAdmin(appName, form)), - modifyAppReset: (appName) => dispatch(appActions.modifyAppReset(appName)), - }; + ) : ( +
+ +
+ )} + + + + + ); } -export default connect(mapStateToProps, mapDispatchToProps)(ChangeAdminForm); +ChangeAdminForm.proptypes = { + registration: PropTypes.object.isRequired, + refetch: PropTypes.func.isRequired, +}; diff --git a/src/components/page-configuration/change-ci-form.tsx b/src/components/page-configuration/change-ci-form.tsx index deef888fc..d07a38047 100644 --- a/src/components/page-configuration/change-ci-form.tsx +++ b/src/components/page-configuration/change-ci-form.tsx @@ -5,45 +5,43 @@ import { Typography, } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { FormEvent, FunctionComponent, useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; - -import { useSaveConfigurationItem } from './use-save-ci'; +import { FormEvent, useState } from 'react'; import { Alert } from '../alert'; import { AppConfigConfigurationItem } from '../app-config-ci'; -import { AppDispatch } from '../../init/store'; import { ApplicationModel } from '../../models/servicenow-api/models/service-now-application'; -import { RequestState } from '../../state/state-utils/request-states'; -import { refreshApp } from '../../state/subscriptions/action-creators'; +import { useModifyRegistrationDetailsMutation } from '../../store/radix-api'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; +import { getFetchErrorMessage } from '../../store/utils'; -export interface ChangeConfigurationItemFormProps { +interface Props { appName: string; configurationItem?: string; + refetch: Function; } -export const ChangeConfigurationItemForm: FunctionComponent< - ChangeConfigurationItemFormProps -> = ({ appName, configurationItem }) => { +export const ChangeConfigurationItemForm = ({ + appName, + configurationItem, + refetch, +}: Props) => { const [newCI, setNewCI] = useState(); - const [saveState, saveFunc, resetState] = useSaveConfigurationItem(appName); - const dispatch = useDispatch(); - - useEffect(() => { - if (saveState.status === RequestState.SUCCESS) { - resetState(); - setNewCI(null); - dispatch(refreshApp(appName)); - } - }, [saveState, resetState, dispatch, appName]); + const [mutate, { isLoading, error }] = useModifyRegistrationDetailsMutation(); - function handleSubmit(ev: FormEvent) { + const handleSubmit = handlePromiseWithToast(async (ev: FormEvent) => { ev.preventDefault(); - saveFunc(newCI.id); - } - const isDirty = newCI && newCI.id !== configurationItem; - const isSaving = saveState.status === RequestState.IN_PROGRESS; + await mutate({ + appName, + applicationRegistrationPatchRequest: { + applicationRegistrationPatch: { + configurationItem: newCI.id, + }, + }, + }).unwrap(); + + await refetch(); + }); return ( @@ -55,11 +53,12 @@ export const ChangeConfigurationItemForm: FunctionComponent<
- {saveState.status === RequestState.FAILURE && ( + {error && (
- Failed to change Configuration Item. {saveState.error} + Failed to change Configuration Item.{' '} + {getFetchErrorMessage(error)}
@@ -67,15 +66,19 @@ export const ChangeConfigurationItemForm: FunctionComponent<
- {saveState.status === RequestState.IN_PROGRESS ? ( + {isLoading ? ( <> Updating… ) : ( - )} @@ -90,4 +93,5 @@ export const ChangeConfigurationItemForm: FunctionComponent< ChangeConfigurationItemForm.propTypes = { appName: PropTypes.string.isRequired, configurationItem: PropTypes.string, + refetch: PropTypes.func.isRequired, }; diff --git a/src/components/page-configuration/change-config-branch-form.tsx b/src/components/page-configuration/change-config-branch-form.tsx index 1f74deffe..e50a93455 100644 --- a/src/components/page-configuration/change-config-branch-form.tsx +++ b/src/components/page-configuration/change-config-branch-form.tsx @@ -7,53 +7,42 @@ import { Typography, } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { - ChangeEvent, - FormEvent, - FunctionComponent, - useEffect, - useState, -} from 'react'; +import { FormEvent, FunctionComponent, useState } from 'react'; import { Link } from 'react-router-dom'; -import { useSaveConfigBranch } from './use-save-config-branch'; - import { Alert } from '../alert'; import { routes } from '../../routes'; -import { RequestState } from '../../state/state-utils/request-states'; import { routeWithParams } from '../../utils/string'; +import { useModifyRegistrationDetailsMutation } from '../../store/radix-api'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; +import { getFetchErrorMessage } from '../../store/utils'; export interface ChangeConfigBranchFormProps { appName: string; configBranch: string; + refetch: Function; } export const ChangeConfigBranchForm: FunctionComponent< ChangeConfigBranchFormProps -> = ({ appName, configBranch }) => { - const [saveState, saveFunc, resetState] = useSaveConfigBranch(appName); +> = ({ appName, configBranch, refetch }) => { const [configBranchState, setConfigBranchState] = useState(configBranch); - const [savedConfigBranchState, setSavedConfigBranchState] = - useState(configBranch); + const [mutate, { isLoading, error }] = useModifyRegistrationDetailsMutation(); - useEffect(() => { - setConfigBranchState(configBranch); - }, [configBranch]); - - function handleSubmit(ev: FormEvent): void { + const handleSubmit = handlePromiseWithToast(async (ev: FormEvent) => { ev.preventDefault(); - saveFunc(configBranchState); - setSavedConfigBranchState(configBranchState); - } - function onBranchChange({ - target: { value }, - }: ChangeEvent): void { - if (saveState.status !== RequestState.IDLE) { - resetState(); - } - setConfigBranchState(value); - } + await mutate({ + appName, + applicationRegistrationPatchRequest: { + applicationRegistrationPatch: { + configBranch: configBranchState, + }, + }, + }).unwrap(); + + await refetch(); + }); return ( @@ -65,11 +54,12 @@ export const ChangeConfigBranchForm: FunctionComponent< - {saveState.status === RequestState.FAILURE && ( + {error && (
- Failed to change Config Branch. {saveState.error} + Failed to change Config Branch.{' '} + {getFetchErrorMessage(error)}
@@ -78,10 +68,10 @@ export const ChangeConfigBranchForm: FunctionComponent< label="Branch" id="branchField" helperText="The name of the branch where Radix will read the radixconfig.yaml from, e.g. 'main' or 'master'" - disabled={saveState.status === RequestState.IN_PROGRESS} + disabled={isLoading} type="text" value={configBranchState} - onChange={onBranchChange} + onChange={(e) => setConfigBranchState(e.target.value)} />
@@ -112,7 +102,7 @@ export const ChangeConfigBranchForm: FunctionComponent<
- {saveState.status === RequestState.IN_PROGRESS ? ( + {isLoading ? ( <> Updating… @@ -121,8 +111,7 @@ export const ChangeConfigBranchForm: FunctionComponent< color="danger" type="submit" disabled={ - savedConfigBranchState === configBranchState || - !(configBranchState?.trim().length > 0) + configBranch === configBranchState || !configBranchState } > Change Config Branch diff --git a/src/components/page-configuration/change-config-file-form.tsx b/src/components/page-configuration/change-config-file-form.tsx index 22f887b98..85c678dfb 100644 --- a/src/components/page-configuration/change-config-file-form.tsx +++ b/src/components/page-configuration/change-config-file-form.tsx @@ -6,84 +6,41 @@ import { Typography, } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { - ChangeEvent, - FormEvent, - FunctionComponent, - useEffect, - useState, -} from 'react'; - -import { usePatchApplicationRegistration } from './use-patch-application-registration'; +import { ChangeEvent, FormEvent, FunctionComponent, useState } from 'react'; import { Alert } from '../alert'; -import { - ApplicationRegistrationModel, - ApplicationRegistrationModelValidationMap, -} from '../../models/radix-api/applications/application-registration'; -import { RequestState } from '../../state/state-utils/request-states'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; +import { useModifyRegistrationDetailsMutation } from '../../store/radix-api'; +import { getFetchErrorMessage } from '../../store/utils'; export interface ChangeConfigFileFormProps { appName: string; radixConfigFullName?: string; - acknowledgeWarnings?: boolean; - app?: ApplicationRegistrationModel; + refetch: Function; } const defaultConfigName = 'radixconfig.yaml'; export const ChangeConfigFileForm: FunctionComponent< ChangeConfigFileFormProps -> = ({ appName, radixConfigFullName }) => { - const [modifyState, patchFunc, resetState] = - usePatchApplicationRegistration(appName); - const [configNameState, setConfigNameState] = useState( - radixConfigFullName ?? defaultConfigName - ); - const [savedConfigNameState, setSavedConfigNameState] = useState( - radixConfigFullName ?? defaultConfigName - ); - const [patchConfigNameProgress, setPatchConfigNameProgress] = useState(false); - const [useAcknowledgeWarnings, setAcknowledgeWarnings] = useState(false); - - useEffect(() => { - setConfigNameState(savedConfigNameState); - }, [savedConfigNameState]); - - useEffect(() => { - if (modifyState.status !== RequestState.IN_PROGRESS) { - setPatchConfigNameProgress(false); - setAcknowledgeWarnings(false); - } - - if ( - modifyState.status === RequestState.SUCCESS && - modifyState.data?.applicationRegistration - ) { - setSavedConfigNameState( - modifyState.data.applicationRegistration.radixConfigFullName - ); - } - }, [modifyState.status, modifyState.data]); +> = ({ appName, radixConfigFullName, refetch }) => { + const [configNameState, setConfigNameState] = useState(); + const [mutate, { isLoading, error }] = useModifyRegistrationDetailsMutation(); - function handleSubmit(ev: FormEvent): void { + const handleSubmit = handlePromiseWithToast(async (ev: FormEvent) => { ev.preventDefault(); - setPatchConfigNameProgress(true); - patchFunc({ - applicationRegistrationPatch: { - radixConfigFullName: configNameState, + + await mutate({ + appName, + applicationRegistrationPatchRequest: { + applicationRegistrationPatch: { + radixConfigFullName: configNameState, + }, }, - acknowledgeWarnings: useAcknowledgeWarnings, - }); - } + }).unwrap(); - function onGithubUrlChange(ev: ChangeEvent): void { - ev.preventDefault(); - if (modifyState.status !== RequestState.IDLE) { - resetState(); - } - setConfigNameState(ev.target.value); - } + await refetch(); + }); return ( @@ -96,41 +53,45 @@ export const ChangeConfigFileForm: FunctionComponent<
- {!patchConfigNameProgress && - modifyState.status === RequestState.FAILURE && ( -
- - Failed to change config file. {modifyState.error} - -
- )} + {error && ( +
+ + Failed to change config file. {getFetchErrorMessage(error)} + +
+ )} ) => + setConfigNameState(e.target.value) + } label="URL" helperText="e.g. 'path/radixconfig.yaml" /> - {modifyState.status === RequestState.IN_PROGRESS ? ( + {isLoading ? (
Updating…
) : ( - !patchConfigNameProgress && ( -
- -
- ) +
+ +
)}
@@ -143,8 +104,5 @@ export const ChangeConfigFileForm: FunctionComponent< ChangeConfigFileForm.propTypes = { appName: PropTypes.string.isRequired, radixConfigFullName: PropTypes.string, - acknowledgeWarnings: PropTypes.bool, - app: PropTypes.shape( - ApplicationRegistrationModelValidationMap - ) as PropTypes.Validator, + refetch: PropTypes.func.isRequired, }; diff --git a/src/components/page-configuration/change-repository-form.tsx b/src/components/page-configuration/change-repository-form.tsx index b4cc7adbe..9f2d2d848 100644 --- a/src/components/page-configuration/change-repository-form.tsx +++ b/src/components/page-configuration/change-repository-form.tsx @@ -8,99 +8,70 @@ import { Typography, } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { - ChangeEvent, - FormEvent, - FunctionComponent, - useEffect, - useState, -} from 'react'; - -import { usePatchApplicationRegistration } from './use-patch-application-registration'; +import { ChangeEvent, FormEvent, FunctionComponent, useState } from 'react'; import imageDeployKey from '../configure-application-github/deploy-key02.png'; import imageWebhook from '../configure-application-github/webhook01.png'; import { Alert } from '../alert'; -import { SimpleAsyncResource } from '../async-resource/simple-async-resource'; +import AnotherAsyncResource from '../async-resource/another-async-resource'; import { Code } from '../code'; import { CompactCopyButton } from '../compact-copy-button'; -import { usePollDeployKeyAndSecret } from '../configure-application-github/use-poll-deploy-key-and-secrets'; -import { - ApplicationRegistrationModel, - ApplicationRegistrationModelValidationMap, -} from '../../models/radix-api/applications/application-registration'; -import { RequestState } from '../../state/state-utils/request-states'; import { configVariables } from '../../utils/config'; +import { + useGetDeployKeyAndSecretQuery, + useModifyRegistrationDetailsMutation, +} from '../../store/radix-api'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; +import { getFetchErrorMessage } from '../../store/utils'; const radixZoneDNS = configVariables.RADIX_CLUSTER_BASE; -export interface ChangeRepositoryFormProps { - appName: string; - repository: string; - acknowledgeWarnings?: boolean; - app?: ApplicationRegistrationModel; -} - -const DeployKey: FunctionComponent<{ appName: string }> = ({ appName }) => { - const [deployKeyState] = usePollDeployKeyAndSecret(appName, 0); +const DeployKey = ({ appName }: { appName: string }) => { + const { data: deployKeAndSecret, ...depAndSecState } = + useGetDeployKeyAndSecretQuery({ appName }); return ( - - {deployKeyState.data?.publicDeployKey} - + + {deployKeAndSecret?.publicDeployKey} + ); }; -export const ChangeRepositoryForm: FunctionComponent< - ChangeRepositoryFormProps -> = ({ appName, repository, acknowledgeWarnings, app }) => { +interface Props { + appName: string; + repository: string; + refetch: Function; + sharedSecret: string; +} +export const ChangeRepositoryForm: FunctionComponent = ({ + appName, + repository, + refetch, + sharedSecret, +}) => { const [currentRepository, setCurrentRepository] = useState(repository); - const [editedRepository, setEditedRepository] = useState(repository); const [useAcknowledgeWarnings, setAcknowledgeWarnings] = useState(false); - const [modifyState, patchFunc, resetState] = - usePatchApplicationRegistration(appName); - const [updateRepositoryProgress, setUpdateRepositoryProgress] = - useState(false); + const [mutate, { isLoading, error, data: modifyState }] = + useModifyRegistrationDetailsMutation(); const webhookURL = `https://webhook.${radixZoneDNS}/events/github?appName=${appName}`; - const applicationRegistration = modifyState.data?.applicationRegistration; - const operationWarnings = modifyState.data?.warnings; - - useEffect(() => { - setEditedRepository(currentRepository); - }, [currentRepository]); - - useEffect(() => { - if (modifyState.status !== RequestState.IN_PROGRESS) { - setUpdateRepositoryProgress(false); - setAcknowledgeWarnings(false); - } - if ( - modifyState.status === RequestState.SUCCESS && - applicationRegistration && - !operationWarnings - ) { - setCurrentRepository(applicationRegistration.repository); - } - }, [applicationRegistration, modifyState.status, operationWarnings]); - function handleSubmit(ev: FormEvent): void { + const handleSubmit = handlePromiseWithToast(async (ev: FormEvent) => { ev.preventDefault(); - setUpdateRepositoryProgress(true); - patchFunc({ - applicationRegistrationPatch: { repository: editedRepository }, - acknowledgeWarnings: useAcknowledgeWarnings, - }); - } - function onGithubUrlChange(ev: ChangeEvent): void { - ev.preventDefault(); - if (modifyState.status !== RequestState.IDLE) { - resetState(); - } - setEditedRepository(ev.target.value); - } + await mutate({ + appName, + applicationRegistrationPatchRequest: { + applicationRegistrationPatch: { + repository: currentRepository, + }, + acknowledgeWarnings: useAcknowledgeWarnings, + }, + }).unwrap(); + + await refetch(); + }); return ( @@ -113,171 +84,165 @@ export const ChangeRepositoryForm: FunctionComponent<
- {!updateRepositoryProgress && - modifyState.status === RequestState.FAILURE && ( -
- - Failed to change repository. {modifyState.error} - -
- )} + {error && ( +
+ + Failed to change repository. {getFetchErrorMessage(error)} + +
+ )} ) => + setCurrentRepository(e.target.value) + } label="URL" helperText="e.g. 'https://github.com/equinor/my-app'" /> - {modifyState.status === RequestState.IN_PROGRESS && ( + {isLoading && (
Updating…
)} - {!updateRepositoryProgress && - modifyState?.status === RequestState.SUCCESS && - operationWarnings && ( -
- - {operationWarnings.map((warning, i) => ( - - {warning} - - ))} - - - setAcknowledgeWarnings(!useAcknowledgeWarnings) - } - /> -
- )} - {!updateRepositoryProgress && - modifyState.status !== RequestState.IN_PROGRESS && ( -
- -
- )} - - {!updateRepositoryProgress && - applicationRegistration && - !operationWarnings && - !(modifyState.status === RequestState.IN_PROGRESS) && ( - <> - - Move the Deploy Key to the new repository - -
- - - Open the{' '} - - Deploy Key page - {' '} - to delete the Deploy Key from the previous repository - - - Open the{' '} - - Add New Deploy Key - {' '} - and follow the steps below - - - 'Add deploy key' steps on GitHub - - - Give the key a name, e.g. "Radix deploy key" - - Copy and paste this key: - - - - Press "Add key" - -
- - Move the Webhook to the new repository - -
- - - Open the{' '} - - Webhook page - {' '} - of the previous repository and delete the existing - Webhook - - - Open the{' '} - - Add Webhook page - {' '} - and follow the steps below - - - 'Add webhook' steps on GitHub - - - As Payload URL, use {webhookURL}{' '} - - - - Choose application/json as Content type + {!isLoading && modifyState?.warnings && ( +
+ + {modifyState?.warnings.map((warning, i) => ( + + {warning} - - The Shared Secret for this application is{' '} - {app.sharedSecret}{' '} - - - Press "Add webhook" - -
- + ))} +
+ + setAcknowledgeWarnings(!useAcknowledgeWarnings) + } + /> +
+ )} + {!isLoading && ( +
+ +
)} + + {!isLoading && ( + <> + + Move the Deploy Key to the new repository + +
+ + + Open the{' '} + + Deploy Key page + {' '} + to delete the Deploy Key from the previous repository + + + Open the{' '} + + Add New Deploy Key + {' '} + and follow the steps below + + + 'Add deploy key' steps on GitHub + + + Give the key a name, e.g. "Radix deploy key" + + Copy and paste this key: + + + + Press "Add key" + +
+ + Move the Webhook to the new repository + +
+ + + Open the{' '} + + Webhook page + {' '} + of the previous repository and delete the existing Webhook + + + Open the{' '} + + Add Webhook page + {' '} + and follow the steps below + + + 'Add webhook' steps on GitHub + + + As Payload URL, use {webhookURL}{' '} + + + + Choose application/json as Content type + + + The Shared Secret for this application is{' '} + {sharedSecret}{' '} + + + Press "Add webhook" + +
+ + )}
@@ -288,8 +253,6 @@ export const ChangeRepositoryForm: FunctionComponent< ChangeRepositoryForm.propTypes = { appName: PropTypes.string.isRequired, repository: PropTypes.string.isRequired, - acknowledgeWarnings: PropTypes.bool, - app: PropTypes.shape( - ApplicationRegistrationModelValidationMap - ) as PropTypes.Validator, + refetch: PropTypes.func.isRequired, + sharedSecret: PropTypes.string.isRequired, }; diff --git a/src/components/page-configuration/delete-application-form.tsx b/src/components/page-configuration/delete-application-form.tsx index 841bbd044..2a3c6ce66 100644 --- a/src/components/page-configuration/delete-application-form.tsx +++ b/src/components/page-configuration/delete-application-form.tsx @@ -7,172 +7,99 @@ import { } from '@equinor/eds-core-react'; import { warning_outlined } from '@equinor/eds-icons'; import * as PropTypes from 'prop-types'; -import { ChangeEvent, Component } from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; +import { ChangeEvent, useState } from 'react'; import { Alert } from '../alert'; -import { errorToast } from '../global-top-nav/styled-toaster'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; import { ScrimPopup } from '../scrim-popup'; -import { RootState } from '../../init/store'; -import { getMemoizedApplicationMeta } from '../../state/application'; -import { actions as appActions } from '../../state/application/action-creators'; import './style.css'; +import { useDeleteApplicationMutation } from '../../store/radix-api'; +import { useNavigate } from 'react-router'; +import { routes } from '../../routes'; -interface DeleteApplicationFormDispatch { - deleteApp: (appName: string) => void; -} - -interface DeleteApplicationFormState { - deleteApplicationMeta?: { - isDeleted?: boolean; - error?: string; - }; -} - -export interface DeleteApplicationFormProps - extends DeleteApplicationFormDispatch, - DeleteApplicationFormState { +interface Props { appName: string; } -export class DeleteApplicationForm extends Component< - DeleteApplicationFormProps, - { visibleScrim: boolean; inputValue: string } -> { - static readonly propTypes: PropTypes.ValidationMap = - { - appName: PropTypes.string.isRequired, - deleteApplicationMeta: PropTypes.shape({ - isDeleted: PropTypes.bool, - error: PropTypes.string, - }), - deleteApp: PropTypes.func.isRequired, - }; - - constructor(props: DeleteApplicationFormProps) { - super(props); - this.state = { visibleScrim: false, inputValue: '' }; - - this.doDelete = this.doDelete.bind(this); - this.handleChange = this.handleChange.bind(this); - this.handleClick = this.handleClick.bind(this); - } - - private doDelete(): void { - this.props.deleteApp(this.props.appName); - } - - private handleChange({ - target: { value }, - }: ChangeEvent): void { - this.setState({ inputValue: value }); - } - - private handleClick(): void { - this.setState((prevState) => ({ - visibleScrim: !prevState.visibleScrim, - ...(prevState.visibleScrim && { inputValue: '' }), - })); - } - - override componentDidUpdate(prevProps: DeleteApplicationFormProps) { - const { appName, deleteApplicationMeta } = this.props; - - if ( - prevProps.deleteApplicationMeta?.error !== deleteApplicationMeta?.error && - deleteApplicationMeta?.error - ) { - errorToast( - `Failed to delete app ${appName}: ${deleteApplicationMeta.error}` - ); - } - } - - override render() { - const { appName } = this.props; - const { inputValue, visibleScrim } = this.state; - - return ( - - - - - Delete application - - - -
+export default function DeleteApplicationForm({ appName }: Props) { + const [mutate] = useDeleteApplicationMutation(); + const [inputValue, setInputValue] = useState(''); + const [visibleScrim, setVisibleScrim] = useState(false); + const navigate = useNavigate(); + + const doDelete = handlePromiseWithToast(async () => { + mutate({ appName }); + setVisibleScrim(false); + navigate(routes.apps); + }, 'Deleted'); + + return ( + + + + + Delete application + + + +
+ + Once you delete an application there is no going back + +
+ +
+
+ + Delete {appName}? + + } + open={visibleScrim} + isDismissable + onClose={() => setVisibleScrim(false)} + > +
+ + + This action can not be undone. + - Once you delete an application there is no going back + You will permanently remove {appName} from + Radix including all its environments. + + If you still want to delete this application and understand the + consequences, type delete in the text field + below. + + ) => + setInputValue(e.target.value) + } + value={inputValue} + />
-
- - Delete {appName}? - - } - open={visibleScrim} - isDismissable - onClose={this.handleClick} - > -
- - - This action can not be undone. - - - You will permanently remove {appName} from - Radix including all its environments. - - - If you still want to delete this application and understand - the consequences, type delete in the text - field below. - - -
- -
-
-
-
-
-
- ); - } + + + + + ); } - -function mapStateToProps(state: RootState): DeleteApplicationFormState { - return { - deleteApplicationMeta: { ...getMemoizedApplicationMeta(state) }, - }; -} - -function mapDispatchToProps(dispatch: Dispatch): DeleteApplicationFormDispatch { - return { - deleteApp: (appName) => dispatch(appActions.deleteAppRequest(appName)), - }; -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(DeleteApplicationForm); +DeleteApplicationForm.propTypes = { + appName: PropTypes.string.isRequired, +}; diff --git a/src/components/page-configuration/image-hubs-accordion.tsx b/src/components/page-configuration/image-hubs-accordion.tsx index ae1215eb3..a629c71c3 100644 --- a/src/components/page-configuration/image-hubs-accordion.tsx +++ b/src/components/page-configuration/image-hubs-accordion.tsx @@ -1,6 +1,6 @@ import { Accordion, List, Typography } from '@equinor/eds-core-react'; import * as PropTypes from 'prop-types'; -import { FunctionComponent, ReactNode, useState } from 'react'; +import { ReactNode, useState } from 'react'; import AsyncResource from '../async-resource/another-async-resource'; import { errorToast, successToast } from '../global-top-nav/styled-toaster'; @@ -17,11 +17,12 @@ import { dataSorter, sortCompareString } from '../../utils/sort-utils'; import './style.css'; -const ImageHubForm: FunctionComponent<{ +interface FormProps { appName: string; secret: ImageHubSecret; fetchSecret: () => void; -}> = ({ appName, secret, fetchSecret }) => { +} +function ImageHubForm({ appName, secret, fetchSecret }: FormProps) { const [trigger, { isLoading }] = radixApi.endpoints.updatePrivateImageHubsSecretValue.useMutation(); @@ -60,14 +61,13 @@ const ImageHubForm: FunctionComponent<{ } /> ); -}; +} -const SecretLink: FunctionComponent< - { title: string; scrimTitle?: ReactNode } & Pick< - Parameters[0], - 'appName' | 'fetchSecret' | 'secret' - > -> = ({ title, scrimTitle, ...rest }) => { +type SecretLinkProp = { + title: string; + scrimTitle?: ReactNode; +} & FormProps; +const SecretLink = ({ title, scrimTitle, ...rest }: SecretLinkProp) => { const [visibleScrim, setVisibleScrim] = useState(false); return ( @@ -95,9 +95,10 @@ const SecretLink: FunctionComponent< ); }; -export const ImageHubsAccordion: FunctionComponent<{ appName: string }> = ({ - appName, -}) => { +type Props = { + appName: string; +}; +export function ImageHubsAccordion({ appName }: Props) { const { data, refetch, ...state } = useGetPrivateImageHubsQuery( { appName }, { skip: !appName } @@ -138,7 +139,7 @@ export const ImageHubsAccordion: FunctionComponent<{ appName: string }> = ({ ); -}; +} ImageHubsAccordion.propTypes = { appName: PropTypes.string.isRequired, diff --git a/src/components/page-configuration/index.tsx b/src/components/page-configuration/index.tsx index b1cbb66ac..dcc01f33a 100644 --- a/src/components/page-configuration/index.tsx +++ b/src/components/page-configuration/index.tsx @@ -1,9 +1,4 @@ import { Typography } from '@equinor/eds-core-react'; -import * as PropTypes from 'prop-types'; -import { Component as ClassComponent } from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; - import { BuildSecretsAccordion } from './build-secrets-accordion'; import ChangeAdminForm from './change-admin-form'; import { ChangeConfigurationItemForm } from './change-ci-form'; @@ -14,44 +9,17 @@ import DeleteApplicationForm from './delete-application-form'; import { ImageHubsAccordion } from './image-hubs-accordion'; import { Overview } from './overview'; -import AsyncResource from '../async-resource'; +import AsyncResource from '../async-resource/another-async-resource'; import { Breadcrumb } from '../breadcrumb'; import { ConfigureApplicationGithub } from '../configure-application-github'; import { DocumentTitle } from '../document-title'; -import { RootState } from '../../init/store'; -import { - ApplicationModel, - ApplicationModelValidationMap, -} from '../../models/radix-api/applications/application'; -import { ApplicationRegistrationModel } from '../../models/radix-api/applications/application-registration'; import { routes } from '../../routes'; -import { getMemoizedApplication } from '../../state/application'; -import { - refreshApp, - subscribeApplication, - unsubscribeApplication, -} from '../../state/subscriptions/action-creators'; import { configVariables } from '../../utils/config'; -import { connectRouteParams, routeParamLoader } from '../../utils/router'; import { routeWithParams } from '../../utils/string'; import './style.css'; - -interface PageConfigurationDispatch { - subscribe: (appName: string) => void; - unsubscribe: (appName: string) => void; - refreshApp: (appName: string) => void; -} - -interface PageConfigurationState { - application?: ApplicationModel; -} - -export interface PageConfigurationProps - extends PageConfigurationDispatch, - PageConfigurationState { - appName: string; -} +import { radixApi, ApplicationRegistration } from '../../store/radix-api'; +import { useParams } from 'react-router-dom'; function getConfigBranch(configBranch: string): string { return configBranch || 'master'; @@ -64,7 +32,7 @@ function getRadixConfigFullName(radixConfigFullName: string): string { function getConfigBranchUrl({ configBranch, repository, -}: ApplicationRegistrationModel): string { +}: ApplicationRegistration): string { return `${repository}/tree/${getConfigBranch(configBranch)}`; } @@ -72,151 +40,105 @@ function getConfigFileUrl({ configBranch, radixConfigFullName, repository, -}: ApplicationRegistrationModel): string { +}: ApplicationRegistration): string { return `${repository}/blob/${configBranch}/${getRadixConfigFullName( radixConfigFullName )}`; } -export class PageConfiguration extends ClassComponent { - static readonly propTypes: PropTypes.ValidationMap = { - appName: PropTypes.string.isRequired, - application: PropTypes.shape( - ApplicationModelValidationMap - ) as PropTypes.Validator, - subscribe: PropTypes.func.isRequired, - unsubscribe: PropTypes.func.isRequired, - refreshApp: PropTypes.func.isRequired, - }; - - override componentDidMount() { - this.props.subscribe(this.props.appName); - } - - override componentDidUpdate(prevProps: Readonly) { - const { appName, subscribe, unsubscribe } = this.props; - if (appName !== prevProps.appName) { - unsubscribe(prevProps.appName); - subscribe(appName); - } - } - - override componentWillUnmount() { - this.props.unsubscribe(this.props.appName); - } - - override render() { - const { - application: { registration }, - appName, - refreshApp, - } = this.props; - - return ( -
- - - - - {registration?.name && ( - <> - -
- GitHub - - Cloned from{' '} - - {registration.repository} - +export default function PageConfiguration() { + const { appName } = useParams(); + const { + data: application, + refetch, + ...reqState + } = radixApi.useGetApplicationQuery({ appName }, { pollingInterval: 15000 }); + const registration = application?.registration; + + return ( +
+ + + + + {registration?.name && ( + <> + +
+ GitHub + + Cloned from{' '} + + {registration.repository} - - Config branch{' '} - - {getConfigBranch(registration.configBranch)} - + + + Config branch{' '} + + {getConfigBranch(registration.configBranch)} - - Config file{' '} - - {getRadixConfigFullName(registration.radixConfigFullName)} - + + + Config file{' '} + + {getRadixConfigFullName(registration.radixConfigFullName)} - + +
+ +
+ App Secrets + + +
+ +
+ Danger Zone + {configVariables.FLAGS.enableChangeAdmin && ( + -
- -
- App Secrets - - -
- -
- Danger Zone - {configVariables.FLAGS.enableChangeAdmin && ( - - )} - - - - - -
- - )} -
-
- ); - } + )} + + + + + +
+ + )} +
+
+ ); } - -function mapStateToProps(state: RootState): PageConfigurationState { - return { - application: { ...getMemoizedApplication(state) }, - }; -} - -function mapDispatchToProps(dispatch: Dispatch): PageConfigurationDispatch { - return { - subscribe: (appName) => dispatch(subscribeApplication(appName)), - unsubscribe: (appName) => dispatch(unsubscribeApplication(appName)), - refreshApp: (appName) => dispatch(refreshApp(appName)), - }; -} - -const ConnectedPageConfiguration = connect( - mapStateToProps, - mapDispatchToProps -)(PageConfiguration); - -const Component = connectRouteParams(ConnectedPageConfiguration); -export { Component, routeParamLoader as loader }; - -export default ConnectedPageConfiguration; diff --git a/src/components/page-configuration/overview.tsx b/src/components/page-configuration/overview.tsx index 991fa2303..f8155d1b4 100644 --- a/src/components/page-configuration/overview.tsx +++ b/src/components/page-configuration/overview.tsx @@ -1,13 +1,7 @@ import { List, Tooltip, Typography } from '@equinor/eds-core-react'; import { AuthCodeMSALBrowserAuthenticationProvider } from '@microsoft/microsoft-graph-client/authProviders/authCodeMsalBrowser'; import * as PropTypes from 'prop-types'; -import { - FunctionComponent, - useCallback, - useEffect, - useRef, - useState, -} from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { Alert } from '../alert'; import { useAppContext } from '../app-context'; @@ -18,15 +12,12 @@ import { AsyncState } from '../../effects/effect-types'; import { RequestState } from '../../state/state-utils/request-states'; import { dataSorter, sortCompareString } from '../../utils/sort-utils'; -export interface OverviewProps { +interface Props { adGroups?: Array; appName: string; } -export const Overview: FunctionComponent = ({ - adGroups, - appName, -}) => { +export function Overview({ adGroups, appName }: Props) { const { graphAuthProvider } = useAppContext(); const mountedRef = useRef(true); @@ -124,7 +115,7 @@ export const Overview: FunctionComponent = ({
); -}; +} Overview.propTypes = { adGroups: PropTypes.arrayOf(PropTypes.string), diff --git a/src/components/page-configuration/use-save-ci.ts b/src/components/page-configuration/use-save-ci.ts deleted file mode 100644 index a3f03aa4f..000000000 --- a/src/components/page-configuration/use-save-ci.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { usePatchJson } from '../../effects'; -import { AsyncRequestResult } from '../../effects/use-async-request'; -import { ApplicationRegistrationPatchRequestModelNormalizer } from '../../models/radix-api/applications/application-registration-patch-request/normalizer'; - -export function useSaveConfigurationItem( - appName: string -): AsyncRequestResult { - const encAppName = encodeURIComponent(appName); - - return usePatchJson(`/applications/${encAppName}`, (configurationItem) => - ApplicationRegistrationPatchRequestModelNormalizer({ - applicationRegistrationPatch: { configurationItem }, - }) - ); -} diff --git a/src/components/page-configuration/use-save-config-branch.ts b/src/components/page-configuration/use-save-config-branch.ts deleted file mode 100644 index 73a094647..000000000 --- a/src/components/page-configuration/use-save-config-branch.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { usePatchJson } from '../../effects'; -import { AsyncRequestResult } from '../../effects/use-async-request'; -import { ApplicationRegistrationPatchRequestModelNormalizer } from '../../models/radix-api/applications/application-registration-patch-request/normalizer'; - -export function useSaveConfigBranch( - appName: string -): AsyncRequestResult { - const encAppName = encodeURIComponent(appName); - - return usePatchJson(`/applications/${encAppName}`, (configBranch) => - ApplicationRegistrationPatchRequestModelNormalizer({ - applicationRegistrationPatch: { configBranch }, - }) - ); -} diff --git a/src/components/page-create-application/dev.tsx b/src/components/page-create-application/dev.tsx index 9a4263d29..367a1baa1 100644 --- a/src/components/page-create-application/dev.tsx +++ b/src/components/page-create-application/dev.tsx @@ -9,6 +9,6 @@ export default ( padding: '16px', }} > - + {}} />
); diff --git a/src/components/page-create-application/index.tsx b/src/components/page-create-application/index.tsx index 5da242fe1..7399fd379 100644 --- a/src/components/page-create-application/index.tsx +++ b/src/components/page-create-application/index.tsx @@ -1,79 +1,37 @@ import { Button, Icon, Typography } from '@equinor/eds-core-react'; import { add } from '@equinor/eds-icons'; -import * as PropTypes from 'prop-types'; -import { FunctionComponent, useEffect, useRef, useState } from 'react'; -import { connect } from 'react-redux'; +import { useRef, useState } from 'react'; import { Link } from 'react-router-dom'; -import { Dispatch } from 'redux'; import { ConfigureApplicationGithub } from '../configure-application-github'; import CreateApplicationForm from '../create-application-form'; import { ScrimPopup } from '../scrim-popup'; -import { RootState } from '../../init/store'; -import { - ApplicationRegistrationUpsertResponseModel, - ApplicationRegistrationUpsertResponseModelValidationMap, -} from '../../models/radix-api/applications/application-registration-upsert-response'; import { routes } from '../../routes'; -import { - getCreationResult, - getCreationState, -} from '../../state/application-creation'; -import { actions as appsActions } from '../../state/application-creation/action-creators'; -import { RequestState } from '../../state/state-utils/request-states'; import { routeWithParams } from '../../utils/string'; import './style.css'; - -interface PageCreateApplicationState { - creationState: RequestState; - creationResponse?: ApplicationRegistrationUpsertResponseModel; -} - -interface PageCreateApplicationDispatch { - resetCreate: () => void; -} - -export interface PageCreateApplicationProps - extends PageCreateApplicationState, - PageCreateApplicationDispatch {} +import { ApplicationRegistration } from '../../store/radix-api'; function scrollToPosition(elementRef: Element, x: number, y: number): void { elementRef.scrollTo?.(x, y); } -const PageCreateApplication: FunctionComponent = ({ - creationState, - creationResponse, - resetCreate, -}) => { +type Props = { refetch: Function }; +export default function PageCreateApplication({ refetch }: Props) { const [visibleScrim, setVisibleScrim] = useState(false); const containerRef = useRef(null); + const [registration, setRegistration] = + useState(null); - useEffect(() => { - if (!visibleScrim) { - resetCreate(); - } - }, [resetCreate, visibleScrim]); - - useEffect(() => { - if (!visibleScrim) return; + const onCloseScrim = () => { + setVisibleScrim(false); + }; - if (creationState === RequestState.FAILURE) { - scrollToPosition( - containerRef.current, - 0, - containerRef.current?.scrollHeight - ); - } else if (creationState === RequestState.SUCCESS) { - if ( - creationResponse.applicationRegistration && - !creationResponse.warnings - ) { - scrollToPosition(containerRef.current, 0, 0); - } - } - }, [creationState, creationResponse, visibleScrim]); + const onCreated = async (newRegistration: ApplicationRegistration) => { + setRegistration(newRegistration); + await refetch(); + scrollToPosition(containerRef.current, 0, 0); + }; return ( <> @@ -89,22 +47,20 @@ const PageCreateApplication: FunctionComponent = ({ setVisibleScrim(false)} + onClose={onCloseScrim} >
- {creationState !== RequestState.SUCCESS || - !creationResponse.applicationRegistration || - creationResponse.warnings ? ( - + {!registration ? ( + ) : (
- The application{' '} - {creationResponse.applicationRegistration.name}{' '} - has been set up + The application {registration.name} has been + set up void 0} useOtherCiToolOptionVisible @@ -115,7 +71,7 @@ const PageCreateApplication: FunctionComponent = ({ @@ -128,28 +84,4 @@ const PageCreateApplication: FunctionComponent = ({ ); -}; - -PageCreateApplication.propTypes = { - creationState: PropTypes.oneOf(Object.values(RequestState)).isRequired, - resetCreate: PropTypes.func.isRequired, - creationResponse: PropTypes.shape( - ApplicationRegistrationUpsertResponseModelValidationMap - ), -}; - -function mapStateToProps(state: RootState): PageCreateApplicationState { - return { - creationState: getCreationState(state), - creationResponse: getCreationResult(state), - }; } - -function mapDispatchToProps(dispatch: Dispatch): PageCreateApplicationDispatch { - return { resetCreate: () => dispatch(appsActions.addAppReset()) }; -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(PageCreateApplication); diff --git a/src/components/page-environment/environment-alerting.tsx b/src/components/page-environment/environment-alerting.tsx index bab4bf509..d5bb62ecb 100644 --- a/src/components/page-environment/environment-alerting.tsx +++ b/src/components/page-environment/environment-alerting.tsx @@ -1,124 +1,60 @@ import { Button, Icon, Typography } from '@equinor/eds-core-react'; import { notifications, notifications_off } from '@equinor/eds-icons'; import * as PropTypes from 'prop-types'; -import { FunctionComponent, useEffect, useState } from 'react'; -import { connect } from 'react-redux'; +import { useState } from 'react'; import { Alerting } from '../alerting'; -import AsyncResource from '../async-resource'; +import AsyncResource from '../async-resource/another-async-resource'; import { ScrimPopup } from '../scrim-popup'; -import { RootState } from '../../init/store'; -import { - AlertingConfigModel, - AlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/alerting-config'; -import { - UpdateAlertingConfigModel, - UpdateAlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/update-alerting-config'; -import { environmentAlertingState } from '../../state/environment-alerting'; -import { actions } from '../../state/environment-alerting/action-creators'; -import { RequestState } from '../../state/state-utils/request-states'; -import { - subscribeEnvironmentAlerting, - unsubscribeEnvironmentAlerting, -} from '../../state/subscriptions/action-creators'; import './style.css'; +import { radixApi, UpdateAlertingConfig } from '../../store/radix-api'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; -interface EnvironmentAlertingState { - alertingConfig?: Readonly; - alertingEditConfig?: Readonly; - enableAlertingRequestState?: RequestState; - disableAlertingRequestState?: RequestState; - updateAlertingRequestState?: RequestState; - enableAlertingLastError: string; - disableAlertingLastError: string; - updateAlertingLastError: string; - isAlertingEditEnabled: boolean; - isAlertingEditDirty: boolean; -} - -interface EnvironmentAlertingDispatch { - enableAlerting: (appName: string, envName: string) => void; - disableAlerting: (appName: string, envName: string) => void; - updateAlerting: ( - appName: string, - envName: string, - request: UpdateAlertingConfigModel - ) => void; - editAlertingEnable: (alertingConfig: AlertingConfigModel) => void; - editAlertingDisable: () => void; - editAlertingSetSlackUrl: (receiver: string, slackUrl: string) => void; - resetEnableAlertingState: (appName: string, envName: string) => void; - resetDisableAlertingState: (appName: string, envName: string) => void; - resetUpdateAlertingState: (appName: string, envName: string) => void; - subscribe: (appName: string, envName: string) => void; - unsubscribe: (appName: string, envName: string) => void; -} - -export interface EnvironmentAlertingProps - extends EnvironmentAlertingState, - EnvironmentAlertingDispatch { +interface Props { appName: string; envName: string; } - -const EnvironmentAlerting: FunctionComponent = ({ - appName, - envName, - subscribe, - unsubscribe, - alertingConfig, - updateAlerting, - enableAlerting, - disableAlerting, - enableAlertingRequestState, - disableAlertingRequestState, - updateAlertingRequestState, - resetEnableAlertingState, - resetDisableAlertingState, - resetUpdateAlertingState, - enableAlertingLastError, - disableAlertingLastError, - updateAlertingLastError, - alertingEditConfig, - editAlertingEnable, - editAlertingDisable, - editAlertingSetSlackUrl, - isAlertingEditEnabled, - isAlertingEditDirty, -}) => { +export default function EnvironmentAlerting({ appName, envName }: Props) { const [visibleScrim, setVisibleScrim] = useState(false); + const { + data: alertingConfig, + refetch, + ...configState + } = radixApi.useGetEnvironmentAlertingConfigQuery({ + appName, + envName, + }); - // Reset subscription on parameter change - // Unsubscribe on unmount - useEffect(() => { - subscribe(appName, envName); - return () => unsubscribe(appName, envName); - }, [subscribe, unsubscribe, appName, envName]); + const [enableAlertMutation, { isLoading: savingEnable }] = + radixApi.useEnableEnvironmentAlertingMutation(); + const [disableAlertMutation, { isLoading: savingDisable }] = + radixApi.useDisableEnvironmentAlertingMutation(); + const [updateAlertMutation, { isLoading: savingUpdate }] = + radixApi.useUpdateEnvironmentAlertingConfigMutation(); - // Reset request states on component unmount - useEffect( - () => () => { - resetDisableAlertingState(appName, envName); - resetEnableAlertingState(appName, envName); - resetUpdateAlertingState(appName, envName); - }, - [ - appName, - envName, - resetDisableAlertingState, - resetEnableAlertingState, - resetUpdateAlertingState, - ] + const enableAlert = handlePromiseWithToast(async () => { + await enableAlertMutation({ appName, envName }).unwrap(); + await refetch(); + }); + const disableAlert = handlePromiseWithToast(async () => { + await disableAlertMutation({ appName, envName }).unwrap(); + await refetch(); + }); + const updateAlert = handlePromiseWithToast( + async (updateAlertingConfig: UpdateAlertingConfig) => { + await updateAlertMutation({ + appName, + envName, + updateAlertingConfig, + }).unwrap(); + await refetch(); + setVisibleScrim(false); + } ); return ( - + {alertingConfig && ( <> @@ -143,24 +79,11 @@ const EnvironmentAlerting: FunctionComponent = ({ >
- updateAlerting(appName, envName, request) - } - enableAlerting={() => enableAlerting(appName, envName)} - disableAlerting={() => disableAlerting(appName, envName)} - enableAlertingRequestState={enableAlertingRequestState} - disableAlertingRequestState={disableAlertingRequestState} - updateAlertingRequestState={updateAlertingRequestState} - enableAlertingLastError={enableAlertingLastError} - disableAlertingLastError={disableAlertingLastError} - updateAlertingLastError={updateAlertingLastError} - alertingEditConfig={alertingEditConfig} - editAlertingEnable={editAlertingEnable} - editAlertingDisable={editAlertingDisable} - editAlertingSetSlackUrl={editAlertingSetSlackUrl} - isAlertingEditEnabled={isAlertingEditEnabled} - isAlertingEditDirty={isAlertingEditDirty} + updateAlerting={updateAlert} + enableAlerting={enableAlert} + disableAlerting={disableAlert} />
@@ -168,84 +91,9 @@ const EnvironmentAlerting: FunctionComponent = ({ )}
); -}; +} EnvironmentAlerting.propTypes = { appName: PropTypes.string.isRequired, envName: PropTypes.string.isRequired, - alertingConfig: PropTypes.shape( - AlertingConfigModelValidationMap - ) as PropTypes.Validator, - alertingEditConfig: PropTypes.shape( - UpdateAlertingConfigModelValidationMap - ) as PropTypes.Validator, - enableAlertingRequestState: PropTypes.oneOf(Object.values(RequestState)), - disableAlertingRequestState: PropTypes.oneOf(Object.values(RequestState)), - updateAlertingRequestState: PropTypes.oneOf(Object.values(RequestState)), - enableAlertingLastError: PropTypes.string, - disableAlertingLastError: PropTypes.string, - updateAlertingLastError: PropTypes.string, - isAlertingEditEnabled: PropTypes.bool.isRequired, - isAlertingEditDirty: PropTypes.bool.isRequired, - enableAlerting: PropTypes.func.isRequired, - disableAlerting: PropTypes.func.isRequired, - updateAlerting: PropTypes.func.isRequired, - editAlertingEnable: PropTypes.func.isRequired, - editAlertingDisable: PropTypes.func.isRequired, - editAlertingSetSlackUrl: PropTypes.func.isRequired, - resetEnableAlertingState: PropTypes.func.isRequired, - resetUpdateAlertingState: PropTypes.func.isRequired, - resetDisableAlertingState: PropTypes.func.isRequired, - subscribe: PropTypes.func.isRequired, - unsubscribe: PropTypes.func.isRequired, }; - -export default connect< - EnvironmentAlertingState, - EnvironmentAlertingDispatch, - {}, - RootState ->( - (state) => ({ - alertingConfig: environmentAlertingState.getAlertingConfig(state), - enableAlertingRequestState: - environmentAlertingState.getEnableAlertingRequestState(state), - disableAlertingRequestState: - environmentAlertingState.getDisableAlertingRequestState(state), - updateAlertingRequestState: - environmentAlertingState.getUpdateAlertingRequestState(state), - enableAlertingLastError: - environmentAlertingState.getEnableAlertingRequestError(state), - disableAlertingLastError: - environmentAlertingState.getDisableAlertingRequestError(state), - updateAlertingLastError: - environmentAlertingState.getUpdateAlertingRequestError(state), - alertingEditConfig: environmentAlertingState.getAlertingEditConfig(state), - isAlertingEditEnabled: - environmentAlertingState.isAlertingEditEnabled(state), - isAlertingEditDirty: environmentAlertingState.isAlertingEditDirty(state), - }), - (dispatch) => ({ - editAlertingEnable: (alertingConfig) => - dispatch(actions.editAlertingEnable(alertingConfig)), - editAlertingDisable: () => dispatch(actions.editAlertingDisable()), - editAlertingSetSlackUrl: (receiver, slackUrl) => - dispatch(actions.editAlertingSetSlackUrl(receiver, slackUrl)), - enableAlerting: (appName, envName) => - dispatch(actions.enableAlertingRequest(appName, envName)), - resetEnableAlertingState: (appName, envName) => - dispatch(actions.enableAlertingReset(appName, envName)), - disableAlerting: (appName, envName) => - dispatch(actions.disableAlertingRequest(appName, envName)), - resetDisableAlertingState: (appName, envName) => - dispatch(actions.disableAlertingReset(appName, envName)), - updateAlerting: (appName, envName, request) => - dispatch(actions.updateAlertingRequest(appName, envName, request)), - resetUpdateAlertingState: (appName, envName) => - dispatch(actions.updateAlertingReset(appName, envName)), - subscribe: (appName, envName) => - dispatch(subscribeEnvironmentAlerting(appName, envName)), - unsubscribe: (appName, envName) => - dispatch(unsubscribeEnvironmentAlerting(appName, envName)), - }) -)(EnvironmentAlerting); diff --git a/src/components/page-pipeline-jobs/application-alerting.tsx b/src/components/page-pipeline-jobs/application-alerting.tsx index 482d60b89..456592ac3 100644 --- a/src/components/page-pipeline-jobs/application-alerting.tsx +++ b/src/components/page-pipeline-jobs/application-alerting.tsx @@ -1,143 +1,63 @@ import { Button, Icon, Typography } from '@equinor/eds-core-react'; import { notifications, notifications_off } from '@equinor/eds-icons'; import * as PropTypes from 'prop-types'; -import { FunctionComponent, useEffect, useState } from 'react'; -import { connect } from 'react-redux'; -import { Dispatch } from 'redux'; +import { useState } from 'react'; import { Alerting } from '../alerting'; -import AsyncResource from '../async-resource'; +import AsyncResource from '../async-resource/another-async-resource'; import { ScrimPopup } from '../scrim-popup'; -import { RootState } from '../../init/store'; -import { - AlertingConfigModel, - AlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/alerting-config'; -import { - UpdateAlertingConfigModel, - UpdateAlertingConfigModelValidationMap, -} from '../../models/radix-api/alerting/update-alerting-config'; -import { applicationAlertingState } from '../../state/application-alerting'; -import { actions as alertingActions } from '../../state/application-alerting/action-creators'; -import { RequestState } from '../../state/state-utils/request-states'; -import { - subscribeApplicationAlerting, - unsubscribeApplicationAlerting, -} from '../../state/subscriptions/action-creators'; import './style.css'; +import { handlePromiseWithToast } from '../global-top-nav/styled-toaster'; +import { radixApi, UpdateAlertingConfig } from '../../store/radix-api'; -interface ApplicationAlertingDispatch { - subscribe: (appName: string) => void; - unsubscribe: (appName: string) => void; - enableAlerting: (appName: string) => void; - disableAlerting: (appName: string) => void; - updateAlerting: (appName: string, request: UpdateAlertingConfigModel) => void; - resetEnableAlertingState: (appName: string) => void; - resetDisableAlertingState: (appName: string) => void; - resetUpdateAlertingState: (appName: string) => void; - editAlertingEnable: (alertingConfig: AlertingConfigModel) => void; - editAlertingDisable: () => void; - editAlertingSetSlackUrl: (receiver: string, slackUrl: string) => void; -} - -interface ApplicationAlertingState { - alertingConfig?: AlertingConfigModel; - alertingEditConfig?: UpdateAlertingConfigModel; - enableAlertingRequestState?: RequestState; - disableAlertingRequestState?: RequestState; - updateAlertingRequestState?: RequestState; - enableAlertingLastError?: string; - disableAlertingLastError?: string; - updateAlertingLastError?: string; - isAlertingEditEnabled: boolean; - isAlertingEditDirty: boolean; -} - -export interface ApplicationAlertingProps - extends ApplicationAlertingDispatch, - ApplicationAlertingState { +interface Props { appName: string; } -const ApplicationAlerting: FunctionComponent = ({ - appName, - - alertingConfig, - alertingEditConfig, - enableAlertingRequestState, - disableAlertingRequestState, - updateAlertingRequestState, - enableAlertingLastError, - disableAlertingLastError, - updateAlertingLastError, - isAlertingEditEnabled, - isAlertingEditDirty, - - subscribe, - unsubscribe, - enableAlerting, - disableAlerting, - updateAlerting, - resetEnableAlertingState, - resetDisableAlertingState, - resetUpdateAlertingState, - editAlertingEnable, - editAlertingDisable, - editAlertingSetSlackUrl, -}) => { +const ApplicationAlerting = ({ appName }: Props) => { const [visibleScrim, setVisibleScrim] = useState(false); - - // Reset subscription on parameter change - // Unsubscribe on unmount - useEffect(() => { - subscribe(appName); - return () => unsubscribe(appName); - }, [appName, subscribe, unsubscribe]); - - // Reset request states on component unmount - useEffect( - () => () => { - resetDisableAlertingState(appName); - resetEnableAlertingState(appName); - resetUpdateAlertingState(appName); - }, - [ - appName, - resetDisableAlertingState, - resetEnableAlertingState, - resetUpdateAlertingState, - ] + const { + data: config, + refetch, + ...state + } = radixApi.useGetApplicationAlertingConfigQuery({ appName }); + + const [enableAlertMutation, { isLoading: savingEnable }] = + radixApi.useEnableApplicationAlertingMutation(); + const [disableAlertMutation, { isLoading: savingDisable }] = + radixApi.useDisableApplicationAlertingMutation(); + const [updateAlertMutation, { isLoading: savingUpdate }] = + radixApi.useUpdateApplicationAlertingConfigMutation(); + + const enableAlert = handlePromiseWithToast(async () => { + await enableAlertMutation({ appName }).unwrap(); + await refetch(); + }); + const disableAlert = handlePromiseWithToast(async () => { + await disableAlertMutation({ appName }).unwrap(); + await refetch(); + }); + const updateAlert = handlePromiseWithToast( + async (updateAlertingConfig: UpdateAlertingConfig) => { + await updateAlertMutation({ appName, updateAlertingConfig }).unwrap(); + await refetch(); + setVisibleScrim(false); + } ); - function updateAlertingCallback(request: UpdateAlertingConfigModel): void { - updateAlerting(appName, request); - } - - function enableAlertingCallback(): void { - enableAlerting(appName); - } - - function disableAlertingCallback(): void { - disableAlerting(appName); - } - return ( - - {alertingConfig && ( + + {config && ( <> Alerting is{' '} - {alertingConfig.enabled ? 'enabled' : 'disabled'} + {config.enabled ? 'enabled' : 'disabled'} {' '} = ({ >
@@ -174,82 +83,6 @@ const ApplicationAlerting: FunctionComponent = ({ ApplicationAlerting.propTypes = { appName: PropTypes.string.isRequired, - - alertingConfig: PropTypes.shape( - AlertingConfigModelValidationMap - ) as PropTypes.Validator, - alertingEditConfig: PropTypes.shape( - UpdateAlertingConfigModelValidationMap - ) as PropTypes.Validator, - enableAlertingRequestState: PropTypes.oneOf(Object.values(RequestState)), - disableAlertingRequestState: PropTypes.oneOf(Object.values(RequestState)), - updateAlertingRequestState: PropTypes.oneOf(Object.values(RequestState)), - enableAlertingLastError: PropTypes.string, - disableAlertingLastError: PropTypes.string, - updateAlertingLastError: PropTypes.string, - isAlertingEditEnabled: PropTypes.bool.isRequired, - isAlertingEditDirty: PropTypes.bool.isRequired, - - subscribe: PropTypes.func.isRequired, - unsubscribe: PropTypes.func.isRequired, - enableAlerting: PropTypes.func.isRequired, - disableAlerting: PropTypes.func.isRequired, - updateAlerting: PropTypes.func.isRequired, - resetEnableAlertingState: PropTypes.func.isRequired, - resetDisableAlertingState: PropTypes.func.isRequired, - resetUpdateAlertingState: PropTypes.func.isRequired, - editAlertingEnable: PropTypes.func.isRequired, - editAlertingDisable: PropTypes.func.isRequired, - editAlertingSetSlackUrl: PropTypes.func.isRequired, }; -function mapStateToProps(state: RootState): ApplicationAlertingState { - return { - alertingConfig: applicationAlertingState.getAlertingConfig(state), - enableAlertingRequestState: - applicationAlertingState.getEnableAlertingRequestState(state), - disableAlertingRequestState: - applicationAlertingState.getDisableAlertingRequestState(state), - updateAlertingRequestState: - applicationAlertingState.getUpdateAlertingRequestState(state), - enableAlertingLastError: - applicationAlertingState.getEnableAlertingRequestError(state), - disableAlertingLastError: - applicationAlertingState.getDisableAlertingRequestError(state), - updateAlertingLastError: - applicationAlertingState.getUpdateAlertingRequestError(state), - alertingEditConfig: applicationAlertingState.getAlertingEditConfig(state), - isAlertingEditEnabled: - applicationAlertingState.isAlertingEditEnabled(state), - isAlertingEditDirty: applicationAlertingState.isAlertingEditDirty(state), - }; -} - -function mapDispatchToProps(dispatch: Dispatch): ApplicationAlertingDispatch { - return { - subscribe: (appName) => dispatch(subscribeApplicationAlerting(appName)), - unsubscribe: (appName) => dispatch(unsubscribeApplicationAlerting(appName)), - enableAlerting: (appName) => - dispatch(alertingActions.enableAlertingRequest(appName)), - disableAlerting: (appName) => - dispatch(alertingActions.disableAlertingRequest(appName)), - updateAlerting: (appName, request) => - dispatch(alertingActions.updateAlertingRequest(appName, request)), - resetEnableAlertingState: (appName) => - dispatch(alertingActions.enableAlertingReset(appName)), - resetDisableAlertingState: (appName) => - dispatch(alertingActions.disableAlertingReset(appName)), - resetUpdateAlertingState: (appName) => - dispatch(alertingActions.updateAlertingReset(appName)), - editAlertingEnable: (alertingConfig) => - dispatch(alertingActions.editAlertingEnable(alertingConfig)), - editAlertingDisable: () => dispatch(alertingActions.editAlertingDisable()), - editAlertingSetSlackUrl: (receiver, slackUrl) => - dispatch(alertingActions.editAlertingSetSlackUrl(receiver, slackUrl)), - }; -} - -export default connect( - mapStateToProps, - mapDispatchToProps -)(ApplicationAlerting); +export default ApplicationAlerting; diff --git a/src/components/secret-form/index.tsx b/src/components/secret-form/index.tsx index 9c4b0488e..d91256612 100644 --- a/src/components/secret-form/index.tsx +++ b/src/components/secret-form/index.tsx @@ -1,5 +1,4 @@ import { Button, TextField, Typography } from '@equinor/eds-core-react'; -import { isNil } from 'lodash'; import * as PropTypes from 'prop-types'; import { ChangeEvent, FunctionComponent, ReactNode, useState } from 'react'; @@ -68,7 +67,7 @@ export const SecretForm: FunctionComponent<{ onClick={async () => { setValue((x) => ({ ...x, previous: value.current })); const result = await onSave(value.current); - if (isNil(result) || result === false) { + if (result) { // void or false, clear previous value to re-enable Save button setValue(({ current }) => ({ current })); } diff --git a/src/effects/use-local-storage.ts b/src/effects/use-local-storage.ts new file mode 100644 index 000000000..41291d2d9 --- /dev/null +++ b/src/effects/use-local-storage.ts @@ -0,0 +1,13 @@ +import { useEffect, useState } from 'react'; + +export default function useLocalStorage(key: string, defaultValue: T) { + const [state, setState] = useState( + () => JSON.parse(localStorage.getItem(key)) || defaultValue + ); + + useEffect(() => { + window.localStorage.setItem(key, JSON.stringify(state)); + }, [state, key]); + + return [state, setState] as const; +} diff --git a/src/models/radix-api/alerting/alert-config/index.ts b/src/models/radix-api/alerting/alert-config/index.ts deleted file mode 100644 index 3c9cf1769..000000000 --- a/src/models/radix-api/alerting/alert-config/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as PropTypes from 'prop-types'; - -export interface AlertConfigModel { - alert: string; - receiver: string; -} - -/* PropTypes validation map for AlertConfigModel */ -export const AlertConfigModelValidationMap: PropTypes.ValidationMap = - { - alert: PropTypes.string.isRequired, - receiver: PropTypes.string.isRequired, - }; diff --git a/src/models/radix-api/alerting/alert-config/normalizer.ts b/src/models/radix-api/alerting/alert-config/normalizer.ts deleted file mode 100644 index 626ebe542..000000000 --- a/src/models/radix-api/alerting/alert-config/normalizer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { AlertConfigModel } from '.'; - -import { ModelNormalizerType } from '../../../model-types'; -import { objectNormalizer } from '../../../model-utils'; - -/** - * Create an AlertConfigModel object - */ -export const AlertConfigModelNormalizer: ModelNormalizerType< - Readonly -> = (props) => Object.freeze(objectNormalizer(props, {})); diff --git a/src/models/radix-api/alerting/alert-config/test-data.ts b/src/models/radix-api/alerting/alert-config/test-data.ts deleted file mode 100644 index d2d1e4b48..000000000 --- a/src/models/radix-api/alerting/alert-config/test-data.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { AlertConfigModel } from '.'; - -import { TestDependencyDataType } from '../../../model-types'; - -/* - * TestData array - * - * Note: First object should always be valid - */ -export const testData: TestDependencyDataType = [ - { - __testDescription: 'Valid full object', - alert: 'a1', - receiver: 'receiver', - }, - { - __testDescription: 'Invalid full object', - __testIsInvalidSample: true, - alert: 'a1', - receiver: ['receiver1'] as unknown as string, - }, - { - __testDescription: 'Invalid partial object', - __testIsInvalidSample: true, - alert: 'a1', - receiver: undefined, - }, - { - __testDescription: 'Invalid empty object', - __testIsInvalidSample: true, - alert: undefined, - receiver: undefined, - }, -]; diff --git a/src/models/radix-api/alerting/alerting-config/index.ts b/src/models/radix-api/alerting/alerting-config/index.ts deleted file mode 100644 index be172e1ca..000000000 --- a/src/models/radix-api/alerting/alerting-config/index.ts +++ /dev/null @@ -1,42 +0,0 @@ -import * as PropTypes from 'prop-types'; - -import { - AlertConfigModel, - AlertConfigModelValidationMap, -} from '../alert-config'; -import { - ReceiverConfigModel, - ReceiverConfigModelValidationMap, -} from '../receiver-config'; -import { - ReceiverConfigSecretStatusModel, - ReceiverConfigSecretStatusModelValidationMap, -} from '../receiver-config-secret-status'; - -export interface AlertingConfigModel { - enabled: boolean; - ready: boolean; - receivers?: Record; - receiverSecretStatus?: Record; - alerts?: Array; - alertNames?: Array; -} - -/* PropTypes validation map for AlertingConfigModel */ -export const AlertingConfigModelValidationMap: PropTypes.ValidationMap = - { - enabled: PropTypes.bool.isRequired, - ready: PropTypes.bool.isRequired, - receivers: PropTypes.objectOf( - PropTypes.shape(ReceiverConfigModelValidationMap) - ), - receiverSecretStatus: PropTypes.objectOf( - PropTypes.shape(ReceiverConfigSecretStatusModelValidationMap) - ), - alerts: PropTypes.arrayOf( - PropTypes.shape( - AlertConfigModelValidationMap - ) as PropTypes.Validator - ), - alertNames: PropTypes.arrayOf(PropTypes.string), - }; diff --git a/src/models/radix-api/alerting/alerting-config/normalizer.ts b/src/models/radix-api/alerting/alerting-config/normalizer.ts deleted file mode 100644 index 07eef113b..000000000 --- a/src/models/radix-api/alerting/alerting-config/normalizer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { AlertingConfigModel } from '.'; - -import { AlertConfigModelNormalizer } from '../alert-config/normalizer'; -import { ReceiverConfigSecretStatusModelNormalizer } from '../receiver-config-secret-status/normalizer'; -import { ReceiverConfigModelNormalizer } from '../receiver-config/normalizer'; -import { ModelNormalizerType } from '../../../model-types'; -import { - arrayNormalizer, - objectNormalizer, - recordNormalizer, -} from '../../../model-utils'; - -/** - * Create an AlertingConfigModel object - */ -export const AlertingConfigModelNormalizer: ModelNormalizerType< - Readonly -> = (props) => - Object.freeze( - objectNormalizer(props, { - receivers: (x) => recordNormalizer(x, ReceiverConfigModelNormalizer), - receiverSecretStatus: (x) => - recordNormalizer(x, ReceiverConfigSecretStatusModelNormalizer), - alerts: (x) => arrayNormalizer(x, AlertConfigModelNormalizer), - }) - ); diff --git a/src/models/radix-api/alerting/alerting-config/test-data.ts b/src/models/radix-api/alerting/alerting-config/test-data.ts deleted file mode 100644 index 6e2cf47f3..000000000 --- a/src/models/radix-api/alerting/alerting-config/test-data.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { AlertingConfigModel } from '.'; - -import { testData as AlertConfigData } from '../alert-config/test-data'; -import { testData as ReceiverConfigData } from '../receiver-config/test-data'; -import { testData as ReceiverConfigSecretStatusData } from '../receiver-config-secret-status/test-data'; -import { TestDependencyDataType } from '../../../model-types'; - -/* - * TestData array - * - * Note: First object should always be valid - */ -export const testData: TestDependencyDataType = [ - { - __testDescription: 'Valid full object', - enabled: true, - ready: false, - receivers: { b: ReceiverConfigData[0] }, - receiverSecretStatus: { a: ReceiverConfigSecretStatusData[0] }, - alerts: [AlertConfigData[0]], - alertNames: ['name1', 'name2'], - }, - { - __testDescription: 'Valid partial object', - enabled: true, - ready: false, - }, - { - __testDescription: 'Invalid full object', - __testIsInvalidSample: true, - enabled: true, - ready: false, - receivers: { b: ReceiverConfigData[0] }, - receiverSecretStatus: { a: ReceiverConfigSecretStatusData[0] }, - alerts: [AlertConfigData[0]], - alertNames: 'name1' as unknown as Array, - }, - { - __testDescription: 'Invalid partial object', - __testIsInvalidSample: true, - enabled: {} as unknown as boolean, - ready: true, - }, - { - __testDescription: 'Invalid empty object', - __testIsInvalidSample: true, - enabled: undefined, - ready: undefined, - }, -]; diff --git a/src/models/radix-api/alerting/receiver-config-secret-status/index.ts b/src/models/radix-api/alerting/receiver-config-secret-status/index.ts deleted file mode 100644 index 18ead8511..000000000 --- a/src/models/radix-api/alerting/receiver-config-secret-status/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as PropTypes from 'prop-types'; - -import { - SlackConfigSecretStatusModel, - SlackConfigSecretStatusModelValidationMap, -} from '../slack-config-secret-status'; - -export interface ReceiverConfigSecretStatusModel { - slackConfig?: SlackConfigSecretStatusModel; -} - -/* PropTypes validation map for ReceiverConfigSecretStatusModel */ -export const ReceiverConfigSecretStatusModelValidationMap: PropTypes.ValidationMap = - { - slackConfig: PropTypes.shape( - SlackConfigSecretStatusModelValidationMap - ) as PropTypes.Validator, - }; diff --git a/src/models/radix-api/alerting/receiver-config-secret-status/normalizer.ts b/src/models/radix-api/alerting/receiver-config-secret-status/normalizer.ts deleted file mode 100644 index 87d8e0838..000000000 --- a/src/models/radix-api/alerting/receiver-config-secret-status/normalizer.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ReceiverConfigSecretStatusModel } from '.'; - -import { SlackConfigSecretStatusModelNormalizer } from '../slack-config-secret-status/normalizer'; -import { ModelNormalizerType } from '../../../model-types'; -import { objectNormalizer } from '../../../model-utils'; - -/** - * Create a ReceiverConfigSecretStatusModel object - */ -export const ReceiverConfigSecretStatusModelNormalizer: ModelNormalizerType< - Readonly -> = (props) => - Object.freeze( - objectNormalizer(props, { - slackConfig: SlackConfigSecretStatusModelNormalizer, - }) - ); diff --git a/src/models/radix-api/alerting/receiver-config-secret-status/test-data.ts b/src/models/radix-api/alerting/receiver-config-secret-status/test-data.ts deleted file mode 100644 index 63df934e5..000000000 --- a/src/models/radix-api/alerting/receiver-config-secret-status/test-data.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ReceiverConfigSecretStatusModel } from '.'; - -import { SlackConfigSecretStatusModel } from '../slack-config-secret-status'; -import { testData as SlackConfigSecretStatusData } from '../slack-config-secret-status/test-data'; -import { TestDependencyDataType } from '../../../model-types'; - -/* - * TestData array - * - * Note: First object should always be valid - */ -export const testData: TestDependencyDataType = - [ - { - __testDescription: 'Valid full object', - slackConfig: SlackConfigSecretStatusData[0], - }, - { - __testDescription: 'Valid empty object', - }, - { - __testDescription: 'Invalid full object', - __testIsInvalidSample: true, - slackConfig: [ - SlackConfigSecretStatusData[0], - ] as unknown as SlackConfigSecretStatusModel, - }, - ]; diff --git a/src/models/radix-api/alerting/receiver-config/index.ts b/src/models/radix-api/alerting/receiver-config/index.ts deleted file mode 100644 index 4f3e8060f..000000000 --- a/src/models/radix-api/alerting/receiver-config/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import * as PropTypes from 'prop-types'; - -import { - SlackConfigModel, - SlackConfigModelValidationMap, -} from '../slack-config'; - -export interface ReceiverConfigModel { - slackConfig?: SlackConfigModel; -} - -/* PropTypes validation map for ReceiverConfigModel */ -export const ReceiverConfigModelValidationMap: PropTypes.ValidationMap = - { - slackConfig: PropTypes.shape( - SlackConfigModelValidationMap - ) as PropTypes.Validator, - }; diff --git a/src/models/radix-api/alerting/receiver-config/normalizer.ts b/src/models/radix-api/alerting/receiver-config/normalizer.ts deleted file mode 100644 index a729c8328..000000000 --- a/src/models/radix-api/alerting/receiver-config/normalizer.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ReceiverConfigModel } from '.'; - -import { SlackConfigModelNormalizer } from '../slack-config/normalizer'; -import { ModelNormalizerType } from '../../../model-types'; -import { objectNormalizer } from '../../../model-utils'; - -/** - * Create a ReceiverConfigModel object - */ -export const ReceiverConfigModelNormalizer: ModelNormalizerType< - Readonly -> = (props) => - Object.freeze( - objectNormalizer(props, { - slackConfig: SlackConfigModelNormalizer, - }) - ); diff --git a/src/models/radix-api/alerting/receiver-config/test-data.ts b/src/models/radix-api/alerting/receiver-config/test-data.ts deleted file mode 100644 index 8e53881d8..000000000 --- a/src/models/radix-api/alerting/receiver-config/test-data.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ReceiverConfigModel } from '.'; - -import { SlackConfigModel } from '../slack-config'; -import { testData as SlackConfigData } from '../slack-config/test-data'; -import { TestDependencyDataType } from '../../../model-types'; - -/* - * TestData array - * - * Note: First object should always be valid - */ -export const testData: TestDependencyDataType = [ - { - __testDescription: 'Valid full object', - slackConfig: SlackConfigData[0], - }, - { - __testDescription: 'Valid empty object', - }, - { - __testDescription: 'Invalid full object', - __testIsInvalidSample: true, - slackConfig: [SlackConfigData[0]] as unknown as SlackConfigModel, - }, -]; diff --git a/src/models/radix-api/alerting/slack-config-secret-status/index.ts b/src/models/radix-api/alerting/slack-config-secret-status/index.ts deleted file mode 100644 index 5e407e0a3..000000000 --- a/src/models/radix-api/alerting/slack-config-secret-status/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as PropTypes from 'prop-types'; - -export interface SlackConfigSecretStatusModel { - webhookUrlConfigured: boolean; -} - -/* PropTypes validation map for SlackConfigSecretStatusModel */ -export const SlackConfigSecretStatusModelValidationMap: PropTypes.ValidationMap = - { - webhookUrlConfigured: PropTypes.bool.isRequired, - }; diff --git a/src/models/radix-api/alerting/slack-config-secret-status/normalizer.ts b/src/models/radix-api/alerting/slack-config-secret-status/normalizer.ts deleted file mode 100644 index b7f5a593b..000000000 --- a/src/models/radix-api/alerting/slack-config-secret-status/normalizer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { SlackConfigSecretStatusModel } from '.'; - -import { ModelNormalizerType } from '../../../model-types'; -import { objectNormalizer } from '../../../model-utils'; - -/** - * Create a SlackConfigSecretStatusModel object - */ -export const SlackConfigSecretStatusModelNormalizer: ModelNormalizerType< - Readonly -> = (props) => Object.freeze(objectNormalizer(props, {})); diff --git a/src/models/radix-api/alerting/slack-config-secret-status/test-data.ts b/src/models/radix-api/alerting/slack-config-secret-status/test-data.ts deleted file mode 100644 index 8f94dff44..000000000 --- a/src/models/radix-api/alerting/slack-config-secret-status/test-data.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SlackConfigSecretStatusModel } from '.'; - -import { TestDependencyDataType } from '../../../model-types'; - -/* - * TestData array - * - * Note: First object should always be valid - */ -export const testData: TestDependencyDataType = [ - { - __testDescription: 'Valid full object', - webhookUrlConfigured: true, - }, - { - __testDescription: 'Invalid full object', - __testIsInvalidSample: true, - webhookUrlConfigured: {} as unknown as boolean, - }, - { - __testDescription: 'Invalid empty object', - __testIsInvalidSample: true, - webhookUrlConfigured: undefined, - }, -]; diff --git a/src/models/radix-api/alerting/slack-config/index.ts b/src/models/radix-api/alerting/slack-config/index.ts deleted file mode 100644 index 167464e34..000000000 --- a/src/models/radix-api/alerting/slack-config/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as PropTypes from 'prop-types'; - -export interface SlackConfigModel { - enabled: boolean; -} - -/* PropTypes validation map for SlackConfigModel */ -export const SlackConfigModelValidationMap: PropTypes.ValidationMap = - { - enabled: PropTypes.bool.isRequired, - }; diff --git a/src/models/radix-api/alerting/slack-config/normalizer.ts b/src/models/radix-api/alerting/slack-config/normalizer.ts deleted file mode 100644 index a95478a80..000000000 --- a/src/models/radix-api/alerting/slack-config/normalizer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { SlackConfigModel } from '.'; - -import { ModelNormalizerType } from '../../../model-types'; -import { objectNormalizer } from '../../../model-utils'; - -/** - * Create a SlackConfigModel object - */ -export const SlackConfigModelNormalizer: ModelNormalizerType< - Readonly -> = (props) => Object.freeze(objectNormalizer(props, {})); diff --git a/src/models/radix-api/alerting/slack-config/test-data.ts b/src/models/radix-api/alerting/slack-config/test-data.ts deleted file mode 100644 index f4506bf1c..000000000 --- a/src/models/radix-api/alerting/slack-config/test-data.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { SlackConfigModel } from '.'; - -import { TestDependencyDataType } from '../../../model-types'; - -/* - * TestData array - * - * Note: First object should always be valid - */ -export const testData: TestDependencyDataType = [ - { - __testDescription: 'Valid full object', - enabled: true, - }, - { - __testDescription: 'Invalid full object', - __testIsInvalidSample: true, - enabled: {} as unknown as boolean, - }, - { - __testDescription: 'Invalid empty object', - __testIsInvalidSample: true, - enabled: undefined, - }, -]; diff --git a/src/models/radix-api/alerting/update-alerting-config/index.ts b/src/models/radix-api/alerting/update-alerting-config/index.ts deleted file mode 100644 index 50efaa64e..000000000 --- a/src/models/radix-api/alerting/update-alerting-config/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as PropTypes from 'prop-types'; - -import { - AlertConfigModel, - AlertConfigModelValidationMap, -} from '../alert-config'; -import { - ReceiverConfigModel, - ReceiverConfigModelValidationMap, -} from '../receiver-config'; -import { - UpdateReceiverConfigSecretsModel, - UpdateReceiverConfigSecretsModelValidationMap, -} from '../update-receiver-config-secrets'; - -export interface UpdateAlertingConfigModel { - receivers?: Record; - receiverSecrets?: Record; - alerts: Array; -} - -/* PropTypes validation map for UpdateAlertingConfigModel */ -export const UpdateAlertingConfigModelValidationMap: PropTypes.ValidationMap = - { - receivers: PropTypes.objectOf( - PropTypes.shape(ReceiverConfigModelValidationMap) - ), - receiverSecrets: PropTypes.objectOf( - PropTypes.shape(UpdateReceiverConfigSecretsModelValidationMap) - ), - alerts: PropTypes.arrayOf( - PropTypes.shape( - AlertConfigModelValidationMap - ) as PropTypes.Validator - ).isRequired, - }; diff --git a/src/models/radix-api/alerting/update-alerting-config/normalizer.ts b/src/models/radix-api/alerting/update-alerting-config/normalizer.ts deleted file mode 100644 index 40083fcab..000000000 --- a/src/models/radix-api/alerting/update-alerting-config/normalizer.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { UpdateAlertingConfigModel } from '.'; - -import { AlertConfigModelNormalizer } from '../alert-config/normalizer'; -import { ReceiverConfigModelNormalizer } from '../receiver-config/normalizer'; -import { UpdateReceiverConfigSecretsModelNormalizer } from '../update-receiver-config-secrets/normalizer'; -import { ModelNormalizerType } from '../../../model-types'; -import { - arrayNormalizer, - objectNormalizer, - recordNormalizer, -} from '../../../model-utils'; - -/** - * Create an UpdateAlertingConfigModel object - */ -export const UpdateAlertingConfigModelNormalizer: ModelNormalizerType< - Readonly -> = (props) => - Object.freeze( - objectNormalizer(props, { - receivers: (x) => recordNormalizer(x, ReceiverConfigModelNormalizer), - receiverSecrets: (x) => - recordNormalizer(x, UpdateReceiverConfigSecretsModelNormalizer), - alerts: (x) => arrayNormalizer(x, AlertConfigModelNormalizer), - }) - ); diff --git a/src/models/radix-api/alerting/update-alerting-config/test-data.ts b/src/models/radix-api/alerting/update-alerting-config/test-data.ts deleted file mode 100644 index ab3425c1d..000000000 --- a/src/models/radix-api/alerting/update-alerting-config/test-data.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { UpdateAlertingConfigModel } from '.'; - -import { AlertConfigModel } from '../alert-config'; -import { testData as AlertConfigData } from '../alert-config/test-data'; -import { testData as ReceiverConfigData } from '../receiver-config/test-data'; -import { testData as UpdateReceiverConfigSecretsData } from '../update-receiver-config-secrets/test-data'; -import { TestDependencyDataType } from '../../../model-types'; - -/* - * TestData array - * - * Note: First object should always be valid - */ -export const testData: TestDependencyDataType = [ - { - __testDescription: 'Valid full object', - receiverSecrets: { a: UpdateReceiverConfigSecretsData[0] }, - receivers: { b: ReceiverConfigData[0] }, - alerts: [AlertConfigData[0]], - }, - { - __testDescription: 'Valid partial object', - alerts: [AlertConfigData[0]], - }, - { - __testDescription: 'Invalid full object', - __testIsInvalidSample: true, - receiverSecrets: { a: UpdateReceiverConfigSecretsData[0] }, - receivers: { b: ReceiverConfigData[4] }, - alerts: AlertConfigData[0] as unknown as Array, - }, - { - __testDescription: 'Invalid partial object', - __testIsInvalidSample: true, - alerts: AlertConfigData[0] as unknown as Array, - }, - { - __testDescription: 'Invalid empty object', - __testIsInvalidSample: true, - alerts: undefined, - }, -]; diff --git a/src/models/radix-api/alerting/update-receiver-config-secrets/index.ts b/src/models/radix-api/alerting/update-receiver-config-secrets/index.ts deleted file mode 100644 index 3deb99b81..000000000 --- a/src/models/radix-api/alerting/update-receiver-config-secrets/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as PropTypes from 'prop-types'; - -import { - UpdateSlackConfigSecretsModel, - UpdateSlackConfigSecretsModelValidationMap, -} from '../update-slack-config-secrets'; - -export interface UpdateReceiverConfigSecretsModel { - slackConfig?: UpdateSlackConfigSecretsModel; -} - -/* PropTypes validation map for UpdateReceiverConfigSecretsModel */ -export const UpdateReceiverConfigSecretsModelValidationMap: PropTypes.ValidationMap = - { - slackConfig: PropTypes.shape(UpdateSlackConfigSecretsModelValidationMap), - }; diff --git a/src/models/radix-api/alerting/update-receiver-config-secrets/normalizer.ts b/src/models/radix-api/alerting/update-receiver-config-secrets/normalizer.ts deleted file mode 100644 index b8e759255..000000000 --- a/src/models/radix-api/alerting/update-receiver-config-secrets/normalizer.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { UpdateReceiverConfigSecretsModel } from '.'; - -import { UpdateSlackConfigSecretsModelNormalizer } from '../update-slack-config-secrets/normalizer'; -import { ModelNormalizerType } from '../../../model-types'; -import { objectNormalizer } from '../../../model-utils'; - -/** - * Create an UpdateReceiverConfigSecretsModel object - */ -export const UpdateReceiverConfigSecretsModelNormalizer: ModelNormalizerType< - Readonly -> = (props) => - Object.freeze( - objectNormalizer(props, { - slackConfig: UpdateSlackConfigSecretsModelNormalizer, - }) - ); diff --git a/src/models/radix-api/alerting/update-receiver-config-secrets/test-data.ts b/src/models/radix-api/alerting/update-receiver-config-secrets/test-data.ts deleted file mode 100644 index 3a9d563b9..000000000 --- a/src/models/radix-api/alerting/update-receiver-config-secrets/test-data.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { UpdateReceiverConfigSecretsModel } from '.'; - -import { testData as UpdateSlackConfigSecretsData } from '../update-slack-config-secrets/test-data'; -import { TestDependencyDataType } from '../../../model-types'; - -/* - * TestData array - * - * Note: First object should always be valid - */ -export const testData: TestDependencyDataType = - [ - { - __testDescription: 'Valid full object', - slackConfig: UpdateSlackConfigSecretsData[0], - }, - { - __testDescription: 'Valid empty object', - }, - { - __testDescription: 'Invalid full object', - __testIsInvalidSample: true, - slackConfig: { webhookUrl: [] as unknown as string }, - }, - ]; diff --git a/src/models/radix-api/alerting/update-slack-config-secrets/index.ts b/src/models/radix-api/alerting/update-slack-config-secrets/index.ts deleted file mode 100644 index 3c970d69f..000000000 --- a/src/models/radix-api/alerting/update-slack-config-secrets/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -import * as PropTypes from 'prop-types'; - -export interface UpdateSlackConfigSecretsModel { - webhookUrl?: string; -} - -/* PropTypes validation map for UpdateSlackConfigSecretsModel */ -export const UpdateSlackConfigSecretsModelValidationMap: PropTypes.ValidationMap = - { - webhookUrl: PropTypes.string, - }; diff --git a/src/models/radix-api/alerting/update-slack-config-secrets/normalizer.ts b/src/models/radix-api/alerting/update-slack-config-secrets/normalizer.ts deleted file mode 100644 index ef4372195..000000000 --- a/src/models/radix-api/alerting/update-slack-config-secrets/normalizer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { UpdateSlackConfigSecretsModel } from '.'; - -import { ModelNormalizerType } from '../../../model-types'; -import { objectNormalizer } from '../../../model-utils'; - -/** - * Create an UpdateSlackConfigSecretsModel object - */ -export const UpdateSlackConfigSecretsModelNormalizer: ModelNormalizerType< - Readonly -> = (props) => Object.freeze(objectNormalizer(props, {})); diff --git a/src/models/radix-api/alerting/update-slack-config-secrets/test-data.ts b/src/models/radix-api/alerting/update-slack-config-secrets/test-data.ts deleted file mode 100644 index 3e08f744b..000000000 --- a/src/models/radix-api/alerting/update-slack-config-secrets/test-data.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { UpdateSlackConfigSecretsModel } from '.'; - -import { TestDependencyDataType } from '../../../model-types'; - -/* - * TestData array - * - * Note: First object should always be valid - */ -export const testData: TestDependencyDataType = [ - { - __testDescription: 'Valid full object', - webhookUrl: 'webhookUrl', - }, - { - __testDescription: 'Valid empty object', - }, - { - __testDescription: 'Invalid full object', - __testIsInvalidSample: true, - webhookUrl: ['webhookUrl'] as unknown as string, - }, -]; diff --git a/src/models/radix-api/test-dependencies.ts b/src/models/radix-api/test-dependencies.ts index 6d7752b79..f8865748e 100644 --- a/src/models/radix-api/test-dependencies.ts +++ b/src/models/radix-api/test-dependencies.ts @@ -6,44 +6,6 @@ import { ValidationMap } from 'prop-types'; import { ModelNormalizerType, TestDependencyDataType } from '../model-types'; -// ALERTING - -import { testData as AlertConfigData } from './alerting/alert-config/test-data'; -import { AlertConfigModelValidationMap } from './alerting/alert-config'; -import { AlertConfigModelNormalizer } from './alerting/alert-config/normalizer'; - -import { testData as AlertingConfigData } from './alerting/alerting-config/test-data'; -import { AlertingConfigModelValidationMap } from './alerting/alerting-config'; -import { AlertingConfigModelNormalizer } from './alerting/alerting-config/normalizer'; - -import { testData as ReceiverConfigData } from './alerting/receiver-config/test-data'; -import { ReceiverConfigModelValidationMap } from './alerting/receiver-config'; -import { ReceiverConfigModelNormalizer } from './alerting/receiver-config/normalizer'; - -import { testData as ReceiverConfigSecretStatusData } from './alerting/receiver-config-secret-status/test-data'; -import { ReceiverConfigSecretStatusModelValidationMap } from './alerting/receiver-config-secret-status'; -import { ReceiverConfigSecretStatusModelNormalizer } from './alerting/receiver-config-secret-status/normalizer'; - -import { testData as SlackConfigData } from './alerting/slack-config/test-data'; -import { SlackConfigModelValidationMap } from './alerting/slack-config'; -import { SlackConfigModelNormalizer } from './alerting/slack-config/normalizer'; - -import { testData as SlackConfigSecretStatusData } from './alerting/slack-config-secret-status/test-data'; -import { SlackConfigSecretStatusModelValidationMap } from './alerting/slack-config-secret-status'; -import { SlackConfigSecretStatusModelNormalizer } from './alerting/slack-config-secret-status/normalizer'; - -import { testData as UpdateAlertingConfigData } from './alerting/update-alerting-config/test-data'; -import { UpdateAlertingConfigModelValidationMap } from './alerting/update-alerting-config'; -import { UpdateAlertingConfigModelNormalizer } from './alerting/update-alerting-config/normalizer'; - -import { testData as UpdateReceiverConfigSecretsData } from './alerting/update-receiver-config-secrets/test-data'; -import { UpdateReceiverConfigSecretsModelValidationMap } from './alerting/update-receiver-config-secrets'; -import { UpdateReceiverConfigSecretsModelNormalizer } from './alerting/update-receiver-config-secrets/normalizer'; - -import { testData as UpdateSlackConfigSecretsData } from './alerting/update-slack-config-secrets/test-data'; -import { UpdateSlackConfigSecretsModelValidationMap } from './alerting/update-slack-config-secrets'; -import { UpdateSlackConfigSecretsModelNormalizer } from './alerting/update-slack-config-secrets/normalizer'; - // APPLICATIONS import { testData as ApplicationData } from './applications/application/test-data'; @@ -225,8 +187,6 @@ import { TLSCertificateModelValidationMap } from './secrets/tls-certificate'; import { TLSCertificateModelNormalizer } from './secrets/tls-certificate/normalizer'; interface TestDependencyComponents { - AlertConfig: T; - AlertingConfig: T; Application: T; ApplicationAlias: T; DNSAlias: T; @@ -259,8 +219,6 @@ interface TestDependencyComponents { PipelineRunTaskStep: T; PodState: T; Port: T; - ReceiverConfig: T; - ReceiverConfigSecretStatus: T; ReplicaSummary: T; ResourceRequirements: T; Resources: T; @@ -269,18 +227,11 @@ interface TestDependencyComponents { ScheduledJobRequest: T; ScheduledJobSummary: T; Secret: T; - SlackConfig: T; - SlackConfigSecretStatus: T; Step: T; TLSCertificate: T; - UpdateAlertingConfig: T; - UpdateReceiverConfigSecrets: T; - UpdateSlackConfigSecrets: T; } export const testData: TestDependencyComponents = { - AlertConfig: AlertConfigData, - AlertingConfig: AlertingConfigData, Application: ApplicationData, ApplicationAlias: ApplicationAliasData, DNSAlias: DNSAliasData, @@ -314,8 +265,6 @@ export const testData: TestDependencyComponents = { PipelineRunTaskStep: PipelineRunTaskStepData, PodState: PodStateData, Port: PortData, - ReceiverConfig: ReceiverConfigData, - ReceiverConfigSecretStatus: ReceiverConfigSecretStatusData, ReplicaSummary: ReplicaSummaryData, ResourceRequirements: ResourceRequirementsData, Resources: ResourcesData, @@ -324,20 +273,13 @@ export const testData: TestDependencyComponents = { ScheduledJobRequest: ScheduledJobRequestData, ScheduledJobSummary: ScheduledJobSummaryData, Secret: SecretData, - SlackConfig: SlackConfigData, - SlackConfigSecretStatus: SlackConfigSecretStatusData, Step: StepData, TLSCertificate: TLSCertificateData, - UpdateAlertingConfig: UpdateAlertingConfigData, - UpdateReceiverConfigSecrets: UpdateReceiverConfigSecretsData, - UpdateSlackConfigSecrets: UpdateSlackConfigSecretsData, }; export const models: TestDependencyComponents< ValidationMap> > = { - AlertConfig: AlertConfigModelValidationMap, - AlertingConfig: AlertingConfigModelValidationMap, Application: ApplicationModelValidationMap, ApplicationAlias: ApplicationAliasModelValidationMap, DNSAlias: DNSAliasModelValidationMap, @@ -373,8 +315,6 @@ export const models: TestDependencyComponents< PipelineRunTaskStep: PipelineRunTaskStepModelValidationMap, PodState: PodStateModelValidationMap, Port: PortModelValidationMap, - ReceiverConfig: ReceiverConfigModelValidationMap, - ReceiverConfigSecretStatus: ReceiverConfigSecretStatusModelValidationMap, ReplicaSummary: ReplicaSummaryNormalizedModelValidationMap, ResourceRequirements: ResourceRequirementsModelValidationMap, Resources: ResourcesModelValidationMap, @@ -383,18 +323,11 @@ export const models: TestDependencyComponents< ScheduledJobRequest: ScheduledJobRequestModelValidationMap, ScheduledJobSummary: ScheduledJobSummaryModelValidationMap, Secret: SecretModelValidationMap, - SlackConfig: SlackConfigModelValidationMap, - SlackConfigSecretStatus: SlackConfigSecretStatusModelValidationMap, Step: StepModelValidationMap, TLSCertificate: TLSCertificateModelValidationMap, - UpdateAlertingConfig: UpdateAlertingConfigModelValidationMap, - UpdateReceiverConfigSecrets: UpdateReceiverConfigSecretsModelValidationMap, - UpdateSlackConfigSecrets: UpdateSlackConfigSecretsModelValidationMap, }; export const normalizers: TestDependencyComponents = { - AlertConfig: AlertConfigModelNormalizer, - AlertingConfig: AlertingConfigModelNormalizer, Application: ApplicationModelNormalizer, ApplicationAlias: ApplicationAliasModelNormalizer, DNSAlias: DNSAliasModelNormalizer, @@ -429,8 +362,6 @@ export const normalizers: TestDependencyComponents = { PipelineRunTaskStep: PipelineRunTaskStepModelNormalizer, PodState: PodStateModelNormalizer, Port: PortModelNormalizer, - ReceiverConfig: ReceiverConfigModelNormalizer, - ReceiverConfigSecretStatus: ReceiverConfigSecretStatusModelNormalizer, ReplicaSummary: ReplicaSummaryModelNormalizer, ResourceRequirements: ResourceRequirementsModelNormalizer, Resources: ResourcesModelNormalizer, @@ -439,11 +370,6 @@ export const normalizers: TestDependencyComponents = { ScheduledJobRequest: ScheduledJobRequestModelNormalizer, ScheduledJobSummary: ScheduledJobSummaryModelNormalizer, Secret: SecretModelNormalizer, - SlackConfig: SlackConfigModelNormalizer, - SlackConfigSecretStatus: SlackConfigSecretStatusModelNormalizer, Step: StepModelNormalizer, TLSCertificate: TLSCertificateModelNormalizer, - UpdateAlertingConfig: UpdateAlertingConfigModelNormalizer, - UpdateReceiverConfigSecrets: UpdateReceiverConfigSecretsModelNormalizer, - UpdateSlackConfigSecrets: UpdateSlackConfigSecretsModelNormalizer, }; diff --git a/src/router.tsx b/src/router.tsx index 9cf74078c..c749334d6 100644 --- a/src/router.tsx +++ b/src/router.tsx @@ -1,8 +1,5 @@ -import { Typography } from '@equinor/eds-core-react'; -import { ComponentType, FunctionComponent } from 'react'; import { createBrowserRouter, Navigate } from 'react-router-dom'; -import { DocumentTitle } from './components/document-title'; import { routes } from './routes'; /** Page Layouts */ @@ -33,34 +30,6 @@ import * as PagePipelineJobNew from './components/page-pipeline-job-new'; import * as PagePipelineRun from './components/page-pipeline-run'; import * as PagePipelineRunTask from './components/page-pipeline-run-task'; -function componentTitleWrapper

( - Component: ComponentType

, - title: string -): FunctionComponent

{ - return (props) => ( - <> - - - - ); -} - -function makeGenericPage

( - Component: ComponentType

, - title: string -): FunctionComponent

{ - return (props) => ( -

-
- {title} -
-
- {componentTitleWrapper(Component, title)(props)} -
-
- ); -} - /** * Radix Web Console page router * @@ -99,7 +68,7 @@ export const router = createBrowserRouter([ { index: true, ...PageAbout, - Component: makeGenericPage(PageAbout.Component, 'About'), + Component: PageAbout.default, }, ], }, @@ -123,12 +92,7 @@ export const router = createBrowserRouter([ { // CONFIGURATION path: routes.appConfig, - children: [ - { - index: true, - ...PageConfiguration, - }, - ], + Component: PageConfiguration.default, }, { // DEPLOYMENTS diff --git a/src/state/alerting-utils/action-creators.js b/src/state/alerting-utils/action-creators.js deleted file mode 100644 index 7edb5601a..000000000 --- a/src/state/alerting-utils/action-creators.js +++ /dev/null @@ -1,83 +0,0 @@ -import { makeActionCreator } from '../state-utils/action-creators'; - -export const alertingActions = (actionPrefix, ...argNames) => { - return { - editAlertingEnable: makeActionCreator( - `${actionPrefix}_EDIT_ENABLE`, - 'payload' - ), - - editAlertingDisable: makeActionCreator(`${actionPrefix}_EDIT_DISABLE`), - - editAlertingSetSlackUrl: makeActionCreator( - `${actionPrefix}_EDIT_SET_SLACKURL`, - 'receiver', - 'slackUrl' - ), - - enableAlertingRequest: makeActionCreator( - `${actionPrefix}_ENABLE_REQUEST`, - ...argNames - ), - - enableAlertingConfirm: makeActionCreator( - `${actionPrefix}_ENABLE_COMPLETE`, - 'payload' - ), - - enableAlertingFail: makeActionCreator( - `${actionPrefix}_ENABLE_FAIL`, - 'error' - ), - - enableAlertingReset: makeActionCreator( - `${actionPrefix}_ENABLE_RESET`, - ...argNames - ), - - disableAlertingRequest: makeActionCreator( - `${actionPrefix}_DISABLE_REQUEST`, - ...argNames - ), - - disableAlertingConfirm: makeActionCreator( - `${actionPrefix}_DISABLE_COMPLETE`, - 'payload' - ), - - disableAlertingFail: makeActionCreator( - `${actionPrefix}_DISABLE_FAIL`, - 'error' - ), - - disableAlertingReset: makeActionCreator( - `${actionPrefix}_DISABLE_RESET`, - ...argNames - ), - - updateAlertingRequest: makeActionCreator( - `${actionPrefix}_UPDATE_REQUEST`, - ...argNames, - 'request' - ), - - updateAlertingConfirm: makeActionCreator( - `${actionPrefix}_UPDATE_COMPLETE`, - 'payload' - ), - - updateAlertingFail: makeActionCreator( - `${actionPrefix}_UPDATE_FAIL`, - 'error' - ), - - updateAlertingReset: makeActionCreator( - `${actionPrefix}_UPDATE_RESET`, - ...argNames - ), - setAlertingSnapshot: makeActionCreator( - `${actionPrefix}_SNAPSHOT`, - 'payload' - ), - }; -}; diff --git a/src/state/alerting-utils/index.js b/src/state/alerting-utils/index.js deleted file mode 100644 index b98e47ff1..000000000 --- a/src/state/alerting-utils/index.js +++ /dev/null @@ -1,46 +0,0 @@ -import { isEqual } from 'lodash'; - -import { RequestState } from '../state-utils/request-states'; -import { makeLocalGetter } from '../../utils/object'; - -export const alertingState = (stateRootKey) => { - const localGetter = makeLocalGetter(stateRootKey); - const requestStatusGetter = (state, requestKey) => - localGetter(state, [requestKey, 'status'], RequestState.IDLE); - const requestErrorGetter = (state, requestKey) => - localGetter(state, [requestKey, 'lastError'], RequestState.IDLE); - - return { - getAlertingConfig: (state) => localGetter(state, 'instance'), - - getAlertingEditConfig: (state) => localGetter(state, 'edit').editConfig, - - isAlertingEditEnabled: (state) => localGetter(state, 'edit').editing, - - isAlertingEditDirty: (state) => { - const editState = localGetter(state, 'edit'); - const dirty = editState.editing - ? !isEqual(editState.editConfig, editState.originalEditConfig) - : false; - return dirty; - }, - - getEnableAlertingRequestState: (state) => - requestStatusGetter(state, 'enableRequest'), - - getDisableAlertingRequestState: (state) => - requestStatusGetter(state, 'disableRequest'), - - getUpdateAlertingRequestState: (state) => - requestStatusGetter(state, 'updateRequest'), - - getEnableAlertingRequestError: (state) => - requestErrorGetter(state, 'enableRequest'), - - getDisableAlertingRequestError: (state) => - requestErrorGetter(state, 'disableRequest'), - - getUpdateAlertingRequestError: (state) => - requestErrorGetter(state, 'updateRequest'), - }; -}; diff --git a/src/state/alerting-utils/reducer.js b/src/state/alerting-utils/reducer.js deleted file mode 100644 index c3c707232..000000000 --- a/src/state/alerting-utils/reducer.js +++ /dev/null @@ -1,87 +0,0 @@ -import { SubscriptionsActionTypes } from '../subscriptions/action-types'; -import { AlertingConfigModelNormalizer } from '../../models/radix-api/alerting/alerting-config/normalizer'; -import { combineReducers } from 'redux'; -import { makeRequestReducer } from '../state-utils/request'; -import { createReducer } from '@reduxjs/toolkit'; -import { cloneDeep } from 'lodash'; -import update from 'immutability-helper'; - -const initialState = null; - -const buildReceiverSecrets = (receviers) => { - const secretsConfig = {}; - if (!receviers) { - return secretsConfig; - } - - for (const [receiverName, receiver] of Object.entries(receviers)) { - secretsConfig[receiverName] = {}; - if (receiver.slackConfig) { - secretsConfig[receiverName]['slackConfig'] = { webhookUrl: undefined }; - } - } - - return secretsConfig; -}; - -export const buildEditConfig = (config) => { - return { - alerts: cloneDeep(config.alerts), - receivers: cloneDeep(config.receivers), - receiverSecrets: buildReceiverSecrets(config.receivers), - }; -}; - -export const alertingReducer = (actionPrefix) => { - const editReducer = createReducer({ editing: false }, (builder) => { - builder.addCase(`${actionPrefix}_EDIT_ENABLE`, (state, action) => { - state.originalEditConfig = buildEditConfig(action.payload); - state.editConfig = cloneDeep(state.originalEditConfig); - state.editing = true; - }); - builder.addCase(`${actionPrefix}_EDIT_DISABLE`, (state) => { - delete state.originalEditConfig; - delete state.editConfig; - state.editing = false; - }); - builder.addCase(`${actionPrefix}_EDIT_SET_SLACKURL`, (state, action) => { - const emptySlackUrl = action.slackUrl - ? action.slackUrl.trim().length === 0 - : true; - state.editConfig = update(state.editConfig, { - receiverSecrets: (rs) => - update(rs, { - [action.receiver]: (r) => - update(r, { - slackConfig: { - $merge: { - webhookUrl: emptySlackUrl - ? undefined - : action.slackUrl.trim(), - }, - }, - }), - }), - }); - }); - }); - - const instanceReducer = (state = initialState, action) => { - switch (action.type) { - case `${actionPrefix}_SNAPSHOT`: - return AlertingConfigModelNormalizer(action.payload); - case SubscriptionsActionTypes.SUBSCRIPTION_ENDED: - return action.resourceName === actionPrefix ? initialState : state; - default: - return state; - } - }; - - return combineReducers({ - edit: editReducer, - instance: instanceReducer, - enableRequest: makeRequestReducer(`${actionPrefix}_ENABLE`), - disableRequest: makeRequestReducer(`${actionPrefix}_DISABLE`), - updateRequest: makeRequestReducer(`${actionPrefix}_UPDATE`), - }); -}; diff --git a/src/state/alerting-utils/sagas.ts b/src/state/alerting-utils/sagas.ts deleted file mode 100644 index 024d41adb..000000000 --- a/src/state/alerting-utils/sagas.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { all, call, put, takeLatest } from 'redux-saga/effects'; - -import { alertingActions } from './action-creators'; - -import { ActionType } from '../state-utils/action-creators'; -import { api as applicationApi } from '../../api/application-alerting'; -import { api as environmentApi } from '../../api/environment-alerting'; -import { UpdateAlertingConfigModel } from '../../models/radix-api/alerting/update-alerting-config'; - -export const alertingSagaFactory = ( - actionPrefix: string, - actions: ReturnType, - api: typeof applicationApi | typeof environmentApi -) => { - function* disableAlertingWatch() { - yield takeLatest(`${actionPrefix}_DISABLE_REQUEST`, disableAlertingFlow); - } - - function* disableAlertingFlow( - action: ActionType< - never, - { appName: string } | { appName: string; envName: string } - > - ) { - try { - const alertingConfig: Awaited> = - yield call(api.disableAlerting, action.meta); - yield put(actions.disableAlertingConfirm(alertingConfig)); - yield put(actions.setAlertingSnapshot(alertingConfig)); - yield put(actions.editAlertingDisable()); - } catch (e) { - yield put(actions.disableAlertingFail(e.message ?? e.toString())); - } - } - - function* enableAlertingWatch() { - yield takeLatest(`${actionPrefix}_ENABLE_REQUEST`, enableAlertingFlow); - } - - function* enableAlertingFlow( - action: ActionType< - never, - { appName: string } | { appName: string; envName: string } - > - ) { - try { - const alertingConfig: Awaited> = - yield call(api.enableAlerting, action.meta); - yield put(actions.enableAlertingConfirm(alertingConfig)); - yield put(actions.setAlertingSnapshot(alertingConfig)); - if (alertingConfig.ready) { - yield put(actions.editAlertingEnable(alertingConfig)); - } - } catch (e) { - yield put(actions.enableAlertingFail(e.message ?? e.toString())); - } - } - - function* updateAlertingWatch() { - yield takeLatest(`${actionPrefix}_UPDATE_REQUEST`, updateAlertingFlow); - } - - function* updateAlertingFlow( - action: ActionType< - never, - | { appName: string; request: UpdateAlertingConfigModel } - | { appName: string; envName: string; request: UpdateAlertingConfigModel } - > - ) { - try { - const alertingConfig: Awaited> = - yield call(api.updateAlerting, action.meta); - yield put(actions.updateAlertingConfirm(alertingConfig)); - yield put(actions.setAlertingSnapshot(alertingConfig)); - yield put(actions.editAlertingDisable()); - } catch (e) { - yield put(actions.updateAlertingFail(e.message ?? e.toString())); - } - } - - function* alertingSaga() { - yield all([ - disableAlertingWatch(), - enableAlertingWatch(), - updateAlertingWatch(), - ]); - } - - return { - alertingSaga, - enableAlertingFlow, - disableAlertingFlow, - updateAlertingFlow, - }; -}; diff --git a/src/state/application-alerting/action-creators.js b/src/state/application-alerting/action-creators.js deleted file mode 100644 index 0342bd0eb..000000000 --- a/src/state/application-alerting/action-creators.js +++ /dev/null @@ -1,5 +0,0 @@ -import { alertingActions } from '../alerting-utils/action-creators'; - -export const actions = alertingActions('APPLICATION_ALERTING', 'appName'); - -export default actions; diff --git a/src/state/application-alerting/index.js b/src/state/application-alerting/index.js deleted file mode 100644 index 4b16e97ec..000000000 --- a/src/state/application-alerting/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import { alertingState } from '../alerting-utils'; - -export const applicationAlertingState = alertingState('applicationAlerting'); diff --git a/src/state/application-alerting/reducer.js b/src/state/application-alerting/reducer.js deleted file mode 100644 index 68ce74cf5..000000000 --- a/src/state/application-alerting/reducer.js +++ /dev/null @@ -1,3 +0,0 @@ -import { alertingReducer } from '../alerting-utils/reducer'; - -export default alertingReducer('APPLICATION_ALERTING'); diff --git a/src/state/application-alerting/sagas.js b/src/state/application-alerting/sagas.js deleted file mode 100644 index 43c3bb2c3..000000000 --- a/src/state/application-alerting/sagas.js +++ /dev/null @@ -1,13 +0,0 @@ -import actionCreators from './action-creators'; -import { api } from '../../api/application-alerting'; -import { alertingSagaFactory } from '../alerting-utils/sagas'; - -const { - alertingSaga, - enableAlertingFlow, - disableAlertingFlow, - updateAlertingFlow, -} = alertingSagaFactory('APPLICATION_ALERTING', actionCreators, api); - -export { enableAlertingFlow, disableAlertingFlow, updateAlertingFlow }; -export default alertingSaga; diff --git a/src/state/application-alerting/sagas.test.js b/src/state/application-alerting/sagas.test.js deleted file mode 100644 index 6cf60da8e..000000000 --- a/src/state/application-alerting/sagas.test.js +++ /dev/null @@ -1,98 +0,0 @@ -import { expectSaga } from 'redux-saga-test-plan'; -import { call } from 'redux-saga-test-plan/matchers'; -import { throwError } from 'redux-saga-test-plan/providers'; - -import { actions } from './action-creators'; -import { - enableAlertingFlow, - disableAlertingFlow, - updateAlertingFlow, -} from './sagas'; - -import { api } from '../../api/application-alerting'; - -describe('application alerting sagas', () => { - describe('enable alerting flow', () => { - it('sends confirm, sets snapshot and enables editing if no error and ready is true', () => { - const action = actions.enableAlertingRequest('fakeApp'); - const response = { ready: true }; - - return expectSaga(enableAlertingFlow, action) - .provide([[call(api.enableAlerting, action.meta), response]]) - .put(actions.enableAlertingConfirm(response)) - .put(actions.setAlertingSnapshot(response)) - .put(actions.editAlertingEnable(response)) - .run(); - }); - - it('sends confirm, sets snapshot if no error and ready is false', () => { - const action = actions.enableAlertingRequest('fakeApp'); - const response = { ready: false }; - - return expectSaga(enableAlertingFlow, action) - .provide([[call(api.enableAlerting, action.meta), response]]) - .put(actions.enableAlertingConfirm(response)) - .put(actions.setAlertingSnapshot(response)) - .not.put(actions.editAlertingEnable(response)) - .run(); - }); - - it('sends fail if there is an error', () => { - const action = actions.enableAlertingRequest('fakeApp'); - const error = new Error('error'); - - return expectSaga(enableAlertingFlow, action) - .provide([[call(api.enableAlerting, action.meta), throwError(error)]]) - .put(actions.enableAlertingFail(error.message)) - .run(); - }); - }); - - describe('disable alerting flow', () => { - it('sends confirm, sets snapshot and disables editing if no error', () => { - const action = actions.disableAlertingRequest('fakeApp'); - const response = {}; - - return expectSaga(disableAlertingFlow, action) - .provide([[call(api.disableAlerting, action.meta), response]]) - .put(actions.disableAlertingConfirm(response)) - .put(actions.setAlertingSnapshot(response)) - .put(actions.editAlertingDisable(response)) - .run(); - }); - - it('sends fail if there is an error', () => { - const action = actions.disableAlertingRequest('fakeApp'); - const error = new Error('error'); - - return expectSaga(disableAlertingFlow, action) - .provide([[call(api.disableAlerting, action.meta), throwError(error)]]) - .put(actions.disableAlertingFail(error.message)) - .run(); - }); - }); - - describe('update alerting flow', () => { - it('sends confirm, sets snapshot and disables editing if no error', () => { - const action = actions.updateAlertingRequest('fakeApp', {}); - const response = {}; - - return expectSaga(updateAlertingFlow, action) - .provide([[call(api.updateAlerting, action.meta), response]]) - .put(actions.updateAlertingConfirm(response)) - .put(actions.setAlertingSnapshot(response)) - .put(actions.editAlertingDisable(response)) - .run(); - }); - - it('sends fail if there is an error', () => { - const action = actions.updateAlertingRequest('fakeApp', {}); - const error = new Error('error'); - - return expectSaga(updateAlertingFlow, action) - .provide([[call(api.updateAlerting, action.meta), throwError(error)]]) - .put(actions.updateAlertingFail(error.message)) - .run(); - }); - }); -}); diff --git a/src/state/application/action-creators.ts b/src/state/application/action-creators.ts index eeefdd181..02c9baaa5 100644 --- a/src/state/application/action-creators.ts +++ b/src/state/application/action-creators.ts @@ -72,25 +72,4 @@ export const actions = { { id: string }, [id: string, error: string] >(actionTypes.APP_MODIFY_FAIL, 'id', 'error'), - - // redundant and pointless to send in the ID, as it will never be of use - /** - * Action creator for resetting an application modification status - * @param {string} id ID of the application - */ - modifyAppReset: makeActionCreator( - actionTypes.APP_MODIFY_RESET, - 'id' - ), - - /** - * Action creator to initiate change of application's admin groups - * @param {string} id ID of the application - * @param {Object} adGroupConfig The new admin group configuration - */ - changeAppAdmin: makeActionCreator< - never, - { id: string; adGroupConfig: AppModifyProps }, - [id: string, adGroupConfig: AppModifyProps] - >(actionTypes.APP_CHANGE_ADMIN, 'id', 'adGroupConfig'), }; diff --git a/src/state/applications-favourite/index.ts b/src/state/applications-favourite/index.ts deleted file mode 100644 index 7162f331b..000000000 --- a/src/state/applications-favourite/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { createSelector, createSlice, PayloadAction } from '@reduxjs/toolkit'; - -import type { RootState } from '../../init/store'; - -const localStorageKey = 'favouriteApplications'; - -const initialState: Array = - JSON.parse(localStorage.getItem(localStorageKey)) ?? []; - -const putInLocalStorage = (state: Array): void => - localStorage.setItem(localStorageKey, JSON.stringify(state)); - -const favouritesSlice = createSlice({ - name: 'favouriteApplications', - initialState, - reducers: { - toggleFavouriteApp(state, { payload }: PayloadAction) { - const idx = state.findIndex((x) => x === payload); - idx < 0 ? state.push(payload) : state.splice(idx, 1); - putInLocalStorage(state); - return state; - }, - }, -}); - -export const getMemoizedFavouriteApplications = createSelector( - (state: RootState) => state.favouriteApplications, - (favouriteApplications) => favouriteApplications -); - -export const { - actions: { toggleFavouriteApp }, - reducer, -} = favouritesSlice; - -export default reducer; diff --git a/src/state/environment-alerting/action-creators.js b/src/state/environment-alerting/action-creators.js deleted file mode 100644 index 5ff284124..000000000 --- a/src/state/environment-alerting/action-creators.js +++ /dev/null @@ -1,9 +0,0 @@ -import { alertingActions } from '../alerting-utils/action-creators'; - -export const actions = alertingActions( - 'ENVIRONMENT_ALERTING', - 'appName', - 'envName' -); - -export default actions; diff --git a/src/state/environment-alerting/index.js b/src/state/environment-alerting/index.js deleted file mode 100644 index ad596239b..000000000 --- a/src/state/environment-alerting/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import { alertingState } from '../alerting-utils'; - -export const environmentAlertingState = alertingState('environmentAlerting'); diff --git a/src/state/environment-alerting/reducer.js b/src/state/environment-alerting/reducer.js deleted file mode 100644 index 0b1b196e0..000000000 --- a/src/state/environment-alerting/reducer.js +++ /dev/null @@ -1,3 +0,0 @@ -import { alertingReducer } from '../alerting-utils/reducer'; - -export default alertingReducer('ENVIRONMENT_ALERTING'); diff --git a/src/state/environment-alerting/sagas.js b/src/state/environment-alerting/sagas.js deleted file mode 100644 index abbf8b806..000000000 --- a/src/state/environment-alerting/sagas.js +++ /dev/null @@ -1,13 +0,0 @@ -import actionCreators from './action-creators'; -import { api } from '../../api/environment-alerting'; -import { alertingSagaFactory } from '../alerting-utils/sagas'; - -const { - alertingSaga, - enableAlertingFlow, - disableAlertingFlow, - updateAlertingFlow, -} = alertingSagaFactory('ENVIRONMENT_ALERTING', actionCreators, api); - -export { enableAlertingFlow, disableAlertingFlow, updateAlertingFlow }; -export default alertingSaga; diff --git a/src/state/environment-alerting/sagas.test.js b/src/state/environment-alerting/sagas.test.js deleted file mode 100644 index f6d91ace0..000000000 --- a/src/state/environment-alerting/sagas.test.js +++ /dev/null @@ -1,98 +0,0 @@ -import { expectSaga } from 'redux-saga-test-plan'; -import { call } from 'redux-saga-test-plan/matchers'; -import { throwError } from 'redux-saga-test-plan/providers'; - -import { actions } from './action-creators'; -import { - enableAlertingFlow, - disableAlertingFlow, - updateAlertingFlow, -} from './sagas'; - -import { api } from '../../api/environment-alerting'; - -describe('application alerting sagas', () => { - describe('enable alerting flow', () => { - it('sends confirm, sets snapshot and enables editing if no error and ready is true', () => { - const action = actions.enableAlertingRequest('fakeApp', 'fakeEnv'); - const response = { ready: true }; - - return expectSaga(enableAlertingFlow, action) - .provide([[call(api.enableAlerting, action.meta), response]]) - .put(actions.enableAlertingConfirm(response)) - .put(actions.setAlertingSnapshot(response)) - .put(actions.editAlertingEnable(response)) - .run(); - }); - - it('sends confirm, sets snapshot if no error and ready is false', () => { - const action = actions.enableAlertingRequest('fakeApp', 'fakeEnv'); - const response = { ready: false }; - - return expectSaga(enableAlertingFlow, action) - .provide([[call(api.enableAlerting, action.meta), response]]) - .put(actions.enableAlertingConfirm(response)) - .put(actions.setAlertingSnapshot(response)) - .not.put(actions.editAlertingEnable(response)) - .run(); - }); - - it('sends fail if there is an error', () => { - const action = actions.enableAlertingRequest('fakeApp', 'fakeEnv'); - const error = new Error('error'); - - return expectSaga(enableAlertingFlow, action) - .provide([[call(api.enableAlerting, action.meta), throwError(error)]]) - .put(actions.enableAlertingFail(error.message)) - .run(); - }); - }); - - describe('disable alerting flow', () => { - it('sends confirm, sets snapshot and disables editing if no error', () => { - const action = actions.disableAlertingRequest('fakeApp', 'fakeEnv'); - const response = {}; - - return expectSaga(disableAlertingFlow, action) - .provide([[call(api.disableAlerting, action.meta), response]]) - .put(actions.disableAlertingConfirm(response)) - .put(actions.setAlertingSnapshot(response)) - .put(actions.editAlertingDisable(response)) - .run(); - }); - - it('sends fail if there is an error', () => { - const action = actions.disableAlertingRequest('fakeApp', 'fakeEnv'); - const error = new Error('error'); - - return expectSaga(disableAlertingFlow, action) - .provide([[call(api.disableAlerting, action.meta), throwError(error)]]) - .put(actions.disableAlertingFail(error.message)) - .run(); - }); - }); - - describe('update alerting flow', () => { - it('sends confirm, sets snapshot and disables editing if no error', () => { - const action = actions.updateAlertingRequest('fakeApp', 'fakeEnv', {}); - const response = {}; - - return expectSaga(updateAlertingFlow, action) - .provide([[call(api.updateAlerting, action.meta), response]]) - .put(actions.updateAlertingConfirm(response)) - .put(actions.setAlertingSnapshot(response)) - .put(actions.editAlertingDisable(response)) - .run(); - }); - - it('sends fail if there is an error', () => { - const action = actions.updateAlertingRequest('fakeApp', 'fakeEnv', {}); - const error = new Error('error'); - - return expectSaga(updateAlertingFlow, action) - .provide([[call(api.updateAlerting, action.meta), throwError(error)]]) - .put(actions.updateAlertingFail(error.message)) - .run(); - }); - }); -}); diff --git a/src/state/root-reducer.ts b/src/state/root-reducer.ts index 2748620ee..350078558 100644 --- a/src/state/root-reducer.ts +++ b/src/state/root-reducer.ts @@ -1,10 +1,7 @@ import application from './application'; -import applicationAlerting from './application-alerting/reducer'; import applicationCreation from './application-creation/reducer'; import component from './component/reducer'; import deployments from './deployments'; -import environmentAlerting from './environment-alerting/reducer'; -import favouriteApplications from './applications-favourite'; import job from './job'; import jobCreation from './job-creation/reducer'; import oauthAuxiliaryResource from './oauth-auxiliary-resource/reducer'; @@ -18,12 +15,9 @@ import subscriptions from './subscriptions'; export const rootReducer = { application, - applicationAlerting, applicationCreation, component, deployments, - environmentAlerting, - favouriteApplications, job, jobCreation, oauthAuxiliaryResource, diff --git a/src/state/root-saga.ts b/src/state/root-saga.ts index 62de7c201..272694326 100644 --- a/src/state/root-saga.ts +++ b/src/state/root-saga.ts @@ -6,8 +6,6 @@ import jobCreation from './job-creation/sagas'; import component from './component/sagas'; import subscriptionRefresh from './subscription-refresh/sagas'; import subscriptions from './subscriptions/sagas'; -import environmentAlerting from './environment-alerting/sagas'; -import applicationAlerting from './application-alerting/sagas'; import oauthAuxiliaryResource from './oauth-auxiliary-resource/sagas'; export function* rootSaga() { @@ -18,8 +16,6 @@ export function* rootSaga() { jobCreation(), subscriptionRefresh(), // TODO: Move into subscriptions() saga subscriptions(), - environmentAlerting(), - applicationAlerting(), oauthAuxiliaryResource(), ]); } diff --git a/tsconfig.json b/tsconfig.json index 3c747c81e..46bdaf6c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,6 +14,7 @@ "esModuleInterop": false, "forceConsistentCasingInFileNames": true, "strict": false, + "strictNullChecks": false, "skipLibCheck": false, "moduleResolution": "Node", "resolveJsonModule": true,