Skip to content

Commit

Permalink
feat: 1529 spar service dependencies status page (#1539)
Browse files Browse the repository at this point in the history
  • Loading branch information
craigyu authored Aug 28, 2024
1 parent 0945692 commit 1a7cb24
Show file tree
Hide file tree
Showing 17 changed files with 543 additions and 63 deletions.
14 changes: 13 additions & 1 deletion frontend/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,19 @@
Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate"
X-Content-Type-Options "nosniff"
Strict-Transport-Security "max-age=31536000"
Content-Security-Policy "base-uri 'self'; connect-src 'self' https://*.gov.bc.ca {$VITE_AWS_DOMAIN} {$VITE_SERVER_URL} https://cognito-idp.ca-central-1.amazonaws.com/; default-src 'self'; font-src 'self' https://1.www.s81c.com/; frame-src 'self' https://*.gov.bc.ca; img-src 'self'; manifest-src 'self'; media-src 'self'; object-src 'none'; script-src 'unsafe-inline' 'report-sample' 'self' https://*.auth.ca-central-1.amazoncognito.com/ https://*.cloudfront.net; style-src 'report-sample' 'self'; worker-src 'none';"
Content-Security-Policy "
base-uri 'self';
connect-src 'self' https://*.gov.bc.ca {$VITE_AWS_DOMAIN} {$VITE_SERVER_URL} https://cognito-idp.ca-central-1.amazonaws.com/ https://*.execute-api.ca-central-1.amazonaws.com;
default-src 'self';
font-src 'self' https://1.www.s81c.com/;
frame-src 'self' https://*.gov.bc.ca; img-src 'self';
manifest-src 'self';
media-src 'self';
object-src 'none';
script-src 'unsafe-inline' 'report-sample' 'self' https://*.auth.ca-central-1.amazoncognito.com/ https://*.cloudfront.net;
style-src 'report-sample' 'self';
worker-src 'none';
"
Referrer-Policy "same-origin"
}

Expand Down
84 changes: 49 additions & 35 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useContext, useEffect } from 'react';
import {
createBrowserRouter, RouterProvider
createBrowserRouter, RouteObject, RouterProvider
} from 'react-router-dom';
import { Amplify } from 'aws-amplify';
import { ClassPrefix } from '@carbon/react';
Expand All @@ -23,32 +23,57 @@ import FourOhFour from './views/FourOhFour';
import ProtectedRoute from './routes/ProtectedRoute';
import { ThemePreference } from './utils/ThemePreference';
import LoginOrgSelection from './views/LoginOrgSelection';
import ServiceStatus from './views/ServiceStatus';

Amplify.configure(awsconfig);

const HTTP_STATUS_TO_NOT_RETRY = [400, 401, 403, 404];
const MAX_RETRIES = 3;

const queryClient = new QueryClient(
{
defaultOptions: {
queries: {
refetchOnMount: false,
refetchOnWindowFocus: false,
// Do not retry on errors defined above
retry: (failureCount, error) => {
if (failureCount > MAX_RETRIES) {
return false;
}
if (isAxiosError(error)) {
const status = error.response?.status;
if (status && HTTP_STATUS_TO_NOT_RETRY.includes(status)) {
return false;
}
}
return true;
}
}
}
}
);

