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 && ( + + )} )}