Skip to content

Commit

Permalink
chore: fix errors and add data owner on milestones (#808)
Browse files Browse the repository at this point in the history
Co-authored-by: Noggling <[email protected]>
  • Loading branch information
Noggling and Noggling authored Oct 16, 2024
1 parent a42d375 commit b3edf8a
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 176 deletions.
5 changes: 5 additions & 0 deletions .changeset/pr-808-2100756531.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

---
"fusion-project-portal": patch
---
Fix errors and add data owner on milestones
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"@equinor/fusion-framework-react-module-signalr": "^2.0.18",
"@equinor/fusion-observable": "^8.1.5",
"@equinor/fusion-react-context-selector": "^0.6.0",
"@equinor/fusion-react-person": "^0.7.0",
"@equinor/fusion-react-person": "^0.9.2",
"@equinor/fusion-react-side-sheet": "1.3.0",
"@equinor/fusion-react-skeleton": "^0.3.0",
"@equinor/fusion-react-styles": "^0.6.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const useUpdateUserRoleQuery = (roles?: Role[], userId?: string) => {
const data = await (
await client
).json<PersonRole>(`/persons/${userId}/roles/${roleName}`, {
method: 'Patch',
method: 'PATCH',
body: JSON.stringify({ isActive }),
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export const LoadingSkeleton = () => (
<Table.Cell>
<Skeleton variant={SkeletonVariant.Text} />
</Table.Cell>
<Table.Cell>
<Skeleton variant={SkeletonVariant.Text} />
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
Expand All @@ -30,6 +33,9 @@ export const LoadingSkeleton = () => (
<Table.Cell>
<Skeleton variant={SkeletonVariant.Text} />
</Table.Cell>
<Table.Cell>
<Skeleton variant={SkeletonVariant.Text} />
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
Expand All @@ -44,6 +50,9 @@ export const LoadingSkeleton = () => (
<Table.Cell>
<Skeleton variant={SkeletonVariant.Text} />
</Table.Cell>
<Table.Cell>
<Skeleton variant={SkeletonVariant.Text} />
</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell>
Expand All @@ -58,6 +67,9 @@ export const LoadingSkeleton = () => (
<Table.Cell>
<Skeleton variant={SkeletonVariant.Text} />
</Table.Cell>
<Table.Cell>
<Skeleton variant={SkeletonVariant.Text} />
</Table.Cell>
</Table.Row>
</>
);
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EdsProvider, Table, Typography } from '@equinor/eds-core-react';
import { Card, EdsProvider, Table, Typography } from '@equinor/eds-core-react';
import { DateTime } from 'luxon';
import { StyledCardWrapper, StyledContent, StyledHeader } from '../project-cards/styles';
import { LoadingSkeleton } from './LoadingSection';
Expand All @@ -7,29 +7,28 @@ import { css } from '@emotion/css';
import { Message } from '@portal/ui';
import { sortByDate, sortMilestones } from './utils';
import { useMemo } from 'react';
import { DataInfo } from '../../../sheared/components/data-info/DataInfo';
import styled from 'styled-components';

function verifyDate(date?: string | null): string {
return new Date(date || '').toString() !== 'Invalid Date'
? DateTime.fromJSDate(new Date(date || '')).toFormat('dd LLL yyyy')
: '-';
}

const styles = {
fullWidth: css`
width: 100%;
`,
noContent: css`
width: 100%;
height: 200px;
display: flex;
justify-content: center;
const Styled = {
Wrapper: styled.div`
max-height: 350px;
overflow: auto;
display: 'grid';
`,
noWrap: css`
width: 1000px;
NoWrap: styled(Table.Cell)`
overflow: hidden;
text-overflow: ellipsis;
`,
table: css`
Table: styled(Table)`
width: 100%;
white-space: nowrap;
min-width: fit-content;
table-layout: fixed;
Expand All @@ -39,69 +38,85 @@ const styles = {
export const Milestones = () => {
const { data, isLoading, error } = useMilestoneQuery();

const componentError = error as Error | undefined;

const milestones = useMemo(() => {
return data?.sort(sortMilestones).sort(sortByDate) || [];
}, [data]);

return (
<StyledCardWrapper elevation="raised">
<StyledHeader>
<Card elevation="raised">
<Card.Header>
<Typography variant="h5">Milestones</Typography>
</StyledHeader>
{componentError ? (
componentError.message.toLowerCase() === 'No Access'.toLowerCase() ? (
<Message type="Warning" title="You don't have access to see project data."></Message>
<DataInfo
title="Milestones"
azureUniqueId="9dfcc1c8-1b9c-4b53-8325-6b2a7786dfaf"
access="Internal"
dataSource="ProCoSys"
/>
</Card.Header>
{error ? (
error.error.code === 'NoDataAccess' ? (
<Message
type="Warning"
title={error.error.message}
messages={
error.error?.accessRequirements
? error.error.accessRequirements.map((a) => `${a.code} - ${a.description}`)
: []
}
></Message>
) : error.error.exceptionType === 'NotFoundError' ? (
<Message type="Error" title={error.title} messages={[error.detail]}></Message>
) : (
<Message type="Error" title="Error">
{componentError.message}
</Message>
<Message type="Error" title={error.message}></Message>
)
) : (
<StyledContent>
<Card.Content>
<EdsProvider density="compact">
<Table className={(styles.fullWidth, styles.table)}>
<Table.Head>
<Table.Row>
<Table.Cell>Milestone</Table.Cell>
<Table.Cell>Description</Table.Cell>
<Table.Cell>Planned</Table.Cell>
<Table.Cell>Forecast</Table.Cell>
</Table.Row>
</Table.Head>
<Table.Body>
{isLoading ? (
<LoadingSkeleton />
) : milestones.length > 0 ? (
milestones.map((milestone) => {
const datePlanned = verifyDate(milestone.datePlanned);
const dateForecast = verifyDate(milestone.dateForecast);
return (
<Table.Row key={milestone.milestone}>
<Table.Cell title={milestone.milestone}>
{milestone.milestone}
</Table.Cell>
<Table.Cell className={styles.noWrap} title={milestone.description}>
{milestone.description}
</Table.Cell>
<Table.Cell title={datePlanned}>{datePlanned}</Table.Cell>
<Table.Cell title={dateForecast}>{dateForecast}</Table.Cell>
</Table.Row>
);
})
) : (
<div className={styles.noContent}>
<Message type="NoContent" title="No content">
There are no milestones awaitable
</Message>
</div>
)}
</Table.Body>
</Table>
<Styled.Wrapper>
<Styled.Table>
<Table.Head sticky>
<Table.Row>
<Table.Cell width={150}>Milestone</Table.Cell>
<Table.Cell>Description</Table.Cell>
<Table.Cell width={120}>Planned</Table.Cell>
<Table.Cell width={120}>Forecast</Table.Cell>
<Table.Cell width={120}>Actual</Table.Cell>
</Table.Row>
</Table.Head>
<Table.Body>
{isLoading ? (
<LoadingSkeleton />
) : milestones.length > 0 ? (
milestones.map((milestone) => {
const datePlanned = verifyDate(milestone.datePlanned);
const dateForecast = verifyDate(milestone.dateForecast);
const dateActual = verifyDate(milestone.dateActual);
return (
<Table.Row key={milestone.milestone}>
<Styled.NoWrap title={milestone.milestone}>
{milestone.milestone}
</Styled.NoWrap>
<Styled.NoWrap title={milestone.description}>
{milestone.description}
</Styled.NoWrap>
<Table.Cell title={datePlanned}>{datePlanned}</Table.Cell>
<Table.Cell title={dateForecast}>{dateForecast}</Table.Cell>
<Table.Cell title={dateActual}>{dateActual}</Table.Cell>
</Table.Row>
);
})
) : (
<Message
type="NoContent"
title="No content - There are no milestones awaitable"
></Message>
)}
</Table.Body>
</Styled.Table>
</Styled.Wrapper>
</EdsProvider>
</StyledContent>
</Card.Content>
)}
</StyledCardWrapper>
</Card>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,21 @@ export type Milestones = {
contractMilestone: string;
};

type MilestoneError = {
error: {
message: string;
code: string;
exceptionType?: string;
accessRequirements: {
code: string;
description: string;
}[];
};
title: string;
detail: string;
message: string;
};

export async function getMilestones(
client: IHttpClient,
contextId?: string,
Expand All @@ -23,10 +38,16 @@ export async function getMilestones(

const res = await client.fetch(`/api/contexts/${contextId}/milestones`, { signal });

if (res.status === 403) throw new Error('No access');
if (res.status === 403) {
const error = (await res.json()) as MilestoneError;
throw error;
}
if (res.status === 404) {
const error = (await res.json()) as MilestoneError;
throw error;
}

const data = (await res.json()) as Milestones[];

if (res.status === 400 && (data as unknown as { detail: string }).detail.includes('Forbidden')) {
throw new Error('No access');
}
Expand All @@ -36,15 +57,18 @@ export async function getMilestones(
}

if (!res.ok) throw new Error('Unknown Error');

return data;
}

export const useMilestoneQuery = () => {
const client = useFramework().modules.serviceDiscovery.createClient('data-proxy');
const { currentContext } = useFramework().modules.context;
const contextId = currentContext?.id;
return useQuery({

return useQuery<Milestones[] | undefined, MilestoneError, Milestones[]>({
queryKey: ['milestones', contextId],
queryFn: async ({ signal }) => getMilestones(await client, contextId, signal),
enabled: Boolean(contextId),
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Icon, Popover, Typography } from '@equinor/eds-core-react';
import { info_circle, title } from '@equinor/eds-icons';
import { tokens } from '@equinor/eds-tokens';
import { PersonCell } from '@equinor/fusion-react-person';
import { useRef, useState } from 'react';
import styled from 'styled-components';

type DataInfoProps = {
title: string;
dataSource: string;
azureUniqueId?: string;
access: 'Internal' | 'External' | 'Restricted';
};

const Style = {
Content: styled.div`
display: flex;
flex-direction: column;
gap: 1rem;
min-width: 200px;
`,
};

export const DataInfo = ({ dataSource, azureUniqueId, access, title }: DataInfoProps) => {
const referenceElement = useRef<HTMLDivElement>(null);
const [isOpen, setIsOpen] = useState(false);

return (
<div
onMouseOver={() => {
setIsOpen(true);
}}
onMouseLeave={() => {
setIsOpen(false);
}}
ref={referenceElement}
>
<Icon data={info_circle} color={tokens.colors.text.static_icons__tertiary.hex} />
<Popover placement="bottom" open={isOpen} anchorEl={referenceElement.current}>
<Popover.Header>{title}</Popover.Header>
<Popover.Content>
<Style.Content>
<div>
<Typography variant="overline"> Data source:</Typography>
<Typography variant="body_short">
<b>{dataSource}</b>
</Typography>
</div>
<div>
<Typography variant="overline"> Access:</Typography>
<Typography variant="body_short">
<b>{access}</b>
</Typography>
</div>

{azureUniqueId && (
<>
<Typography variant="overline">Contact person</Typography>
<PersonCell azureId={azureUniqueId} showAvatar subHeading={(u) => u.mail} />
</>
)}
</Style.Content>
</Popover.Content>
</Popover>
</div>
);
};
Loading

0 comments on commit b3edf8a

Please sign in to comment.