diff --git a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx index a899851400..2b2b46cf25 100644 --- a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx +++ b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx @@ -7,7 +7,6 @@ import useApplicationsApi from '../../../hooks/api/actions/useApplicationsApi/us import useToast from '../../../hooks/useToast'; import { IApplication } from '../../../interfaces/application'; - interface IApplicationUpdateProps { application: IApplication; } @@ -17,19 +16,22 @@ export const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { const { appName, icon, url, description } = application; const [localUrl, setLocalUrl] = useState(url || ''); const [localDescription, setLocalDescription] = useState(description || ''); - const { setToastApiError } = useToast(); + const { setToastData, setToastApiError } = useToast(); const commonStyles = useCommonStyles(); const handleChange = ( - evt: ChangeEvent<{ name?: string | undefined; value: unknown }> + evt: ChangeEvent<{ name?: string | undefined; value: unknown }>, + field: string, + value: string ) => { evt.preventDefault(); try { - storeApplicationMetaData( - appName, - 'icon', - evt.target.value as string - ); + storeApplicationMetaData(appName, field, value); + setToastData({ + type: 'success', + title: 'Updated Successfully', + text: `${field} successfully updated`, + }); } catch (e: any) { setToastApiError(e.toString()); } @@ -45,7 +47,9 @@ export const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { label="Icon" options={icons.map(v => ({ key: v, label: v }))} value={icon || 'apps'} - onChange={e => handleChange(e)} + onChange={e => + handleChange(e, 'icon', e.target.value as string) + } /> @@ -57,9 +61,7 @@ export const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { type="url" variant="outlined" size="small" - onBlur={() => - storeApplicationMetaData(appName, 'url', localUrl) - } + onBlur={e => handleChange(e, 'url', localUrl)} /> @@ -70,12 +72,8 @@ export const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { size="small" rows={2} onChange={e => setLocalDescription(e.target.value)} - onBlur={() => - storeApplicationMetaData( - appName, - 'description', - localDescription - ) + onBlur={e => + handleChange(e, 'description', localDescription) } /> diff --git a/frontend/src/store/application/actions.js b/frontend/src/store/application/actions.js new file mode 100644 index 0000000000..0a2b62423b --- /dev/null +++ b/frontend/src/store/application/actions.js @@ -0,0 +1,58 @@ +import api from './api'; +import { dispatchError } from '../util'; +import { MUTE_ERROR } from '../error/actions'; + +export const RECEIVE_ALL_APPLICATIONS = 'RECEIVE_ALL_APPLICATIONS'; +export const ERROR_RECEIVE_ALL_APPLICATIONS = 'ERROR_RECEIVE_ALL_APPLICATIONS'; +export const ERROR_UPDATING_APPLICATION_DATA = 'ERROR_UPDATING_APPLICATION_DATA'; + +export const RECEIVE_APPLICATION = 'RECEIVE_APPLICATION'; +export const UPDATE_APPLICATION_FIELD = 'UPDATE_APPLICATION_FIELD'; +export const DELETE_APPLICATION = 'DELETE_APPLICATION'; +export const ERROR_DELETE_APPLICATION = 'ERROR_DELETE_APPLICATION'; + +const recieveAllApplications = json => ({ + type: RECEIVE_ALL_APPLICATIONS, + value: json, +}); + +const recieveApplication = json => ({ + type: RECEIVE_APPLICATION, + value: json, +}); + +export function fetchAll() { + return dispatch => + api + .fetchAll() + .then(json => dispatch(recieveAllApplications(json))) + .catch(dispatchError(dispatch, ERROR_RECEIVE_ALL_APPLICATIONS)); +} + +export function storeApplicationMetaData(appName, key, value) { + return dispatch => + api + .storeApplicationMetaData(appName, key, value) + .then(() => { + const info = `${appName} successfully updated!`; + setTimeout(() => dispatch({ type: MUTE_ERROR, error: info }), 1000); + dispatch({ type: UPDATE_APPLICATION_FIELD, appName, key, value, info }); + }) + .catch(dispatchError(dispatch, ERROR_UPDATING_APPLICATION_DATA)); +} + +export function fetchApplication(appName) { + return dispatch => + api + .fetchApplication(appName) + .then(json => dispatch(recieveApplication(json))) + .catch(dispatchError(dispatch, ERROR_RECEIVE_ALL_APPLICATIONS)); +} + +export function deleteApplication(appName) { + return dispatch => + api + .deleteApplication(appName) + .then(() => dispatch({ type: DELETE_APPLICATION, appName })) + .catch(dispatchError(dispatch, ERROR_DELETE_APPLICATION)); +} diff --git a/frontend/src/store/application/api.js b/frontend/src/store/application/api.js new file mode 100644 index 0000000000..5a941d27f4 --- /dev/null +++ b/frontend/src/store/application/api.js @@ -0,0 +1,52 @@ +import { formatApiPath } from '../../utils/format-path'; +import { throwIfNotSuccess, headers } from '../api-helper'; + +const URI = formatApiPath('api/admin/metrics/applications'); + +function fetchAll() { + return fetch(URI, { headers, credentials: 'include' }) + .then(throwIfNotSuccess) + .then(response => response.json()); +} + +function fetchApplication(appName) { + return fetch(`${URI}/${appName}`, { headers, credentials: 'include' }) + .then(throwIfNotSuccess) + .then(response => response.json()); +} + +function fetchApplicationsWithStrategyName(strategyName) { + return fetch(`${URI}?strategyName=${strategyName}`, { + headers, + credentials: 'include', + }) + .then(throwIfNotSuccess) + .then(response => response.json()); +} + +function storeApplicationMetaData(appName, key, value) { + const data = {}; + data[key] = value; + return fetch(`${URI}/${appName}`, { + method: 'POST', + headers, + body: JSON.stringify(data), + credentials: 'include', + }).then(throwIfNotSuccess); +} + +function deleteApplication(appName) { + return fetch(`${URI}/${appName}`, { + method: 'DELETE', + headers, + credentials: 'include', + }).then(throwIfNotSuccess); +} + +export default { + fetchApplication, + fetchAll, + fetchApplicationsWithStrategyName, + storeApplicationMetaData, + deleteApplication, +}; diff --git a/frontend/src/store/application/index.js b/frontend/src/store/application/index.js new file mode 100644 index 0000000000..06da890586 --- /dev/null +++ b/frontend/src/store/application/index.js @@ -0,0 +1,30 @@ +import { fromJS, List, Map } from 'immutable'; +import { RECEIVE_ALL_APPLICATIONS, RECEIVE_APPLICATION, UPDATE_APPLICATION_FIELD, DELETE_APPLICATION } from './actions'; +import { USER_LOGOUT, USER_LOGIN } from '../user/actions'; + +function getInitState() { + return fromJS({ list: [], apps: {} }); +} + +const store = (state = getInitState(), action) => { + switch (action.type) { + case RECEIVE_APPLICATION: + return state.setIn(['apps', action.value.appName], new Map(action.value)); + case RECEIVE_ALL_APPLICATIONS: + return state.set('list', new List(action.value.applications)); + case UPDATE_APPLICATION_FIELD: + return state.setIn(['apps', action.appName, action.key], action.value); + case DELETE_APPLICATION: { + const index = state.get('list').findIndex(item => item.appName === action.appName); + const result = state.removeIn(['list', index]); + return result.removeIn(['apps', action.appName]); + } + case USER_LOGOUT: + case USER_LOGIN: + return getInitState(); + default: + return state; + } +}; + +export default store;