diff --git a/frontend/src/component/application/ApplicationList/ApplicationList.tsx b/frontend/src/component/application/ApplicationList/ApplicationList.tsx index 19d31c7f86..d324e910c3 100644 --- a/frontend/src/component/application/ApplicationList/ApplicationList.tsx +++ b/frontend/src/component/application/ApplicationList/ApplicationList.tsx @@ -1,41 +1,37 @@ -import { useEffect, useMemo, useState } from 'react'; -import { CircularProgress, Link } from '@mui/material'; +import { useMemo } from 'react'; +import { + Avatar, + CircularProgress, + Icon, + Link, + styled, + Typography, + useTheme, +} from '@mui/material'; import { Warning } from '@mui/icons-material'; -import { AppsLinkList, styles as themeStyles } from 'component/common'; +import { styles as themeStyles } from 'component/common'; import { PageContent } from 'component/common/PageContent/PageContent'; import { PageHeader } from 'component/common/PageHeader/PageHeader'; import useApplications from 'hooks/api/getters/useApplications/useApplications'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; -import { useSearchParams } from 'react-router-dom'; import { Search } from 'component/common/Search/Search'; -import { safeRegExp } from '@server/util/escape-regex'; - -type PageQueryType = Partial>; +import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; +import { + SortableTableHeader, + Table, + TableBody, + TableCell, + TableRow, +} from 'component/common/Table'; +import { useGlobalFilter, useSortBy, useTable } from 'react-table'; +import { sortTypes } from 'utils/sortTypes'; +import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; +import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; +import { TextCell } from 'component/common/Table/cells/TextCell/TextCell'; export const ApplicationList = () => { - const { applications, loading } = useApplications(); - const [searchParams, setSearchParams] = useSearchParams(); - const [searchValue, setSearchValue] = useState( - searchParams.get('search') || '' - ); - - useEffect(() => { - const tableState: PageQueryType = {}; - if (searchValue) { - tableState.search = searchValue; - } - - setSearchParams(tableState, { - replace: true, - }); - }, [searchValue, setSearchParams]); - - const filteredApplications = useMemo(() => { - const regExp = safeRegExp(searchValue, 'i'); - return searchValue - ? applications?.filter(a => regExp.test(a.appName)) - : applications; - }, [applications, searchValue]); + const { applications: data, loading } = useApplications(); + const theme = useTheme(); const renderNoApplications = () => ( <> @@ -56,25 +52,115 @@ export const ApplicationList = () => { ); - if (!filteredApplications) { + const initialState = useMemo( + () => ({ + sortBy: [{ id: 'name', desc: false }], + hiddenColumns: ['description', 'sortOrder'], + }), + [] + ); + + const columns = useMemo( + () => [ + { + id: 'Icon', + Cell: ({ + row: { + original: { icon }, + }, + }: any) => ( + + {icon} + + } + /> + ), + disableGlobalFilter: true, + }, + { + Header: 'Name', + accessor: 'appName', + width: '50%', + Cell: ({ + row: { + original: { appName, description }, + }, + }: any) => ( + + ), + sortType: 'alphanumeric', + }, + { + Header: 'Project(environment)', + accessor: 'usage', + width: '50%', + Cell: () => ( + + + not connected + + + ), + sortType: 'alphanumeric', + }, + { + accessor: 'description', + disableSortBy: true, + }, + { + accessor: 'sortOrder', + disableGlobalFilter: true, + sortType: 'number', + }, + ], + [] + ); + + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + state: { globalFilter }, + setGlobalFilter, + } = useTable( + { + columns: columns as any[], // TODO: fix after `react-table` v8 update + data, + initialState, + sortTypes, + autoResetGlobalFilter: false, + autoResetSortBy: false, + disableSortRemove: true, + }, + useGlobalFilter, + useSortBy + ); + + if (!data) { return ; } - let applicationCount = - filteredApplications.length < applications.length - ? `${filteredApplications.length} of ${applications.length}` - : applications.length; - return ( <> } /> @@ -82,8 +168,37 @@ export const ApplicationList = () => { >
0} - show={} + condition={data.length > 0} + show={ + + + + + {rows.map(row => { + prepareRow(row); + return ( + + {row.cells.map(cell => ( + + {cell.render( + 'Cell' + )} + + ))} + + ); + })} + +
+
+ } elseShow={ >; + +export const OldApplicationList = () => { + const { applications, loading } = useApplications(); + const [searchParams, setSearchParams] = useSearchParams(); + const [searchValue, setSearchValue] = useState( + searchParams.get('search') || '' + ); + + useEffect(() => { + const tableState: PageQueryType = {}; + if (searchValue) { + tableState.search = searchValue; + } + + setSearchParams(tableState, { + replace: true, + }); + }, [searchValue, setSearchParams]); + + const filteredApplications = useMemo(() => { + const regExp = safeRegExp(searchValue, 'i'); + return searchValue + ? applications?.filter(a => regExp.test(a.appName)) + : applications; + }, [applications, searchValue]); + + const renderNoApplications = () => ( + <> +
+
+
+ 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 ; + } + + let applicationCount = + filteredApplications.length < applications.length + ? `${filteredApplications.length} of ${applications.length}` + : applications.length; + + return ( + <> + + } + /> + } + > +
+ 0} + show={} + elseShow={ + ...loading
} + elseShow={renderNoApplications()} + /> + } + /> +
+
+ + ); +}; diff --git a/frontend/src/component/application/ApplicationList/TemporaryApplicationListWrapper.tsx b/frontend/src/component/application/ApplicationList/TemporaryApplicationListWrapper.tsx new file mode 100644 index 0000000000..120bd5e536 --- /dev/null +++ b/frontend/src/component/application/ApplicationList/TemporaryApplicationListWrapper.tsx @@ -0,0 +1,36 @@ +import { useMemo } from 'react'; +import { Avatar, CircularProgress, Icon, Link } from '@mui/material'; +import { Warning } from '@mui/icons-material'; +import { styles as themeStyles } from 'component/common'; +import { PageContent } from 'component/common/PageContent/PageContent'; +import { PageHeader } from 'component/common/PageHeader/PageHeader'; +import useApplications from 'hooks/api/getters/useApplications/useApplications'; +import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; +import { Search } from 'component/common/Search/Search'; +import { SearchHighlightProvider } from 'component/common/Table/SearchHighlightContext/SearchHighlightContext'; +import { + SortableTableHeader, + Table, + TableBody, + TableCell, + TableRow, +} from '../../common/Table'; +import { useGlobalFilter, useSortBy, useTable } from 'react-table'; +import { sortTypes } from 'utils/sortTypes'; +import { IconCell } from 'component/common/Table/cells/IconCell/IconCell'; +import { LinkCell } from 'component/common/Table/cells/LinkCell/LinkCell'; +import useUiConfig from '../../../hooks/api/getters/useUiConfig/useUiConfig'; +import { ApplicationList } from './ApplicationList'; +import { OldApplicationList } from './OldApplicationList'; + +export const TemporaryApplicationListWrapper = () => { + const { uiConfig } = useUiConfig(); + + return ( + } + elseShow={} + /> + ); +}; diff --git a/frontend/src/component/menu/routes.ts b/frontend/src/component/menu/routes.ts index 484342dcb8..4055881f64 100644 --- a/frontend/src/component/menu/routes.ts +++ b/frontend/src/component/menu/routes.ts @@ -19,7 +19,6 @@ import EditProject from 'component/project/Project/EditProject/EditProject'; import CreateFeature from 'component/feature/CreateFeature/CreateFeature'; import EditFeature from 'component/feature/EditFeature/EditFeature'; import { ApplicationEdit } from 'component/application/ApplicationEdit/ApplicationEdit'; -import { ApplicationList } from 'component/application/ApplicationList/ApplicationList'; import ContextList from 'component/context/ContextList/ContextList/ContextList'; import RedirectFeatureView from 'component/feature/RedirectFeatureView/RedirectFeatureView'; import { CreateAddon } from 'component/addons/CreateAddon/CreateAddon'; @@ -44,6 +43,7 @@ import { LazyAdmin } from 'component/admin/LazyAdmin'; import { LazyProject } from 'component/project/Project/LazyProject'; import { LoginHistory } from 'component/loginHistory/LoginHistory'; import { FeatureTypesList } from 'component/featureTypes/FeatureTypesList'; +import { TemporaryApplicationListWrapper } from 'component/application/ApplicationList/TemporaryApplicationListWrapper'; export const routes: IRoute[] = [ // Splash @@ -179,7 +179,7 @@ export const routes: IRoute[] = [ { path: '/applications', title: 'Applications', - component: ApplicationList, + component: TemporaryApplicationListWrapper, type: 'protected', menu: { mobile: true, advanced: true }, }, diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts index fcbd073237..64421cc4e3 100644 --- a/frontend/src/interfaces/uiConfig.ts +++ b/frontend/src/interfaces/uiConfig.ts @@ -58,6 +58,7 @@ export interface IFlags { segmentChangeRequests?: boolean; changeRequestReject?: boolean; lastSeenByEnvironment?: boolean; + newApplicationList?: boolean; } export interface IVersionInfo { diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts index da73b1c41f..4fb67a01e9 100644 --- a/src/lib/types/experimental.ts +++ b/src/lib/types/experimental.ts @@ -30,7 +30,8 @@ export type IFlagKey = | 'lastSeenByEnvironment' | 'segmentChangeRequests' | 'changeRequestReject' - | 'customRootRolesKillSwitch'; + | 'customRootRolesKillSwitch' + | 'newApplicationList'; export type IFlags = Partial<{ [key in IFlagKey]: boolean | Variant }>; diff --git a/src/server-dev.ts b/src/server-dev.ts index fe4fee7c64..769c110eb8 100644 --- a/src/server-dev.ts +++ b/src/server-dev.ts @@ -45,6 +45,7 @@ process.nextTick(async () => { frontendNavigationUpdate: true, lastSeenByEnvironment: true, segmentChangeRequests: true, + newApplicationList: true, }, }, authentication: {