1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

feat: connected instance ui api integration (#6343)

This commit is contained in:
Mateusz Kwasniewski 2024-02-27 08:30:31 +01:00 committed by GitHub
parent 227abd8bba
commit ae257d5957
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 91 additions and 121 deletions

View File

@ -1,99 +1,38 @@
import { useMemo } from 'react';
import { FC, useEffect, useMemo, useState } from 'react';
import useApplication from 'hooks/api/getters/useApplication/useApplication';
import { WarningAmber } from '@mui/icons-material';
import { formatDateYMDHMS } from 'utils/formatDate';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import { useConnectedInstancesTable } from './useConnectedInstancesTable';
import { ConnectedInstancesTable } from './ConnectedInstancesTable';
import { IApplication } from 'interfaces/application';
import { useQueryParam } from 'use-query-params';
import { styled } from '@mui/material';
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
import { Box, ToggleButton, ToggleButtonGroup } from '@mui/material';
import { useApplicationOverview } from 'hooks/api/getters/useApplicationOverview/useApplicationOverview';
import { useConnectedInstances } from 'hooks/api/getters/useConnectedInstances/useConnectedInstances';
const Container = styled('div')(({ theme }) => ({
'* + *': {
marginBlockStart: theme.spacing(2),
},
}));
const EnvironmentSelectionContainer = styled('div')(({ theme }) => ({
label: {
'--padding-horizontal': theme.spacing(3),
'--padding-vertical': theme.spacing(1),
color: theme.palette.primary.main,
background: theme.palette.background,
paddingInline: 'var(--padding-horizontal)',
paddingBlock: 'var(--padding-vertical)',
border: `1px solid ${theme.palette.background.alternative}`,
borderInlineStart: 'none',
fontWeight: 'bold',
position: 'relative',
svg: {
color: theme.palette.warning.main,
position: 'absolute',
fontSize: theme.fontSizes.bodySize,
top: 'calc(var(--padding-horizontal) * .12)',
right: 'calc(var(--padding-horizontal) * .2)',
},
},
'label:first-of-type': {
borderInlineStart: `1px solid ${theme.palette.background.alternative}`,
borderRadius: `${theme.shape.borderRadiusMedium}px 0 0 ${theme.shape.borderRadiusMedium}px`,
},
'label:last-of-type': {
borderRadius: `0 ${theme.shape.borderRadiusMedium}px ${theme.shape.borderRadiusMedium}px 0`,
},
'label:has(input:checked)': {
background: theme.palette.background.alternative,
color: theme.palette.primary.contrastText,
svg: {
color: 'inherit',
},
},
'label:focus-within': {
outline: `2px solid ${theme.palette.background.alternative}`,
outlineOffset: theme.spacing(0.5),
},
fieldset: {
border: 'none',
padding: 0,
margin: 0,
},
legend: {
marginBlockEnd: theme.spacing(3),
},
'.visually-hidden': {
border: 0,
clip: 'rect(0 0 0 0)',
height: 'auto',
margin: 0,
overflow: 'hidden',
padding: 0,
position: 'absolute',
width: '1px',
whiteSpace: 'nowrap',
},
}));
export const ConnectedInstances = () => {
export const ConnectedInstances: FC = () => {
const name = useRequiredPathParam('name');
const { application } = useApplication(name);
const [currentEnvironment, setCurrentEnvironment] =
useQueryParam('environment');
const availableEnvironments = new Set(
application?.instances.map(
// @ts-expect-error: the type definition here is incomplete. It
// should be updated as part of this project.
(instance) => instance.environment,
),
const { data: applicationOverview } = useApplicationOverview(name);
const availableEnvironments = applicationOverview.environments.map(
(env) => env.name,
);
const allEnvironmentsSorted = Array.from(availableEnvironments)
.sort((a, b) => a.localeCompare(b))
.map((env) => ({ name: env, problemsDetected: false }));
const allEnvironmentsSorted = Array.from(availableEnvironments).sort(
(a, b) => a.localeCompare(b),
);
const [currentEnvironment, setCurrentEnvironment] = useState(
allEnvironmentsSorted[0],
);
const { data: connectedInstances } = useConnectedInstances(
name,
currentEnvironment,
);
useEffect(() => {
if (!currentEnvironment && availableEnvironments.length > 0) {
setCurrentEnvironment(availableEnvironments[0]);
}
}, [JSON.stringify(availableEnvironments)]);
const tableData = useMemo(() => {
const map = ({
@ -123,41 +62,28 @@ export const ConnectedInstances = () => {
useConnectedInstancesTable(tableData);
return (
<Container>
<EnvironmentSelectionContainer>
<fieldset>
<legend>
Select which environment to display data for. Only
environments that have received traffic for this
application will be shown here.
</legend>
<Box>
<Box sx={{ mb: 3 }}>
<Box sx={{ mb: 2 }}>
Select which environment to display data for. Only
environments that have received traffic for this application
will be shown here.
</Box>
<ToggleButtonGroup
color='primary'
value={currentEnvironment}
exclusive
onChange={(event, value) => {
if (value !== null) {
setCurrentEnvironment(value);
}
}}
>
{allEnvironmentsSorted.map((env) => {
return (
<label key={env.name}>
{env.name}
<ConditionallyRender
condition={env.problemsDetected}
show={
<WarningAmber titleAccess='Problems detected' />
}
/>
<input
defaultChecked={
currentEnvironment === env.name
}
className='visually-hidden'
type='radio'
name='active-environment'
onClick={() => {
setCurrentEnvironment(env.name);
}}
/>
</label>
);
return <ToggleButton value={env}>{env}</ToggleButton>;
})}
</fieldset>
</EnvironmentSelectionContainer>
</ToggleButtonGroup>
</Box>
<ConnectedInstancesTable
loading={false}
headerGroups={headerGroups}
@ -166,6 +92,6 @@ export const ConnectedInstances = () => {
getTableProps={getTableProps}
rows={rows}
/>
</Container>
</Box>
);
};

View File

@ -14,7 +14,7 @@ export const useConnectedInstancesTable = (
instanceData: ConnectedInstancesTableData[],
) => {
const initialState = useMemo(
() => ({ sortBy: [{ id: 'instanceId' }] }),
() => ({ sortBy: [{ id: 'lastSeen', desc: true }] }),
[],
);

View File

@ -0,0 +1,44 @@
import { SWRConfiguration } from 'swr';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
type ConnectedInstancesSchema = {
instances: {
instanceId: string;
sdk: string;
clientIp: string;
lastSeen: string;
}[];
};
export const useConnectedInstances = (
application: string,
environment?: string,
options: SWRConfiguration = {},
) => {
const path = formatApiPath(
`api/admin/metrics/instances/${application}/${environment}`,
);
const { data, error } = useConditionalSWR<ConnectedInstancesSchema>(
Boolean(environment),
{ instances: [] },
path,
fetcher,
options,
);
return {
data: data || { instances: [] },
error,
loading: !error && !data,
};
};
const fetcher = async (path: string): Promise<ConnectedInstancesSchema> => {
const res = await fetch(path).then(
handleErrorResponses('Connected instances'),
);
const data = await res.json();
return data;
};