From 800afc95ae735c05dc2617c21d375de7c2dd5db9 Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Mon, 28 Aug 2023 16:56:37 +1000 Subject: [PATCH 01/20] Switch to a new progress event for build status --- package.json | 2 +- src/Panel.tsx | 16 +++--- src/Tool.tsx | 8 ++- src/constants.ts | 15 +++++- src/index.ts | 50 +++++++++++++------ .../VisualTests/BuildStatus.stories.ts | 20 ++++++++ src/screens/VisualTests/BuildStatus.tsx | 5 ++ yarn.lock | 8 +-- 8 files changed, 95 insertions(+), 29 deletions(-) create mode 100644 src/screens/VisualTests/BuildStatus.stories.ts create mode 100644 src/screens/VisualTests/BuildStatus.tsx diff --git a/package.json b/package.json index 0c3992e4..f2d5970b 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "dependencies": { "@storybook/csf-tools": "7.4.0-alpha.2", "@storybook/design-system": "^7.15.15", - "chromatic": "6.24.0", + "chromatic": "6.25.0-canary.0", "date-fns": "^2.30.0", "pluralize": "^8.0.0", "urql": "^4.0.3", diff --git a/src/Panel.tsx b/src/Panel.tsx index 90f203f1..e1ce054c 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -7,8 +7,8 @@ import React, { useCallback, useState } from "react"; import { ADDON_ID, - BUILD_ANNOUNCED, - BUILD_STARTED, + BUILD_PROGRESS, + BuildProgressPayload, DEV_BUILD_ID_KEY, GIT_INFO, GitInfoPayload, @@ -60,10 +60,14 @@ export const Panel = ({ active, api }: PanelProps) => { const emit = useChannel( { [START_BUILD]: () => setIsStarting(true), - [BUILD_STARTED]: () => setIsStarting(false), - [BUILD_ANNOUNCED]: (buildId: string) => { - setLastBuildId(buildId); - localStorage.setItem(DEV_BUILD_ID_KEY, buildId); + [BUILD_PROGRESS]: ({ step, id }: BuildProgressPayload) => { + if (step === "build") { + setLastBuildId(id); + localStorage.setItem(DEV_BUILD_ID_KEY, id); + } + if (step === "snapshot" || step === "complete") { + setIsStarting(false); + } }, [GIT_INFO]: (info: GitInfoPayload) => { setGitInfo(info); diff --git a/src/Tool.tsx b/src/Tool.tsx index 6f09ad5b..3560d88e 100644 --- a/src/Tool.tsx +++ b/src/Tool.tsx @@ -3,7 +3,7 @@ import { useChannel } from "@storybook/manager-api"; import React, { useState } from "react"; import { ProgressIcon } from "./components/icons/ProgressIcon"; -import { BUILD_STARTED, START_BUILD, TOOL_ID } from "./constants"; +import { BUILD_PROGRESS, BuildProgressPayload, START_BUILD, TOOL_ID } from "./constants"; export const Tool = () => { const [isStarting, setIsStarting] = useState(false); @@ -11,7 +11,11 @@ export const Tool = () => { const emit = useChannel( { [START_BUILD]: () => setIsStarting(true), - [BUILD_STARTED]: () => setIsStarting(false), + [BUILD_PROGRESS]: ({ step, id }: BuildProgressPayload) => { + if (step === "snapshot" || step === "complete") { + setIsStarting(false); + } + }, }, [] ); diff --git a/src/constants.ts b/src/constants.ts index dbcf5b2d..357a024d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -16,9 +16,20 @@ export const DEV_BUILD_ID_KEY = `${ADDON_ID}/dev-build-id`; export const GIT_INFO = `${ADDON_ID}/gitInfo`; export type GitInfoPayload = Omit; + export const START_BUILD = `${ADDON_ID}/startBuild`; -export const BUILD_ANNOUNCED = `${ADDON_ID}/buildAnnounced`; -export const BUILD_STARTED = `${ADDON_ID}/buildStarted`; +export const BUILD_PROGRESS = `${ADDON_ID}/buildProgress`; +export type BuildProgressPayload = { + // Possibly this should be a type exported by the CLI -- these correspond to tasks + /** The step of the build process we have reached */ + step: "initialize" | "build" | "upload" | "verify" | "snapshot" | "complete"; + /** The id of the build, available after the initialize step */ + id?: string; + /** progress pertains to the current step, and may not be set */ + progress?: number; + /** total pertains to the current step, and may not be set */ + total?: number; +}; export const UPDATE_PROJECT = `${ADDON_ID}/updateProject`; export type UpdateProjectPayload = { diff --git a/src/index.ts b/src/index.ts index 511d6e90..418aa1af 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,8 +5,8 @@ import { readConfig, writeConfig } from "@storybook/csf-tools"; import { getGitInfo, GitInfo, run } from "chromatic/node"; import { - BUILD_ANNOUNCED, - BUILD_STARTED, + BUILD_PROGRESS, + BuildProgressPayload, CHROMATIC_ADDON_NAME, CHROMATIC_BASE_URL, GIT_INFO, @@ -55,8 +55,7 @@ async function serverChannel( ) { let projectToken = initialProjectToken; channel.on(START_BUILD, async () => { - let announced = false; - let started = false; + let step: BuildProgressPayload["step"] = "initialize"; await run({ // Currently we have to have these flags. // We should move the checks to after flags have been parsed into options. @@ -70,19 +69,42 @@ async function serverChannel( // Builds initiated from the addon are always considered local isLocalBuild: true, onTaskComplete(ctx) { - console.log(`Completed task '${ctx.title}'`); - if (!announced && ctx.announcedBuild) { - console.debug("emitting", BUILD_ANNOUNCED, ctx.announcedBuild.id); - channel.emit(BUILD_ANNOUNCED, ctx.announcedBuild.id); - announced = true; + let newStep: BuildProgressPayload["step"]; + if (step === "initialize" && ctx.announcedBuild) { + newStep = "build"; } - if (announced && !started && ctx.build) { - console.debug("emitting", BUILD_STARTED, ctx.build.status); - channel.emit(BUILD_STARTED, ctx.build.status); - started = true; + if (step === "upload" && ctx.isolatorUrl) { + newStep = "verify"; } + if (["build", "upload"].includes(step) && ctx.build) { + newStep = "snapshot"; + } + if (ctx.build?.status !== "IN_PROGRESS") { + newStep = "complete"; + } + + if (newStep !== step) { + step = newStep; + channel.emit(BUILD_PROGRESS, { + step, + id: ctx.announcedBuild.id, + } satisfies BuildProgressPayload); + } + }, + onTaskProgress(ctx, { progress, total, unit }) { + if (unit === "bytes") { + step = "upload"; + } else { + step = "snapshot"; + } + + channel.emit(BUILD_PROGRESS, { + step, + id: ctx.announcedBuild.id, + progress, + total, + } satisfies BuildProgressPayload); }, - // as any due to CLI mistyping: https://github.com/chromaui/chromatic-cli/pull/800 }, }); }); diff --git a/src/screens/VisualTests/BuildStatus.stories.ts b/src/screens/VisualTests/BuildStatus.stories.ts new file mode 100644 index 00000000..ff727d7b --- /dev/null +++ b/src/screens/VisualTests/BuildStatus.stories.ts @@ -0,0 +1,20 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { withFigmaDesign } from "../../utils/withFigmaDesign"; +import { BuildStatus } from "./BuildStatus"; + +const meta = { + component: BuildStatus, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + // build: passedBuild, + }, + parameters: withFigmaDesign( + "https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=508-525764&t=18c1zI1SMe76dWYk-4" + ), +}; diff --git a/src/screens/VisualTests/BuildStatus.tsx b/src/screens/VisualTests/BuildStatus.tsx new file mode 100644 index 00000000..c6bd5e57 --- /dev/null +++ b/src/screens/VisualTests/BuildStatus.tsx @@ -0,0 +1,5 @@ +import React from "react"; + +export function BuildStatus({}) { + return
Build Status
; +} diff --git a/yarn.lock b/yarn.lock index 5fd8052d..3faf8663 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5453,10 +5453,10 @@ chownr@^2.0.0: resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece" integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== -chromatic@6.24.0: - version "6.24.0" - resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-6.24.0.tgz#007561facf1c3d444195cf640195dbb19c2cc4e1" - integrity sha512-i09yqXa5P1qsNntohW8SN3MYWitdz8j6FazOGrN7v2nEvPauP50vTKFYKxBXq68kp904ede/06D4TZdkfp55sw== +chromatic@6.25.0-canary.0: + version "6.25.0-canary.0" + resolved "https://registry.yarnpkg.com/chromatic/-/chromatic-6.25.0-canary.0.tgz#1121c5afcf335a2b5ee1fbe5c709b0f42dbbd1d3" + integrity sha512-oiytax7ozb/b4bFLTRuDQ60uUxpmT7W/O4yjm49YhYfpXFbKsDMDyyLjJZnE3ntMJxyhF5uSehuZFnS0CXaemw== ci-info@^3.2.0: version "3.8.0" From e5f77d06ae2bf288c4dc8e1f6fc663537986343d Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Mon, 28 Aug 2023 16:59:21 +1000 Subject: [PATCH 02/20] Small fix --- src/Tool.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Tool.tsx b/src/Tool.tsx index 3560d88e..3d044c3f 100644 --- a/src/Tool.tsx +++ b/src/Tool.tsx @@ -11,7 +11,7 @@ export const Tool = () => { const emit = useChannel( { [START_BUILD]: () => setIsStarting(true), - [BUILD_PROGRESS]: ({ step, id }: BuildProgressPayload) => { + [BUILD_PROGRESS]: ({ step }: BuildProgressPayload) => { if (step === "snapshot" || step === "complete") { setIsStarting(false); } From e33cb62b1f45ac604625c1950a50fde78208cf98 Mon Sep 17 00:00:00 2001 From: Tom Coleman Date: Tue, 29 Aug 2023 12:09:45 +1000 Subject: [PATCH 03/20] First pass over `BuildProgress` UI --- src/Panel.tsx | 6 +- src/index.ts | 2 +- .../VisualTests/BuildProgress.stories.ts | 81 +++++++++++++++++++ src/screens/VisualTests/BuildProgress.tsx | 53 ++++++++++++ .../VisualTests/BuildStatus.stories.ts | 20 ----- src/screens/VisualTests/BuildStatus.tsx | 5 -- .../VisualTests/VisualTests.stories.tsx | 11 +++ src/screens/VisualTests/VisualTests.tsx | 10 +++ 8 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 src/screens/VisualTests/BuildProgress.stories.ts create mode 100644 src/screens/VisualTests/BuildProgress.tsx delete mode 100644 src/screens/VisualTests/BuildStatus.stories.ts delete mode 100644 src/screens/VisualTests/BuildStatus.tsx diff --git a/src/Panel.tsx b/src/Panel.tsx index e1ce054c..aa6a25de 100644 --- a/src/Panel.tsx +++ b/src/Panel.tsx @@ -56,11 +56,14 @@ export const Panel = ({ active, api }: PanelProps) => { const [isStarting, setIsStarting] = useState(false); const [lastBuildId, setLastBuildId] = useState(storedBuildId); const [gitInfo, setGitInfo] = useState(initialGitInfo); + const [buildProgress, setBuildProgress] = useState(); const emit = useChannel( { [START_BUILD]: () => setIsStarting(true), - [BUILD_PROGRESS]: ({ step, id }: BuildProgressPayload) => { + [BUILD_PROGRESS]: (nextBuildProgress: BuildProgressPayload) => { + setBuildProgress(nextBuildProgress); + const { step, id } = nextBuildProgress; if (step === "build") { setLastBuildId(id); localStorage.setItem(DEV_BUILD_ID_KEY, id); @@ -116,6 +119,7 @@ export const Panel = ({ active, api }: PanelProps) => { projectId={projectId} gitInfo={gitInfo} isStarting={isStarting} + buildProgress={buildProgress} lastDevBuildId={lastBuildId} startDevBuild={() => isStarting || emit(START_BUILD)} setAccessToken={setAccessToken} diff --git a/src/index.ts b/src/index.ts index 418aa1af..8d7dcdad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -87,7 +87,7 @@ async function serverChannel( step = newStep; channel.emit(BUILD_PROGRESS, { step, - id: ctx.announcedBuild.id, + id: ctx.announcedBuild?.id, } satisfies BuildProgressPayload); } }, diff --git a/src/screens/VisualTests/BuildProgress.stories.ts b/src/screens/VisualTests/BuildProgress.stories.ts new file mode 100644 index 00000000..223cf5b0 --- /dev/null +++ b/src/screens/VisualTests/BuildProgress.stories.ts @@ -0,0 +1,81 @@ +import type { Meta, StoryObj } from "@storybook/react"; + +import { withFigmaDesign } from "../../utils/withFigmaDesign"; +import { BuildProgress } from "./BuildProgress"; + +const meta = { + component: BuildProgress, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Initialize: Story = { + args: { + buildProgress: { + step: "initialize", + }, + }, + parameters: withFigmaDesign( + "https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=2303-353260&mode=design&t=vlcsXN2x67tQaQdy-0" + ), +}; + +export const Build: Story = { + args: { + buildProgress: { + step: "build", + }, + }, + parameters: withFigmaDesign( + "https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=2303-353260&mode=design&t=vlcsXN2x67tQaQdy-0" + ), +}; + +export const Upload: Story = { + args: { + buildProgress: { + step: "upload", + progress: 500, + total: 1000, + }, + }, + parameters: withFigmaDesign( + "https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=2303-370243&mode=design&t=vlcsXN2x67tQaQdy-0" + ), +}; + +export const Verify: Story = { + args: { + buildProgress: { + step: "verify", + }, + }, + parameters: withFigmaDesign( + "https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=2303-371149&mode=design&t=vlcsXN2x67tQaQdy-0" + ), +}; + +export const Snapshot: Story = { + args: { + buildProgress: { + step: "snapshot", + progress: 25, + total: 50, + }, + }, + parameters: withFigmaDesign( + "https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=2303-373686&mode=design&t=vlcsXN2x67tQaQdy-0" + ), +}; + +export const Complete: Story = { + args: { + buildProgress: { + step: "complete", + }, + }, + parameters: withFigmaDesign( + "https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=2303-375342&mode=design&t=vlcsXN2x67tQaQdy-0" + ), +}; diff --git a/src/screens/VisualTests/BuildProgress.tsx b/src/screens/VisualTests/BuildProgress.tsx new file mode 100644 index 00000000..1759c50e --- /dev/null +++ b/src/screens/VisualTests/BuildProgress.tsx @@ -0,0 +1,53 @@ +import { styled } from "@storybook/theming"; +import React from "react"; + +import { BuildProgressPayload } from "../../constants"; + +export const Header = styled.div(({ theme }) => ({ + color: theme.color.darkest, + background: theme.background.app, + padding: "10px", + lineHeight: "18px", + position: "relative", +})); + +export const Bar = styled.div<{ percentage: number }>(({ theme, percentage }) => ({ + display: "block", + position: "absolute", + top: "0", + height: "100%", + left: "0", + width: `${percentage}%`, + transition: "all 150ms ease-out", + backgroundColor: "#E3F3FF", +})); + +export const Text = styled.div({ + position: "relative", + zIndex: 1, +}); + +type BuildProgressProps = { + buildProgress: BuildProgressPayload; +}; + +const messageMap: Record = { + initialize: "📦 Validating Storybook files...", + build: "📦 Validating Storybook files...", + upload: "📡 Uploading to Chromatic...", // TODO represent progress in bytes + verify: "🛠️ Initiating build...", // TODO build number + snapshot: "👀 Running visual tests...", // TODO count + complete: "🎉 Visual tests completed!", +}; + +export function BuildProgress({ buildProgress }: BuildProgressProps) { + const percentage = + (buildProgress.total ? buildProgress.progress / buildProgress.total : 0.35) * 100; + + return ( +
+   + {messageMap[buildProgress.step]} +
+ ); +} diff --git a/src/screens/VisualTests/BuildStatus.stories.ts b/src/screens/VisualTests/BuildStatus.stories.ts deleted file mode 100644 index ff727d7b..00000000 --- a/src/screens/VisualTests/BuildStatus.stories.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; - -import { withFigmaDesign } from "../../utils/withFigmaDesign"; -import { BuildStatus } from "./BuildStatus"; - -const meta = { - component: BuildStatus, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -export const Default: Story = { - args: { - // build: passedBuild, - }, - parameters: withFigmaDesign( - "https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=508-525764&t=18c1zI1SMe76dWYk-4" - ), -}; diff --git a/src/screens/VisualTests/BuildStatus.tsx b/src/screens/VisualTests/BuildStatus.tsx deleted file mode 100644 index c6bd5e57..00000000 --- a/src/screens/VisualTests/BuildStatus.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import React from "react"; - -export function BuildStatus({}) { - return
Build Status
; -} diff --git a/src/screens/VisualTests/VisualTests.stories.tsx b/src/screens/VisualTests/VisualTests.stories.tsx index 220cbc3a..e2c15475 100644 --- a/src/screens/VisualTests/VisualTests.stories.tsx +++ b/src/screens/VisualTests/VisualTests.stories.tsx @@ -302,6 +302,17 @@ export const Pending: Story = { }, }; +export const PendingWithSecondBuildInProgress: Story = { + ...Pending, + args: { + buildProgress: { + step: "upload", + progress: 1000, + total: 2000, + }, + }, +}; + export const ToggleSnapshot: Story = { parameters: { ...withBuild(pendingBuild), diff --git a/src/screens/VisualTests/VisualTests.tsx b/src/screens/VisualTests/VisualTests.tsx index 39f9415d..acd6fa6b 100644 --- a/src/screens/VisualTests/VisualTests.tsx +++ b/src/screens/VisualTests/VisualTests.tsx @@ -13,6 +13,7 @@ import { IconButton } from "../../components/IconButton"; import { ProgressIcon } from "../../components/icons/ProgressIcon"; import { Bar, Col, Row, Section, Sections, Text } from "../../components/layout"; import { Text as CenterText } from "../../components/Text"; +import { BuildProgressPayload } from "../../constants"; import { getFragment, graphql } from "../../gql"; import { AddonVisualTestsBuildQuery, @@ -24,6 +25,7 @@ import { TestStatus, } from "../../gql/graphql"; import { statusMap, StatusUpdate, testsToStatusUpdate } from "../../utils/testsToStatusUpdate"; +import { BuildProgress } from "./BuildProgress"; import { RenderSettings } from "./RenderSettings"; import { SnapshotComparison } from "./SnapshotComparison"; import { StoryInfo } from "./StoryInfo"; @@ -198,6 +200,7 @@ interface VisualTestsProps { projectId: string; gitInfo: Pick; isStarting: boolean; + buildProgress?: BuildProgressPayload; lastDevBuildId?: string; startDevBuild: () => void; setAccessToken: (accessToken: string | null) => void; @@ -207,6 +210,7 @@ interface VisualTestsProps { export const VisualTests = ({ isStarting, + buildProgress, lastDevBuildId, startDevBuild, setAccessToken, @@ -323,6 +327,9 @@ export const VisualTests = ({ ); } + // TODO -- we need to drop this when the build is selected + const buildStatus = buildProgress && ; + const tests = [ ...getFragment( FragmentStoryTestFields, @@ -337,6 +344,7 @@ export const VisualTests = ({ if (isSkipped) { return ( + {buildStatus}
This story was skipped @@ -365,6 +373,8 @@ export const VisualTests = ({ return ( + {buildStatus} +