1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-04 00:18:01 +01:00

[wip] add data to ui (#8710)

Hooks up the project status lifecycle data to the UI. Adds some minor
refactoring as part of that effort.

## Other files

There's been some small changes to
`frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleStageIcon.tsx`
and `frontend/src/hooks/useLoading.ts` as well to accommodate their
usage here and to remove unused stuff. The inline comments mention the
same thing but for posterity (especially after this is merged), the
comments are:

For
`frontend/src/component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleStageIcon.tsx`:

> The icon only needs the name to pick.
https://github.com/Unleash/unleash/pull/7049 deliberately changed the
logic so that the completed stage gets the same icon regardless of its
status. As such, to make the icon easier to use other places (such as in
the lifecycle widget), we'll only require the name.

For `frontend/src/hooks/useLoading.ts`:
> There's no reason we should only be able to put refs on divs, as far
as I'm aware. TS was complaining that that a `ul` couldn't hold a div
reference, so I gave it a type parameter that defaults to the old
version.
This commit is contained in:
Thomas Heartman 2024-11-12 11:35:42 +01:00 committed by GitHub
parent 24a30e5ec3
commit 3bc9fe9a9a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 76 additions and 73 deletions

View File

@ -6,18 +6,16 @@ import { ReactComponent as CompletedStageIcon } from 'assets/icons/stage-complet
import { ReactComponent as ArchivedStageIcon } from 'assets/icons/stage-archived.svg';
import type { LifecycleStage } from './LifecycleStage';
export const FeatureLifecycleStageIcon: FC<{ stage: LifecycleStage }> = ({
stage,
}) => {
export const FeatureLifecycleStageIcon: FC<{
stage: Pick<LifecycleStage, 'name'>;
}> = ({ stage }) => {
if (stage.name === 'archived') {
return <ArchivedStageIcon />;
} else if (stage.name === 'pre-live') {
return <PreLiveStageIcon />;
} else if (stage.name === 'live') {
return <LiveStageIcon />;
} else if (stage.name === 'completed' && stage.status === 'kept') {
return <CompletedStageIcon />;
} else if (stage.name === 'completed' && stage.status === 'discarded') {
} else if (stage.name === 'completed') {
return <CompletedStageIcon />;
} else {
return <InitialStageIcon />;

View File

@ -1,6 +1,9 @@
import { styled } from '@mui/material';
import { FeatureLifecycleStageIcon } from 'component/feature/FeatureView/FeatureOverview/FeatureLifecycle/FeatureLifecycleStageIcon';
import { useProjectStatus } from 'hooks/api/getters/useProjectStatus/useProjectStatus';
import useLoading from 'hooks/useLoading';
import { useRequiredPathParam } from 'hooks/useRequiredPathParam';
import type { FC } from 'react';
import { Link } from 'react-router-dom';
const LifecycleBox = styled('li')(({ theme }) => ({
@ -41,10 +44,6 @@ const Stats = styled('dl')(({ theme }) => ({
},
}));
const NegativeStat = styled('span')(({ theme }) => ({
color: theme.palette.warning.contrastText,
}));
const NoData = styled('span')({
fontWeight: 'normal',
});
@ -53,123 +52,128 @@ const LinkNoUnderline = styled(Link)({
textDecoration: 'none',
});
const AverageDaysStat: FC<{ averageDays?: number | null }> = ({
averageDays,
}) => {
const Content = () => {
if (averageDays === null || averageDays === undefined) {
return <NoData>No data</NoData>;
}
return `${averageDays} days`;
};
return (
<Stats>
<dt>Avg. time in stage</dt>
<dd data-loading-project-lifecycle-summary>
<Content />
</dd>
</Stats>
);
};
export const ProjectLifecycleSummary = () => {
const projectId = useRequiredPathParam('projectId');
const { data, loading } = useProjectStatus(projectId);
const loadingRef = useLoading<HTMLUListElement>(
loading,
'[data-loading-project-lifecycle-summary=true]',
);
return (
<Wrapper>
<Wrapper ref={loadingRef}>
<LifecycleBox>
<p>
<Counter>
<BigNumber>15</BigNumber>
<BigNumber data-loading-project-lifecycle-summary>
{data?.lifecycleSummary.initial.currentFlags ?? 0}
</BigNumber>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{
name: 'initial',
enteredStageAt: '',
}}
stage={{ name: 'initial' }}
/>
</Counter>
<span>flags in initial</span>
</p>
<Stats>
<dt>Avg. time in stage</dt>
<dd>
<NegativeStat>21 days</NegativeStat>
</dd>
</Stats>
<AverageDaysStat
averageDays={data?.lifecycleSummary.initial.averageDays}
/>
</LifecycleBox>
<LifecycleBox>
<p>
<Counter>
<BigNumber>3</BigNumber>
<BigNumber data-loading-project-lifecycle-summary>
{data?.lifecycleSummary.preLive.currentFlags ?? 0}
</BigNumber>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{
name: 'pre-live',
enteredStageAt: '',
environments: [],
}}
stage={{ name: 'pre-live' }}
/>
</Counter>
<span>flags in pre-live</span>
</p>
<Stats>
<dt>Avg. time in stage</dt>
<dd>18 days</dd>
</Stats>
<AverageDaysStat
averageDays={data?.lifecycleSummary.preLive.averageDays}
/>
</LifecycleBox>
<LifecycleBox>
<p>
<Counter>
<BigNumber>2</BigNumber>
<BigNumber data-loading-project-lifecycle-summary>
{data?.lifecycleSummary.live.currentFlags ?? 0}
</BigNumber>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{
name: 'live',
enteredStageAt: '',
environments: [],
}}
stage={{ name: 'live' }}
/>
</Counter>
<span>flags in live</span>
</p>
<Stats>
<dt>Avg. time in stage</dt>
<dd>10 days</dd>
</Stats>
<AverageDaysStat
averageDays={data?.lifecycleSummary.live.averageDays}
/>
</LifecycleBox>
<LifecycleBox>
<p>
<Counter>
<BigNumber>6</BigNumber>
<BigNumber data-loading-project-lifecycle-summary>
{data?.lifecycleSummary.completed.currentFlags ?? 0}
</BigNumber>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{
name: 'completed',
enteredStageAt: '',
environments: [],
status: 'kept',
}}
stage={{ name: 'completed' }}
/>
</Counter>
<span>
<LinkNoUnderline
to={`/projects/${projectId}/placeholder`}
>
flags
</LinkNoUnderline>{' '}
in cleanup
</span>
<span>flags in cleanup</span>
</p>
<Stats>
<dt>Avg. time in stage</dt>
<dd>
<NoData>No data</NoData>
</dd>
</Stats>
<AverageDaysStat
averageDays={data?.lifecycleSummary.completed.averageDays}
/>
</LifecycleBox>
<LifecycleBox>
<p>
<Counter>
<BigNumber>15</BigNumber>
<BigNumber data-loading-project-lifecycle-summary>
{data?.lifecycleSummary.archived.currentFlags ?? 0}
</BigNumber>
<FeatureLifecycleStageIcon
aria-hidden='true'
stage={{
name: 'archived',
enteredStageAt: '',
}}
stage={{ name: 'archived' }}
/>
</Counter>
<span>flags in archived</span>
</p>
<Stats>
<dt>This month</dt>
<dd>3 flags archived</dd>
<dd data-loading-project-lifecycle-summary>
{data?.lifecycleSummary.archived.currentFlags ?? 0}{' '}
flags archived
</dd>
</Stats>
</LifecycleBox>
</Wrapper>

View File

@ -1,9 +1,10 @@
import { createRef, useLayoutEffect } from 'react';
type refElement = HTMLDivElement;
const useLoading = (loading: boolean, selector = '[data-loading=true]') => {
const ref = createRef<refElement>();
const useLoading = <T extends HTMLElement = HTMLDivElement>(
loading: boolean,
selector = '[data-loading=true]',
) => {
const ref = createRef<T>();
useLayoutEffect(() => {
if (ref.current) {
const elements = ref.current.querySelectorAll(selector);