Skip to content

Commit

Permalink
[wip] add data to ui (#8710)
Browse files Browse the repository at this point in the history
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.
#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.
  • Loading branch information
thomasheartman authored Nov 12, 2024
1 parent 24a30e5 commit 3bc9fe9
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 />;
Expand Down
Original file line number Diff line number Diff line change
@@ -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 }) => ({
Expand Down Expand Up @@ -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',
});
Expand All @@ -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>
Expand Down
9 changes: 5 additions & 4 deletions frontend/src/hooks/useLoading.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down

0 comments on commit 3bc9fe9

Please sign in to comment.