mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: empty state application list improvements (#6579)
This commit is contained in:
		
							parent
							
								
									45634689f8
								
							
						
					
					
						commit
						59ee0b3bbe
					
				@ -1,201 +0,0 @@
 | 
				
			|||||||
import { useMemo } from 'react';
 | 
					 | 
				
			||||||
import { Avatar, CircularProgress, Icon, Link } from '@mui/material';
 | 
					 | 
				
			||||||
import Warning from '@mui/icons-material/Warning';
 | 
					 | 
				
			||||||
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 '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 { ApplicationUsageCell } from './ApplicationUsageCell/ApplicationUsageCell';
 | 
					 | 
				
			||||||
import { ApplicationSchema } from 'openapi';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const ApplicationList = () => {
 | 
					 | 
				
			||||||
    const { applications: data, loading } = useApplications();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const renderNoApplications = () => (
 | 
					 | 
				
			||||||
        <>
 | 
					 | 
				
			||||||
            <section style={{ textAlign: 'center' }}>
 | 
					 | 
				
			||||||
                <Warning titleAccess='Warning' /> <br />
 | 
					 | 
				
			||||||
                <br />
 | 
					 | 
				
			||||||
                Oh snap, it does not seem like you have connected any
 | 
					 | 
				
			||||||
                applications. To connect your application to Unleash you will
 | 
					 | 
				
			||||||
                require a Client SDK.
 | 
					 | 
				
			||||||
                <br />
 | 
					 | 
				
			||||||
                <br />
 | 
					 | 
				
			||||||
                You can read more about how to use Unleash in your application
 | 
					 | 
				
			||||||
                in the{' '}
 | 
					 | 
				
			||||||
                <Link href='https://docs.getunleash.io/docs/sdks/'>
 | 
					 | 
				
			||||||
                    documentation.
 | 
					 | 
				
			||||||
                </Link>
 | 
					 | 
				
			||||||
            </section>
 | 
					 | 
				
			||||||
        </>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const initialState = useMemo(
 | 
					 | 
				
			||||||
        () => ({
 | 
					 | 
				
			||||||
            sortBy: [{ id: 'name', desc: false }],
 | 
					 | 
				
			||||||
            hiddenColumns: ['description', 'sortOrder'],
 | 
					 | 
				
			||||||
        }),
 | 
					 | 
				
			||||||
        [],
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const columns = useMemo(
 | 
					 | 
				
			||||||
        () => [
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                id: 'Icon',
 | 
					 | 
				
			||||||
                Cell: ({
 | 
					 | 
				
			||||||
                    row: {
 | 
					 | 
				
			||||||
                        original: { icon },
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                }: any) => (
 | 
					 | 
				
			||||||
                    <IconCell
 | 
					 | 
				
			||||||
                        icon={
 | 
					 | 
				
			||||||
                            <Avatar>
 | 
					 | 
				
			||||||
                                <Icon>{icon || 'apps'}</Icon>
 | 
					 | 
				
			||||||
                            </Avatar>
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                disableGlobalFilter: true,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Header: 'Name',
 | 
					 | 
				
			||||||
                accessor: 'appName',
 | 
					 | 
				
			||||||
                width: '50%',
 | 
					 | 
				
			||||||
                Cell: ({
 | 
					 | 
				
			||||||
                    row: {
 | 
					 | 
				
			||||||
                        original: { appName, description },
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                }: any) => (
 | 
					 | 
				
			||||||
                    <LinkCell
 | 
					 | 
				
			||||||
                        title={appName}
 | 
					 | 
				
			||||||
                        to={`/applications/${appName}`}
 | 
					 | 
				
			||||||
                        subtitle={description}
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                sortType: 'alphanumeric',
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Header: 'Project(environment)',
 | 
					 | 
				
			||||||
                accessor: 'usage',
 | 
					 | 
				
			||||||
                width: '50%',
 | 
					 | 
				
			||||||
                Cell: ({
 | 
					 | 
				
			||||||
                    row: { original },
 | 
					 | 
				
			||||||
                }: {
 | 
					 | 
				
			||||||
                    row: { original: ApplicationSchema };
 | 
					 | 
				
			||||||
                }) => <ApplicationUsageCell usage={original.usage} />,
 | 
					 | 
				
			||||||
                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 <CircularProgress variant='indeterminate' />;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <>
 | 
					 | 
				
			||||||
            <PageContent
 | 
					 | 
				
			||||||
                header={
 | 
					 | 
				
			||||||
                    <PageHeader
 | 
					 | 
				
			||||||
                        title={`Applications (${rows.length})`}
 | 
					 | 
				
			||||||
                        actions={
 | 
					 | 
				
			||||||
                            <Search
 | 
					 | 
				
			||||||
                                initialValue={globalFilter}
 | 
					 | 
				
			||||||
                                onChange={setGlobalFilter}
 | 
					 | 
				
			||||||
                            />
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
                <div className={themeStyles.fullwidth}>
 | 
					 | 
				
			||||||
                    <ConditionallyRender
 | 
					 | 
				
			||||||
                        condition={data.length > 0}
 | 
					 | 
				
			||||||
                        show={
 | 
					 | 
				
			||||||
                            <SearchHighlightProvider value={globalFilter}>
 | 
					 | 
				
			||||||
                                <Table {...getTableProps()}>
 | 
					 | 
				
			||||||
                                    <SortableTableHeader
 | 
					 | 
				
			||||||
                                        headerGroups={headerGroups}
 | 
					 | 
				
			||||||
                                    />
 | 
					 | 
				
			||||||
                                    <TableBody {...getTableBodyProps()}>
 | 
					 | 
				
			||||||
                                        {rows.map((row) => {
 | 
					 | 
				
			||||||
                                            prepareRow(row);
 | 
					 | 
				
			||||||
                                            return (
 | 
					 | 
				
			||||||
                                                <TableRow
 | 
					 | 
				
			||||||
                                                    hover
 | 
					 | 
				
			||||||
                                                    {...row.getRowProps()}
 | 
					 | 
				
			||||||
                                                >
 | 
					 | 
				
			||||||
                                                    {row.cells.map((cell) => (
 | 
					 | 
				
			||||||
                                                        <TableCell
 | 
					 | 
				
			||||||
                                                            {...cell.getCellProps()}
 | 
					 | 
				
			||||||
                                                        >
 | 
					 | 
				
			||||||
                                                            {cell.render(
 | 
					 | 
				
			||||||
                                                                'Cell',
 | 
					 | 
				
			||||||
                                                            )}
 | 
					 | 
				
			||||||
                                                        </TableCell>
 | 
					 | 
				
			||||||
                                                    ))}
 | 
					 | 
				
			||||||
                                                </TableRow>
 | 
					 | 
				
			||||||
                                            );
 | 
					 | 
				
			||||||
                                        })}
 | 
					 | 
				
			||||||
                                    </TableBody>
 | 
					 | 
				
			||||||
                                </Table>
 | 
					 | 
				
			||||||
                            </SearchHighlightProvider>
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        elseShow={
 | 
					 | 
				
			||||||
                            <ConditionallyRender
 | 
					 | 
				
			||||||
                                condition={loading}
 | 
					 | 
				
			||||||
                                show={<div>...loading</div>}
 | 
					 | 
				
			||||||
                                elseShow={renderNoApplications()}
 | 
					 | 
				
			||||||
                            />
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    />
 | 
					 | 
				
			||||||
                </div>
 | 
					 | 
				
			||||||
            </PageContent>
 | 
					 | 
				
			||||||
        </>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
@ -28,9 +28,9 @@ test('Display applications list', async () => {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test('Display no applications', async () => {
 | 
					test('Display no applications connected', async () => {
 | 
				
			||||||
    setupApi([]);
 | 
					    setupApi([]);
 | 
				
			||||||
    render(<PaginatedApplicationList />);
 | 
					    render(<PaginatedApplicationList />);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await screen.findByText('Warning');
 | 
					    await screen.findByText(/To connect your application to Unleash/);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,5 @@
 | 
				
			|||||||
import { useMemo } from 'react';
 | 
					import { useMemo } from 'react';
 | 
				
			||||||
import { Avatar, Icon, Link } from '@mui/material';
 | 
					import { Avatar, Icon, Link, styled } from '@mui/material';
 | 
				
			||||||
import Warning from '@mui/icons-material/Warning';
 | 
					 | 
				
			||||||
import { styles as themeStyles } from 'component/common';
 | 
					import { styles as themeStyles } from 'component/common';
 | 
				
			||||||
import { PageContent } from 'component/common/PageContent/PageContent';
 | 
					import { PageContent } from 'component/common/PageContent/PageContent';
 | 
				
			||||||
import { PageHeader } from 'component/common/PageHeader/PageHeader';
 | 
					import { PageHeader } from 'component/common/PageHeader/PageHeader';
 | 
				
			||||||
@ -26,23 +25,34 @@ import { withTableState } from 'utils/withTableState';
 | 
				
			|||||||
import useLoading from 'hooks/useLoading';
 | 
					import useLoading from 'hooks/useLoading';
 | 
				
			||||||
import mapValues from 'lodash.mapvalues';
 | 
					import mapValues from 'lodash.mapvalues';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const renderNoApplications = () => (
 | 
					const InfoMessage = styled('div')(({ theme }) => ({
 | 
				
			||||||
    <>
 | 
					    textAlign: 'center',
 | 
				
			||||||
        <section style={{ textAlign: 'center' }}>
 | 
					    padding: theme.spacing(9, 0, 9, 0),
 | 
				
			||||||
            <Warning titleAccess='Warning' /> <br />
 | 
					    minHeight: '150px',
 | 
				
			||||||
            <br />
 | 
					}));
 | 
				
			||||||
            Oh snap, it does not seem like you have connected any applications.
 | 
					
 | 
				
			||||||
            To connect your application to Unleash you will require a Client
 | 
					const renderNoResults = (query: string | null | undefined) => {
 | 
				
			||||||
            SDK.
 | 
					    if (typeof query === 'string' && query.length > 0) {
 | 
				
			||||||
            <br />
 | 
					        return renderNoMatchingSearch(query);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <InfoMessage>
 | 
				
			||||||
 | 
					            You don't have have any connected applications. To connect your
 | 
				
			||||||
 | 
					            application to Unleash you will require a{' '}
 | 
				
			||||||
 | 
					            <Link href='https://docs.getunleash.io/docs/sdks/'>Client SDK</Link>
 | 
				
			||||||
 | 
					            .
 | 
				
			||||||
            <br />
 | 
					            <br />
 | 
				
			||||||
            You can read more about how to use Unleash in your application in
 | 
					            You can read more about how to use Unleash in your application in
 | 
				
			||||||
            the{' '}
 | 
					            the{' '}
 | 
				
			||||||
            <Link href='https://docs.getunleash.io/docs/sdks/'>
 | 
					            <Link href='https://docs.getunleash.io/docs/sdks/'>
 | 
				
			||||||
                documentation.
 | 
					                documentation.
 | 
				
			||||||
            </Link>
 | 
					            </Link>
 | 
				
			||||||
        </section>
 | 
					        </InfoMessage>
 | 
				
			||||||
    </>
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const renderNoMatchingSearch = (query: string) => (
 | 
				
			||||||
 | 
					    <InfoMessage>No application found matching "{query}"</InfoMessage>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const columnHelper = createColumnHelper<ApplicationSchema>();
 | 
					const columnHelper = createColumnHelper<ApplicationSchema>();
 | 
				
			||||||
@ -125,8 +135,6 @@ export const PaginatedApplicationList = () => {
 | 
				
			|||||||
        }),
 | 
					        }),
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const rows = table.getRowModel().rows;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const { offset, limit, query, sortBy, sortOrder, ...filterState } =
 | 
					    const { offset, limit, query, sortBy, sortOrder, ...filterState } =
 | 
				
			||||||
        tableState;
 | 
					        tableState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -165,7 +173,7 @@ export const PaginatedApplicationList = () => {
 | 
				
			|||||||
                                </div>
 | 
					                                </div>
 | 
				
			||||||
                            </SearchHighlightProvider>
 | 
					                            </SearchHighlightProvider>
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        elseShow={renderNoApplications()}
 | 
					                        elseShow={renderNoResults(query)}
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
            </PageContent>
 | 
					            </PageContent>
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user