Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: fix errors and add data owner on milestones #808

Merged
merged 2 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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