/**
* Create an app structure containing all the routes.
*
* @returns {JSX.Element} instance of the app ready to use.
*/
const App: React.FC = () => {
const HTTP_STATUS_TO_NOT_RETRY = [400, 401, 403, 404];
const MAX_RETRIES = 3;

const { signed, isCurrentAuthUser, selectedClientRoles } = useContext(AuthContext);

useEffect(() => {
isCurrentAuthUser(window.location.pathname);
}, []);

const roleSelectionRouter = createBrowserRouter([
const roleSelectionRoutes: RouteObject[] = [
{
path: ROUTES.ALL_ROUTES,
element: <LoginOrgSelection />
}
]);
];

const signedRouter = createBrowserRouter([
const signedRoutes: RouteObject[] = [
{
path: ROUTES.ROOT,
element: <ProtectedRoute />,
Expand All @@ -63,47 +88,36 @@ const App: React.FC = () => {
path: ROUTES.ALL_ROUTES,
element: <FourOhFour />
}
]);
];

const notSignedRouter = createBrowserRouter([
const notSignedRoutes: RouteObject[] = [
{
path: ROUTES.ALL_ROUTES,
element: <Landing />
}
]);
];

const queryClient = new QueryClient(
const sharedRoutes: RouteObject[] = [
{
defaultOptions: {
queries: {
refetchOnMount: false,
refetchOnWindowFocus: false,
// Do not retry on errors defined above
retry: (failureCount, error) => {
if (failureCount > MAX_RETRIES) {
return false;
}
if (isAxiosError(error)) {
const status = error.response?.status;
if (status && HTTP_STATUS_TO_NOT_RETRY.includes(status)) {
return false;
}
}
return true;
}
}
}
path: ROUTES.SERVICE_STATUS,
element: <ServiceStatus />
}
);
];

const getBrowserRouter = () => {
const selectBrowserRoutes = () => {
if (!signed) {
return notSignedRouter;
return notSignedRoutes;
}
if (selectedClientRoles) {
return signedRouter;
return signedRoutes;
}
return roleSelectionRouter;
return roleSelectionRoutes;
};

const getBrowserRouter = () => {
const selectedRoutes = selectBrowserRoutes();
selectedRoutes.push(...sharedRoutes);
return createBrowserRouter(selectedRoutes);
};

return (
Expand Down
18 changes: 12 additions & 6 deletions frontend/src/components/BCHeader/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,24 @@ export const navItems = [
// }
];

export const supportItems = {
name: 'Support',
items: [
// Uncomment these as necessary :)
// export const supportItems = {
// name: 'Support',
// items: [
// {
// name: 'Need help?',
// icon: 'Help',
// link: '#',
// disabled: true
// }
// ]
// };
// },
{
name: 'Service status',
icon: 'CloudMonitoring',
link: '/service-status',
disabled: false
}
]
};

export const componentTexts = {
headerTitle: 'SPAR',
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/BCHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ const BCHeader = () => {
category.items.map((navItem) => (
<SideNavLink
key={navItem.name}
className={navItem.disabled ? 'disabled-side-nav-option' : ''}
className={navItem.disabled ? 'disabled-side-nav-option' : 'side-nav-option'}
renderIcon={Icons[navItem.icon]}
isActive={window.location.pathname.includes(navItem.link)}
onClick={navItem.disabled ? null : () => navigate(navItem.link)}
Expand All @@ -206,7 +206,7 @@ const BCHeader = () => {
<SideNavLink
key={supportItem.name}
renderIcon={Icons[supportItem.icon]}
className={supportItem.disabled ? 'disabled-side-nav-option' : ''}
className={supportItem.disabled ? 'disabled-side-nav-option' : 'side-nav-option'}
onClick={supportItem.disabled ? null : () => navigate(supportItem.link)}
>
{supportItem.name}
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/components/BCHeader/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@
font-weight: bold;
}

.side-nav-option{
cursor: pointer;
}

.disabled-side-nav-option {
cursor: not-allowed;

Expand Down
72 changes: 72 additions & 0 deletions frontend/src/components/ServiceStatusCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import React from 'react';
import { Row, Column, Loading } from '@carbon/react';
import { useQueryClient } from '@tanstack/react-query';
import * as Icons from '@carbon/icons-react';

import { DependencyDefinition } from '../../views/ServiceStatus/definitions';

import './styles.scss';

type ServiceStatusCardProps = {
dependencyObj: DependencyDefinition
}

const ServiceStatusCard = ({ dependencyObj } : ServiceStatusCardProps) => {
const qc = useQueryClient();

const { CheckmarkFilled, ErrorFilled } = Icons;
const Icon = Icons[dependencyObj.icon];

return (
<Row className="service-status-row">
<Column className="service-status-col">
<div className="top-section">
<div className="title-and-icon">
<Icon
className={
qc.getQueryState([dependencyObj.queryKey])?.status === 'error' ? 'dep-icon-error' : 'dep-icon'
}
/>
<h4>
{dependencyObj.name}
</h4>
</div>

<div>
{
qc.getQueryState([dependencyObj.queryKey])?.status === 'success'
&& qc.getQueryState([dependencyObj.queryKey])?.fetchStatus !== 'fetching'
? <CheckmarkFilled className="success-svg" />
: null
}
{
qc.getQueryState([dependencyObj.queryKey])?.fetchStatus === 'fetching'
? <Loading small withOverlay={false} />
: null
}
{
qc.getQueryState([dependencyObj.queryKey])?.status === 'error'
&& qc.getQueryState([dependencyObj.queryKey])?.fetchStatus !== 'fetching'
? <ErrorFilled className="error-svg" />
: null
}
</div>
</div>
<div>
{
qc.getQueryState([dependencyObj.queryKey])?.status === 'success'
? 'Normal'
: null
}
{
qc.getQueryState([dependencyObj.queryKey])?.status === 'error'
? 'Down'
: null
}
</div>
</Column>
</Row>
);
};

export default ServiceStatusCard;
61 changes: 61 additions & 0 deletions frontend/src/components/ServiceStatusCard/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
@use '@bcgov-nr/nr-theme/design-tokens/colors.scss' as colors;
@use '@bcgov-nr/nr-theme/design-tokens/variables.scss' as vars;

.service-status-row {
padding-top: 2rem;
margin: 0 0.5rem;

.service-status-col {
display: flex;
flex-direction: column;
min-height: 6.5rem;
justify-content: space-between;
padding: 1.2rem 2rem;
border: 1px solid rgba(0, 0, 0, 0.196);
border-radius: 0.5rem;

.top-section {
display: flex;
flex-direction: row;
justify-content: space-between;

.title-and-icon {
display: flex;
flex-direction: row;

h4 {
font-weight: bold;
}

.dep-icon,
.dep-icon-error {
min-width: 2rem;
min-height: 2rem;
margin-right: 1rem;
}

.dep-icon {
fill: colors.$blue-60;
}

.dep-icon-error {
fill: colors.$red-70;
}
}


}

.success-svg {
min-width: 1.5rem;
min-height: 1.5rem;
fill: colors.$green-60;
}

.error-svg {
min-width: 1.5rem;
min-height: 1.5rem;
fill: colors.$red-60;
}
}
}
7 changes: 7 additions & 0 deletions frontend/src/config/TimeUnits.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ export const SEVEN_SECONDS = 7 * ONE_SECOND;

export const TEN_SECONDS = 10 * ONE_SECOND;

export const FIFTEEN_SECONDS = 15 * ONE_SECOND;

/**
* Time needed to go to Mars.
*/
export const THIRTY_SECONDS = 30 * ONE_SECOND;

export const ONE_MINUTE = 60 * ONE_SECOND;

export const TWO_MINUTE = 2 * ONE_MINUTE;
Expand Down
Loading

0 comments on commit 1a7cb24

Please sign in to comment.