From 38e549d87953f15fb9bcd23a26cf2df73d1b8350 Mon Sep 17 00:00:00 2001 From: Youssef Date: Fri, 4 Feb 2022 07:56:17 +0100 Subject: [PATCH 01/17] refactor: application-view to ApplicationView with useApplication hook --- ...plication-view.jsx => ApplicationView.tsx} | 88 +++++++++++-------- .../application/application-edit-component.js | 2 +- .../getters/useApplication/useApplication.ts | 50 +++++++++++ .../useApplications/useApplications.ts | 41 +++++++++ 4 files changed, 144 insertions(+), 37 deletions(-) rename frontend/src/component/application/{application-view.jsx => ApplicationView.tsx} (75%) create mode 100644 frontend/src/hooks/api/getters/useApplication/useApplication.ts create mode 100644 frontend/src/hooks/api/getters/useApplications/useApplications.ts diff --git a/frontend/src/component/application/application-view.jsx b/frontend/src/component/application/ApplicationView.tsx similarity index 75% rename from frontend/src/component/application/application-view.jsx rename to frontend/src/component/application/ApplicationView.tsx index 5c0a0c485d..471875690c 100644 --- a/frontend/src/component/application/application-view.jsx +++ b/frontend/src/component/application/ApplicationView.tsx @@ -1,6 +1,5 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import PropTypes from 'prop-types'; +import { useContext } from 'react'; +import { Link, useParams } from 'react-router-dom'; import { Grid, List, @@ -9,8 +8,13 @@ import { ListItemAvatar, Typography, } from '@material-ui/core'; -import { Report, Extension, Timeline, FlagRounded } from '@material-ui/icons'; - +import { + Report, + Extension, + Timeline, + FlagRounded, + SvgIconComponent, +} from '@material-ui/icons'; import { shorten } from '../common'; import { CREATE_FEATURE, @@ -18,21 +22,30 @@ import { } from '../providers/AccessProvider/permissions'; import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; import { getTogglePath } from '../../utils/route-path-helpers'; -function ApplicationView({ - seenToggles, - hasAccess, - strategies, - instances, - formatFullDateTime, -}) { - const notFoundListItem = ({ createUrl, name, permission }) => ( +import useApplication from '../../hooks/api/getters/useApplication/useApplication'; +import AccessContext from '../../contexts/AccessContext'; +import { formatFullDateTimeWithLocale } from '../common/util'; +const ApplicationView = () => { + const { hasAccess } = useContext(AccessContext); + const { name } = useParams<{ name: string }>(); + const { application } = useApplication(name); + const { instances, strategies, seenToggles } = application; + const notFoundListItem = ({ + createUrl, + name, + permission, + }: { + createUrl: string; + name: string; + permission: string; + }) => ( - + {name}} @@ -54,13 +67,18 @@ function ApplicationView({ /> ); - // eslint-disable-next-line react/prop-types const foundListItem = ({ viewUrl, name, description, Icon, i, + }: { + viewUrl: string; + name: string; + description: string; + Icon: SvgIconComponent; + i: number; }) => ( @@ -83,10 +101,7 @@ function ApplicationView({
{seenToggles.map( - ( - { name, description, notFound, project }, - i - ) => ( + ({ name, description, notFound, project }, i) => ( {instances.map( - ({ instanceId, clientIp, lastSeen, sdkVersion }) => ( + ({ + instanceId, + clientIp, + lastSeen, + sdkVersion, + }: { + instanceId: string; + clientIp: string; + lastSeen: string; + sdkVersion: string; + }) => ( @@ -152,16 +177,18 @@ function ApplicationView({ primary={ {instanceId} {(sdkVersion)}} + elseShow={{instanceId}} /> } secondary={ {clientIp} last seen at{' '} - {formatFullDateTime(lastSeen)} + {formatFullDateTimeWithLocale( + lastSeen + )} } @@ -173,17 +200,6 @@ function ApplicationView({ ); -} - -ApplicationView.propTypes = { - createUrl: PropTypes.string, - name: PropTypes.string, - permission: PropTypes.string, - instances: PropTypes.array.isRequired, - seenToggles: PropTypes.array.isRequired, - strategies: PropTypes.array.isRequired, - hasAccess: PropTypes.func.isRequired, - formatFullDateTime: PropTypes.func.isRequired, }; export default ApplicationView; diff --git a/frontend/src/component/application/application-edit-component.js b/frontend/src/component/application/application-edit-component.js index ecab61cd3e..f3b7e7677a 100644 --- a/frontend/src/component/application/application-edit-component.js +++ b/frontend/src/component/application/application-edit-component.js @@ -19,7 +19,7 @@ import { formatDateWithLocale, } from '../common/util'; import { UPDATE_APPLICATION } from '../providers/AccessProvider/permissions'; -import ApplicationView from './application-view'; +import ApplicationView from './ApplicationView'; import ApplicationUpdate from './application-update'; import TabNav from '../common/TabNav/TabNav'; import Dialogue from '../common/Dialogue'; diff --git a/frontend/src/hooks/api/getters/useApplication/useApplication.ts b/frontend/src/hooks/api/getters/useApplication/useApplication.ts new file mode 100644 index 0000000000..782b63b820 --- /dev/null +++ b/frontend/src/hooks/api/getters/useApplication/useApplication.ts @@ -0,0 +1,50 @@ +import useSWR, { mutate, SWRConfiguration } from 'swr'; +import { useState, useEffect } from 'react'; +import { formatApiPath } from '../../../../utils/format-path'; +import handleErrorResponses from '../httpErrorResponseHandler'; + +const useApplication = (name: string, options: SWRConfiguration = {}) => { + const fetcher = async () => { + const path = formatApiPath(`api/admin/metrics/applications/${name}`); + return fetch(path, { + method: 'GET', + }) + .then(handleErrorResponses('Application')) + .then(res => res.json()); + }; + + const FEATURE_CACHE_KEY = `api/admin/metrics/applications/${name}`; + + const { data, error } = useSWR(FEATURE_CACHE_KEY, fetcher, { + ...options, + }); + + const [loading, setLoading] = useState(!error && !data); + + const refetchApplication = () => { + mutate(FEATURE_CACHE_KEY); + }; + + useEffect(() => { + setLoading(!error && !data); + }, [data, error]); + + return { + application: data || { + appName: name, + color: null, + createdAt: '2022-02-02T21:04:00.268Z', + descriotion: '', + instances: [], + strategies: [], + seenToggles: [], + url: '', + }, + error, + loading, + refetchApplication, + FEATURE_CACHE_KEY, + }; +}; + +export default useApplication; diff --git a/frontend/src/hooks/api/getters/useApplications/useApplications.ts b/frontend/src/hooks/api/getters/useApplications/useApplications.ts new file mode 100644 index 0000000000..13efed5a8d --- /dev/null +++ b/frontend/src/hooks/api/getters/useApplications/useApplications.ts @@ -0,0 +1,41 @@ +import useSWR, { mutate, SWRConfiguration } from 'swr'; +import { useState, useEffect } from 'react'; +import { formatApiPath } from '../../../../utils/format-path'; +import handleErrorResponses from '../httpErrorResponseHandler'; + +const useApplications = (options: SWRConfiguration = {}) => { + const fetcher = async () => { + const path = formatApiPath('api/admin/metrics/applications'); + return fetch(path, { + method: 'GET', + }) + .then(handleErrorResponses('Context data')) + .then(res => res.json()); + }; + + const FEATURE_CACHE_KEY = 'api/admin/metrics/applications'; + + const { data, error } = useSWR(FEATURE_CACHE_KEY, fetcher, { + ...options, + }); + + const [loading, setLoading] = useState(!error && !data); + + const refetchApplications = () => { + mutate(FEATURE_CACHE_KEY); + }; + + useEffect(() => { + setLoading(!error && !data); + }, [data, error]); + + return { + applications: data?.applications || {}, + error, + loading, + refetchApplications, + FEATURE_CACHE_KEY, + }; +}; + +export default useApplications; From 47a1a47d28385c61a0cbd6ba47f6de6ad92468ce Mon Sep 17 00:00:00 2001 From: Youssef Date: Fri, 4 Feb 2022 08:14:16 +0100 Subject: [PATCH 02/17] refactor: add useApplicationsApi --- .../useApplicationsApi/useApplicationsApi.ts | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 frontend/src/hooks/api/actions/useApplicationsApi/useApplicationsApi.ts diff --git a/frontend/src/hooks/api/actions/useApplicationsApi/useApplicationsApi.ts b/frontend/src/hooks/api/actions/useApplicationsApi/useApplicationsApi.ts new file mode 100644 index 0000000000..09aa3cb4b3 --- /dev/null +++ b/frontend/src/hooks/api/actions/useApplicationsApi/useApplicationsApi.ts @@ -0,0 +1,62 @@ +import useAPI from '../useApi/useApi'; + +const useApplicationsApi = () => { + const { makeRequest, createRequest, errors, loading } = useAPI({ + propagateErrors: true, + }); + + const URI = 'api/admin/metrics/applications'; + + const storeApplicationMetaData = async ( + appName: string, + key: string, + value: string + ) => { + const data: { [key: string]: any } = {}; + data[key] = value; + const path = `${URI}/${appName}`; + const req = createRequest(path, { + method: 'POST', + body: JSON.stringify(data), + }); + try { + const res = await makeRequest(req.caller, req.id); + return res; + } catch (e) { + throw e; + } + }; + + const deleteApplication = async (appName: string) => { + const path = `${URI}/${appName}`; + const req = createRequest(path, { method: 'DELETE' }); + + try { + const res = await makeRequest(req.caller, req.id); + return res; + } catch (e) { + throw e; + } + }; + + const fetchApplicationsWithStrategyName = async (strategyName: string) => { + const path = `${URI}?strategyName=${strategyName}`; + const req = createRequest(path, { method: 'GET' }); + try { + const res = await makeRequest(req.caller, req.id); + return res; + } catch (e) { + throw e; + } + }; + + return { + storeApplicationMetaData, + fetchApplicationsWithStrategyName, + deleteApplication, + errors, + loading, + }; +}; + +export default useApplicationsApi; From c316382ba50d7cf0e79cdcedf89e9f8bfc286bd1 Mon Sep 17 00:00:00 2001 From: Youssef Date: Fri, 4 Feb 2022 11:27:59 +0100 Subject: [PATCH 03/17] refactor: create new EditApplication component --- .../component/application/ApplicationView.tsx | 10 +- .../component/application/EditApplication.tsx | 162 ++++++++++++++++++ .../src/component/common/TabNav/TabNav.jsx | 7 +- frontend/src/component/menu/routes.js | 4 +- .../getters/useApplication/useApplication.ts | 2 +- 5 files changed, 177 insertions(+), 8 deletions(-) create mode 100644 frontend/src/component/application/EditApplication.tsx diff --git a/frontend/src/component/application/ApplicationView.tsx b/frontend/src/component/application/ApplicationView.tsx index 471875690c..1ada6f1e55 100644 --- a/frontend/src/component/application/ApplicationView.tsx +++ b/frontend/src/component/application/ApplicationView.tsx @@ -129,7 +129,7 @@ const ApplicationView = () => {
- {strategies.map(({ name, description, notFound }, i) => ( + {strategies.map(({ name, description, notFound }, i: number) => ( { createUrl: '/strategies/create', name, permission: CREATE_STRATEGY, - i, })} elseShow={foundListItem({ viewUrl: '/strategies/view', name, Icon: Extension, - enabled: undefined, description, i, })} @@ -178,7 +176,11 @@ const ApplicationView = () => { {instanceId} {(sdkVersion)}} + show={ + + {instanceId} {sdkVersion} + + } elseShow={{instanceId}} /> } diff --git a/frontend/src/component/application/EditApplication.tsx b/frontend/src/component/application/EditApplication.tsx new file mode 100644 index 0000000000..956af65f53 --- /dev/null +++ b/frontend/src/component/application/EditApplication.tsx @@ -0,0 +1,162 @@ +/* eslint react/no-multi-comp:off */ +import { useContext, useEffect, useState } from 'react'; +import { + Avatar, + Link, + Icon, + IconButton, + Button, + LinearProgress, + Typography, +} from '@material-ui/core'; +import { Link as LinkIcon } from '@material-ui/icons'; + +import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; +import { + formatFullDateTimeWithLocale, + formatDateWithLocale, +} from '../common/util'; +import { UPDATE_APPLICATION } from '../providers/AccessProvider/permissions'; +import ApplicationView from './ApplicationView'; +import ApplicationUpdate from './application-update'; +import TabNav from '../common/TabNav/TabNav'; +import Dialogue from '../common/Dialogue'; +import PageContent from '../common/PageContent'; +import HeaderTitle from '../common/HeaderTitle'; +import AccessContext from '../../contexts/AccessContext'; +import useApplicationsApi from '../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; +import useApplication from '../../hooks/api/getters/useApplication/useApplication'; +import { useHistory, useParams } from 'react-router-dom'; + +const EditApplication = () => { + const history = useHistory(); + const { name } = useParams<{ name: string }>(); + const { refetchApplication, application } = useApplication(name); + const { appName, url, description, icon = 'apps', createdAt } = application; + const { hasAccess } = useContext(AccessContext); + const { storeApplicationMetaData, deleteApplication } = + useApplicationsApi(); + + const [loading, setLoading] = useState(true); + const [showDialog, setShowDialog] = useState(false); + + useEffect(() => { + refetchApplication(); + setLoading(false); + // eslint-disable-next-line + }, []); + + const toggleModal = () => { + setShowDialog(!showDialog); + }; + + // missing the settings hook (locale) + //const formatDate = v => formatDateWithLocale(v, locale); + + const onDeleteApplication = async (evt: Event) => { + evt.preventDefault(); + await deleteApplication(appName); + history.push('/applications'); + }; + + const renderModal = () => ( + + ); + + const tabData = [ + { + label: 'Application overview', + component: , + }, + { + label: 'Edit application', + component: ( + + ), + }, + ]; + + if (loading) { + return ( +
+

Loading...

+ +
+ ); + } else if (!application) { + return

Application ({appName}) not found

; + } + return ( + + + {icon || 'apps'} + + {appName} + + } + actions={ + <> + + + + } + /> + + + Delete + + } + /> + + } + /> + } + > +
+ {description || ''} + + {/* // need to use formatDate once we have the useSettings hook ready */} + Created: {createdAt} + +
+ + {renderModal()} + + + } + /> +
+ ); +}; + +export default EditApplication; diff --git a/frontend/src/component/common/TabNav/TabNav.jsx b/frontend/src/component/common/TabNav/TabNav.jsx index 47687c5618..b8730f143c 100644 --- a/frontend/src/component/common/TabNav/TabNav.jsx +++ b/frontend/src/component/common/TabNav/TabNav.jsx @@ -13,7 +13,12 @@ const a11yProps = index => ({ 'aria-controls': `tabpanel-${index}`, }); -const TabNav = ({ tabData, className, navClass, startingTab = 0 }) => { +const TabNav = ({ + tabData, + className = '', + navClass = '', + startingTab = 0, +}) => { const styles = useStyles(); const [activeTab, setActiveTab] = useState(startingTab); const history = useHistory(); diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index 8359ee178a..b12d4f31da 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -9,7 +9,6 @@ import HistoryTogglePage from '../../page/history/toggle'; import ShowArchive from '../../page/archive/show'; import Archive from '../../page/archive'; import Applications from '../../page/applications'; -import ApplicationView from '../../page/applications/view'; import ContextFields from '../../page/context'; import ListTagTypes from '../../page/tag-types'; import Addons from '../../page/addons'; @@ -47,6 +46,7 @@ import EditProject from '../project/Project/EditProject/EditProject'; import CreateProject from '../project/Project/CreateProject/CreateProject'; import CreateFeature from '../feature/CreateFeature/CreateFeature/CreateFeature'; import EditFeature from '../feature/CreateFeature/EditFeature/EditFeature'; +import EditApplication from '../application/EditApplication'; export const routes = [ // Project @@ -195,7 +195,7 @@ export const routes = [ path: '/applications/:name', title: ':name', parent: '/applications', - component: ApplicationView, + component: EditApplication, type: 'protected', layout: 'main', menu: {}, diff --git a/frontend/src/hooks/api/getters/useApplication/useApplication.ts b/frontend/src/hooks/api/getters/useApplication/useApplication.ts index 782b63b820..6802f3f64a 100644 --- a/frontend/src/hooks/api/getters/useApplication/useApplication.ts +++ b/frontend/src/hooks/api/getters/useApplication/useApplication.ts @@ -32,7 +32,7 @@ const useApplication = (name: string, options: SWRConfiguration = {}) => { return { application: data || { appName: name, - color: null, + color: '', createdAt: '2022-02-02T21:04:00.268Z', descriotion: '', instances: [], From fb403255efa77f4d68ab83092d23f85a2bd734ac Mon Sep 17 00:00:00 2001 From: Youssef Date: Fri, 4 Feb 2022 12:45:08 +0100 Subject: [PATCH 04/17] refactor: create ApplicationList component --- .../component/application/ApplicationList.tsx | 70 +++++++++++++++++++ .../application/ApplicationUpdate.tsx | 70 +++++++++++++++++++ .../component/application/EditApplication.tsx | 10 +-- 3 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 frontend/src/component/application/ApplicationList.tsx create mode 100644 frontend/src/component/application/ApplicationUpdate.tsx diff --git a/frontend/src/component/application/ApplicationList.tsx b/frontend/src/component/application/ApplicationList.tsx new file mode 100644 index 0000000000..c60f023b7f --- /dev/null +++ b/frontend/src/component/application/ApplicationList.tsx @@ -0,0 +1,70 @@ +import React, { Component, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { CircularProgress } from '@material-ui/core'; +import { Warning } from '@material-ui/icons'; + +import { AppsLinkList, styles as commonStyles } from '../common'; +import SearchField from '../common/SearchField/SearchField'; +import PageContent from '../common/PageContent/PageContent'; +import HeaderTitle from '../common/HeaderTitle'; +import useApplications from '../../hooks/api/getters/useApplications/useApplications'; + +const ApplicationList = () => { + //missing useSettings + const { applications, refetchApplications } = useApplications(); + + useEffect(() => { + refetchApplications(); + }, []); + + if (!applications) { + return ; + } + return ( + <> +
+ +
+ }> +
+ {applications.length > 0 ? ( + + ) : ( + + )} +
+
+ + ); +}; + +export default ApplicationList; + +const Empty = () => ( + +
+
+
+ Oh snap, it does not seem like you have connected any applications. + To connect your application to Unleash you will require a Client + SDK. +
+
+ You can read more about how to use Unleash in your application in + the{' '} + documentation. +
+
+); + +class ClientStrategies extends Component { + static propTypes = { + applications: PropTypes.array, + fetchAll: PropTypes.func.isRequired, + settings: PropTypes.object.isRequired, + updateSetting: PropTypes.func.isRequired, + }; +} diff --git a/frontend/src/component/application/ApplicationUpdate.tsx b/frontend/src/component/application/ApplicationUpdate.tsx new file mode 100644 index 0000000000..bc4244785e --- /dev/null +++ b/frontend/src/component/application/ApplicationUpdate.tsx @@ -0,0 +1,70 @@ +import { useState } from 'react'; +import { TextField, Grid } from '@material-ui/core'; +import { useCommonStyles } from '../../common.styles'; +import icons from './icon-names'; +import GeneralSelect from '../common/GeneralSelect/GeneralSelect'; +import useApplicationsApi from '../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; + +const ApplicationUpdate = ({ application }) => { + const { storeApplicationMetaData } = useApplicationsApi(); + const { appName, icon, url, description } = application; + const [localUrl, setLocalUrl] = useState(url); + const [localDescription, setLocalDescription] = useState(description); + const commonStyles = useCommonStyles(); + + return ( + + + + ({ key: v, label: v }))} + value={icon || 'apps'} + onChange={e => + storeApplicationMetaData( + appName, + 'icon', + e.target.value as string + ) + } + /> + + + setLocalUrl(e.target.value)} + label="Application URL" + placeholder="https://example.com" + type="url" + variant="outlined" + size="small" + onBlur={() => + storeApplicationMetaData(appName, 'url', localUrl) + } + /> + + + setLocalDescription(e.target.value)} + onBlur={() => + storeApplicationMetaData( + appName, + 'description', + localDescription + ) + } + /> + + + + ); +}; + +export default ApplicationUpdate; diff --git a/frontend/src/component/application/EditApplication.tsx b/frontend/src/component/application/EditApplication.tsx index 956af65f53..248726c4af 100644 --- a/frontend/src/component/application/EditApplication.tsx +++ b/frontend/src/component/application/EditApplication.tsx @@ -18,7 +18,7 @@ import { } from '../common/util'; import { UPDATE_APPLICATION } from '../providers/AccessProvider/permissions'; import ApplicationView from './ApplicationView'; -import ApplicationUpdate from './application-update'; +import ApplicationUpdate from './ApplicationUpdate'; import TabNav from '../common/TabNav/TabNav'; import Dialogue from '../common/Dialogue'; import PageContent from '../common/PageContent'; @@ -67,7 +67,6 @@ const EditApplication = () => { title="Are you sure you want to delete this application?" /> ); - const tabData = [ { label: 'Application overview', @@ -75,12 +74,7 @@ const EditApplication = () => { }, { label: 'Edit application', - component: ( - - ), + component: , }, ]; From f342d4d9049591f3017cd07fc8d2012dcab7f933 Mon Sep 17 00:00:00 2001 From: Youssef Date: Fri, 4 Feb 2022 16:26:51 +0100 Subject: [PATCH 05/17] refactor: finish ApplicationList and add it to routes --- .../component/application/ApplicationList.tsx | 71 +++++++++---------- .../application/ApplicationUpdate.tsx | 4 +- .../component/application/EditApplication.tsx | 3 +- .../common/SearchField/SearchField.jsx | 2 +- frontend/src/component/menu/routes.js | 4 +- 5 files changed, 40 insertions(+), 44 deletions(-) diff --git a/frontend/src/component/application/ApplicationList.tsx b/frontend/src/component/application/ApplicationList.tsx index c60f023b7f..7bbed87f7b 100644 --- a/frontend/src/component/application/ApplicationList.tsx +++ b/frontend/src/component/application/ApplicationList.tsx @@ -1,5 +1,4 @@ -import React, { Component, useEffect } from 'react'; -import PropTypes from 'prop-types'; +import React, { useEffect, useMemo, useState } from 'react'; import { CircularProgress } from '@material-ui/core'; import { Warning } from '@material-ui/icons'; @@ -10,28 +9,52 @@ import HeaderTitle from '../common/HeaderTitle'; import useApplications from '../../hooks/api/getters/useApplications/useApplications'; const ApplicationList = () => { - //missing useSettings const { applications, refetchApplications } = useApplications(); - + const [filter, setFilter] = useState(''); useEffect(() => { refetchApplications(); + // eslint-disable-next-line }, []); - if (!applications) { + const filteredApplications = useMemo(() => { + const regExp = new RegExp(filter, 'i'); + return filter + ? applications?.filter(a => regExp.test(a.appName)) + : applications; + }, [applications, filter]); + + const Empty = () => ( + +
+
+
+ Oh snap, it does not seem like you have connected any + applications. To connect your application to Unleash you will + require a Client SDK. +
+
+ You can read more about how to use Unleash in your application + in the{' '} + + documentation. + +
+
+ ); + + if (!filteredApplications) { return ; } + return ( <>
- +
}>
- {applications.length > 0 ? ( - + {filteredApplications.length > 0 ? ( + ) : ( )} @@ -42,29 +65,3 @@ const ApplicationList = () => { }; export default ApplicationList; - -const Empty = () => ( - -
-
-
- Oh snap, it does not seem like you have connected any applications. - To connect your application to Unleash you will require a Client - SDK. -
-
- You can read more about how to use Unleash in your application in - the{' '} - documentation. -
-
-); - -class ClientStrategies extends Component { - static propTypes = { - applications: PropTypes.array, - fetchAll: PropTypes.func.isRequired, - settings: PropTypes.object.isRequired, - updateSetting: PropTypes.func.isRequired, - }; -} diff --git a/frontend/src/component/application/ApplicationUpdate.tsx b/frontend/src/component/application/ApplicationUpdate.tsx index bc4244785e..29c7587472 100644 --- a/frontend/src/component/application/ApplicationUpdate.tsx +++ b/frontend/src/component/application/ApplicationUpdate.tsx @@ -8,8 +8,8 @@ import useApplicationsApi from '../../hooks/api/actions/useApplicationsApi/useAp const ApplicationUpdate = ({ application }) => { const { storeApplicationMetaData } = useApplicationsApi(); const { appName, icon, url, description } = application; - const [localUrl, setLocalUrl] = useState(url); - const [localDescription, setLocalDescription] = useState(description); + const [localUrl, setLocalUrl] = useState(url || ''); + const [localDescription, setLocalDescription] = useState(description || ''); const commonStyles = useCommonStyles(); return ( diff --git a/frontend/src/component/application/EditApplication.tsx b/frontend/src/component/application/EditApplication.tsx index 248726c4af..15975228ff 100644 --- a/frontend/src/component/application/EditApplication.tsx +++ b/frontend/src/component/application/EditApplication.tsx @@ -34,8 +34,7 @@ const EditApplication = () => { const { refetchApplication, application } = useApplication(name); const { appName, url, description, icon = 'apps', createdAt } = application; const { hasAccess } = useContext(AccessContext); - const { storeApplicationMetaData, deleteApplication } = - useApplicationsApi(); + const { deleteApplication } = useApplicationsApi(); const [loading, setLoading] = useState(true); const [showDialog, setShowDialog] = useState(false); diff --git a/frontend/src/component/common/SearchField/SearchField.jsx b/frontend/src/component/common/SearchField/SearchField.jsx index dd404b85a5..3345293569 100644 --- a/frontend/src/component/common/SearchField/SearchField.jsx +++ b/frontend/src/component/common/SearchField/SearchField.jsx @@ -7,7 +7,7 @@ import SearchIcon from '@material-ui/icons/Search'; import { useStyles } from './styles'; -function SearchField({ updateValue, className }) { +function SearchField({ updateValue, className = '' }) { const styles = useStyles(); const [localValue, setLocalValue] = useState(''); diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index b12d4f31da..b945b93b84 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -8,7 +8,6 @@ import HistoryPage from '../../page/history'; import HistoryTogglePage from '../../page/history/toggle'; import ShowArchive from '../../page/archive/show'; import Archive from '../../page/archive'; -import Applications from '../../page/applications'; import ContextFields from '../../page/context'; import ListTagTypes from '../../page/tag-types'; import Addons from '../../page/addons'; @@ -47,6 +46,7 @@ import CreateProject from '../project/Project/CreateProject/CreateProject'; import CreateFeature from '../feature/CreateFeature/CreateFeature/CreateFeature'; import EditFeature from '../feature/CreateFeature/EditFeature/EditFeature'; import EditApplication from '../application/EditApplication'; +import ApplicationList from '../application/ApplicationList'; export const routes = [ // Project @@ -203,7 +203,7 @@ export const routes = [ { path: '/applications', title: 'Applications', - component: Applications, + component: ApplicationList, type: 'protected', layout: 'main', menu: { mobile: true, advanced: true }, From caa719ed3196c95104ef8c7d5fd96c5ce3927186 Mon Sep 17 00:00:00 2001 From: Youssef Date: Tue, 8 Feb 2022 09:56:22 +0100 Subject: [PATCH 06/17] fix: resolve unused deps and fix routes --- frontend/src/component/application/EditApplication.tsx | 9 ++++----- frontend/src/component/menu/routes.js | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/src/component/application/EditApplication.tsx b/frontend/src/component/application/EditApplication.tsx index 15975228ff..5bb95972df 100644 --- a/frontend/src/component/application/EditApplication.tsx +++ b/frontend/src/component/application/EditApplication.tsx @@ -10,12 +10,11 @@ import { Typography, } from '@material-ui/core'; import { Link as LinkIcon } from '@material-ui/icons'; - import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; -import { - formatFullDateTimeWithLocale, - formatDateWithLocale, -} from '../common/util'; +// import { +// formatFullDateTimeWithLocale, +// formatDateWithLocale, +// } from '../common/util'; import { UPDATE_APPLICATION } from '../providers/AccessProvider/permissions'; import ApplicationView from './ApplicationView'; import ApplicationUpdate from './ApplicationUpdate'; diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index 1baf6fa80c..01a77035db 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -39,8 +39,8 @@ import EditTagType from '../tagTypes/EditTagType/EditTagType'; import CreateTagType from '../tagTypes/CreateTagType/CreateTagType'; import EditProject from '../project/Project/EditProject/EditProject'; import CreateProject from '../project/Project/CreateProject/CreateProject'; -import CreateFeature from '../feature/CreateFeature/CreateFeature/CreateFeature'; -import EditFeature from '../feature/CreateFeature/EditFeature/EditFeature'; +import CreateFeature from '../feature/CreateFeature/CreateFeature'; +import EditFeature from '../feature/EditFeature/EditFeature'; import EditApplication from '../application/EditApplication'; import ApplicationList from '../application/ApplicationList'; import ContextList from '../context/ContextList/ContextList'; From 6f2e7be85254775bebd34d6aecc3aa7e538e54d8 Mon Sep 17 00:00:00 2001 From: Youssef Date: Tue, 8 Feb 2022 14:12:53 +0100 Subject: [PATCH 07/17] refactor: use locationSettings in application for date format --- .../component/application/EditApplication.tsx | 18 ++++++++++-------- frontend/src/component/menu/routes.js | 2 -- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/src/component/application/EditApplication.tsx b/frontend/src/component/application/EditApplication.tsx index 5bb95972df..d431754d4f 100644 --- a/frontend/src/component/application/EditApplication.tsx +++ b/frontend/src/component/application/EditApplication.tsx @@ -11,10 +11,9 @@ import { } from '@material-ui/core'; import { Link as LinkIcon } from '@material-ui/icons'; import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; -// import { -// formatFullDateTimeWithLocale, -// formatDateWithLocale, -// } from '../common/util'; +import { + formatDateWithLocale, +} from '../common/util'; import { UPDATE_APPLICATION } from '../providers/AccessProvider/permissions'; import ApplicationView from './ApplicationView'; import ApplicationUpdate from './ApplicationUpdate'; @@ -26,6 +25,7 @@ import AccessContext from '../../contexts/AccessContext'; import useApplicationsApi from '../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; import useApplication from '../../hooks/api/getters/useApplication/useApplication'; import { useHistory, useParams } from 'react-router-dom'; +import { useLocationSettings } from '../../hooks/useLocationSettings'; const EditApplication = () => { const history = useHistory(); @@ -34,6 +34,9 @@ const EditApplication = () => { const { appName, url, description, icon = 'apps', createdAt } = application; const { hasAccess } = useContext(AccessContext); const { deleteApplication } = useApplicationsApi(); + const { locationSettings } = useLocationSettings(); + + console.log(locationSettings) const [loading, setLoading] = useState(true); const [showDialog, setShowDialog] = useState(false); @@ -48,8 +51,8 @@ const EditApplication = () => { setShowDialog(!showDialog); }; - // missing the settings hook (locale) - //const formatDate = v => formatDateWithLocale(v, locale); + const formatDate = (v: Date) => + formatDateWithLocale(v, locationSettings.locale); const onDeleteApplication = async (evt: Event) => { evt.preventDefault(); @@ -134,8 +137,7 @@ const EditApplication = () => {
{description || ''} - {/* // need to use formatDate once we have the useSettings hook ready */} - Created: {createdAt} + Created: {formatDate(createdAt)}
Date: Tue, 8 Feb 2022 16:38:08 +0100 Subject: [PATCH 08/17] refactor: remove unused components --- ...ditApplication.tsx => ApplicationEdit.tsx} | 6 +- .../application/ApplicationUpdate.tsx | 18 +- .../application/application-edit-component.js | 196 ------------------ .../application-edit-container.jsx | 30 --- .../application/application-list-component.js | 69 ------ .../application/application-list-container.js | 15 -- .../application/application-update.jsx | 72 ------- .../{icon-names.js => icon-names.ts} | 0 frontend/src/component/menu/routes.js | 4 +- 9 files changed, 21 insertions(+), 389 deletions(-) rename frontend/src/component/application/{EditApplication.tsx => ApplicationEdit.tsx} (98%) delete mode 100644 frontend/src/component/application/application-edit-component.js delete mode 100644 frontend/src/component/application/application-edit-container.jsx delete mode 100644 frontend/src/component/application/application-list-component.js delete mode 100644 frontend/src/component/application/application-list-container.js delete mode 100644 frontend/src/component/application/application-update.jsx rename frontend/src/component/application/{icon-names.js => icon-names.ts} (100%) diff --git a/frontend/src/component/application/EditApplication.tsx b/frontend/src/component/application/ApplicationEdit.tsx similarity index 98% rename from frontend/src/component/application/EditApplication.tsx rename to frontend/src/component/application/ApplicationEdit.tsx index d431754d4f..edfd3b1667 100644 --- a/frontend/src/component/application/EditApplication.tsx +++ b/frontend/src/component/application/ApplicationEdit.tsx @@ -27,7 +27,7 @@ import useApplication from '../../hooks/api/getters/useApplication/useApplicatio import { useHistory, useParams } from 'react-router-dom'; import { useLocationSettings } from '../../hooks/useLocationSettings'; -const EditApplication = () => { +const ApplicationEdit = () => { const history = useHistory(); const { name } = useParams<{ name: string }>(); const { refetchApplication, application } = useApplication(name); @@ -36,8 +36,6 @@ const EditApplication = () => { const { deleteApplication } = useApplicationsApi(); const { locationSettings } = useLocationSettings(); - console.log(locationSettings) - const [loading, setLoading] = useState(true); const [showDialog, setShowDialog] = useState(false); @@ -153,4 +151,4 @@ const EditApplication = () => { ); }; -export default EditApplication; +export default ApplicationEdit; diff --git a/frontend/src/component/application/ApplicationUpdate.tsx b/frontend/src/component/application/ApplicationUpdate.tsx index 29c7587472..c6e425aa12 100644 --- a/frontend/src/component/application/ApplicationUpdate.tsx +++ b/frontend/src/component/application/ApplicationUpdate.tsx @@ -5,7 +5,23 @@ import icons from './icon-names'; import GeneralSelect from '../common/GeneralSelect/GeneralSelect'; import useApplicationsApi from '../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; -const ApplicationUpdate = ({ application }) => { +interface IApplication { + appName: string; + color: string; + createdAt: string; + description: string; + icon: string; + instances: []; + links: object; + seenToggles: []; + strategies: []; + url: string; +} +interface IApplicationUpdateProps { + application: IApplication; +} + +const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { const { storeApplicationMetaData } = useApplicationsApi(); const { appName, icon, url, description } = application; const [localUrl, setLocalUrl] = useState(url || ''); diff --git a/frontend/src/component/application/application-edit-component.js b/frontend/src/component/application/application-edit-component.js deleted file mode 100644 index f242e5b37c..0000000000 --- a/frontend/src/component/application/application-edit-component.js +++ /dev/null @@ -1,196 +0,0 @@ -/* eslint react/no-multi-comp:off */ -import React, { PureComponent } from 'react'; -import PropTypes from 'prop-types'; - -import { - Avatar, - Link, - Icon, - IconButton, - Button, - LinearProgress, - Typography, -} from '@material-ui/core'; -import { Link as LinkIcon } from '@material-ui/icons'; - -import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; -import { - formatFullDateTimeWithLocale, - formatDateWithLocale, -} from '../common/util'; -import { UPDATE_APPLICATION } from '../providers/AccessProvider/permissions'; -import ApplicationView from './ApplicationView'; -import ApplicationUpdate from './application-update'; -import TabNav from '../common/TabNav/TabNav'; -import Dialogue from '../common/Dialogue'; -import PageContent from '../common/PageContent'; -import HeaderTitle from '../common/HeaderTitle'; -import AccessContext from '../../contexts/AccessContext'; -class ClientApplications extends PureComponent { - static contextType = AccessContext; - - static propTypes = { - fetchApplication: PropTypes.func.isRequired, - appName: PropTypes.string, - application: PropTypes.object, - locationSettings: PropTypes.object.isRequired, - storeApplicationMetaData: PropTypes.func.isRequired, - deleteApplication: PropTypes.func.isRequired, - history: PropTypes.object.isRequired, - }; - - constructor(props) { - super(); - this.state = { - activeTab: 0, - loading: !props.application, - prompt: false, - }; - } - - componentDidMount() { - this.props - .fetchApplication(this.props.appName) - .finally(() => this.setState({ loading: false })); - } - formatFullDateTime = v => - formatFullDateTimeWithLocale(v, this.props.locationSettings.locale); - formatDate = v => formatDateWithLocale(v, this.props.locationSettings.locale); - - deleteApplication = async evt => { - evt.preventDefault(); - // if (window.confirm('Are you sure you want to remove this application?')) { - const { deleteApplication, appName } = this.props; - await deleteApplication(appName); - this.props.history.push('/applications'); - // } - }; - - render() { - if (this.state.loading) { - return ( -
-

Loading...

- -
- ); - } else if (!this.props.application) { - return

Application ({this.props.appName}) not found

; - } - const { hasAccess } = this.context; - const { application, storeApplicationMetaData } = this.props; - const { - appName, - instances, - strategies, - seenToggles, - url, - description, - icon = 'apps', - createdAt, - } = application; - - const toggleModal = () => { - this.setState(prev => ({ ...prev, prompt: !prev.prompt })); - }; - - const renderModal = () => ( - - ); - - const tabData = [ - { - label: 'Application overview', - component: ( - - ), - }, - { - label: 'Edit application', - component: ( - - ), - }, - ]; - - return ( - - - {icon || 'apps'} - - {appName} - - } - actions={ - <> - - - - } - /> - - - Delete - - } - /> - - } - /> - } - > -
- {description || ''} - - Created: {this.formatDate(createdAt)} - -
- - {renderModal()} - - -
- } - /> -
- ); - } -} - -export default ClientApplications; diff --git a/frontend/src/component/application/application-edit-container.jsx b/frontend/src/component/application/application-edit-container.jsx deleted file mode 100644 index 4c732b0ed5..0000000000 --- a/frontend/src/component/application/application-edit-container.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import { connect } from 'react-redux'; -import ApplicationEdit from './application-edit-component'; -import { - deleteApplication, - fetchApplication, - storeApplicationMetaData, -} from '../../store/application/actions'; -import { useLocationSettings } from '../../hooks/useLocationSettings'; - -const ApplicationEditContainer = props => { - const { locationSettings } = useLocationSettings(); - - return ; -}; - -const mapStateToProps = (state, props) => { - let application = state.applications.getIn(['apps', props.appName]); - if (application) { - application = application.toJS(); - } - return { - application, - }; -}; - -export default connect(mapStateToProps, { - fetchApplication, - storeApplicationMetaData, - deleteApplication, -})(ApplicationEditContainer); diff --git a/frontend/src/component/application/application-list-component.js b/frontend/src/component/application/application-list-component.js deleted file mode 100644 index 65da487cfe..0000000000 --- a/frontend/src/component/application/application-list-component.js +++ /dev/null @@ -1,69 +0,0 @@ -import React, { useEffect, useMemo, useState } from 'react'; -import PropTypes from 'prop-types'; -import { CircularProgress } from '@material-ui/core'; -import { Warning } from '@material-ui/icons'; - -import { AppsLinkList, styles as commonStyles } from '../common'; -import SearchField from '../common/SearchField/SearchField'; -import PageContent from '../common/PageContent/PageContent'; -import HeaderTitle from '../common/HeaderTitle'; - -const Empty = () => ( - -
-
-
- Oh snap, it does not seem like you have connected any applications. - To connect your application to Unleash you will require a Client - SDK. -
-
- You can read more about how to use Unleash in your application in - the{' '} - documentation. -
-
-); - -const ClientStrategies = ({ fetchAll, applications }) => { - const [filter, setFilter] = useState(''); - - useEffect(() => { - fetchAll(); - }, [fetchAll]); - - const filteredApplications = useMemo(() => { - const regExp = new RegExp(filter, 'i'); - return filter - ? applications?.filter(a => regExp.test(a.appName)) - : applications; - }, [applications, filter]); - - if (!filteredApplications) { - return ; - } - - return ( - <> -
- -
- }> -
- {filteredApplications.length > 0 ? ( - - ) : ( - - )} -
-
- - ); -}; - -ClientStrategies.propTypes = { - applications: PropTypes.array, - fetchAll: PropTypes.func.isRequired, -}; - -export default ClientStrategies; diff --git a/frontend/src/component/application/application-list-container.js b/frontend/src/component/application/application-list-container.js deleted file mode 100644 index 201ceece2d..0000000000 --- a/frontend/src/component/application/application-list-container.js +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; -import ApplicationList from './application-list-component'; -import { fetchAll } from '../../store/application/actions'; - -const mapStateToProps = state => ({ - applications: state.applications.get('list').toJS(), -}); - -const mapDispatchToProps = { - fetchAll, -}; - -const Container = connect(mapStateToProps, mapDispatchToProps)(ApplicationList); - -export default Container; diff --git a/frontend/src/component/application/application-update.jsx b/frontend/src/component/application/application-update.jsx deleted file mode 100644 index 842d088b4d..0000000000 --- a/frontend/src/component/application/application-update.jsx +++ /dev/null @@ -1,72 +0,0 @@ -import React, { useState } from 'react'; -import PropTypes from 'prop-types'; -import { TextField, Grid } from '@material-ui/core'; -import { useCommonStyles } from '../../common.styles'; -import icons from './icon-names'; -import GeneralSelect from '../common/GeneralSelect/GeneralSelect'; - -function ApplicationUpdate({ application, storeApplicationMetaData }) { - const { appName, icon, url, description } = application; - const [localUrl, setLocalUrl] = useState(url); - const [localDescription, setLocalDescription] = useState(description); - const commonStyles = useCommonStyles(); - - return ( - - - - ({ key: v, label: v }))} - value={icon || 'apps'} - onChange={e => - storeApplicationMetaData( - appName, - 'icon', - e.target.value - ) - } - /> - - - setLocalUrl(e.target.value)} - label="Application URL" - placeholder="https://example.com" - type="url" - variant="outlined" - size="small" - onBlur={() => - storeApplicationMetaData(appName, 'url', localUrl) - } - /> - - - setLocalDescription(e.target.value)} - onBlur={() => - storeApplicationMetaData( - appName, - 'description', - localDescription - ) - } - /> - - - - ); -} - -ApplicationUpdate.propTypes = { - application: PropTypes.object.isRequired, - storeApplicationMetaData: PropTypes.func.isRequired, -}; - -export default ApplicationUpdate; diff --git a/frontend/src/component/application/icon-names.js b/frontend/src/component/application/icon-names.ts similarity index 100% rename from frontend/src/component/application/icon-names.js rename to frontend/src/component/application/icon-names.ts diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index b79a753365..c39561f1b7 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -41,7 +41,7 @@ import EditProject from '../project/Project/EditProject/EditProject'; import CreateProject from '../project/Project/CreateProject/CreateProject'; import CreateFeature from '../feature/CreateFeature/CreateFeature'; import EditFeature from '../feature/EditFeature/EditFeature'; -import EditApplication from '../application/EditApplication'; +import ApplicationEdit from '../application/ApplicationEdit'; import ApplicationList from '../application/ApplicationList'; import ContextList from '../context/ContextList/ContextList'; import RedirectFeatureView from '../feature/RedirectFeatureView/RedirectFeatureView'; @@ -194,7 +194,7 @@ export const routes = [ path: '/applications/:name', title: ':name', parent: '/applications', - component: EditApplication, + component: ApplicationEdit, type: 'protected', layout: 'main', menu: {}, From b12f0b9d0d53c4cec4ce63bc8a0201e620deb778 Mon Sep 17 00:00:00 2001 From: Youssef Date: Wed, 9 Feb 2022 11:23:37 +0100 Subject: [PATCH 09/17] fix: tests --- .../application-edit-component-test.js.snap | 157 ++---------------- .../application-edit-component-test.js | 27 +-- 2 files changed, 30 insertions(+), 154 deletions(-) diff --git a/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap b/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap index 9f749cf23a..7fcf9dc024 100644 --- a/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap +++ b/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap @@ -22,112 +22,22 @@ exports[`renders correctly if no application 1`] = ` `; exports[`renders correctly with permissions 1`] = ` -
+
+

+ Loading... +

-
-

- -
- - apps - -
- test-app -
-

-
- -
-
-
-
-

- app description -

-

- Created: - - Invalid Date - -

-
+ className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar1Indeterminate" + style={Object {}} + /> +
`; @@ -178,47 +88,12 @@ exports[`renders correctly without permission 1`] = ` apps
- test-app
+ />
- app description +

Created: - Invalid Date + 02/02/2022

diff --git a/frontend/src/component/application/__tests__/application-edit-component-test.js b/frontend/src/component/application/__tests__/application-edit-component-test.js index f57babdbd6..e78573c722 100644 --- a/frontend/src/component/application/__tests__/application-edit-component-test.js +++ b/frontend/src/component/application/__tests__/application-edit-component-test.js @@ -1,12 +1,9 @@ -import React from 'react'; - import { ThemeProvider } from '@material-ui/core'; -import ClientApplications from '../application-edit-component'; +import ApplicationEdit from '../ApplicationEdit'; import renderer from 'react-test-renderer'; import { MemoryRouter } from 'react-router-dom'; import { ADMIN } from '../../providers/AccessProvider/permissions'; import theme from '../../../themes/main-theme'; - import { createFakeStore } from '../../../accessStoreFake'; import AccessProvider from '../../providers/AccessProvider/AccessProvider'; @@ -14,13 +11,17 @@ test('renders correctly if no application', () => { const tree = renderer .create( - Promise.resolve({})} - storeApplicationMetaData={jest.fn()} - deleteApplication={jest.fn()} - history={{}} - locationSettings={{ locale: 'en-GB' }} - /> + + + Promise.resolve({})} + storeApplicationMetaData={jest.fn()} + deleteApplication={jest.fn()} + history={{}} + locationSettings={{ locale: 'en-GB' }} + /> + + ) .toJSON(); @@ -34,7 +35,7 @@ test('renders correctly without permission', () => { - Promise.resolve({})} storeApplicationMetaData={jest.fn()} deleteApplication={jest.fn()} @@ -97,7 +98,7 @@ test('renders correctly with permissions', () => { - Promise.resolve({})} storeApplicationMetaData={jest.fn()} history={{}} From a6e1e60e2cd82bbd31092f083f6d91b9387b5b2d Mon Sep 17 00:00:00 2001 From: Youssef Date: Wed, 9 Feb 2022 16:15:07 +0100 Subject: [PATCH 10/17] refactor: change based on PR feedback --- .../{ => ApplicationEdit}/ApplicationEdit.tsx | 75 +++++++++---------- .../{ => ApplicationList}/ApplicationList.tsx | 26 +++---- .../ApplicationUpdate.tsx | 8 +- .../{ => ApplicationView}/ApplicationView.tsx | 14 ++-- .../application-edit-component-test.js | 2 +- frontend/src/component/menu/routes.js | 4 +- .../useApplicationsApi/useApplicationsApi.ts | 12 --- .../useApplicationsForStrategy.ts | 40 ++++++++++ 8 files changed, 101 insertions(+), 80 deletions(-) rename frontend/src/component/application/{ => ApplicationEdit}/ApplicationEdit.tsx (64%) rename frontend/src/component/application/{ => ApplicationList}/ApplicationList.tsx (72%) rename frontend/src/component/application/{ => ApplicationUpdate}/ApplicationUpdate.tsx (91%) rename frontend/src/component/application/{ => ApplicationView}/ApplicationView.tsx (94%) create mode 100644 frontend/src/hooks/api/getters/useApplicationsForStrategy/useApplicationsForStrategy.ts diff --git a/frontend/src/component/application/ApplicationEdit.tsx b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx similarity index 64% rename from frontend/src/component/application/ApplicationEdit.tsx rename to frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx index edfd3b1667..f272533c19 100644 --- a/frontend/src/component/application/ApplicationEdit.tsx +++ b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx @@ -1,50 +1,43 @@ /* eslint react/no-multi-comp:off */ -import { useContext, useEffect, useState } from 'react'; +import { useContext, useState } from 'react'; import { Avatar, Link, Icon, IconButton, - Button, LinearProgress, Typography, } from '@material-ui/core'; import { Link as LinkIcon } from '@material-ui/icons'; -import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; -import { - formatDateWithLocale, -} from '../common/util'; -import { UPDATE_APPLICATION } from '../providers/AccessProvider/permissions'; -import ApplicationView from './ApplicationView'; -import ApplicationUpdate from './ApplicationUpdate'; -import TabNav from '../common/TabNav/TabNav'; -import Dialogue from '../common/Dialogue'; -import PageContent from '../common/PageContent'; -import HeaderTitle from '../common/HeaderTitle'; -import AccessContext from '../../contexts/AccessContext'; -import useApplicationsApi from '../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; -import useApplication from '../../hooks/api/getters/useApplication/useApplication'; +import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; +import { formatDateWithLocale } from '../../common/util'; +import { UPDATE_APPLICATION } from '../../providers/AccessProvider/permissions'; +import ApplicationView from '../ApplicationView/ApplicationView'; +import ApplicationUpdate from '../ApplicationUpdate/ApplicationUpdate'; +import TabNav from '../../common/TabNav/TabNav'; +import Dialogue from '../../common/Dialogue'; +import PageContent from '../../common/PageContent'; +import HeaderTitle from '../../common/HeaderTitle'; +import AccessContext from '../../../contexts/AccessContext'; +import useApplicationsApi from '../../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; +import useApplication from '../../../hooks/api/getters/useApplication/useApplication'; import { useHistory, useParams } from 'react-router-dom'; -import { useLocationSettings } from '../../hooks/useLocationSettings'; +import { useLocationSettings } from '../../../hooks/useLocationSettings'; +import useToast from '../../../hooks/useToast'; +import PermissionButton from '../../common/PermissionButton/PermissionButton'; const ApplicationEdit = () => { const history = useHistory(); const { name } = useParams<{ name: string }>(); - const { refetchApplication, application } = useApplication(name); + const { application, loading } = useApplication(name); const { appName, url, description, icon = 'apps', createdAt } = application; const { hasAccess } = useContext(AccessContext); const { deleteApplication } = useApplicationsApi(); const { locationSettings } = useLocationSettings(); + const { setToastData, setToastApiError } = useToast(); - const [loading, setLoading] = useState(true); const [showDialog, setShowDialog] = useState(false); - useEffect(() => { - refetchApplication(); - setLoading(false); - // eslint-disable-next-line - }, []); - const toggleModal = () => { setShowDialog(!showDialog); }; @@ -54,8 +47,17 @@ const ApplicationEdit = () => { const onDeleteApplication = async (evt: Event) => { evt.preventDefault(); - await deleteApplication(appName); - history.push('/applications'); + try { + await deleteApplication(appName); + setToastData({ + title: 'Deleted Successfully', + text: 'Application deleted successfully', + type: 'success', + }); + history.push('/applications'); + } catch (e: any) { + setToastApiError(e.toString()); + } }; const renderModal = () => ( @@ -115,18 +117,13 @@ const ApplicationEdit = () => { } /> - - Delete - - } - /> + + Delete + } /> diff --git a/frontend/src/component/application/ApplicationList.tsx b/frontend/src/component/application/ApplicationList/ApplicationList.tsx similarity index 72% rename from frontend/src/component/application/ApplicationList.tsx rename to frontend/src/component/application/ApplicationList/ApplicationList.tsx index 7bbed87f7b..fb175e8d83 100644 --- a/frontend/src/component/application/ApplicationList.tsx +++ b/frontend/src/component/application/ApplicationList/ApplicationList.tsx @@ -1,20 +1,16 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { CircularProgress } from '@material-ui/core'; import { Warning } from '@material-ui/icons'; -import { AppsLinkList, styles as commonStyles } from '../common'; -import SearchField from '../common/SearchField/SearchField'; -import PageContent from '../common/PageContent/PageContent'; -import HeaderTitle from '../common/HeaderTitle'; -import useApplications from '../../hooks/api/getters/useApplications/useApplications'; +import { AppsLinkList, styles as commonStyles } from '../../common'; +import SearchField from '../../common/SearchField/SearchField'; +import PageContent from '../../common/PageContent/PageContent'; +import HeaderTitle from '../../common/HeaderTitle'; +import useApplications from '../../../hooks/api/getters/useApplications/useApplications'; const ApplicationList = () => { - const { applications, refetchApplications } = useApplications(); + const { applications } = useApplications(); const [filter, setFilter] = useState(''); - useEffect(() => { - refetchApplications(); - // eslint-disable-next-line - }, []); const filteredApplications = useMemo(() => { const regExp = new RegExp(filter, 'i'); @@ -23,8 +19,8 @@ const ApplicationList = () => { : applications; }, [applications, filter]); - const Empty = () => ( - + const RenderNoApplications = () => ( + <>


@@ -39,7 +35,7 @@ const ApplicationList = () => { documentation.
-
+ ); if (!filteredApplications) { @@ -56,7 +52,7 @@ const ApplicationList = () => { {filteredApplications.length > 0 ? ( ) : ( - + )} diff --git a/frontend/src/component/application/ApplicationUpdate.tsx b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx similarity index 91% rename from frontend/src/component/application/ApplicationUpdate.tsx rename to frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx index c6e425aa12..eed4150074 100644 --- a/frontend/src/component/application/ApplicationUpdate.tsx +++ b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx @@ -1,9 +1,9 @@ import { useState } from 'react'; import { TextField, Grid } from '@material-ui/core'; -import { useCommonStyles } from '../../common.styles'; -import icons from './icon-names'; -import GeneralSelect from '../common/GeneralSelect/GeneralSelect'; -import useApplicationsApi from '../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; +import { useCommonStyles } from '../../../common.styles'; +import icons from '../icon-names'; +import GeneralSelect from '../../common/GeneralSelect/GeneralSelect'; +import useApplicationsApi from '../../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; interface IApplication { appName: string; diff --git a/frontend/src/component/application/ApplicationView.tsx b/frontend/src/component/application/ApplicationView/ApplicationView.tsx similarity index 94% rename from frontend/src/component/application/ApplicationView.tsx rename to frontend/src/component/application/ApplicationView/ApplicationView.tsx index 1ada6f1e55..5ba8b86838 100644 --- a/frontend/src/component/application/ApplicationView.tsx +++ b/frontend/src/component/application/ApplicationView/ApplicationView.tsx @@ -15,16 +15,16 @@ import { FlagRounded, SvgIconComponent, } from '@material-ui/icons'; -import { shorten } from '../common'; +import { shorten } from '../../common'; import { CREATE_FEATURE, CREATE_STRATEGY, -} from '../providers/AccessProvider/permissions'; -import ConditionallyRender from '../common/ConditionallyRender/ConditionallyRender'; -import { getTogglePath } from '../../utils/route-path-helpers'; -import useApplication from '../../hooks/api/getters/useApplication/useApplication'; -import AccessContext from '../../contexts/AccessContext'; -import { formatFullDateTimeWithLocale } from '../common/util'; +} from '../../providers/AccessProvider/permissions'; +import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; +import { getTogglePath } from '../../../utils/route-path-helpers'; +import useApplication from '../../../hooks/api/getters/useApplication/useApplication'; +import AccessContext from '../../../contexts/AccessContext'; +import { formatFullDateTimeWithLocale } from '../../common/util'; const ApplicationView = () => { const { hasAccess } = useContext(AccessContext); const { name } = useParams<{ name: string }>(); diff --git a/frontend/src/component/application/__tests__/application-edit-component-test.js b/frontend/src/component/application/__tests__/application-edit-component-test.js index e78573c722..bceb3822aa 100644 --- a/frontend/src/component/application/__tests__/application-edit-component-test.js +++ b/frontend/src/component/application/__tests__/application-edit-component-test.js @@ -1,5 +1,5 @@ import { ThemeProvider } from '@material-ui/core'; -import ApplicationEdit from '../ApplicationEdit'; +import ApplicationEdit from '../ApplicationEdit/ApplicationEdit'; import renderer from 'react-test-renderer'; import { MemoryRouter } from 'react-router-dom'; import { ADMIN } from '../../providers/AccessProvider/permissions'; diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index c39561f1b7..daab1f5d7f 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -41,8 +41,8 @@ import EditProject from '../project/Project/EditProject/EditProject'; import CreateProject from '../project/Project/CreateProject/CreateProject'; import CreateFeature from '../feature/CreateFeature/CreateFeature'; import EditFeature from '../feature/EditFeature/EditFeature'; -import ApplicationEdit from '../application/ApplicationEdit'; -import ApplicationList from '../application/ApplicationList'; +import ApplicationEdit from '../application/ApplicationEdit/ApplicationEdit'; +import ApplicationList from '../application/ApplicationList/ApplicationList'; import ContextList from '../context/ContextList/ContextList'; import RedirectFeatureView from '../feature/RedirectFeatureView/RedirectFeatureView'; diff --git a/frontend/src/hooks/api/actions/useApplicationsApi/useApplicationsApi.ts b/frontend/src/hooks/api/actions/useApplicationsApi/useApplicationsApi.ts index 09aa3cb4b3..6bf8d6e03b 100644 --- a/frontend/src/hooks/api/actions/useApplicationsApi/useApplicationsApi.ts +++ b/frontend/src/hooks/api/actions/useApplicationsApi/useApplicationsApi.ts @@ -39,20 +39,8 @@ const useApplicationsApi = () => { } }; - const fetchApplicationsWithStrategyName = async (strategyName: string) => { - const path = `${URI}?strategyName=${strategyName}`; - const req = createRequest(path, { method: 'GET' }); - try { - const res = await makeRequest(req.caller, req.id); - return res; - } catch (e) { - throw e; - } - }; - return { storeApplicationMetaData, - fetchApplicationsWithStrategyName, deleteApplication, errors, loading, diff --git a/frontend/src/hooks/api/getters/useApplicationsForStrategy/useApplicationsForStrategy.ts b/frontend/src/hooks/api/getters/useApplicationsForStrategy/useApplicationsForStrategy.ts new file mode 100644 index 0000000000..8a537e910f --- /dev/null +++ b/frontend/src/hooks/api/getters/useApplicationsForStrategy/useApplicationsForStrategy.ts @@ -0,0 +1,40 @@ +import useSWR, { mutate, SWRConfiguration } from 'swr'; +import { useState, useEffect } from 'react'; +import { formatApiPath } from '../../../../utils/format-path'; +import handleErrorResponses from '../httpErrorResponseHandler'; + +const path = formatApiPath(`api/admin/metrics/applications`); +const KEY = `api/admin/metrics/applications`; + +const useApplicationsForStrategy = ( + strategyName: string, + options: SWRConfiguration = {} +) => { + const fetcher = async () => { + const res = await fetch(`${path}?strategyName=${strategyName}`, { + method: 'GET', + }).then(handleErrorResponses('Application')); + return res.json(); + }; + + const { data, error } = useSWR(KEY, fetcher, options); + const [loading, setLoading] = useState(!error && !data); + + const refetchAddons = () => { + mutate(KEY); + }; + + useEffect(() => { + setLoading(!error && !data); + }, [data, error]); + + return { + addons: data?.addons || [], + providers: data?.providers || [], + error, + loading, + refetchAddons, + }; +}; + +export default useApplicationsForStrategy; From c10525108e4442822636ed2ce0880cde9aa60d76 Mon Sep 17 00:00:00 2001 From: Youssef Date: Wed, 9 Feb 2022 19:09:02 +0100 Subject: [PATCH 11/17] refactor: add handleChange --- .../ApplicationUpdate/ApplicationUpdate.tsx | 27 +++++++++---- .../useApplicationsForStrategy.ts | 40 ------------------- 2 files changed, 19 insertions(+), 48 deletions(-) delete mode 100644 frontend/src/hooks/api/getters/useApplicationsForStrategy/useApplicationsForStrategy.ts diff --git a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx index eed4150074..a295c84212 100644 --- a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx +++ b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx @@ -1,9 +1,10 @@ -import { useState } from 'react'; +import { ChangeEvent, useState } from 'react'; import { TextField, Grid } from '@material-ui/core'; import { useCommonStyles } from '../../../common.styles'; import icons from '../icon-names'; import GeneralSelect from '../../common/GeneralSelect/GeneralSelect'; import useApplicationsApi from '../../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; +import useToast from '../../../hooks/useToast'; interface IApplication { appName: string; @@ -26,8 +27,24 @@ const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { const { appName, icon, url, description } = application; const [localUrl, setLocalUrl] = useState(url || ''); const [localDescription, setLocalDescription] = useState(description || ''); + const { setToastApiError } = useToast(); const commonStyles = useCommonStyles(); + const handleChange = ( + evt: ChangeEvent<{ name?: string | undefined; value: unknown }> + ) => { + evt.preventDefault(); + try { + storeApplicationMetaData( + appName, + 'icon', + evt.target.value as string + ); + } catch (e: any) { + setToastApiError(e.toString()); + } + }; + return ( @@ -38,13 +55,7 @@ const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { label="Icon" options={icons.map(v => ({ key: v, label: v }))} value={icon || 'apps'} - onChange={e => - storeApplicationMetaData( - appName, - 'icon', - e.target.value as string - ) - } + onChange={e => handleChange(e)} /> diff --git a/frontend/src/hooks/api/getters/useApplicationsForStrategy/useApplicationsForStrategy.ts b/frontend/src/hooks/api/getters/useApplicationsForStrategy/useApplicationsForStrategy.ts deleted file mode 100644 index 8a537e910f..0000000000 --- a/frontend/src/hooks/api/getters/useApplicationsForStrategy/useApplicationsForStrategy.ts +++ /dev/null @@ -1,40 +0,0 @@ -import useSWR, { mutate, SWRConfiguration } from 'swr'; -import { useState, useEffect } from 'react'; -import { formatApiPath } from '../../../../utils/format-path'; -import handleErrorResponses from '../httpErrorResponseHandler'; - -const path = formatApiPath(`api/admin/metrics/applications`); -const KEY = `api/admin/metrics/applications`; - -const useApplicationsForStrategy = ( - strategyName: string, - options: SWRConfiguration = {} -) => { - const fetcher = async () => { - const res = await fetch(`${path}?strategyName=${strategyName}`, { - method: 'GET', - }).then(handleErrorResponses('Application')); - return res.json(); - }; - - const { data, error } = useSWR(KEY, fetcher, options); - const [loading, setLoading] = useState(!error && !data); - - const refetchAddons = () => { - mutate(KEY); - }; - - useEffect(() => { - setLoading(!error && !data); - }, [data, error]); - - return { - addons: data?.addons || [], - providers: data?.providers || [], - error, - loading, - refetchAddons, - }; -}; - -export default useApplicationsForStrategy; From 4d0d39891a5554d0af500c3c400e1e6a3a42c859 Mon Sep 17 00:00:00 2001 From: Youssef Date: Thu, 10 Feb 2022 09:23:11 +0100 Subject: [PATCH 12/17] fix: update PR based on feedback --- .../api/getters/useApplication/useApplication.ts | 11 ++++++----- .../api/getters/useApplications/useApplications.ts | 13 +++++++------ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frontend/src/hooks/api/getters/useApplication/useApplication.ts b/frontend/src/hooks/api/getters/useApplication/useApplication.ts index 6802f3f64a..7cc8344ca0 100644 --- a/frontend/src/hooks/api/getters/useApplication/useApplication.ts +++ b/frontend/src/hooks/api/getters/useApplication/useApplication.ts @@ -4,8 +4,9 @@ import { formatApiPath } from '../../../../utils/format-path'; import handleErrorResponses from '../httpErrorResponseHandler'; const useApplication = (name: string, options: SWRConfiguration = {}) => { + const path = formatApiPath(`api/admin/metrics/applications/${name}`); + const fetcher = async () => { - const path = formatApiPath(`api/admin/metrics/applications/${name}`); return fetch(path, { method: 'GET', }) @@ -13,16 +14,16 @@ const useApplication = (name: string, options: SWRConfiguration = {}) => { .then(res => res.json()); }; - const FEATURE_CACHE_KEY = `api/admin/metrics/applications/${name}`; + const APPLICATION_CACHE_KEY = `api/admin/metrics/applications/${name}`; - const { data, error } = useSWR(FEATURE_CACHE_KEY, fetcher, { + const { data, error } = useSWR(APPLICATION_CACHE_KEY, fetcher, { ...options, }); const [loading, setLoading] = useState(!error && !data); const refetchApplication = () => { - mutate(FEATURE_CACHE_KEY); + mutate(APPLICATION_CACHE_KEY); }; useEffect(() => { @@ -43,7 +44,7 @@ const useApplication = (name: string, options: SWRConfiguration = {}) => { error, loading, refetchApplication, - FEATURE_CACHE_KEY, + APPLICATION_CACHE_KEY, }; }; diff --git a/frontend/src/hooks/api/getters/useApplications/useApplications.ts b/frontend/src/hooks/api/getters/useApplications/useApplications.ts index 13efed5a8d..c2c4ea1464 100644 --- a/frontend/src/hooks/api/getters/useApplications/useApplications.ts +++ b/frontend/src/hooks/api/getters/useApplications/useApplications.ts @@ -3,26 +3,27 @@ import { useState, useEffect } from 'react'; import { formatApiPath } from '../../../../utils/format-path'; import handleErrorResponses from '../httpErrorResponseHandler'; +const path = formatApiPath('api/admin/metrics/applications'); + const useApplications = (options: SWRConfiguration = {}) => { const fetcher = async () => { - const path = formatApiPath('api/admin/metrics/applications'); return fetch(path, { method: 'GET', }) - .then(handleErrorResponses('Context data')) + .then(handleErrorResponses('Applications data')) .then(res => res.json()); }; - const FEATURE_CACHE_KEY = 'api/admin/metrics/applications'; + const APPLICATIONS_CACHE_KEY = 'api/admin/metrics/applications'; - const { data, error } = useSWR(FEATURE_CACHE_KEY, fetcher, { + const { data, error } = useSWR(APPLICATIONS_CACHE_KEY, fetcher, { ...options, }); const [loading, setLoading] = useState(!error && !data); const refetchApplications = () => { - mutate(FEATURE_CACHE_KEY); + mutate(APPLICATIONS_CACHE_KEY); }; useEffect(() => { @@ -34,7 +35,7 @@ const useApplications = (options: SWRConfiguration = {}) => { error, loading, refetchApplications, - FEATURE_CACHE_KEY, + APPLICATIONS_CACHE_KEY, }; }; From df448e66e847a33b84db53d2cdd306d947cc45b4 Mon Sep 17 00:00:00 2001 From: Youssef Date: Thu, 10 Feb 2022 09:33:09 +0100 Subject: [PATCH 13/17] refactor: add application interface and add use applications output interface --- .../ApplicationUpdate/ApplicationUpdate.tsx | 14 ++------------ .../api/getters/useApplication/useApplication.ts | 14 +++++++++++++- .../api/getters/useApplications/useApplications.ts | 13 ++++++++++++- frontend/src/interfaces/application.ts | 12 ++++++++++++ 4 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 frontend/src/interfaces/application.ts diff --git a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx index a295c84212..ac8dc84115 100644 --- a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx +++ b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx @@ -5,19 +5,9 @@ import icons from '../icon-names'; import GeneralSelect from '../../common/GeneralSelect/GeneralSelect'; import useApplicationsApi from '../../../hooks/api/actions/useApplicationsApi/useApplicationsApi'; import useToast from '../../../hooks/useToast'; +import { IApplication } from '../../../interfaces/application'; + -interface IApplication { - appName: string; - color: string; - createdAt: string; - description: string; - icon: string; - instances: []; - links: object; - seenToggles: []; - strategies: []; - url: string; -} interface IApplicationUpdateProps { application: IApplication; } diff --git a/frontend/src/hooks/api/getters/useApplication/useApplication.ts b/frontend/src/hooks/api/getters/useApplication/useApplication.ts index 7cc8344ca0..7453b35a0d 100644 --- a/frontend/src/hooks/api/getters/useApplication/useApplication.ts +++ b/frontend/src/hooks/api/getters/useApplication/useApplication.ts @@ -2,8 +2,20 @@ import useSWR, { mutate, SWRConfiguration } from 'swr'; import { useState, useEffect } from 'react'; import { formatApiPath } from '../../../../utils/format-path'; import handleErrorResponses from '../httpErrorResponseHandler'; +import { IApplication } from '../../../../interfaces/application'; -const useApplication = (name: string, options: SWRConfiguration = {}) => { +interface IUseApplicationOutput { + application: IApplication; + refetchApplication: () => void; + loading: boolean; + error?: Error; + APPLICATION_CACHE_KEY: string; +} + +const useApplication = ( + name: string, + options: SWRConfiguration = {} +): IUseApplicationOutput => { const path = formatApiPath(`api/admin/metrics/applications/${name}`); const fetcher = async () => { diff --git a/frontend/src/hooks/api/getters/useApplications/useApplications.ts b/frontend/src/hooks/api/getters/useApplications/useApplications.ts index c2c4ea1464..c6d15753d7 100644 --- a/frontend/src/hooks/api/getters/useApplications/useApplications.ts +++ b/frontend/src/hooks/api/getters/useApplications/useApplications.ts @@ -2,10 +2,21 @@ import useSWR, { mutate, SWRConfiguration } from 'swr'; import { useState, useEffect } from 'react'; import { formatApiPath } from '../../../../utils/format-path'; import handleErrorResponses from '../httpErrorResponseHandler'; +import { IApplication } from '../../../../interfaces/application'; const path = formatApiPath('api/admin/metrics/applications'); -const useApplications = (options: SWRConfiguration = {}) => { +interface IUseApplicationsOutput { + applications: IApplication[]; + refetchApplications: () => void; + loading: boolean; + error?: Error; + APPLICATIONS_CACHE_KEY: string; +} + +const useApplications = ( + options: SWRConfiguration = {} +): IUseApplicationsOutput => { const fetcher = async () => { return fetch(path, { method: 'GET', diff --git a/frontend/src/interfaces/application.ts b/frontend/src/interfaces/application.ts new file mode 100644 index 0000000000..17b79deb9d --- /dev/null +++ b/frontend/src/interfaces/application.ts @@ -0,0 +1,12 @@ +export interface IApplication { + appName: string; + color: string; + createdAt: string; + description: string; + icon: string; + instances: []; + links: object; + seenToggles: []; + strategies: []; + url: string; +} From 3fb8a4f5f3ad0e72a121beda80702e85c7890d88 Mon Sep 17 00:00:00 2001 From: Fredrik Oseberg Date: Thu, 10 Feb 2022 10:05:50 +0100 Subject: [PATCH 14/17] refactor: loading --- .../ApplicationList/ApplicationList.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/frontend/src/component/application/ApplicationList/ApplicationList.tsx b/frontend/src/component/application/ApplicationList/ApplicationList.tsx index fb175e8d83..ae13460aa4 100644 --- a/frontend/src/component/application/ApplicationList/ApplicationList.tsx +++ b/frontend/src/component/application/ApplicationList/ApplicationList.tsx @@ -7,9 +7,10 @@ import SearchField from '../../common/SearchField/SearchField'; import PageContent from '../../common/PageContent/PageContent'; import HeaderTitle from '../../common/HeaderTitle'; import useApplications from '../../../hooks/api/getters/useApplications/useApplications'; +import ConditionallyRender from '../../common/ConditionallyRender'; const ApplicationList = () => { - const { applications } = useApplications(); + const { applications, loading } = useApplications(); const [filter, setFilter] = useState(''); const filteredApplications = useMemo(() => { @@ -49,11 +50,17 @@ const ApplicationList = () => { }>
- {filteredApplications.length > 0 ? ( - - ) : ( - - )} + 0} + show={} + elseShow={ + ...loading
} + elseShow={} + /> + } + />
From ef8e3dcbfaa0271c25a4d4bc6339829a70baee6d Mon Sep 17 00:00:00 2001 From: Youssef Date: Thu, 10 Feb 2022 10:36:53 +0100 Subject: [PATCH 15/17] refactor: use explicit export and delete unused files --- .../ApplicationEdit/ApplicationEdit.tsx | 12 ++-- .../ApplicationList/ApplicationList.tsx | 8 +-- .../ApplicationUpdate/ApplicationUpdate.tsx | 4 +- .../ApplicationView/ApplicationView.tsx | 43 +++++++------- frontend/src/component/menu/routes.js | 5 +- frontend/src/page/applications/index.js | 6 -- frontend/src/page/applications/view.js | 12 ---- frontend/src/store/application/actions.js | 58 ------------------- frontend/src/store/application/api.js | 52 ----------------- frontend/src/store/application/index.js | 30 ---------- 10 files changed, 33 insertions(+), 197 deletions(-) delete mode 100644 frontend/src/page/applications/index.js delete mode 100644 frontend/src/page/applications/view.js delete mode 100644 frontend/src/store/application/actions.js delete mode 100644 frontend/src/store/application/api.js delete mode 100644 frontend/src/store/application/index.js diff --git a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx index f272533c19..4a8d6e0a04 100644 --- a/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx +++ b/frontend/src/component/application/ApplicationEdit/ApplicationEdit.tsx @@ -12,8 +12,8 @@ import { Link as LinkIcon } from '@material-ui/icons'; import ConditionallyRender from '../../common/ConditionallyRender/ConditionallyRender'; import { formatDateWithLocale } from '../../common/util'; import { UPDATE_APPLICATION } from '../../providers/AccessProvider/permissions'; -import ApplicationView from '../ApplicationView/ApplicationView'; -import ApplicationUpdate from '../ApplicationUpdate/ApplicationUpdate'; +import { ApplicationView } from '../ApplicationView/ApplicationView'; +import { ApplicationUpdate } from '../ApplicationUpdate/ApplicationUpdate'; import TabNav from '../../common/TabNav/TabNav'; import Dialogue from '../../common/Dialogue'; import PageContent from '../../common/PageContent'; @@ -26,7 +26,7 @@ import { useLocationSettings } from '../../../hooks/useLocationSettings'; import useToast from '../../../hooks/useToast'; import PermissionButton from '../../common/PermissionButton/PermissionButton'; -const ApplicationEdit = () => { +export const ApplicationEdit = () => { const history = useHistory(); const { name } = useParams<{ name: string }>(); const { application, loading } = useApplication(name); @@ -42,7 +42,7 @@ const ApplicationEdit = () => { setShowDialog(!showDialog); }; - const formatDate = (v: Date) => + const formatDate = (v: string) => formatDateWithLocale(v, locationSettings.locale); const onDeleteApplication = async (evt: Event) => { @@ -109,7 +109,7 @@ const ApplicationEdit = () => { actions={ <> @@ -147,5 +147,3 @@ const ApplicationEdit = () => { ); }; - -export default ApplicationEdit; diff --git a/frontend/src/component/application/ApplicationList/ApplicationList.tsx b/frontend/src/component/application/ApplicationList/ApplicationList.tsx index ae13460aa4..a7eae9f593 100644 --- a/frontend/src/component/application/ApplicationList/ApplicationList.tsx +++ b/frontend/src/component/application/ApplicationList/ApplicationList.tsx @@ -9,7 +9,7 @@ import HeaderTitle from '../../common/HeaderTitle'; import useApplications from '../../../hooks/api/getters/useApplications/useApplications'; import ConditionallyRender from '../../common/ConditionallyRender'; -const ApplicationList = () => { +export const ApplicationList = () => { const { applications, loading } = useApplications(); const [filter, setFilter] = useState(''); @@ -20,7 +20,7 @@ const ApplicationList = () => { : applications; }, [applications, filter]); - const RenderNoApplications = () => ( + const renderNoApplications = () => ( <>

@@ -57,7 +57,7 @@ const ApplicationList = () => { ...loading} - elseShow={} + elseShow={renderNoApplications()} /> } /> @@ -66,5 +66,3 @@ const ApplicationList = () => { ); }; - -export default ApplicationList; diff --git a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx index ac8dc84115..a899851400 100644 --- a/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx +++ b/frontend/src/component/application/ApplicationUpdate/ApplicationUpdate.tsx @@ -12,7 +12,7 @@ interface IApplicationUpdateProps { application: IApplication; } -const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { +export const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { const { storeApplicationMetaData } = useApplicationsApi(); const { appName, icon, url, description } = application; const [localUrl, setLocalUrl] = useState(url || ''); @@ -83,5 +83,3 @@ const ApplicationUpdate = ({ application }: IApplicationUpdateProps) => { ); }; - -export default ApplicationUpdate; diff --git a/frontend/src/component/application/ApplicationView/ApplicationView.tsx b/frontend/src/component/application/ApplicationView/ApplicationView.tsx index 5ba8b86838..5dbeb736ae 100644 --- a/frontend/src/component/application/ApplicationView/ApplicationView.tsx +++ b/frontend/src/component/application/ApplicationView/ApplicationView.tsx @@ -25,7 +25,8 @@ import { getTogglePath } from '../../../utils/route-path-helpers'; import useApplication from '../../../hooks/api/getters/useApplication/useApplication'; import AccessContext from '../../../contexts/AccessContext'; import { formatFullDateTimeWithLocale } from '../../common/util'; -const ApplicationView = () => { + +export const ApplicationView = () => { const { hasAccess } = useContext(AccessContext); const { name } = useParams<{ name: string }>(); const { application } = useApplication(name); @@ -129,24 +130,26 @@ const ApplicationView = () => {
- {strategies.map(({ name, description, notFound }, i: number) => ( - - ))} + {strategies.map( + ({ name, description, notFound }, i: number) => ( + + ) + )} @@ -203,5 +206,3 @@ const ApplicationView = () => { ); }; - -export default ApplicationView; diff --git a/frontend/src/component/menu/routes.js b/frontend/src/component/menu/routes.js index daab1f5d7f..b0724648e5 100644 --- a/frontend/src/component/menu/routes.js +++ b/frontend/src/component/menu/routes.js @@ -41,12 +41,11 @@ import EditProject from '../project/Project/EditProject/EditProject'; import CreateProject from '../project/Project/CreateProject/CreateProject'; import CreateFeature from '../feature/CreateFeature/CreateFeature'; import EditFeature from '../feature/EditFeature/EditFeature'; -import ApplicationEdit from '../application/ApplicationEdit/ApplicationEdit'; -import ApplicationList from '../application/ApplicationList/ApplicationList'; +import { ApplicationEdit } from '../application/ApplicationEdit/ApplicationEdit'; +import { ApplicationList } from '../application/ApplicationList/ApplicationList'; import ContextList from '../context/ContextList/ContextList'; import RedirectFeatureView from '../feature/RedirectFeatureView/RedirectFeatureView'; - export const routes = [ // Project diff --git a/frontend/src/page/applications/index.js b/frontend/src/page/applications/index.js deleted file mode 100644 index 2d5b6874a7..0000000000 --- a/frontend/src/page/applications/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import ApplicationListConmponent from '../../component/application/application-list-container'; - -const render = () => ; - -export default render; diff --git a/frontend/src/page/applications/view.js b/frontend/src/page/applications/view.js deleted file mode 100644 index 95d163ca64..0000000000 --- a/frontend/src/page/applications/view.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ApplicationEditComponent from '../../component/application/application-edit-container'; - -const render = ({ match: { params }, history }) => ; - -render.propTypes = { - match: PropTypes.object.isRequired, - history: PropTypes.object.isRequired, -}; - -export default render; diff --git a/frontend/src/store/application/actions.js b/frontend/src/store/application/actions.js deleted file mode 100644 index 0a2b62423b..0000000000 --- a/frontend/src/store/application/actions.js +++ /dev/null @@ -1,58 +0,0 @@ -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 deleted file mode 100644 index 5a941d27f4..0000000000 --- a/frontend/src/store/application/api.js +++ /dev/null @@ -1,52 +0,0 @@ -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 deleted file mode 100644 index 06da890586..0000000000 --- a/frontend/src/store/application/index.js +++ /dev/null @@ -1,30 +0,0 @@ -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; From 104adda8fc24e4d888ea55ca89cffd4dddce40d6 Mon Sep 17 00:00:00 2001 From: Youssef Date: Thu, 10 Feb 2022 11:02:53 +0100 Subject: [PATCH 16/17] refactor: restore application store and add toast --- .../ApplicationUpdate/ApplicationUpdate.tsx | 34 +++++------ frontend/src/store/application/actions.js | 58 +++++++++++++++++++ frontend/src/store/application/api.js | 52 +++++++++++++++++ frontend/src/store/application/index.js | 30 ++++++++++ 4 files changed, 156 insertions(+), 18 deletions(-) create mode 100644 frontend/src/store/application/actions.js create mode 100644 frontend/src/store/application/api.js create mode 100644 frontend/src/store/application/index.js 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; From ac7ae9c8a1d67deb4076c1b9f9fd5b6de02b5b94 Mon Sep 17 00:00:00 2001 From: Youssef Date: Thu, 10 Feb 2022 11:24:36 +0100 Subject: [PATCH 17/17] fix: test and update snapshot --- .../application-edit-component-test.js.snap | 81 +------ .../application-edit-component-test.js | 223 +++++++++--------- 2 files changed, 128 insertions(+), 176 deletions(-) diff --git a/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap b/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap index 7fcf9dc024..55a4015dff 100644 --- a/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap +++ b/frontend/src/component/application/__tests__/__snapshots__/application-edit-component-test.js.snap @@ -43,77 +43,22 @@ exports[`renders correctly with permissions 1`] = ` `; exports[`renders correctly without permission 1`] = ` -
+
+

+ Loading... +

-
-

- -
- - apps - -
-
-

-
-
-
-
-
-
-

- -

-

- Created: - - 02/02/2022 - -

-
+ className="MuiLinearProgress-bar MuiLinearProgress-barColorPrimary MuiLinearProgress-bar1Indeterminate" + style={Object {}} + /> +
`; diff --git a/frontend/src/component/application/__tests__/application-edit-component-test.js b/frontend/src/component/application/__tests__/application-edit-component-test.js index bceb3822aa..7a48f3e00a 100644 --- a/frontend/src/component/application/__tests__/application-edit-component-test.js +++ b/frontend/src/component/application/__tests__/application-edit-component-test.js @@ -1,26 +1,29 @@ import { ThemeProvider } from '@material-ui/core'; -import ApplicationEdit from '../ApplicationEdit/ApplicationEdit'; +import { ApplicationEdit } from '../ApplicationEdit/ApplicationEdit'; import renderer from 'react-test-renderer'; import { MemoryRouter } from 'react-router-dom'; import { ADMIN } from '../../providers/AccessProvider/permissions'; import theme from '../../../themes/main-theme'; import { createFakeStore } from '../../../accessStoreFake'; import AccessProvider from '../../providers/AccessProvider/AccessProvider'; +import UIProvider from '../../providers/UIProvider/UIProvider'; test('renders correctly if no application', () => { const tree = renderer .create( - - Promise.resolve({})} - storeApplicationMetaData={jest.fn()} - deleteApplication={jest.fn()} - history={{}} - locationSettings={{ locale: 'en-GB' }} - /> - + + + Promise.resolve({})} + storeApplicationMetaData={jest.fn()} + deleteApplication={jest.fn()} + history={{}} + locationSettings={{ locale: 'en-GB' }} + /> + + ) @@ -34,54 +37,56 @@ test('renders correctly without permission', () => { .create( - - Promise.resolve({})} - storeApplicationMetaData={jest.fn()} - deleteApplication={jest.fn()} - history={{}} - application={{ - appName: 'test-app', - instances: [ - { - instanceId: 'instance-1', - clientIp: '123.123.123.123', - lastSeen: '2017-02-23T15:56:49', - sdkVersion: '4.0', - }, - ], - strategies: [ - { - name: 'StrategyA', - description: 'A description', - }, - { - name: 'StrategyB', - description: 'B description', - notFound: true, - }, - ], - seenToggles: [ - { - name: 'ToggleA', - description: 'this is A toggle', - enabled: true, - project: 'default', - }, - { - name: 'ToggleB', - description: 'this is B toggle', - enabled: false, - notFound: true, - project: 'default', - }, - ], - url: 'http://example.org', - description: 'app description', - }} - locationSettings={{ locale: 'en-GB' }} - /> - + + + Promise.resolve({})} + storeApplicationMetaData={jest.fn()} + deleteApplication={jest.fn()} + history={{}} + application={{ + appName: 'test-app', + instances: [ + { + instanceId: 'instance-1', + clientIp: '123.123.123.123', + lastSeen: '2017-02-23T15:56:49', + sdkVersion: '4.0', + }, + ], + strategies: [ + { + name: 'StrategyA', + description: 'A description', + }, + { + name: 'StrategyB', + description: 'B description', + notFound: true, + }, + ], + seenToggles: [ + { + name: 'ToggleA', + description: 'this is A toggle', + enabled: true, + project: 'default', + }, + { + name: 'ToggleB', + description: 'this is B toggle', + enabled: false, + notFound: true, + project: 'default', + }, + ], + url: 'http://example.org', + description: 'app description', + }} + locationSettings={{ locale: 'en-GB' }} + /> + + ) @@ -95,56 +100,58 @@ test('renders correctly with permissions', () => { .create( - - Promise.resolve({})} - storeApplicationMetaData={jest.fn()} - history={{}} - deleteApplication={jest.fn()} - application={{ - appName: 'test-app', - instances: [ - { - instanceId: 'instance-1', - clientIp: '123.123.123.123', - lastSeen: '2017-02-23T15:56:49', - sdkVersion: '4.0', - }, - ], - strategies: [ - { - name: 'StrategyA', - description: 'A description', - }, - { - name: 'StrategyB', - description: 'B description', - notFound: true, - }, - ], - seenToggles: [ - { - name: 'ToggleA', - description: 'this is A toggle', - enabled: true, - project: 'default', - }, - { - name: 'ToggleB', - description: 'this is B toggle', - enabled: false, - notFound: true, - project: 'default', - }, - ], - url: 'http://example.org', - description: 'app description', - }} - locationSettings={{ locale: 'en-GB' }} - /> - + + + Promise.resolve({})} + storeApplicationMetaData={jest.fn()} + history={{}} + deleteApplication={jest.fn()} + application={{ + appName: 'test-app', + instances: [ + { + instanceId: 'instance-1', + clientIp: '123.123.123.123', + lastSeen: '2017-02-23T15:56:49', + sdkVersion: '4.0', + }, + ], + strategies: [ + { + name: 'StrategyA', + description: 'A description', + }, + { + name: 'StrategyB', + description: 'B description', + notFound: true, + }, + ], + seenToggles: [ + { + name: 'ToggleA', + description: 'this is A toggle', + enabled: true, + project: 'default', + }, + { + name: 'ToggleB', + description: 'this is B toggle', + enabled: false, + notFound: true, + project: 'default', + }, + ], + url: 'http://example.org', + description: 'app description', + }} + locationSettings={{ locale: 'en-GB' }} + /> + + )