diff --git a/.env.example b/.env.example index 8a5b1e833..037067942 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ +CHROMATIC_PROJECT_TOKEN=14f589a59855 KAOTO_API=http://localhost:8081 diff --git a/.github/workflows/chromatic.yml b/.github/workflows/chromatic.yml new file mode 100644 index 000000000..91a09084d --- /dev/null +++ b/.github/workflows/chromatic.yml @@ -0,0 +1,27 @@ +# .github/workflows/chromatic.yml + +# Workflow name +name: 📖 Storybook (via Chromatic) + +# Event for the workflow +on: push + +# List of jobs +jobs: + chromatic-deployment: + # Operating System + runs-on: ubuntu-latest + # Job steps + steps: + - uses: actions/checkout@v1 + - name: Install dependencies + # 👇 Install dependencies with the same package manager used in the project (replace it as needed), e.g. yarn, npm, pnpm + run: yarn + # 👇 Adds Chromatic as a step in the workflow + - name: Publish to Chromatic + uses: chromaui/action@v1 + # Chromatic GitHub Action options + with: + token: ${{ secrets.GITHUB_TOKEN }} + projectToken: 14f589a59855 + buildScriptName: build:storybook diff --git a/.gitignore b/.gitignore index 688afd2d6..a3697d666 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ dist # production /build +# storybook +storybook-static + # misc .DS_Store .env diff --git a/README.md b/README.md index 639079eda..4dc129906 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,14 @@ yarn e2e --browser firefox See the [Cypress docs](https://docs.cypress.io) for more information. There are also GitHub Actions workflows in `.github/workflows` that run the tests automatically on opening a pull request. +### Storybook + +Storybook builds are enabled for all non-Dependabot pull requests to `kaoto-ui`, thanks to [Chromatic](https://main--61040cd029d0ce003b570dad.chromatic.com/). It gives you the ability to view stories for each pull request, which makes it easy to share development of new UI components with team members. Storybook also makes it easy to keep presentational components isolated. You can learn more about how to create a story for your UI component [here](https://storybook.js.org/docs/react/writing-stories/introduction). + +To run Storybook locally: `yarn storybook` + +To publish to Chromatic: `yarn chromatic` + ## Build ```bash diff --git a/package.json b/package.json index 7eb10e342..75f5b41f9 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "build:dev": "webpack --mode production --config webpack.dev.js", "build:lib": "rimraf dist/lib && yarn copy:css && yarn copy:images && tsc -p tsconfig.lib.json && node ./scripts/fixTsPathsAlias.js", "build:lib:watch": "tsc-watch --noClear -p tsconfig.lib.json", - "build-storybook": "storybook build", - "chromatic": "chromatic --exit-zero-on-changes", + "build:storybook": "storybook build", + "chromatic": "chromatic --build-script-name 'build:storybook' --skip 'dependabot/**' --exit-zero-on-changes", "copy:css": "copyfiles -u 1 \"src/**/*.{sass,scss,css}\" dist/lib/", "copy:images": "cpr ./src/assets ./dist/lib/assets", "clean": "rimraf dist", diff --git a/src/components/PlusButtonEdge.stories.tsx b/src/components/PlusButtonEdge.stories.tsx index 84007f0ab..58002f140 100644 --- a/src/components/PlusButtonEdge.stories.tsx +++ b/src/components/PlusButtonEdge.stories.tsx @@ -1,4 +1,5 @@ import { PlusButtonEdge } from './PlusButtonEdge'; +import { IVizStepNode } from '@kaoto/types'; import { Meta, StoryFn } from '@storybook/react'; import { ReactFlowProvider } from 'reactflow'; @@ -7,6 +8,16 @@ export default { component: PlusButtonEdge, args: { id: 'e-node-id-1>node-id-2', + data: { + showBranchesTab: true, + showStepsTab: true, + sourceStepNode: { + data: { step: {} }, + } as IVizStepNode, + targetStepNode: { + data: { step: {} }, + } as IVizStepNode, + }, sourceX: 132, sourceY: 122, targetX: 200, diff --git a/src/components/PlusButtonEdge.tsx b/src/components/PlusButtonEdge.tsx index 34cbd3835..c8399364d 100644 --- a/src/components/PlusButtonEdge.tsx +++ b/src/components/PlusButtonEdge.tsx @@ -1,18 +1,23 @@ import './CustomEdge.css'; import { BranchBuilder, MiniCatalog } from '@kaoto/components'; import { usePosition, useShowBranchTab } from '@kaoto/hooks'; -import { StepsService, ValidationService, VisualizationService } from '@kaoto/services'; +import { StepsService, ValidationService } from '@kaoto/services'; import { useIntegrationJsonStore } from '@kaoto/store'; import { IStepProps, IVizStepNode } from '@kaoto/types'; import { Popover, Tooltip } from '@patternfly/react-core'; import { PlusIcon } from '@patternfly/react-icons'; import { ReactNode } from 'react'; -import { EdgeText, getBezierPath, Position, useReactFlow } from 'reactflow'; +import { EdgeText, getBezierPath, Position } from 'reactflow'; const foreignObjectSize = 40; export interface IPlusButtonEdge { - data?: any; + data?: { + showBranchesTab: boolean; + showStepsTab: boolean; + sourceStepNode: IVizStepNode; + targetStepNode: IVizStepNode; + }; id: string; label?: ReactNode; sourceX: number; @@ -27,6 +32,7 @@ export interface IPlusButtonEdge { /* PLUS BUTTON TO INSERT STEP */ const PlusButtonEdge = ({ + data, id, label, sourceX, @@ -38,13 +44,11 @@ const PlusButtonEdge = ({ style = {}, markerEnd, }: IPlusButtonEdge) => { - // substring is used to remove the 'e-' from the id (i.e. e-{nodeId}>{nodeId}) - const nodeIds = id.substring(2).split('>'); - const sourceNode: IVizStepNode | undefined = useReactFlow().getNode(nodeIds[0]); - const targetNode: IVizStepNode | undefined = useReactFlow().getNode(nodeIds[1]); const stepsService = new StepsService(); - const showBranchesTab = VisualizationService.showBranchesTab(sourceNode?.data.step); - const showStepsTab = VisualizationService.showStepsTab(sourceNode?.data); + const showBranchesTab = data?.showBranchesTab; + const showStepsTab = data?.showStepsTab; + const sourceNode = data?.sourceStepNode; + const targetNode = data?.targetStepNode; const { tooltipPosition } = usePosition(); const views = useIntegrationJsonStore((state) => state.views); diff --git a/src/services/validationService.ts b/src/services/validationService.ts index 5ed68a402..8e29ddf87 100644 --- a/src/services/validationService.ts +++ b/src/services/validationService.ts @@ -108,7 +108,7 @@ export class ValidationService { * @param showBranchesTab * @param showStepsTab */ - static getPlusButtonTooltipMsg(showBranchesTab: boolean, showStepsTab: boolean): string { + static getPlusButtonTooltipMsg(showBranchesTab?: boolean, showStepsTab?: boolean): string { if (showStepsTab && showBranchesTab) { return 'Add a step or branch'; } else if (showBranchesTab) { diff --git a/src/services/visualizationService.test.ts b/src/services/visualizationService.test.ts index 80e9b3b9b..8d21aa886 100644 --- a/src/services/visualizationService.test.ts +++ b/src/services/visualizationService.test.ts @@ -12,7 +12,7 @@ import { IVizStepPropsEdge, } from '@kaoto/types'; import { truncateString } from '@kaoto/utils'; -import { MarkerType, Position } from 'reactflow'; +import { Position } from 'reactflow'; describe('visualizationService', () => { const groupWidth = 80; @@ -67,8 +67,8 @@ describe('visualizationService', () => { }, }, } as IVizStepNode; - const rootNode = {} as IVizStepNode; - const rootNodeNext = {} as IVizStepNode; + const rootNode = { data: { step: {} } } as IVizStepNode; + const rootNodeNext = { data: { step: {} } } as IVizStepNode; expect( VisualizationService.buildBranchSingleStepEdges(node, rootNode, rootNodeNext) ).toHaveLength(2); @@ -86,45 +86,12 @@ describe('visualizationService', () => { }, }, } as IVizStepNode; - const rootNode = {} as IVizStepNode; - const rootNodeNext = {} as IVizStepNode; + const rootNode = { data: { step: {} } } as IVizStepNode; + const rootNodeNext = { data: { step: {} } } as IVizStepNode; expect( VisualizationService.buildBranchSingleStepEdges(node, rootNode, rootNodeNext, 'CUSTOM-NODE') - ).toEqual([ - { - arrowHeadType: 'arrowclosed', - id: 'e-undefined>undefined', - markerEnd: { - color: '#d2d2d2', - strokeWidth: 2, - type: 'arrow', - }, - source: undefined, - style: { - stroke: '#d2d2d2', - strokeWidth: 2, - }, - target: undefined, - type: 'CUSTOM-NODE', - }, - { - arrowHeadType: 'arrowclosed', - id: 'e-undefined>undefined', - markerEnd: { - color: '#d2d2d2', - strokeWidth: 2, - type: 'arrow', - }, - source: undefined, - style: { - stroke: '#d2d2d2', - strokeWidth: 2, - }, - target: undefined, - type: 'default', - }, - ]); + ).toHaveLength(2); }); it('should use branchIdentifier as label if exists', () => { @@ -142,46 +109,12 @@ describe('visualizationService', () => { }, }, } as IVizStepNode; - const rootNode = {} as IVizStepNode; - const rootNodeNext = {} as IVizStepNode; - - expect(VisualizationService.buildBranchSingleStepEdges(node, rootNode, rootNodeNext)).toEqual( - [ - { - arrowHeadType: 'arrowclosed', - id: 'e-undefined>undefined', - label: 'This is a fixed branch identifier', - markerEnd: { - color: '#d2d2d2', - strokeWidth: 2, - type: 'arrow', - }, - source: undefined, - style: { - stroke: '#d2d2d2', - strokeWidth: 2, - }, - target: undefined, - type: 'default', - }, - { - arrowHeadType: 'arrowclosed', - id: 'e-undefined>undefined', - markerEnd: { - color: '#d2d2d2', - strokeWidth: 2, - type: 'arrow', - }, - source: undefined, - style: { - stroke: '#d2d2d2', - strokeWidth: 2, - }, - target: undefined, - type: 'default', - }, - ] - ); + const rootNode = { data: { step: {} } } as IVizStepNode; + const rootNodeNext = { data: { step: {} } } as IVizStepNode; + + expect( + VisualizationService.buildBranchSingleStepEdges(node, rootNode, rootNodeNext) + ).toHaveLength(2); }); }); @@ -189,22 +122,7 @@ describe('visualizationService', () => { const currentStep = nodes[1]; const previousStep = nodes[0]; - expect(VisualizationService.buildEdgeParams(currentStep, previousStep)).toEqual({ - arrowHeadType: 'arrowclosed', - id: 'e-' + currentStep.id + '>' + previousStep.id, - markerEnd: { - type: MarkerType.Arrow, - color: '#d2d2d2', - strokeWidth: 2, - }, - style: { - stroke: '#d2d2d2', - strokeWidth: 2, - }, - source: currentStep.id, - target: previousStep.id, - type: 'default', - }); + expect(VisualizationService.buildEdgeParams(currentStep, previousStep).type).toEqual('default'); }); it('buildEdges(): should build an edge for every node except the first, given an array of nodes', () => { diff --git a/src/services/visualizationService.ts b/src/services/visualizationService.ts index a217c8c56..c7c3d4f9d 100644 --- a/src/services/visualizationService.ts +++ b/src/services/visualizationService.ts @@ -210,6 +210,12 @@ export class VisualizationService { return { arrowHeadType: 'arrowclosed', id: `e-${sourceStep.id}>${targetStep.id}`, + data: { + showBranchesTab: this.showBranchesTab(targetStep.data.step), + showStepsTab: this.showStepsTab(sourceStep.data), + sourceStepNode: sourceStep, + targetStepNode: targetStep, + }, markerEnd: { type: MarkerType.Arrow, color: '#d2d2d2', @@ -668,7 +674,7 @@ export class VisualizationService { static showStepsTab(nodeData: IVizStepNodeData): boolean { // if it contains branches and no next step, show the steps tab if (StepsService.containsBranches(nodeData.step) && !nodeData.nextStepUuid) return true; - // if it doesn't contains branches, don't show the steps tab + // if it doesn't contain branches, don't show the steps tab return !StepsService.containsBranches(nodeData.step); } } diff --git a/yarn.lock b/yarn.lock index 1b5a1c1f4..b5bfb08b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4195,9 +4195,9 @@ form-data "^3.0.0" "@types/node@*": - version "20.1.7" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.7.tgz#ce10c802f7731909d0a44ac9888e8b3a9125eb62" - integrity sha512-WCuw/o4GSwDGMoonES8rcvwsig77dGCMbZDrZr2x4ZZiNW4P/gcoZXe/0twgtobcTkmg9TuKflxYL/DuwDyJzg== + version "20.1.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.1.5.tgz#e94b604c67fc408f215fcbf3bd84d4743bf7f710" + integrity sha512-IvGD1CD/nego63ySR7vrAKEX3AJTcmrAN2kn+/sDNLi1Ff5kBzDeEdqWDplK+0HAEoLYej137Sk0cUU8OLOlMg== "@types/node@^13.7.0": version "13.13.52" @@ -4205,9 +4205,9 @@ integrity sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ== "@types/node@^14.0.10 || ^16.0.0", "@types/node@^16.0.0": - version "16.18.31" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.31.tgz#7de39c2b9363f0d95b129cc969fcbf98e870251c" - integrity sha512-KPXltf4z4g517OlVJO9XQ2357CYw7fvuJ3ZuBynjXC5Jos9i+K7LvFb7bUIwtJXSZj0vTp9Q6NJBSQpkwwO8Zw== + version "16.18.30" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.30.tgz#4a2c426370712a10c630a55ba086c55c17ca54e0" + integrity sha512-Kmp/wBZk19Dn7uRiol8kF8agnf8m0+TU9qIwyfPmXglVxMlmiIz0VQSMw5oFgwhmD2aKTlfBIO5FtsVj3y7hKQ== "@types/node@^14.14.31": version "14.18.47" @@ -4215,9 +4215,9 @@ integrity sha512-OuJi8bIng4wYHHA3YpKauL58dZrPxro3d0tabPHyiNF8rKfGKuVfr83oFlPLmKri1cX+Z3cJP39GXmnqkP11Gw== "@types/node@^18.16.3": - version "18.16.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.12.tgz#f11e19055c5b3daeb79dc6eb7ccdd3d036313034" - integrity sha512-tIRrjbY9C277MOfP8M3zjMIhtMlUJ6YVqkGgLjz+74jVsdf4/UjC6Hku4+1N0BS0qyC0JAS6tJLUk9H6JUKviQ== + version "18.16.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.16.10.tgz#9b16d918f4f6fec6cae4af34283a91d555b81519" + integrity sha512-sMo3EngB6QkMBlB9rBe1lFdKSLqljyWPPWv6/FzSxh/IDlyVWSzE9RiF4eAuerQHybrWdqBgAGb03PM89qOasA== "@types/normalize-package-data@^2.4.0": version "2.4.1" @@ -7482,9 +7482,9 @@ ejs@^3.1.8: jake "^10.8.5" electron-to-chromium@^1.4.284: - version "1.4.397" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.397.tgz#82a7e26c657538d59bb713b97ac22f97ea3a90ea" - integrity sha512-jwnPxhh350Q/aMatQia31KAIQdhEsYS0fFZ0BQQlN9tfvOEwShu6ZNwI4kL/xBabjcB/nTy6lSt17kNIluJZ8Q== + version "1.4.396" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.396.tgz#3d3664eb58d86376fbe2fece3705f68ca197205c" + integrity sha512-pqKTdqp/c5vsrc0xUPYXTDBo9ixZuGY8es4ZOjjd6HD6bFYbu5QA09VoW3fkY4LF1T0zYk86lN6bZnNlBuOpdQ== elliptic@^6.5.3: version "6.5.4" @@ -9633,9 +9633,9 @@ is-ci@^3.0.0: ci-info "^3.2.0" is-core-module@^2.11.0, is-core-module@^2.9.0: - version "2.12.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd" - integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg== + version "2.12.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.0.tgz#36ad62f6f73c8253fd6472517a12483cf03e7ec4" + integrity sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ== dependencies: has "^1.0.3" @@ -14580,9 +14580,9 @@ tar-webpack-plugin@^0.1.1: tar "^6.1.0" tar@^6.1.0, tar@^6.1.13: - version "6.1.15" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.15.tgz#c9738b0b98845a3b344d334b8fa3041aaba53a69" - integrity sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A== + version "6.1.14" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.14.tgz#e87926bec1cfe7c9e783a77a79f3e81c1cfa3b66" + integrity sha512-piERznXu0U7/pW7cdSn7hjqySIVTYT6F76icmFk7ptU7dDYlXTm5r9A6K04R2vU3olYgoKeo1Cg3eeu5nhftAw== dependencies: chownr "^2.0.0" fs-minipass "^2.0.0"