From 7df3b2736b4d9df9714d485eda33d462b5f5b4c7 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Tue, 19 Nov 2024 09:33:22 +0000 Subject: [PATCH 01/17] add alert to the create workspace page No logic or api interaction yet. Just checking presentation and enumerating error cases. --- .../CreateWorkspacePageView.tsx | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 1c158b8225e2f..baab6fd01b077 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -305,6 +305,29 @@ export const CreateWorkspacePageView: FC = ({ )} + + + {/* TODO (SasSwart): + Is this a scenario that can occur for free-tier users? They have a default organisation and no external provisioners as far as I know. + Nothing stops them from using a template with tags defined. Will those tags interfere with job selection on internal provisioners? + */} + {/* TODO (SasSwart): + There are multiple error scenarios here. Do they each need specific copy, or is a general message fine? + * Could there be no provisioners whatsoever, or do we always expect at least the internal provisioners to run? + * There may be provisioners, but none with the requisite tags. + * There may be provisioners with the requisite tags, but they may not have been seen by coderd for more an unacceptable duration + and therefore be considered stale. + * There may be provisioners with the requisite tags that have been recently seen and are actively processing jobs, but what if the queue for jobs is long? + Should we warn about expected delays? + */} + {/* TODO (SasSwart): Considering the above, do we want to keep the alert simple here, but provide a link to the provisioner list page and show alerts there? */} + {/* TODO (SasSwart): Do we need a stuck jobs page which lists the jobs queue with an alert for why each may be stuck? */} + + This organization does not have any provisioners compatible with this workspace. Before you create a template, you'll need to configure a provisioner. + + + + Date: Tue, 19 Nov 2024 09:48:25 +0000 Subject: [PATCH 02/17] improve error case documentation --- .../pages/CreateWorkspacePage/CreateWorkspacePageView.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index baab6fd01b077..766542cbedc44 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -307,12 +307,11 @@ export const CreateWorkspacePageView: FC = ({ - {/* TODO (SasSwart): - Is this a scenario that can occur for free-tier users? They have a default organisation and no external provisioners as far as I know. - Nothing stops them from using a template with tags defined. Will those tags interfere with job selection on internal provisioners? - */} {/* TODO (SasSwart): There are multiple error scenarios here. Do they each need specific copy, or is a general message fine? + * If a free tier user with no organisations or external provisioners uses a template which requires tags: + * can they provide tags to the internal provisioners to accept the job? + * If not, the alert copy below will be confusing, because they don't use the organisations feature and we mention it. * Could there be no provisioners whatsoever, or do we always expect at least the internal provisioners to run? * There may be provisioners, but none with the requisite tags. * There may be provisioners with the requisite tags, but they may not have been seen by coderd for more an unacceptable duration From 0f78afc32ddfd9ef60b1ca34f49f70414543ada9 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Wed, 20 Nov 2024 20:40:09 +0000 Subject: [PATCH 03/17] add provisioner health hook --- site/src/api/api.ts | 10 ++++- .../provisioners/useCompatibleProvisioners.ts | 29 ++++++++++++++ .../CreateTemplatePage/BuildLogsDrawer.tsx | 38 +++++++++++++++++++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 site/src/modules/provisioners/useCompatibleProvisioners.ts diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 3ad195f2bd9e4..7b509d3382f61 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -682,12 +682,20 @@ class ApiMethods { /** * @param organization Can be the organization's ID or name + * @param tags to filter provisioner daemons by. */ getProvisionerDaemonsByOrganization = async ( organization: string, + tags?: Record ): Promise => { + const params = new URLSearchParams(); + + if (tags) { + params.append('tags', encodeURIComponent(JSON.stringify(tags))); + } + const response = await this.axios.get( - `/api/v2/organizations/${organization}/provisionerdaemons`, + `/api/v2/organizations/${organization}/provisionerdaemons?${params.toString()}` ); return response.data; }; diff --git a/site/src/modules/provisioners/useCompatibleProvisioners.ts b/site/src/modules/provisioners/useCompatibleProvisioners.ts new file mode 100644 index 0000000000000..1693890742034 --- /dev/null +++ b/site/src/modules/provisioners/useCompatibleProvisioners.ts @@ -0,0 +1,29 @@ +import { API } from "api/api"; +import { ProvisionerDaemon } from "api/typesGenerated"; +import { useEffect, useState } from "react"; + +export const useCompatibleProvisioners = (organization: string | undefined, tags: Record | undefined) => { + const [compatibleProvisioners, setCompatibleProvisioners] = useState([]) + + useEffect(() => { + (async () => { + if (!organization) { + setCompatibleProvisioners([]) + return + } + + try { + const provisioners = await API.getProvisionerDaemonsByOrganization( + organization, + tags, + ); + + setCompatibleProvisioners(provisioners); + } catch (error) { + setCompatibleProvisioners([]) + } + })(); + }, [organization, tags]) + + return compatibleProvisioners +} diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx index 5af38b649c695..b6dec43fdab89 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx @@ -12,6 +12,7 @@ import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs"; import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs"; import { type FC, useLayoutEffect, useRef } from "react"; import { navHeight } from "theme/constants"; +import { useCompatibleProvisioners } from "modules/provisioners/useCompatibleProvisioners"; type BuildLogsDrawerProps = { error: unknown; @@ -27,6 +28,31 @@ export const BuildLogsDrawer: FC = ({ variablesSectionRef, ...drawerProps }) => { + const compatibleProvisioners = useCompatibleProvisioners( + templateVersion?.organization_id, + templateVersion?.job.tags + ); + const compatibleProvisionersUnhealthy = compatibleProvisioners.reduce((allUnhealthy, provisioner) => { + if (!allUnhealthy) { + // If we've found one healthy provisioner, then we don't need to look at the rest + return allUnhealthy; + } + // Otherwise, all provisioners so far have been unhealthy, so we check the next one + + // If a provisioner has no last_seen_at value, then it's considered unhealthy + if (!provisioner.last_seen_at) { + return allUnhealthy; + } + + // If a provisioner has not been seen within the last 60 seconds, then it's considered unhealthy + const lastSeen = new Date(provisioner.last_seen_at); + const oneMinuteAgo = new Date(Date.now() - 60000); + const unhealthy = lastSeen < oneMinuteAgo; + + + return allUnhealthy && unhealthy; + }, true); + const logs = useWatchVersionLogs(templateVersion); const logsContainer = useRef(null); @@ -65,6 +91,18 @@ export const BuildLogsDrawer: FC = ({ + { !compatibleProvisioners && !logs ? ( + // If there are no compatible provisioners, warn that this job may be stuck + <>No compatible provisioners + ) : compatibleProvisionersUnhealthy && !logs ? ( + // If there are compatible provisioners in the db, but they have not reported recent health checks, + // warn that the job might be stuck + <>Compatible provisioners are potentially unhealthy. Your job might be delayed + ) : ( + // If there are compatible provisioners and at least one was recently seen, no warning is necessary. + <> + )} + {isMissingVariables ? ( { From f0f72167c2299a1a10112ec06e36bda95bf67f8b Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 21 Nov 2024 05:23:15 +0000 Subject: [PATCH 04/17] show provisioner health warnings for templates --- .../provisioners/useCompatibleProvisioners.ts | 23 +++++++++++++++ .../CreateTemplatePage/BuildLogsDrawer.tsx | 28 ++----------------- .../CreateWorkspacePageView.tsx | 22 --------------- .../TemplateVersionEditor.tsx | 24 +++++++++++++++- 4 files changed, 49 insertions(+), 48 deletions(-) diff --git a/site/src/modules/provisioners/useCompatibleProvisioners.ts b/site/src/modules/provisioners/useCompatibleProvisioners.ts index 1693890742034..0b8a7cb18f77e 100644 --- a/site/src/modules/provisioners/useCompatibleProvisioners.ts +++ b/site/src/modules/provisioners/useCompatibleProvisioners.ts @@ -27,3 +27,26 @@ export const useCompatibleProvisioners = (organization: string | undefined, tags return compatibleProvisioners } + +export const provisionersUnhealthy = (provisioners : ProvisionerDaemon[]) => { + return provisioners.reduce((allUnhealthy, provisioner) => { + if (!allUnhealthy) { + // If we've found one healthy provisioner, then we don't need to look at the rest + return allUnhealthy; + } + // Otherwise, all provisioners so far have been unhealthy, so we check the next one + + // If a provisioner has no last_seen_at value, then it's considered unhealthy + if (!provisioner.last_seen_at) { + return allUnhealthy; + } + + // If a provisioner has not been seen within the last 60 seconds, then it's considered unhealthy + const lastSeen = new Date(provisioner.last_seen_at); + const oneMinuteAgo = new Date(Date.now() - 60000); + const unhealthy = lastSeen < oneMinuteAgo; + + + return allUnhealthy && unhealthy; + }, true); +} diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx index b6dec43fdab89..725a57e791600 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx @@ -12,7 +12,7 @@ import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs"; import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs"; import { type FC, useLayoutEffect, useRef } from "react"; import { navHeight } from "theme/constants"; -import { useCompatibleProvisioners } from "modules/provisioners/useCompatibleProvisioners"; +import { provisionersUnhealthy, useCompatibleProvisioners } from "modules/provisioners/useCompatibleProvisioners"; type BuildLogsDrawerProps = { error: unknown; @@ -32,26 +32,7 @@ export const BuildLogsDrawer: FC = ({ templateVersion?.organization_id, templateVersion?.job.tags ); - const compatibleProvisionersUnhealthy = compatibleProvisioners.reduce((allUnhealthy, provisioner) => { - if (!allUnhealthy) { - // If we've found one healthy provisioner, then we don't need to look at the rest - return allUnhealthy; - } - // Otherwise, all provisioners so far have been unhealthy, so we check the next one - - // If a provisioner has no last_seen_at value, then it's considered unhealthy - if (!provisioner.last_seen_at) { - return allUnhealthy; - } - - // If a provisioner has not been seen within the last 60 seconds, then it's considered unhealthy - const lastSeen = new Date(provisioner.last_seen_at); - const oneMinuteAgo = new Date(Date.now() - 60000); - const unhealthy = lastSeen < oneMinuteAgo; - - - return allUnhealthy && unhealthy; - }, true); + const compatibleProvisionersUnhealthy = provisionersUnhealthy(compatibleProvisioners); const logs = useWatchVersionLogs(templateVersion); const logsContainer = useRef(null); @@ -94,13 +75,10 @@ export const BuildLogsDrawer: FC = ({ { !compatibleProvisioners && !logs ? ( // If there are no compatible provisioners, warn that this job may be stuck <>No compatible provisioners - ) : compatibleProvisionersUnhealthy && !logs ? ( + ) : compatibleProvisionersUnhealthy && !logs && ( // If there are compatible provisioners in the db, but they have not reported recent health checks, // warn that the job might be stuck <>Compatible provisioners are potentially unhealthy. Your job might be delayed - ) : ( - // If there are compatible provisioners and at least one was recently seen, no warning is necessary. - <> )} {isMissingVariables ? ( diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx index 766542cbedc44..1c158b8225e2f 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageView.tsx @@ -305,28 +305,6 @@ export const CreateWorkspacePageView: FC = ({ )} - - - {/* TODO (SasSwart): - There are multiple error scenarios here. Do they each need specific copy, or is a general message fine? - * If a free tier user with no organisations or external provisioners uses a template which requires tags: - * can they provide tags to the internal provisioners to accept the job? - * If not, the alert copy below will be confusing, because they don't use the organisations feature and we mention it. - * Could there be no provisioners whatsoever, or do we always expect at least the internal provisioners to run? - * There may be provisioners, but none with the requisite tags. - * There may be provisioners with the requisite tags, but they may not have been seen by coderd for more an unacceptable duration - and therefore be considered stale. - * There may be provisioners with the requisite tags that have been recently seen and are actively processing jobs, but what if the queue for jobs is long? - Should we warn about expected delays? - */} - {/* TODO (SasSwart): Considering the above, do we want to keep the alert simple here, but provide a link to the provisioner list page and show alerts there? */} - {/* TODO (SasSwart): Do we need a stuck jobs page which lists the jobs queue with an alert for why each may be stuck? */} - - This organization does not have any provisioners compatible with this workspace. Before you create a template, you'll need to configure a provisioner. - - - - = ({ const [renameFileOpen, setRenameFileOpen] = useState(); const [dirty, setDirty] = useState(false); + const compatibleProvisioners = useCompatibleProvisioners( + templateVersion?.organization_id, + templateVersion?.job.tags + ); + const compatibleProvisionersUnhealthy = provisionersUnhealthy(compatibleProvisioners); + const triggerPreview = useCallback(async () => { await onPreview(fileTree); setSelectedTab("logs"); @@ -581,7 +588,7 @@ export const TemplateVersionEditor: FC = ({ css={[styles.logs, styles.tabContent]} ref={logsContentRef} > - {templateVersion.job.error && ( + {templateVersion.job.error ? (
= ({ {templateVersion.job.error}
+ ) : compatibleProvisionersUnhealthy && ( +
+ + Build may be delayed + No Compatible Provisioner Daemons have been recently seen + +
)} {buildLogs && buildLogs.length > 0 ? ( From a3eeb9c52d4ce16e8135dcbc1885b489327fe162 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 21 Nov 2024 05:40:14 +0000 Subject: [PATCH 05/17] consistent formatting for alerts across pages --- .../CreateTemplatePage/BuildLogsDrawer.tsx | 28 +++++++++++++++++-- .../TemplateVersionEditor.tsx | 15 +++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx index 725a57e791600..4591e7d8be000 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx @@ -13,6 +13,8 @@ import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/Worksp import { type FC, useLayoutEffect, useRef } from "react"; import { navHeight } from "theme/constants"; import { provisionersUnhealthy, useCompatibleProvisioners } from "modules/provisioners/useCompatibleProvisioners"; +import { Alert, AlertTitle } from "@mui/material"; +import { AlertDetail } from "components/Alert/Alert"; type BuildLogsDrawerProps = { error: unknown; @@ -74,11 +76,33 @@ export const BuildLogsDrawer: FC = ({ { !compatibleProvisioners && !logs ? ( // If there are no compatible provisioners, warn that this job may be stuck - <>No compatible provisioners + + Build stuck + No Compatible Provisioner Daemons have been configured + ) : compatibleProvisionersUnhealthy && !logs && ( // If there are compatible provisioners in the db, but they have not reported recent health checks, // warn that the job might be stuck - <>Compatible provisioners are potentially unhealthy. Your job might be delayed + + Build may be delayed + Compatible Provisioner Daemons have been silent for a while. This may result in a delayed build + )} {isMissingVariables ? ( diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 0d91c1abf62b8..8a2f98263c46d 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -603,6 +603,19 @@ export const TemplateVersionEditor: FC = ({ {templateVersion.job.error} + ) : !compatibleProvisioners ? ( + + Build stuck + No Compatible Provisioner Daemons have been configured + ) : compatibleProvisionersUnhealthy && (
= ({ }} > Build may be delayed - No Compatible Provisioner Daemons have been recently seen + Compatible Provisioner Daemons have been silent for a while. This may result in a delayed build
)} From 3cf53ef367a097226f4698c8785532a79f95d00c Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Fri, 22 Nov 2024 13:31:16 +0000 Subject: [PATCH 06/17] Finalise Provisioner Warnings for Templates and template versions --- site/src/api/queries/organizations.ts | 9 +- .../modules/provisioners/ProvisionerAlert.tsx | 30 ++++++ .../provisioners/useCompatibleProvisioners.ts | 28 ------ .../BuildLogsDrawer.stories.tsx | 47 ++++++++- .../CreateTemplatePage/BuildLogsDrawer.tsx | 70 +++++++------ .../TemplateVersionEditor.stories.tsx | 97 ++++++++++++++++++- .../TemplateVersionEditor.tsx | 86 ++++++++-------- site/src/testHelpers/storybook.tsx | 20 ++++ 8 files changed, 270 insertions(+), 117 deletions(-) create mode 100644 site/src/modules/provisioners/ProvisionerAlert.tsx diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index d1df8f409dcdf..7e36b48fdb33d 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -115,16 +115,17 @@ export const organizations = () => { }; }; -export const getProvisionerDaemonsKey = (organization: string) => [ +export const getProvisionerDaemonsKey = (organization: string, tags?: Record) => [ "organization", organization, + tags, "provisionerDaemons", ]; -export const provisionerDaemons = (organization: string) => { +export const provisionerDaemons = (organization: string, tags?: Record) => { return { - queryKey: getProvisionerDaemonsKey(organization), - queryFn: () => API.getProvisionerDaemonsByOrganization(organization), + queryKey: getProvisionerDaemonsKey(organization, tags), + queryFn: () => API.getProvisionerDaemonsByOrganization(organization, tags), }; }; diff --git a/site/src/modules/provisioners/ProvisionerAlert.tsx b/site/src/modules/provisioners/ProvisionerAlert.tsx new file mode 100644 index 0000000000000..c6c9ec1a13bf1 --- /dev/null +++ b/site/src/modules/provisioners/ProvisionerAlert.tsx @@ -0,0 +1,30 @@ +import { Alert, AlertColor, AlertTitle } from "@mui/material"; +import { AlertDetail } from "components/Alert/Alert"; +import { type FC } from "react"; + +type ProvisionerAlertProps = { + title: string, + detail: string, + severity: AlertColor, +} + +export const ProvisionerAlert : FC = ({ + title, + detail, + severity, +}) => { + return ( + ({ + borderRadius: 0, + border: 0, + borderBottom: `1px solid ${theme.palette.divider}`, + borderLeft: `2px solid ${theme.palette.error.main}`, + })} + > + {title} + {detail} + + ); +}; diff --git a/site/src/modules/provisioners/useCompatibleProvisioners.ts b/site/src/modules/provisioners/useCompatibleProvisioners.ts index 0b8a7cb18f77e..568eac9b1f628 100644 --- a/site/src/modules/provisioners/useCompatibleProvisioners.ts +++ b/site/src/modules/provisioners/useCompatibleProvisioners.ts @@ -1,32 +1,4 @@ -import { API } from "api/api"; import { ProvisionerDaemon } from "api/typesGenerated"; -import { useEffect, useState } from "react"; - -export const useCompatibleProvisioners = (organization: string | undefined, tags: Record | undefined) => { - const [compatibleProvisioners, setCompatibleProvisioners] = useState([]) - - useEffect(() => { - (async () => { - if (!organization) { - setCompatibleProvisioners([]) - return - } - - try { - const provisioners = await API.getProvisionerDaemonsByOrganization( - organization, - tags, - ); - - setCompatibleProvisioners(provisioners); - } catch (error) { - setCompatibleProvisioners([]) - } - })(); - }, [organization, tags]) - - return compatibleProvisioners -} export const provisionersUnhealthy = (provisioners : ProvisionerDaemon[]) => { return provisioners.reduce((allUnhealthy, provisioner) => { diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx index afc3c1321a6b4..b2981757a659d 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx @@ -5,7 +5,7 @@ import { MockTemplateVersion, MockWorkspaceBuildLogs, } from "testHelpers/entities"; -import { withWebSocket } from "testHelpers/storybook"; +import { withProvisioners, withWebSocket } from "testHelpers/storybook"; import { BuildLogsDrawer } from "./BuildLogsDrawer"; const meta: Meta = { @@ -34,6 +34,51 @@ export const MissingVariables: Story = { }, }; +export const NoProvisioners: Story = { + args: { + templateVersion: {...MockTemplateVersion, organization_id: "org-id"}, + }, + decorators: [withProvisioners], + parameters: { + organization_id: "org-id", + tags: MockTemplateVersion.job.tags, + provisioners: [], + } +}; + +export const ProvisionersUnhealthy: Story = { + args: { + templateVersion: {...MockTemplateVersion, organization_id: "org-id"}, + }, + decorators: [withProvisioners], + parameters: { + organization_id: "org-id", + tags: MockTemplateVersion.job.tags, + provisioners: [ + { + last_seen_at: new Date(new Date().getTime() - 5 * 60 * 1000).toISOString() + }, + ], + } +}; + +export const ProvisionersHealthy: Story = { + args: { + templateVersion: {...MockTemplateVersion, organization_id: "org-id"}, + }, + decorators: [withProvisioners], + parameters: { + organization_id: "org-id", + tags: MockTemplateVersion.job.tags, + provisioners: [ + { + last_seen_at: new Date() + }, + ], + } +}; + + export const Logs: Story = { args: { templateVersion: { diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx index 4591e7d8be000..5b2ef65ea8edb 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx @@ -1,4 +1,4 @@ -import type { Interpolation, Theme } from "@emotion/react"; +import { type Interpolation, type Theme } from "@emotion/react"; import Close from "@mui/icons-material/Close"; import WarningOutlined from "@mui/icons-material/WarningOutlined"; import Button from "@mui/material/Button"; @@ -12,9 +12,10 @@ import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs"; import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs"; import { type FC, useLayoutEffect, useRef } from "react"; import { navHeight } from "theme/constants"; -import { provisionersUnhealthy, useCompatibleProvisioners } from "modules/provisioners/useCompatibleProvisioners"; -import { Alert, AlertTitle } from "@mui/material"; -import { AlertDetail } from "components/Alert/Alert"; +import { provisionersUnhealthy } from "modules/provisioners/useCompatibleProvisioners"; +import { useQuery } from "react-query"; +import { provisionerDaemons } from "api/queries/organizations"; +import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; type BuildLogsDrawerProps = { error: unknown; @@ -30,11 +31,15 @@ export const BuildLogsDrawer: FC = ({ variablesSectionRef, ...drawerProps }) => { - const compatibleProvisioners = useCompatibleProvisioners( - templateVersion?.organization_id, - templateVersion?.job.tags + const org = templateVersion?.organization_id + const { + data: compatibleProvisioners, + isLoading: provisionerDaemonsLoading, + isError: couldntGetProvisioners, + } = useQuery( + org ? provisionerDaemons(org, templateVersion?.job.tags) : { enabled: false} ); - const compatibleProvisionersUnhealthy = provisionersUnhealthy(compatibleProvisioners); + const compatibleProvisionersUnhealthy = !compatibleProvisioners || provisionersUnhealthy(compatibleProvisioners); const logs = useWatchVersionLogs(templateVersion); const logsContainer = useRef(null); @@ -74,35 +79,26 @@ export const BuildLogsDrawer: FC = ({ - { !compatibleProvisioners && !logs ? ( - // If there are no compatible provisioners, warn that this job may be stuck - - Build stuck - No Compatible Provisioner Daemons have been configured - - ) : compatibleProvisionersUnhealthy && !logs && ( - // If there are compatible provisioners in the db, but they have not reported recent health checks, - // warn that the job might be stuck - - Build may be delayed - Compatible Provisioner Daemons have been silent for a while. This may result in a delayed build - + { !logs && !provisionerDaemonsLoading && ( + couldntGetProvisioners ? ( + + ) : (!compatibleProvisioners || compatibleProvisioners.length == 0) ? ( + + ) : compatibleProvisionersUnhealthy && ( + + ) )} {isMissingVariables ? ( diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx index 1382aa100a1dc..c5610dc389ee4 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx @@ -16,7 +16,7 @@ import { MockWorkspaceResourceSensitive, MockWorkspaceVolumeResource, } from "testHelpers/entities"; -import { withDashboardProvider } from "testHelpers/storybook"; +import { withDashboardProvider, withProvisioners } from "testHelpers/storybook"; import { TemplateVersionEditor } from "./TemplateVersionEditor"; const meta: Meta = { @@ -49,6 +49,101 @@ type Story = StoryObj; export const Example: Story = {}; +export const UndefinedLogs: Story = { + args: { + defaultTab: "logs", + buildLogs: undefined, + templateVersion: { + ...MockTemplateVersion, + job: MockRunningProvisionerJob, + }, + }, +}; + +export const EmptyLogs: Story = { + args: { + defaultTab: "logs", + buildLogs: [], + templateVersion: { + ...MockTemplateVersion, + job: MockRunningProvisionerJob, + }, + }, +}; + +export const CouldntGetProvisioners: Story = { + args: { + defaultTab: "logs", + buildLogs: [], + templateVersion: { + ...MockTemplateVersion, + job: MockRunningProvisionerJob, + }, + }, +}; + +export const NoProvisioners: Story = { + args: { + defaultTab: "logs", + buildLogs: [], + templateVersion: { + ...MockTemplateVersion, + job: MockRunningProvisionerJob, + organization_id: "org-id", + }, + }, + decorators: [withProvisioners], + parameters: { + organization_id: "org-id", + tags: MockRunningProvisionerJob.tags, + provisioners: [], + } +}; + +export const UnhealthyProvisioners: Story = { + args: { + defaultTab: "logs", + buildLogs: [], + templateVersion: { + ...MockTemplateVersion, + job: MockRunningProvisionerJob, + organization_id: "org-id" + }, + }, + decorators: [withProvisioners], + parameters: { + organization_id: "org-id", + tags: MockRunningProvisionerJob.tags, + provisioners: [ + { + last_seen_at: new Date(new Date().getTime() - 5 * 60 * 1000).toISOString() + }, + ], + } +}; + +export const HealthyProvisioners: Story = { + args: { + defaultTab: "logs", + buildLogs: [], + templateVersion: { + ...MockTemplateVersion, + job: MockRunningProvisionerJob, + organization_id: "org-id" + }, + }, + decorators: [withProvisioners], + parameters: { + organization_id: "org-id", + tags: MockRunningProvisionerJob.tags, + provisioners: [ + { + last_seen_at: new Date(), + }, + ], + } +}; + export const Logs: Story = { args: { defaultTab: "logs", diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 8a2f98263c46d..b0840e798ff9d 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -60,7 +60,10 @@ import { MonacoEditor } from "./MonacoEditor"; import { ProvisionerTagsPopover } from "./ProvisionerTagsPopover"; import { PublishTemplateVersionDialog } from "./PublishTemplateVersionDialog"; import { TemplateVersionStatusBadge } from "./TemplateVersionStatusBadge"; -import { provisionersUnhealthy, useCompatibleProvisioners } from "modules/provisioners/useCompatibleProvisioners"; +import { provisionersUnhealthy } from "modules/provisioners/useCompatibleProvisioners"; +import { useQuery } from "react-query"; +import { provisionerDaemons } from "api/queries/organizations"; +import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab @@ -128,11 +131,15 @@ export const TemplateVersionEditor: FC = ({ const [renameFileOpen, setRenameFileOpen] = useState(); const [dirty, setDirty] = useState(false); - const compatibleProvisioners = useCompatibleProvisioners( - templateVersion?.organization_id, - templateVersion?.job.tags + const org = templateVersion?.organization_id + const { + data: compatibleProvisioners, + isLoading: provisionerDaemonsLoading, + isError: couldntGetProvisioners, + } = useQuery( + org ? provisionerDaemons(org, templateVersion?.job.tags) : { enabled: false} ); - const compatibleProvisionersUnhealthy = provisionersUnhealthy(compatibleProvisioners); + const compatibleProvisionersUnhealthy = !compatibleProvisioners || provisionersUnhealthy(compatibleProvisioners); const triggerPreview = useCallback(async () => { await onPreview(fileTree); @@ -199,6 +206,8 @@ export const TemplateVersionEditor: FC = ({ linkToTemplate(template.organization_name, template.name), ); + const gotBuildLogs = buildLogs && buildLogs.length > 0; + return ( <>
@@ -588,49 +597,34 @@ export const TemplateVersionEditor: FC = ({ css={[styles.logs, styles.tabContent]} ref={logsContentRef} > - {templateVersion.job.error ? ( -
- - Error during the build - {templateVersion.job.error} - -
- ) : !compatibleProvisioners ? ( - + )} + + {!templateVersion.job.error && !gotBuildLogs && !provisionerDaemonsLoading && ( + couldntGetProvisioners ? ( + - Build stuck - No Compatible Provisioner Daemons have been configured - - ) : compatibleProvisionersUnhealthy && ( -
- + ) : !compatibleProvisioners || compatibleProvisioners.length == 0 ? ( + - Build may be delayed - Compatible Provisioner Daemons have been silent for a while. This may result in a delayed build - -
+ title="Build Stuck" + detail="No Compatible Provisioner Daemons have been configured" + /> + ) : compatibleProvisionersUnhealthy && ( + + ) )} {buildLogs && buildLogs.length > 0 ? ( diff --git a/site/src/testHelpers/storybook.tsx b/site/src/testHelpers/storybook.tsx index e905a9b412c2c..6b23c102ee2a8 100644 --- a/site/src/testHelpers/storybook.tsx +++ b/site/src/testHelpers/storybook.tsx @@ -17,6 +17,7 @@ import { MockDeploymentConfig, MockEntitlements, } from "./entities"; +import { getProvisionerDaemonsKey } from "api/queries/organizations"; export const withDashboardProvider = ( Story: FC, @@ -121,6 +122,25 @@ export const withAuthProvider = (Story: FC, { parameters }: StoryContext) => { ); }; +export const withProvisioners = (Story: FC, { parameters }: StoryContext) => { + if (!parameters.organization_id) { + throw new Error("You forgot to add `parameters.` to your story"); + } + if (!parameters.provisioners) { + throw new Error("You forgot to add `parameters.provisioners` to your story"); + } + if (!parameters.tags) { + throw new Error("You forgot to add `parameters.tags` to your story"); + } + + const queryClient = useQueryClient(); + queryClient.setQueryData(getProvisionerDaemonsKey(parameters.organization_id, parameters.tags), parameters.provisioners); + + return ( + + ) +}; + export const withGlobalSnackbar = (Story: FC) => ( <> From e281d6b7530ef2f2f695b4f736dee19d9a78f644 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Fri, 22 Nov 2024 14:08:26 +0000 Subject: [PATCH 07/17] linter fixes --- site/src/modules/provisioners/ProvisionerAlert.tsx | 9 ++++++--- site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx | 4 ++-- .../TemplateVersionEditorPage/TemplateVersionEditor.tsx | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/site/src/modules/provisioners/ProvisionerAlert.tsx b/site/src/modules/provisioners/ProvisionerAlert.tsx index c6c9ec1a13bf1..86ecbed233f62 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.tsx @@ -1,6 +1,9 @@ -import { Alert, AlertColor, AlertTitle } from "@mui/material"; +import { Theme } from "@mui/material"; +import Alert from "@mui/material/Alert"; +import AlertTitle from "@mui/material/AlertTitle"; +import type { AlertColor } from "@mui/material/Alert"; import { AlertDetail } from "components/Alert/Alert"; -import { type FC } from "react"; +import type { FC } from "react"; type ProvisionerAlertProps = { title: string, @@ -16,7 +19,7 @@ export const ProvisionerAlert : FC = ({ return ( ({ + css={(theme: Theme) => ({ borderRadius: 0, border: 0, borderBottom: `1px solid ${theme.palette.divider}`, diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx index 5b2ef65ea8edb..5bd1cc1b1e073 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx @@ -1,4 +1,4 @@ -import { type Interpolation, type Theme } from "@emotion/react"; +import type { Interpolation, Theme } from "@emotion/react"; import Close from "@mui/icons-material/Close"; import WarningOutlined from "@mui/icons-material/WarningOutlined"; import Button from "@mui/material/Button"; @@ -86,7 +86,7 @@ export const BuildLogsDrawer: FC = ({ title="Something went wrong" detail="Could not determine provisioner status. Your template build may fail. If your template does not build, please contact your administrator" /> - ) : (!compatibleProvisioners || compatibleProvisioners.length == 0) ? ( + ) : (!compatibleProvisioners || compatibleProvisioners.length === 0) ? ( = ({ title="Something went wrong" detail="Could not determine provisioner status. Your template build may fail. If your template does not build, please contact your administrator" /> - ) : !compatibleProvisioners || compatibleProvisioners.length == 0 ? ( + ) : !compatibleProvisioners || compatibleProvisioners.length === 0 ? ( Date: Wed, 27 Nov 2024 08:55:31 +0000 Subject: [PATCH 08/17] use matched_provisioners instead of a second api call --- site/src/api/api.ts | 2 +- .../provisioners/ProvisionerAlert.stories.tsx | 41 +++++++++ .../modules/provisioners/ProvisionerAlert.tsx | 87 ++++++++++++++++--- .../provisioners/useCompatibleProvisioners.ts | 24 ----- .../BuildLogsDrawer.stories.tsx | 53 +++++------ .../CreateTemplatePage/BuildLogsDrawer.tsx | 40 ++------- .../TemplateVersionEditor.stories.tsx | 56 +++--------- .../TemplateVersionEditor.tsx | 45 +++------- site/src/testHelpers/storybook.tsx | 2 +- 9 files changed, 176 insertions(+), 174 deletions(-) create mode 100644 site/src/modules/provisioners/ProvisionerAlert.stories.tsx delete mode 100644 site/src/modules/provisioners/useCompatibleProvisioners.ts diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 7b509d3382f61..d0d6299735c4d 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -691,7 +691,7 @@ class ApiMethods { const params = new URLSearchParams(); if (tags) { - params.append('tags', encodeURIComponent(JSON.stringify(tags))); + params.append('tags', JSON.stringify(tags)); } const response = await this.axios.get( diff --git a/site/src/modules/provisioners/ProvisionerAlert.stories.tsx b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx new file mode 100644 index 0000000000000..aee070ef05076 --- /dev/null +++ b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx @@ -0,0 +1,41 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { chromatic } from "testHelpers/chromatic"; +import { MockTemplateVersion } from "testHelpers/entities"; +import { ProvisionerAlert } from "./ProvisionerAlert"; + +const meta: Meta = { + title: "modules/provisioners/ProvisionerAlert", + parameters: { + chromatic, + layout: "centered", + }, + component: ProvisionerAlert, + args: { + matchingProvisioners: 0, + availableProvisioners: 0, + tags: MockTemplateVersion.job.tags, + }, +}; + +export default meta; +type Story = StoryObj; + +export const HealthyProvisioners: Story = { + args: { + matchingProvisioners: 1, + availableProvisioners: 1, + } +}; + +export const NoMatchingProvisioners: Story = { + args: { + matchingProvisioners: 0, + } +}; + +export const NoAvailableProvisioners: Story = { + args: { + matchingProvisioners: 1, + availableProvisioners: 0, + } +}; diff --git a/site/src/modules/provisioners/ProvisionerAlert.tsx b/site/src/modules/provisioners/ProvisionerAlert.tsx index 86ecbed233f62..5e257ab618b75 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.tsx @@ -1,25 +1,80 @@ -import { Theme } from "@mui/material"; -import Alert from "@mui/material/Alert"; +import Alert, { AlertColor } from "@mui/material/Alert"; import AlertTitle from "@mui/material/AlertTitle"; -import type { AlertColor } from "@mui/material/Alert"; +import { Stack } from "components/Stack/Stack"; import { AlertDetail } from "components/Alert/Alert"; -import type { FC } from "react"; +import { type FC } from "react"; +import { ProvisionerTag } from "modules/provisioners/ProvisionerTag"; -type ProvisionerAlertProps = { - title: string, - detail: string, - severity: AlertColor, +interface ProvisionerAlertProps { + matchingProvisioners: number | undefined, + availableProvisioners: number | undefined, + tags: Record } export const ProvisionerAlert : FC = ({ + matchingProvisioners, + availableProvisioners, + tags +}) => { + let title, detail: string; + switch (true) { + case (matchingProvisioners === 0): + title="Provisioning Cannot Proceed" + detail="There are no provisioners that accept the required tags. Please contact your administrator. Once a compatible provisioner becomes available, provisioning will continue." + break; + case (availableProvisioners === 0): + title="Provisioning Delayed" + detail="Provisioners that accept the required tags are currently anavailable. This may delay your build. Please contact your administrator if your build does not complete." + break; + default: + return null; + } + + return ( + ({ + borderRadius: 0, + border: 0, + borderBottom: `1px solid ${theme.palette.divider}`, + borderLeft: `2px solid ${theme.palette.error.main}`, + })} + > + {title} + +
{detail}
+ + {Object.entries(tags) + .filter(([key]) => key !== "owner") + .map(([key, value]) => ( + + ))} + +
+
+ ); +}; + +interface ProvisionerJobErrorProps { + title: string + detail: string + severity: AlertColor + tags: Record +} + +export const ProvisionerJobAlert : FC = ({ title, detail, severity, + tags, }) => { return ( ({ + css={(theme) => ({ borderRadius: 0, border: 0, borderBottom: `1px solid ${theme.palette.divider}`, @@ -27,7 +82,19 @@ export const ProvisionerAlert : FC = ({ })} > {title} - {detail} + +
{detail}
+ + {Object.entries(tags) + .filter(([key]) => key !== "owner") + .map(([key, value]) => ( + + ))} + +
); }; diff --git a/site/src/modules/provisioners/useCompatibleProvisioners.ts b/site/src/modules/provisioners/useCompatibleProvisioners.ts deleted file mode 100644 index 568eac9b1f628..0000000000000 --- a/site/src/modules/provisioners/useCompatibleProvisioners.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ProvisionerDaemon } from "api/typesGenerated"; - -export const provisionersUnhealthy = (provisioners : ProvisionerDaemon[]) => { - return provisioners.reduce((allUnhealthy, provisioner) => { - if (!allUnhealthy) { - // If we've found one healthy provisioner, then we don't need to look at the rest - return allUnhealthy; - } - // Otherwise, all provisioners so far have been unhealthy, so we check the next one - - // If a provisioner has no last_seen_at value, then it's considered unhealthy - if (!provisioner.last_seen_at) { - return allUnhealthy; - } - - // If a provisioner has not been seen within the last 60 seconds, then it's considered unhealthy - const lastSeen = new Date(provisioner.last_seen_at); - const oneMinuteAgo = new Date(Date.now() - 60000); - const unhealthy = lastSeen < oneMinuteAgo; - - - return allUnhealthy && unhealthy; - }, true); -} diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx index b2981757a659d..bf8ab4d71329f 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx @@ -5,7 +5,7 @@ import { MockTemplateVersion, MockWorkspaceBuildLogs, } from "testHelpers/entities"; -import { withProvisioners, withWebSocket } from "testHelpers/storybook"; +import { withWebSocket } from "testHelpers/storybook"; import { BuildLogsDrawer } from "./BuildLogsDrawer"; const meta: Meta = { @@ -36,46 +36,39 @@ export const MissingVariables: Story = { export const NoProvisioners: Story = { args: { - templateVersion: {...MockTemplateVersion, organization_id: "org-id"}, + templateVersion: { + ...MockTemplateVersion, + matched_provisioners: { + count: 0, + available: 0, + } + }, }, - decorators: [withProvisioners], - parameters: { - organization_id: "org-id", - tags: MockTemplateVersion.job.tags, - provisioners: [], - } }; export const ProvisionersUnhealthy: Story = { args: { - templateVersion: {...MockTemplateVersion, organization_id: "org-id"}, + templateVersion: { + ...MockTemplateVersion, + matched_provisioners: { + count: 1, + available: 0, + } + }, }, - decorators: [withProvisioners], - parameters: { - organization_id: "org-id", - tags: MockTemplateVersion.job.tags, - provisioners: [ - { - last_seen_at: new Date(new Date().getTime() - 5 * 60 * 1000).toISOString() - }, - ], - } }; export const ProvisionersHealthy: Story = { args: { - templateVersion: {...MockTemplateVersion, organization_id: "org-id"}, + templateVersion: { + ...MockTemplateVersion, + organization_id: "org-id", + matched_provisioners: { + count: 1, + available: 1, + } + }, }, - decorators: [withProvisioners], - parameters: { - organization_id: "org-id", - tags: MockTemplateVersion.job.tags, - provisioners: [ - { - last_seen_at: new Date() - }, - ], - } }; diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx index 5bd1cc1b1e073..d15440d76cdfb 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx @@ -12,9 +12,6 @@ import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs"; import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs"; import { type FC, useLayoutEffect, useRef } from "react"; import { navHeight } from "theme/constants"; -import { provisionersUnhealthy } from "modules/provisioners/useCompatibleProvisioners"; -import { useQuery } from "react-query"; -import { provisionerDaemons } from "api/queries/organizations"; import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; type BuildLogsDrawerProps = { @@ -31,15 +28,8 @@ export const BuildLogsDrawer: FC = ({ variablesSectionRef, ...drawerProps }) => { - const org = templateVersion?.organization_id - const { - data: compatibleProvisioners, - isLoading: provisionerDaemonsLoading, - isError: couldntGetProvisioners, - } = useQuery( - org ? provisionerDaemons(org, templateVersion?.job.tags) : { enabled: false} - ); - const compatibleProvisionersUnhealthy = !compatibleProvisioners || provisionersUnhealthy(compatibleProvisioners); + const matchingProvisioners = templateVersion?.matched_provisioners?.count + const availableProvisioners = templateVersion?.matched_provisioners?.available const logs = useWatchVersionLogs(templateVersion); const logsContainer = useRef(null); @@ -79,26 +69,12 @@ export const BuildLogsDrawer: FC = ({ - { !logs && !provisionerDaemonsLoading && ( - couldntGetProvisioners ? ( - - ) : (!compatibleProvisioners || compatibleProvisioners.length === 0) ? ( - - ) : compatibleProvisionersUnhealthy && ( - - ) + { !logs && ( + )} {isMissingVariables ? ( diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx index c5610dc389ee4..f7061cf8275ec 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.stories.tsx @@ -16,7 +16,7 @@ import { MockWorkspaceResourceSensitive, MockWorkspaceVolumeResource, } from "testHelpers/entities"; -import { withDashboardProvider, withProvisioners } from "testHelpers/storybook"; +import { withDashboardProvider } from "testHelpers/storybook"; import { TemplateVersionEditor } from "./TemplateVersionEditor"; const meta: Meta = { @@ -71,17 +71,6 @@ export const EmptyLogs: Story = { }, }; -export const CouldntGetProvisioners: Story = { - args: { - defaultTab: "logs", - buildLogs: [], - templateVersion: { - ...MockTemplateVersion, - job: MockRunningProvisionerJob, - }, - }, -}; - export const NoProvisioners: Story = { args: { defaultTab: "logs", @@ -89,37 +78,27 @@ export const NoProvisioners: Story = { templateVersion: { ...MockTemplateVersion, job: MockRunningProvisionerJob, - organization_id: "org-id", + matched_provisioners: { + count: 0, + available: 0 + } }, }, - decorators: [withProvisioners], - parameters: { - organization_id: "org-id", - tags: MockRunningProvisionerJob.tags, - provisioners: [], - } }; -export const UnhealthyProvisioners: Story = { +export const UnavailableProvisioners: Story = { args: { defaultTab: "logs", buildLogs: [], templateVersion: { ...MockTemplateVersion, job: MockRunningProvisionerJob, - organization_id: "org-id" + matched_provisioners: { + count: 1, + available: 0 + } }, }, - decorators: [withProvisioners], - parameters: { - organization_id: "org-id", - tags: MockRunningProvisionerJob.tags, - provisioners: [ - { - last_seen_at: new Date(new Date().getTime() - 5 * 60 * 1000).toISOString() - }, - ], - } }; export const HealthyProvisioners: Story = { @@ -129,19 +108,12 @@ export const HealthyProvisioners: Story = { templateVersion: { ...MockTemplateVersion, job: MockRunningProvisionerJob, - organization_id: "org-id" + matched_provisioners: { + count: 1, + available: 1 + } }, }, - decorators: [withProvisioners], - parameters: { - organization_id: "org-id", - tags: MockRunningProvisionerJob.tags, - provisioners: [ - { - last_seen_at: new Date(), - }, - ], - } }; export const Logs: Story = { diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 1e56f692701c1..7524ea3dc0072 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -60,10 +60,7 @@ import { MonacoEditor } from "./MonacoEditor"; import { ProvisionerTagsPopover } from "./ProvisionerTagsPopover"; import { PublishTemplateVersionDialog } from "./PublishTemplateVersionDialog"; import { TemplateVersionStatusBadge } from "./TemplateVersionStatusBadge"; -import { provisionersUnhealthy } from "modules/provisioners/useCompatibleProvisioners"; -import { useQuery } from "react-query"; -import { provisionerDaemons } from "api/queries/organizations"; -import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; +import { ProvisionerAlert, ProvisionerJobAlert } from "modules/provisioners/ProvisionerAlert"; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab @@ -130,16 +127,9 @@ export const TemplateVersionEditor: FC = ({ const [deleteFileOpen, setDeleteFileOpen] = useState(); const [renameFileOpen, setRenameFileOpen] = useState(); const [dirty, setDirty] = useState(false); + const matchingProvisioners = templateVersion.matched_provisioners?.count + const availableProvisioners = templateVersion.matched_provisioners?.available - const org = templateVersion?.organization_id - const { - data: compatibleProvisioners, - isLoading: provisionerDaemonsLoading, - isError: couldntGetProvisioners, - } = useQuery( - org ? provisionerDaemons(org, templateVersion?.job.tags) : { enabled: false} - ); - const compatibleProvisionersUnhealthy = !compatibleProvisioners || provisionersUnhealthy(compatibleProvisioners); const triggerPreview = useCallback(async () => { await onPreview(fileTree); @@ -598,33 +588,20 @@ export const TemplateVersionEditor: FC = ({ ref={logsContentRef} > {templateVersion.job.error && ( - )} - {!templateVersion.job.error && !gotBuildLogs && !provisionerDaemonsLoading && ( - couldntGetProvisioners ? ( - - ) : !compatibleProvisioners || compatibleProvisioners.length === 0 ? ( - - ) : compatibleProvisionersUnhealthy && ( - - ) + {!gotBuildLogs && ( + )} {buildLogs && buildLogs.length > 0 ? ( diff --git a/site/src/testHelpers/storybook.tsx b/site/src/testHelpers/storybook.tsx index 6b23c102ee2a8..ce6338d7e3fa0 100644 --- a/site/src/testHelpers/storybook.tsx +++ b/site/src/testHelpers/storybook.tsx @@ -124,7 +124,7 @@ export const withAuthProvider = (Story: FC, { parameters }: StoryContext) => { export const withProvisioners = (Story: FC, { parameters }: StoryContext) => { if (!parameters.organization_id) { - throw new Error("You forgot to add `parameters.` to your story"); + throw new Error("You forgot to add `parameters.organization_id` to your story"); } if (!parameters.provisioners) { throw new Error("You forgot to add `parameters.provisioners` to your story"); From b228257e33099b9bf77595bceccda7feee291b80 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Wed, 27 Nov 2024 09:36:14 +0000 Subject: [PATCH 09/17] provide a key to provisioner tags to keep react happy --- .../provisioners/ProvisionerAlert.stories.tsx | 14 ++++++++++++++ site/src/modules/provisioners/ProvisionerAlert.tsx | 6 ++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/site/src/modules/provisioners/ProvisionerAlert.stories.tsx b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx index aee070ef05076..8539fbe26845f 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.stories.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx @@ -27,6 +27,20 @@ export const HealthyProvisioners: Story = { } }; +export const UndefinedMatchingProvisioners: Story = { + args: { + matchingProvisioners: undefined, + availableProvisioners: undefined + } +}; + +export const UndefinedAvailableProvisioners: Story = { + args: { + matchingProvisioners: 1, + availableProvisioners: undefined + } +}; + export const NoMatchingProvisioners: Story = { args: { matchingProvisioners: 0, diff --git a/site/src/modules/provisioners/ProvisionerAlert.tsx b/site/src/modules/provisioners/ProvisionerAlert.tsx index 5e257ab618b75..b1d0a18fbf9f8 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.tsx @@ -48,6 +48,7 @@ export const ProvisionerAlert : FC = ({ .filter(([key]) => key !== "owner") .map(([key, value]) => ( @@ -58,14 +59,14 @@ export const ProvisionerAlert : FC = ({ ); }; -interface ProvisionerJobErrorProps { +interface ProvisionerJobAlertProps { title: string detail: string severity: AlertColor tags: Record } -export const ProvisionerJobAlert : FC = ({ +export const ProvisionerJobAlert : FC = ({ title, detail, severity, @@ -89,6 +90,7 @@ export const ProvisionerJobAlert : FC = ({ .filter(([key]) => key !== "owner") .map(([key, value]) => ( From 8b91dc0fdb6c538ef8217c974d1b86d3fa605f87 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Wed, 27 Nov 2024 09:40:12 +0000 Subject: [PATCH 10/17] fix linting issues --- site/src/modules/provisioners/ProvisionerAlert.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/site/src/modules/provisioners/ProvisionerAlert.tsx b/site/src/modules/provisioners/ProvisionerAlert.tsx index b1d0a18fbf9f8..2d634c222223f 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.tsx @@ -1,8 +1,8 @@ -import Alert, { AlertColor } from "@mui/material/Alert"; +import Alert, { type AlertColor } from "@mui/material/Alert"; import AlertTitle from "@mui/material/AlertTitle"; import { Stack } from "components/Stack/Stack"; import { AlertDetail } from "components/Alert/Alert"; -import { type FC } from "react"; +import type { FC } from "react"; import { ProvisionerTag } from "modules/provisioners/ProvisionerTag"; interface ProvisionerAlertProps { @@ -16,7 +16,8 @@ export const ProvisionerAlert : FC = ({ availableProvisioners, tags }) => { - let title, detail: string; + let title: string; + let detail: string; switch (true) { case (matchingProvisioners === 0): title="Provisioning Cannot Proceed" From bec2913df6be69005347db022d65b41764c74b0e Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Wed, 27 Nov 2024 10:11:45 +0000 Subject: [PATCH 11/17] make fmt --- site/src/api/api.ts | 6 +-- site/src/api/queries/organizations.ts | 15 +++--- site/src/components/Alert/Alert.tsx | 3 ++ .../provisioners/ProvisionerAlert.stories.tsx | 14 ++--- .../modules/provisioners/ProvisionerAlert.tsx | 52 ++++++++----------- .../BuildLogsDrawer.stories.tsx | 7 ++- .../CreateTemplatePage/BuildLogsDrawer.tsx | 9 ++-- .../TemplateVersionEditor.stories.tsx | 12 ++--- .../TemplateVersionEditor.tsx | 13 ++--- site/src/testHelpers/storybook.tsx | 19 ++++--- 10 files changed, 77 insertions(+), 73 deletions(-) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index d0d6299735c4d..cfba27408e9c6 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -686,16 +686,16 @@ class ApiMethods { */ getProvisionerDaemonsByOrganization = async ( organization: string, - tags?: Record + tags?: Record, ): Promise => { const params = new URLSearchParams(); if (tags) { - params.append('tags', JSON.stringify(tags)); + params.append("tags", JSON.stringify(tags)); } const response = await this.axios.get( - `/api/v2/organizations/${organization}/provisionerdaemons?${params.toString()}` + `/api/v2/organizations/${organization}/provisionerdaemons?${params.toString()}`, ); return response.data; }; diff --git a/site/src/api/queries/organizations.ts b/site/src/api/queries/organizations.ts index 7e36b48fdb33d..c3f5a4ebd3ced 100644 --- a/site/src/api/queries/organizations.ts +++ b/site/src/api/queries/organizations.ts @@ -115,14 +115,15 @@ export const organizations = () => { }; }; -export const getProvisionerDaemonsKey = (organization: string, tags?: Record) => [ - "organization", - organization, - tags, - "provisionerDaemons", -]; +export const getProvisionerDaemonsKey = ( + organization: string, + tags?: Record, +) => ["organization", organization, tags, "provisionerDaemons"]; -export const provisionerDaemons = (organization: string, tags?: Record) => { +export const provisionerDaemons = ( + organization: string, + tags?: Record, +) => { return { queryKey: getProvisionerDaemonsKey(organization, tags), queryFn: () => API.getProvisionerDaemonsByOrganization(organization, tags), diff --git a/site/src/components/Alert/Alert.tsx b/site/src/components/Alert/Alert.tsx index df741a1924fa9..7750a6bc7d1e8 100644 --- a/site/src/components/Alert/Alert.tsx +++ b/site/src/components/Alert/Alert.tsx @@ -1,4 +1,5 @@ import MuiAlert, { + type AlertColor as MuiAlertColor, type AlertProps as MuiAlertProps, // biome-ignore lint/nursery/noRestrictedImports: Used as base component } from "@mui/material/Alert"; @@ -11,6 +12,8 @@ import { useState, } from "react"; +export type AlertColor = MuiAlertColor; + export type AlertProps = MuiAlertProps & { actions?: ReactNode; dismissible?: boolean; diff --git a/site/src/modules/provisioners/ProvisionerAlert.stories.tsx b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx index 8539fbe26845f..b14c9f4258b38 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.stories.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx @@ -24,32 +24,32 @@ export const HealthyProvisioners: Story = { args: { matchingProvisioners: 1, availableProvisioners: 1, - } + }, }; export const UndefinedMatchingProvisioners: Story = { args: { matchingProvisioners: undefined, - availableProvisioners: undefined - } + availableProvisioners: undefined, + }, }; export const UndefinedAvailableProvisioners: Story = { args: { matchingProvisioners: 1, - availableProvisioners: undefined - } + availableProvisioners: undefined, + }, }; export const NoMatchingProvisioners: Story = { args: { matchingProvisioners: 0, - } + }, }; export const NoAvailableProvisioners: Story = { args: { matchingProvisioners: 1, availableProvisioners: 0, - } + }, }; diff --git a/site/src/modules/provisioners/ProvisionerAlert.tsx b/site/src/modules/provisioners/ProvisionerAlert.tsx index 2d634c222223f..a16bb7b602547 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.tsx @@ -1,31 +1,33 @@ -import Alert, { type AlertColor } from "@mui/material/Alert"; import AlertTitle from "@mui/material/AlertTitle"; -import { Stack } from "components/Stack/Stack"; +import { Alert, type AlertColor } from "components/Alert/Alert"; import { AlertDetail } from "components/Alert/Alert"; -import type { FC } from "react"; +import { Stack } from "components/Stack/Stack"; import { ProvisionerTag } from "modules/provisioners/ProvisionerTag"; +import type { FC } from "react"; interface ProvisionerAlertProps { - matchingProvisioners: number | undefined, - availableProvisioners: number | undefined, - tags: Record + matchingProvisioners: number | undefined; + availableProvisioners: number | undefined; + tags: Record; } -export const ProvisionerAlert : FC = ({ +export const ProvisionerAlert: FC = ({ matchingProvisioners, availableProvisioners, - tags + tags, }) => { let title: string; let detail: string; switch (true) { - case (matchingProvisioners === 0): - title="Provisioning Cannot Proceed" - detail="There are no provisioners that accept the required tags. Please contact your administrator. Once a compatible provisioner becomes available, provisioning will continue." + case matchingProvisioners === 0: + title = "Provisioning Cannot Proceed"; + detail = + "There are no provisioners that accept the required tags. Please contact your administrator. Once a compatible provisioner becomes available, provisioning will continue."; break; - case (availableProvisioners === 0): - title="Provisioning Delayed" - detail="Provisioners that accept the required tags are currently anavailable. This may delay your build. Please contact your administrator if your build does not complete." + case availableProvisioners === 0: + title = "Provisioning Delayed"; + detail = + "Provisioners that accept the required tags are currently anavailable. This may delay your build. Please contact your administrator if your build does not complete."; break; default: return null; @@ -48,11 +50,7 @@ export const ProvisionerAlert : FC = ({ {Object.entries(tags) .filter(([key]) => key !== "owner") .map(([key, value]) => ( - + ))} @@ -61,13 +59,13 @@ export const ProvisionerAlert : FC = ({ }; interface ProvisionerJobAlertProps { - title: string - detail: string - severity: AlertColor - tags: Record + title: string; + detail: string; + severity: AlertColor; + tags: Record; } -export const ProvisionerJobAlert : FC = ({ +export const ProvisionerJobAlert: FC = ({ title, detail, severity, @@ -90,11 +88,7 @@ export const ProvisionerJobAlert : FC = ({ {Object.entries(tags) .filter(([key]) => key !== "owner") .map(([key, value]) => ( - + ))} diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx index bf8ab4d71329f..0665689ef6aa7 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx @@ -41,7 +41,7 @@ export const NoProvisioners: Story = { matched_provisioners: { count: 0, available: 0, - } + }, }, }, }; @@ -53,7 +53,7 @@ export const ProvisionersUnhealthy: Story = { matched_provisioners: { count: 1, available: 0, - } + }, }, }, }; @@ -66,12 +66,11 @@ export const ProvisionersHealthy: Story = { matched_provisioners: { count: 1, available: 1, - } + }, }, }, }; - export const Logs: Story = { args: { templateVersion: { diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx index d15440d76cdfb..8ea86964066d4 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx @@ -8,11 +8,11 @@ import { visuallyHidden } from "@mui/utils"; import { JobError } from "api/queries/templates"; import type { TemplateVersion } from "api/typesGenerated"; import { Loader } from "components/Loader/Loader"; +import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs"; import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs"; import { type FC, useLayoutEffect, useRef } from "react"; import { navHeight } from "theme/constants"; -import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; type BuildLogsDrawerProps = { error: unknown; @@ -28,8 +28,9 @@ export const BuildLogsDrawer: FC = ({ variablesSectionRef, ...drawerProps }) => { - const matchingProvisioners = templateVersion?.matched_provisioners?.count - const availableProvisioners = templateVersion?.matched_provisioners?.available + const matchingProvisioners = templateVersion?.matched_provisioners?.count; + const availableProvisioners = + templateVersion?.matched_provisioners?.available; const logs = useWatchVersionLogs(templateVersion); const logsContainer = useRef(null); @@ -69,7 +70,7 @@ export const BuildLogsDrawer: FC = ({ - { !logs && ( + {!logs && ( = ({ const [deleteFileOpen, setDeleteFileOpen] = useState(); const [renameFileOpen, setRenameFileOpen] = useState(); const [dirty, setDirty] = useState(false); - const matchingProvisioners = templateVersion.matched_provisioners?.count - const availableProvisioners = templateVersion.matched_provisioners?.available - + const matchingProvisioners = templateVersion.matched_provisioners?.count; + const availableProvisioners = templateVersion.matched_provisioners?.available; const triggerPreview = useCallback(async () => { await onPreview(fileTree); diff --git a/site/src/testHelpers/storybook.tsx b/site/src/testHelpers/storybook.tsx index ce6338d7e3fa0..514d34e0265e8 100644 --- a/site/src/testHelpers/storybook.tsx +++ b/site/src/testHelpers/storybook.tsx @@ -1,6 +1,7 @@ import type { StoryContext } from "@storybook/react"; import { withDefaultFeatures } from "api/api"; import { getAuthorizationKey } from "api/queries/authCheck"; +import { getProvisionerDaemonsKey } from "api/queries/organizations"; import { hasFirstUserKey, meKey } from "api/queries/users"; import type { Entitlements } from "api/typesGenerated"; import { GlobalSnackbar } from "components/GlobalSnackbar/GlobalSnackbar"; @@ -17,7 +18,6 @@ import { MockDeploymentConfig, MockEntitlements, } from "./entities"; -import { getProvisionerDaemonsKey } from "api/queries/organizations"; export const withDashboardProvider = ( Story: FC, @@ -124,21 +124,26 @@ export const withAuthProvider = (Story: FC, { parameters }: StoryContext) => { export const withProvisioners = (Story: FC, { parameters }: StoryContext) => { if (!parameters.organization_id) { - throw new Error("You forgot to add `parameters.organization_id` to your story"); + throw new Error( + "You forgot to add `parameters.organization_id` to your story", + ); } if (!parameters.provisioners) { - throw new Error("You forgot to add `parameters.provisioners` to your story"); + throw new Error( + "You forgot to add `parameters.provisioners` to your story", + ); } if (!parameters.tags) { throw new Error("You forgot to add `parameters.tags` to your story"); } const queryClient = useQueryClient(); - queryClient.setQueryData(getProvisionerDaemonsKey(parameters.organization_id, parameters.tags), parameters.provisioners); + queryClient.setQueryData( + getProvisionerDaemonsKey(parameters.organization_id, parameters.tags), + parameters.provisioners, + ); - return ( - - ) + return ; }; export const withGlobalSnackbar = (Story: FC) => ( From 4796a32ac1c51dcdce5fbfa73817454d4683fff4 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 28 Nov 2024 11:25:42 +0000 Subject: [PATCH 12/17] Copy updates --- .../provisioners/ProvisionerAlert.stories.tsx | 43 ++--------- .../modules/provisioners/ProvisionerAlert.tsx | 74 +++---------------- .../ProvisionerStatusAlert.stories.tsx | 55 ++++++++++++++ .../provisioners/ProvisionerStatusAlert.tsx | 48 ++++++++++++ .../BuildLogsDrawer.stories.tsx | 1 - .../CreateTemplatePage/BuildLogsDrawer.tsx | 16 ++-- .../TemplateVersionEditor.tsx | 20 +++-- 7 files changed, 139 insertions(+), 118 deletions(-) create mode 100644 site/src/modules/provisioners/ProvisionerStatusAlert.stories.tsx create mode 100644 site/src/modules/provisioners/ProvisionerStatusAlert.tsx diff --git a/site/src/modules/provisioners/ProvisionerAlert.stories.tsx b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx index b14c9f4258b38..6babfcaf9dbc5 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.stories.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx @@ -1,6 +1,5 @@ import type { Meta, StoryObj } from "@storybook/react"; import { chromatic } from "testHelpers/chromatic"; -import { MockTemplateVersion } from "testHelpers/entities"; import { ProvisionerAlert } from "./ProvisionerAlert"; const meta: Meta = { @@ -11,45 +10,19 @@ const meta: Meta = { }, component: ProvisionerAlert, args: { - matchingProvisioners: 0, - availableProvisioners: 0, - tags: MockTemplateVersion.job.tags, + title: "Title", + detail: "Detail", + severity: "info", + tags: {"tag": "tagValue"} }, }; export default meta; type Story = StoryObj; -export const HealthyProvisioners: Story = { +export const Info: Story = {}; +export const NullTags: Story = { args: { - matchingProvisioners: 1, - availableProvisioners: 1, - }, -}; - -export const UndefinedMatchingProvisioners: Story = { - args: { - matchingProvisioners: undefined, - availableProvisioners: undefined, - }, -}; - -export const UndefinedAvailableProvisioners: Story = { - args: { - matchingProvisioners: 1, - availableProvisioners: undefined, - }, -}; - -export const NoMatchingProvisioners: Story = { - args: { - matchingProvisioners: 0, - }, -}; - -export const NoAvailableProvisioners: Story = { - args: { - matchingProvisioners: 1, - availableProvisioners: 0, - }, + tags: undefined + } }; diff --git a/site/src/modules/provisioners/ProvisionerAlert.tsx b/site/src/modules/provisioners/ProvisionerAlert.tsx index a16bb7b602547..685556d644274 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.tsx @@ -3,69 +3,15 @@ import { Alert, type AlertColor } from "components/Alert/Alert"; import { AlertDetail } from "components/Alert/Alert"; import { Stack } from "components/Stack/Stack"; import { ProvisionerTag } from "modules/provisioners/ProvisionerTag"; -import type { FC } from "react"; - +import { FC } from "react"; interface ProvisionerAlertProps { - matchingProvisioners: number | undefined; - availableProvisioners: number | undefined; - tags: Record; -} - -export const ProvisionerAlert: FC = ({ - matchingProvisioners, - availableProvisioners, - tags, -}) => { - let title: string; - let detail: string; - switch (true) { - case matchingProvisioners === 0: - title = "Provisioning Cannot Proceed"; - detail = - "There are no provisioners that accept the required tags. Please contact your administrator. Once a compatible provisioner becomes available, provisioning will continue."; - break; - case availableProvisioners === 0: - title = "Provisioning Delayed"; - detail = - "Provisioners that accept the required tags are currently anavailable. This may delay your build. Please contact your administrator if your build does not complete."; - break; - default: - return null; - } - - return ( - ({ - borderRadius: 0, - border: 0, - borderBottom: `1px solid ${theme.palette.divider}`, - borderLeft: `2px solid ${theme.palette.error.main}`, - })} - > - {title} - -
{detail}
- - {Object.entries(tags) - .filter(([key]) => key !== "owner") - .map(([key, value]) => ( - - ))} - -
-
- ); -}; - -interface ProvisionerJobAlertProps { title: string; detail: string; severity: AlertColor; tags: Record; } -export const ProvisionerJobAlert: FC = ({ +export const ProvisionerAlert: FC = ({ title, detail, severity, @@ -74,18 +20,20 @@ export const ProvisionerJobAlert: FC = ({ return ( ({ - borderRadius: 0, - border: 0, - borderBottom: `1px solid ${theme.palette.divider}`, - borderLeft: `2px solid ${theme.palette.error.main}`, - })} + css={(theme) => { + return { + borderRadius: 0, + border: 0, + borderBottom: `1px solid ${theme.palette.divider}`, + borderLeft: `2px solid ${theme.palette[severity].main}`, + }; + }} > {title}
{detail}
- {Object.entries(tags) + {Object.entries(tags ?? {}) .filter(([key]) => key !== "owner") .map(([key, value]) => ( diff --git a/site/src/modules/provisioners/ProvisionerStatusAlert.stories.tsx b/site/src/modules/provisioners/ProvisionerStatusAlert.stories.tsx new file mode 100644 index 0000000000000..d4f746e99c417 --- /dev/null +++ b/site/src/modules/provisioners/ProvisionerStatusAlert.stories.tsx @@ -0,0 +1,55 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { chromatic } from "testHelpers/chromatic"; +import { MockTemplateVersion } from "testHelpers/entities"; +import { ProvisionerStatusAlert } from "./ProvisionerStatusAlert"; + +const meta: Meta = { + title: "modules/provisioners/ProvisionerStatusAlert", + parameters: { + chromatic, + layout: "centered", + }, + component: ProvisionerStatusAlert, + args: { + matchingProvisioners: 0, + availableProvisioners: 0, + tags: MockTemplateVersion.job.tags, + }, +}; + +export default meta; +type Story = StoryObj; + +export const HealthyProvisioners: Story = { + args: { + matchingProvisioners: 1, + availableProvisioners: 1, + }, +}; + +export const UndefinedMatchingProvisioners: Story = { + args: { + matchingProvisioners: undefined, + availableProvisioners: undefined, + }, +}; + +export const UndefinedAvailableProvisioners: Story = { + args: { + matchingProvisioners: 1, + availableProvisioners: undefined, + }, +}; + +export const NoMatchingProvisioners: Story = { + args: { + matchingProvisioners: 0, + }, +}; + +export const NoAvailableProvisioners: Story = { + args: { + matchingProvisioners: 1, + availableProvisioners: 0, + }, +}; diff --git a/site/src/modules/provisioners/ProvisionerStatusAlert.tsx b/site/src/modules/provisioners/ProvisionerStatusAlert.tsx new file mode 100644 index 0000000000000..fdc71346c64fa --- /dev/null +++ b/site/src/modules/provisioners/ProvisionerStatusAlert.tsx @@ -0,0 +1,48 @@ +import { AlertColor } from "components/Alert/Alert"; +import type { FC } from "react"; +import { ProvisionerAlert } from "./ProvisionerAlert"; + +interface ProvisionerStatusAlertProps { + matchingProvisioners: number | undefined; + availableProvisioners: number | undefined; + tags: Record; +} + +export const ProvisionerStatusAlert: FC = ({ + matchingProvisioners, + availableProvisioners, + tags, +}) => { + let title: string; + let detail: string; + let severity: AlertColor; + switch (true) { + case matchingProvisioners === 0: + title = "Build pending provisioner deployment"; + detail = + "Your build has been enqueued, but there are no provisioners that accept the required tags. Once a compatible provisioner becomes available, your build will continue. Please contact your administrator."; + severity = "warning"; + break; + case availableProvisioners === 0: + title = "Build delayed"; + detail = + "Provisioners that accept the required tags have not responded for longer than expected. This may delay your build. Please contact your administrator if your build does not complete."; + severity = "warning"; + break; + default: + title = "Build enqueued"; + detail = + "Your build has been enqueued and will begin once a provisioner becomes available to process it."; + severity = "info"; + } + + return ( + + ); +}; + diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx index 0665689ef6aa7..29229fadfd0ad 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.stories.tsx @@ -62,7 +62,6 @@ export const ProvisionersHealthy: Story = { args: { templateVersion: { ...MockTemplateVersion, - organization_id: "org-id", matched_provisioners: { count: 1, available: 1, diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx index 8ea86964066d4..7d3e19b322128 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx @@ -8,7 +8,7 @@ import { visuallyHidden } from "@mui/utils"; import { JobError } from "api/queries/templates"; import type { TemplateVersion } from "api/typesGenerated"; import { Loader } from "components/Loader/Loader"; -import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; +import { ProvisionerStatusAlert } from "modules/provisioners/ProvisionerStatusAlert"; import { useWatchVersionLogs } from "modules/templates/useWatchVersionLogs"; import { WorkspaceBuildLogs } from "modules/workspaces/WorkspaceBuildLogs/WorkspaceBuildLogs"; import { type FC, useLayoutEffect, useRef } from "react"; @@ -70,13 +70,7 @@ export const BuildLogsDrawer: FC = ({ - {!logs && ( - - )} + {} {isMissingVariables ? ( = ({ drawerProps.onClose(); }} /> + ) : !logs ? ( + ) : logs ? (
diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 333aab21b4542..2eace0e9ee82c 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -28,10 +28,6 @@ import { } from "components/FullPageLayout/Topbar"; import { Loader } from "components/Loader/Loader"; import { linkToTemplate, useLinks } from "modules/navigation"; -import { - ProvisionerAlert, - ProvisionerJobAlert, -} from "modules/provisioners/ProvisionerAlert"; import { TemplateFileTree } from "modules/templates/TemplateFiles/TemplateFileTree"; import { isBinaryData } from "modules/templates/TemplateFiles/isBinaryData"; import { TemplateResourcesTable } from "modules/templates/TemplateResourcesTable/TemplateResourcesTable"; @@ -63,6 +59,8 @@ import { MonacoEditor } from "./MonacoEditor"; import { ProvisionerTagsPopover } from "./ProvisionerTagsPopover"; import { PublishTemplateVersionDialog } from "./PublishTemplateVersionDialog"; import { TemplateVersionStatusBadge } from "./TemplateVersionStatusBadge"; +import { ProvisionerStatusAlert } from "modules/provisioners/ProvisionerStatusAlert"; +import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab @@ -588,22 +586,22 @@ export const TemplateVersionEditor: FC = ({ css={[styles.logs, styles.tabContent]} ref={logsContentRef} > - {templateVersion.job.error && ( - + {templateVersion.job.error ? ( + - )} - - {!gotBuildLogs && ( - )} +
{buildLogs && buildLogs.length > 0 ? ( Date: Thu, 28 Nov 2024 11:26:53 +0000 Subject: [PATCH 13/17] make fmt --- .../provisioners/ProvisionerAlert.stories.tsx | 6 ++-- .../modules/provisioners/ProvisionerAlert.tsx | 2 +- .../provisioners/ProvisionerStatusAlert.tsx | 3 +- .../TemplateVersionEditor.tsx | 34 ++++++++++--------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/site/src/modules/provisioners/ProvisionerAlert.stories.tsx b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx index 6babfcaf9dbc5..d9ca1501d6611 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.stories.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.stories.tsx @@ -13,7 +13,7 @@ const meta: Meta = { title: "Title", detail: "Detail", severity: "info", - tags: {"tag": "tagValue"} + tags: { tag: "tagValue" }, }, }; @@ -23,6 +23,6 @@ type Story = StoryObj; export const Info: Story = {}; export const NullTags: Story = { args: { - tags: undefined - } + tags: undefined, + }, }; diff --git a/site/src/modules/provisioners/ProvisionerAlert.tsx b/site/src/modules/provisioners/ProvisionerAlert.tsx index 685556d644274..54d9ab8473e87 100644 --- a/site/src/modules/provisioners/ProvisionerAlert.tsx +++ b/site/src/modules/provisioners/ProvisionerAlert.tsx @@ -3,7 +3,7 @@ import { Alert, type AlertColor } from "components/Alert/Alert"; import { AlertDetail } from "components/Alert/Alert"; import { Stack } from "components/Stack/Stack"; import { ProvisionerTag } from "modules/provisioners/ProvisionerTag"; -import { FC } from "react"; +import type { FC } from "react"; interface ProvisionerAlertProps { title: string; detail: string; diff --git a/site/src/modules/provisioners/ProvisionerStatusAlert.tsx b/site/src/modules/provisioners/ProvisionerStatusAlert.tsx index fdc71346c64fa..54a2b56704877 100644 --- a/site/src/modules/provisioners/ProvisionerStatusAlert.tsx +++ b/site/src/modules/provisioners/ProvisionerStatusAlert.tsx @@ -1,4 +1,4 @@ -import { AlertColor } from "components/Alert/Alert"; +import type { AlertColor } from "components/Alert/Alert"; import type { FC } from "react"; import { ProvisionerAlert } from "./ProvisionerAlert"; @@ -45,4 +45,3 @@ export const ProvisionerStatusAlert: FC = ({ /> ); }; - diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 2eace0e9ee82c..e4c35d30ebfbd 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -28,6 +28,8 @@ import { } from "components/FullPageLayout/Topbar"; import { Loader } from "components/Loader/Loader"; import { linkToTemplate, useLinks } from "modules/navigation"; +import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; +import { ProvisionerStatusAlert } from "modules/provisioners/ProvisionerStatusAlert"; import { TemplateFileTree } from "modules/templates/TemplateFiles/TemplateFileTree"; import { isBinaryData } from "modules/templates/TemplateFiles/isBinaryData"; import { TemplateResourcesTable } from "modules/templates/TemplateResourcesTable/TemplateResourcesTable"; @@ -59,8 +61,6 @@ import { MonacoEditor } from "./MonacoEditor"; import { ProvisionerTagsPopover } from "./ProvisionerTagsPopover"; import { PublishTemplateVersionDialog } from "./PublishTemplateVersionDialog"; import { TemplateVersionStatusBadge } from "./TemplateVersionStatusBadge"; -import { ProvisionerStatusAlert } from "modules/provisioners/ProvisionerStatusAlert"; -import { ProvisionerAlert } from "modules/provisioners/ProvisionerAlert"; type Tab = "logs" | "resources" | undefined; // Undefined is to hide the tab @@ -587,20 +587,22 @@ export const TemplateVersionEditor: FC = ({ ref={logsContentRef} >
- {templateVersion.job.error ? ( - - ) : !gotBuildLogs && ( - - )} + {templateVersion.job.error ? ( + + ) : ( + !gotBuildLogs && ( + + ) + )}
{buildLogs && buildLogs.length > 0 ? ( From aec9cba559ebf7b3f3384b1f5372b581cc5b19ab Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 28 Nov 2024 12:14:28 +0000 Subject: [PATCH 14/17] add loader back in to build logs drawer --- .../pages/CreateTemplatePage/BuildLogsDrawer.tsx | 15 ++++++++------- .../TemplateVersionEditor.tsx | 6 +++--- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx index 7d3e19b322128..4eb1805b60e36 100644 --- a/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx +++ b/site/src/pages/CreateTemplatePage/BuildLogsDrawer.tsx @@ -84,18 +84,19 @@ export const BuildLogsDrawer: FC = ({ drawerProps.onClose(); }} /> - ) : !logs ? ( - ) : logs ? (
) : ( - + <> + + + )} diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index e4c35d30ebfbd..778c1b9673fbd 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -586,14 +586,15 @@ export const TemplateVersionEditor: FC = ({ css={[styles.logs, styles.tabContent]} ref={logsContentRef} > -
{templateVersion.job.error ? ( +
+
) : ( !gotBuildLogs && ( = ({ /> ) )} -
- {buildLogs && buildLogs.length > 0 ? ( + {gotBuildLogs ? ( Date: Thu, 28 Nov 2024 12:19:50 +0000 Subject: [PATCH 15/17] simplify logic --- .../TemplateVersionEditor.tsx | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 778c1b9673fbd..23b4741cd5199 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -586,7 +586,7 @@ export const TemplateVersionEditor: FC = ({ css={[styles.logs, styles.tabContent]} ref={logsContentRef} > - {templateVersion.job.error ? ( + {templateVersion.job.error ? (
= ({ tags={templateVersion.job.tags} />
- ) : ( - !gotBuildLogs && ( - - ) - )} - - {gotBuildLogs ? ( + ) : gotBuildLogs ? ( ) : ( - + <> + + + )} )} From 0aed543ce5033a2110fd108de4dfee5f34fe7186 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 28 Nov 2024 12:30:10 +0000 Subject: [PATCH 16/17] fix logic --- .../TemplateVersionEditor.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 23b4741cd5199..9b55bd9e8e616 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -595,13 +595,7 @@ export const TemplateVersionEditor: FC = ({ tags={templateVersion.job.tags} /> - ) : gotBuildLogs ? ( - - ) : ( + ) : !gotBuildLogs && ( <> = ({ )} + + { + gotBuildLogs && ( + + ) + } )} From 0bd9478920af9a950ce8ed134f37126bf67660c6 Mon Sep 17 00:00:00 2001 From: Sas Swart Date: Thu, 28 Nov 2024 12:37:29 +0000 Subject: [PATCH 17/17] make fmt --- .../TemplateVersionEditor.tsx | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx index 9b55bd9e8e616..858f57dd59493 100644 --- a/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx +++ b/site/src/pages/TemplateVersionEditorPage/TemplateVersionEditor.tsx @@ -595,26 +595,26 @@ export const TemplateVersionEditor: FC = ({ tags={templateVersion.job.tags} /> - ) : !gotBuildLogs && ( - <> - - - + ) : ( + !gotBuildLogs && ( + <> + + + + ) )} - { - gotBuildLogs && ( - - ) - } + {gotBuildLogs && ( + + )} )} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy