Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Implement accept and batch accept actions using reviewTest mutation #16

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export const Tool = () => {
onClick={runDevBuild}
>
{state.isRunning ? (
<ProgressIcon onButton />
<ProgressIcon onButton="IconButton" style={{ marginRight: 6 }} />
) : (
<Icons icon="play" style={{ marginRight: 6 }} />
)}
Expand Down
4 changes: 2 additions & 2 deletions src/components/icons/ProgressIcon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { css, styled } from "@storybook/theming";

const { rotate360 } = animation;

export const ProgressIcon = styled.div<{ onButton?: boolean }>(
export const ProgressIcon = styled.div<{ onButton?: boolean | "IconButton" }>(
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
({ theme }) => ({
width: 12,
height: 12,
Expand All @@ -23,7 +23,7 @@ export const ProgressIcon = styled.div<{ onButton?: boolean }>(
({ onButton }) =>
onButton &&
css({
margin: "0 6px 0 0",
margin: onButton === "IconButton" ? 1 : 0,
borderWidth: 1,
borderLeftColor: "currentcolor",
borderBottomColor: "currentcolor",
Expand Down
5 changes: 5 additions & 0 deletions src/gql/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const documents = {
"\n query Build($hasBuildId: Boolean!, $buildId: ID!, $projectId: ID!, $branch: String!) {\n build(id: $buildId) @include(if: $hasBuildId) {\n ...BuildFields\n }\n project(id: $projectId) @skip(if: $hasBuildId) {\n name\n lastBuild(branches: [$branch]) {\n ...BuildFields\n }\n }\n }\n": types.BuildDocument,
"\n fragment BuildFields on Build {\n __typename\n id\n number\n branch\n commit\n status\n browsers {\n id\n key\n name\n }\n ... on StartedBuild {\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n ... on CompletedBuild {\n result\n changeCount: testCount(results: [ADDED, CHANGED, FIXED])\n startedAt\n tests {\n nodes {\n ...TestFields\n }\n }\n }\n }\n": types.BuildFieldsFragmentDoc,
"\n fragment TestFields on Test {\n id\n status\n result\n webUrl\n comparisons {\n id\n result\n browser {\n id\n key\n name\n version\n }\n captureDiff {\n diffImage {\n imageUrl\n }\n }\n headCapture {\n captureImage {\n imageUrl\n }\n }\n viewport {\n id\n name\n width\n isDefault\n }\n }\n parameters {\n viewport {\n id\n name\n width\n isDefault\n }\n }\n story {\n storyId\n }\n }\n": types.TestFieldsFragmentDoc,
"\n mutation ReviewTest($input: ReviewTestInput!) {\n reviewTest(input: $input) {\n updatedTests {\n id\n status\n }\n userErrors {\n ... on UserError {\n __typename\n message\n }\n ... on BuildSupersededError {\n build {\n id\n }\n }\n ... on TestUnreviewableError {\n test {\n id\n }\n }\n }\n }\n }\n": types.ReviewTestDocument,
};

/**
Expand Down Expand Up @@ -54,6 +55,10 @@ export function graphql(source: "\n fragment BuildFields on Build {\n __type
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n fragment TestFields on Test {\n id\n status\n result\n webUrl\n comparisons {\n id\n result\n browser {\n id\n key\n name\n version\n }\n captureDiff {\n diffImage {\n imageUrl\n }\n }\n headCapture {\n captureImage {\n imageUrl\n }\n }\n viewport {\n id\n name\n width\n isDefault\n }\n }\n parameters {\n viewport {\n id\n name\n width\n isDefault\n }\n }\n story {\n storyId\n }\n }\n"): (typeof documents)["\n fragment TestFields on Test {\n id\n status\n result\n webUrl\n comparisons {\n id\n result\n browser {\n id\n key\n name\n version\n }\n captureDiff {\n diffImage {\n imageUrl\n }\n }\n headCapture {\n captureImage {\n imageUrl\n }\n }\n viewport {\n id\n name\n width\n isDefault\n }\n }\n parameters {\n viewport {\n id\n name\n width\n isDefault\n }\n }\n story {\n storyId\n }\n }\n"];
/**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function graphql(source: "\n mutation ReviewTest($input: ReviewTestInput!) {\n reviewTest(input: $input) {\n updatedTests {\n id\n status\n }\n userErrors {\n ... on UserError {\n __typename\n message\n }\n ... on BuildSupersededError {\n build {\n id\n }\n }\n ... on TestUnreviewableError {\n test {\n id\n }\n }\n }\n }\n }\n"): (typeof documents)["\n mutation ReviewTest($input: ReviewTestInput!) {\n reviewTest(input: $input) {\n updatedTests {\n id\n status\n }\n userErrors {\n ... on UserError {\n __typename\n message\n }\n ... on BuildSupersededError {\n build {\n id\n }\n }\n ... on TestUnreviewableError {\n test {\n id\n }\n }\n }\n }\n }\n"];

export function graphql(source: string) {
return (documents as any)[source] ?? {};
Expand Down
10 changes: 9 additions & 1 deletion src/gql/graphql.ts

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions src/screens/VisualTests/BuildInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,23 @@ export const BuildInfo = ({
{isOutdated && (
<Col push>
<Button small secondary onClick={runDevBuild} disabled={isRunning}>
{isRunning ? <ProgressIcon onButton /> : <Icons icon="play" />}
{isRunning ? (
<ProgressIcon onButton style={{ marginRight: 6 }} />
) : (
<Icons icon="play" />
)}
Run tests
</Button>
</Col>
)}
{status === BuildStatus.Failed && (
<Col push>
<Button small secondary onClick={runDevBuild} disabled={isRunning}>
{isRunning ? <ProgressIcon onButton /> : <Icons icon="play" />}
{isRunning ? (
<ProgressIcon onButton style={{ marginRight: 6 }} />
) : (
<Icons icon="play" />
)}
Rerun tests
</Button>
</Col>
Expand Down
46 changes: 41 additions & 5 deletions src/screens/VisualTests/SnapshotComparison.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import React, { ComponentProps, useState } from "react";

import { BrowserSelector } from "../../components/BrowserSelector";
import { IconButton } from "../../components/IconButton";
import { ProgressIcon } from "../../components/icons/ProgressIcon";
import { Bar, Col } from "../../components/layout";
import { Placeholder } from "../../components/Placeholder";
import { SnapshotImage } from "../../components/SnapshotImage";
import { TooltipMenu } from "../../components/TooltipMenu";
import { ViewportSelector } from "../../components/ViewportSelector";
import { ComparisonResult, TestFieldsFragment } from "../../gql/graphql";
import { ComparisonResult, ReviewTestBatch, TestFieldsFragment } from "../../gql/graphql";

const Divider = styled.div(({ theme }) => ({
backgroundColor: theme.appBorderColor,
Expand All @@ -19,17 +21,21 @@ const Divider = styled.div(({ theme }) => ({
interface SnapshotSectionProps {
test?: TestFieldsFragment;
changeCount: number;
isAccepting: boolean;
isInProgress: boolean;
browserResults: ComponentProps<typeof BrowserSelector>["browserResults"];
viewportResults: ComponentProps<typeof ViewportSelector>["viewportResults"];
onAccept: (testId: TestFieldsFragment["id"], batch?: ReviewTestBatch) => void;
}

export const SnapshotComparison = ({
test,
changeCount,
isAccepting,
isInProgress,
browserResults,
viewportResults,
onAccept,
}: SnapshotSectionProps) => {
const [diffVisible, setDiffVisible] = useState(true);

Expand Down Expand Up @@ -76,12 +82,42 @@ export const SnapshotComparison = ({
{changeCount > 0 && (
<>
<Col push>
<IconButton secondary>Accept</IconButton>
<IconButton secondary onClick={() => onAccept(test.id)}>
Accept
</IconButton>
</Col>
<Col>
<IconButton secondary>
<Icons icon="batchaccept" />
</IconButton>
<TooltipMenu
placement="bottom"
links={[
{
id: "logout",
title: "Accept all viewports",
center: "Accept all unreviewed changes to this story",
onClick: () => onAccept(test.id, ReviewTestBatch.Spec),
disabled: isAccepting,
loading: isAccepting,
},
{
id: "learn",
title: "Accept this component",
center: "Accept all unreviewed changes for this component",
onClick: () => onAccept(test.id, ReviewTestBatch.Component),
disabled: isAccepting,
loading: isAccepting,
},
]}
>
{(active) => (
<IconButton secondary active={active}>
{isAccepting ? (
<ProgressIcon onButton="IconButton" />
) : (
<Icons icon="batchaccept" />
)}
</IconButton>
)}
</TooltipMenu>
</Col>
</>
)}
Expand Down
26 changes: 26 additions & 0 deletions src/screens/VisualTests/VisualTests.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ const withGraphQLQuery = (...args: Parameters<typeof graphql.query>) => ({
},
});

const withGraphQLMutation = (...args: Parameters<typeof graphql.mutation>) => ({
msw: {
handlers: [graphql.mutation(...args)],
},
});

const withBuild = (build: AnnouncedBuild | PublishedBuild | StartedBuild | CompletedBuild) =>
withGraphQLQuery("Build", (req, res, ctx) => res(ctx.data({ build } as BuildQuery)));

Expand Down Expand Up @@ -338,6 +344,26 @@ export const Pending: Story = {
},
};

export const Accepting: Story = {
parameters: {
msw: {
handlers: [
...withBuild(pendingBuild).msw.handlers,
...withGraphQLMutation("ReviewTest", (req, res, ctx) =>
res(ctx.status(200), ctx.data({}), ctx.delay("infinite"))
).msw.handlers,
],
},
...withFigmaDesign(
"https://www.figma.com/file/GFEbCgCVDtbZhngULbw2gP/Visual-testing-in-Storybook?type=design&node-id=508-304718&t=0rxMQnkxsVpVj1qy-4"
),
},
play: playAll(async ({ canvasElement }) => {
const button = await findByRole(canvasElement, "button", { name: "Accept" });
await fireEvent.click(button);
}),
};

export const Accepted: Story = {
parameters: {
...withBuild(acceptedBuild),
Expand Down
43 changes: 41 additions & 2 deletions src/screens/VisualTests/VisualTests.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Loader } from "@storybook/components";
import { Icon } from "@storybook/design-system";
import React, { useEffect, useState } from "react";
import { useQuery } from "urql";
import React, { useCallback, useEffect, useState } from "react";
import { useMutation, useQuery } from "urql";

import { IconButton } from "../../components/IconButton";
import { Bar, Col, Row, Section, Sections, Text } from "../../components/layout";
Expand All @@ -10,6 +10,8 @@ import { getFragment, graphql } from "../../gql";
import {
BuildQuery,
BuildQueryVariables,
ReviewTestBatch,
ReviewTestInputStatus,
TestFieldsFragment,
TestResult,
TestStatus,
Expand Down Expand Up @@ -117,6 +119,33 @@ const FragmentTestFields = graphql(/* GraphQL */ `
}
`);

const MutationReviewTest = graphql(/* GraphQL */ `
mutation ReviewTest($input: ReviewTestInput!) {
reviewTest(input: $input) {
updatedTests {
id
status
}
ghengeveld marked this conversation as resolved.
Show resolved Hide resolved
userErrors {
... on UserError {
__typename
message
}
... on BuildSupersededError {
build {
id
}
}
... on TestUnreviewableError {
test {
id
}
}
}
}
}
`);

interface VisualTestsProps {
projectId: string;
branch?: string;
Expand Down Expand Up @@ -156,6 +185,14 @@ export const VisualTests = ({
},
});

const [{ fetching: isAccepting }, reviewTest] = useMutation(MutationReviewTest);

const onAccept = useCallback(
(testId: string, batch: ReviewTestBatch) =>
reviewTest({ input: { testId, status: ReviewTestInputStatus.Accepted, batch } }),
[reviewTest]
);

useEffect(() => {
if (isRunning && data?.build && "result" in data.build) {
setIsOutdated(false);
Expand Down Expand Up @@ -309,10 +346,12 @@ export const VisualTests = ({
{...{
test,
changeCount,
isAccepting,
isInProgress,
isOutdated,
browserResults,
viewportResults,
onAccept,
}}
/>
</Section>
Expand Down