diff --git a/.storybook/main.ts b/.storybook/main.ts index d672bac76..e97e1f08f 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -56,7 +56,6 @@ const config: StorybookConfig = { ...resolve, alias: { ...alias, - "@/services/app-service": path.resolve(__dirname, "../source/javascripts/services/app-service.mock.ts"), "@": path.resolve(__dirname, "../source/javascripts"), }, }; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index d54d2f510..4274b49dd 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,8 +1,9 @@ -import React from "react"; +import "@/typings/globals.d.ts"; + import { Provider } from "@bitrise/bitkit"; import type { Preview } from "@storybook/react"; import { initialize, mswLoader } from "msw-storybook-addon"; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; initialize(); @@ -13,9 +14,42 @@ const queryClient = new QueryClient({ retry: 3, }, }, -}) +}); const preview: Preview = { + beforeEach: () => { + process.env.MODE = "website"; + window.parent.globalProps = { + user: { + slug: "user-1", + username: "ninja", + }, + account: { + slug: "account-1", + name: "Mando", + }, + }; + window.parent.pageProps = { + abilities: { + canRunBuilds: true, + }, + limits: { + uniqueStepLimit: undefined, + }, + project: { + slug: "asd-123", + name: "Mock Project", + defaultBranch: "main", + buildTriggerToken: "bt-1", + }, + }; + + return () => { + process.env.MODE = "cli"; + window.parent.globalProps = undefined; + window.parent.pageProps = undefined; + }; + }, parameters: { actions: { argTypesRegex: "^on[A-Z].*" }, controls: { @@ -34,9 +68,7 @@ const preview: Preview = { ), ], - loaders: [ - mswLoader, - ], + loaders: [mswLoader], }; export default preview; diff --git a/bitrise-plugin.yml b/bitrise-plugin.yml index ec8a75f29..a53ecde6f 100644 --- a/bitrise-plugin.yml +++ b/bitrise-plugin.yml @@ -2,9 +2,9 @@ name: workflow-editor description: |- Bitrise Workflow Editor. executable: - osx: https://github.com/bitrise-io/bitrise-workflow-editor/releases/download/1.3.502/workflow-editor-Darwin-x86_64 - osx-arm64: https://github.com/bitrise-io/bitrise-workflow-editor/releases/download/1.3.502/workflow-editor-Darwin-arm64 - linux: https://github.com/bitrise-io/bitrise-workflow-editor/releases/download/1.3.502/workflow-editor-Linux-x86_64 + osx: https://github.com/bitrise-io/bitrise-workflow-editor/releases/download/1.3.510/workflow-editor-Darwin-x86_64 + osx-arm64: https://github.com/bitrise-io/bitrise-workflow-editor/releases/download/1.3.510/workflow-editor-Darwin-arm64 + linux: https://github.com/bitrise-io/bitrise-workflow-editor/releases/download/1.3.510/workflow-editor-Linux-x86_64 requirements: - tool: bitrise min_version: 1.18.0 diff --git a/package-lock.json b/package-lock.json index 7f8f1f230..9a03c4623 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bitrise-workflow-editor", - "version": "1.3.502", + "version": "1.3.510", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bitrise-workflow-editor", - "version": "1.3.502", + "version": "1.3.510", "license": "MIT", "dependencies": { "@bitrise/bitkit": "^13.132.0", diff --git a/package.json b/package.json index 17832fe3b..30caa9c3c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Bitrise", "license": "MIT", "name": "bitrise-workflow-editor", - "version": "1.3.502", + "version": "1.3.510", "description": "Bitrise workflow editor", "homepage": "https://github.com/bitrise-io/bitrise-workflow-editor#readme", "repository": { diff --git a/source/javascripts/_componentRegister.js b/source/javascripts/_componentRegister.js index b91d1055a..1bf4bcec3 100644 --- a/source/javascripts/_componentRegister.js +++ b/source/javascripts/_componentRegister.js @@ -140,25 +140,21 @@ angular .component( "rWorkflowToolbar", register(WorkflowToolbar, [ - "organizationSlug", - "defaultBranch", - "uniqueStepCount", - "uniqueStepLimit", "workflows", "selectedWorkflow", "selectWorkflow", "createWorkflow", "chainWorkflow", - "deleteSelectedWorkflow", + "deleteWorkflow", "rearrangeWorkflows", + "uniqueStepCount", "canRunWorkflow", "isRunWorkflowDisabled", - "runWorkflow", ]), ) .component( "rWorkflowEmptyState", - register(WorkflowEmptyState, ["onAddWorkflow"]), + register(WorkflowEmptyState, ["onCreateWorkflow"]), ) .component( "rWorkflowRecipesInfoBanner", @@ -213,7 +209,6 @@ angular "onLoadSecrets", "onCreateEnvVar", "onLoadEnvVars", - "appSlug", "secretsWriteNew", ]), ) diff --git a/source/javascripts/components/ConfigurationYmlSource/ConfigurationYmlSourceDialog.tsx b/source/javascripts/components/ConfigurationYmlSource/ConfigurationYmlSourceDialog.tsx index ebe90c5b0..b6f055efc 100644 --- a/source/javascripts/components/ConfigurationYmlSource/ConfigurationYmlSourceDialog.tsx +++ b/source/javascripts/components/ConfigurationYmlSource/ConfigurationYmlSourceDialog.tsx @@ -21,7 +21,6 @@ import CopyToClipboard from 'react-copy-to-clipboard'; import useGetAppConfigFromRepoCallback from '@/hooks/api/useGetAppConfigFromRepoCallback'; import useUpdatePipelineConfigCallback from '@/hooks/api/useUpdatePipelineConfigCallback'; import usePostAppConfigCallback from '@/hooks/api/usePostAppConfigCallback'; -import useFeatureFlag from '@/hooks/useFeatureFlag'; import { AppConfig } from '@/models/AppConfig'; import DateFormatter from '@/utils/dateFormatter'; import { segmentTrack } from '@/utils/segmentTracking'; @@ -187,8 +186,6 @@ const ConfigurationYmlSourceDialog = (props: ConfigurationYmlSourceDialogProps) const isDialogDisabled = getAppConfigFromRepoLoading || updatePipelineConfigLoading || postAppConfigLoading; - const isModularYAMLMentionsEnabled = useFeatureFlag('enable-modular-yaml-mentions'); - const onCopyClick = () => { toast({ title: ' Copied to clipboard', @@ -258,18 +255,16 @@ const ConfigurationYmlSourceDialog = (props: ConfigurationYmlSourceDialogProps) > - Multiple configuration files will be merged into a single file.{' '} - - Learn more - - - ) : undefined + <> + Multiple configuration files will be merged into a single file.{' '} + + Learn more + + } marginBlockEnd="12" value="git" @@ -353,28 +348,26 @@ const ConfigurationYmlSourceDialog = (props: ConfigurationYmlSourceDialogProps) - {isModularYAMLMentionsEnabled && ( - - Split up your configuration{' '} - - (optional) - - - - Follow this guide - {' '} - to split up your configuration into smaller, more manageable files. This feature is only available - for Workspaces on{' '} - - Enterprise plan. - + + Split up your configuration{' '} + + (optional) + + + + Follow this guide + {' '} + to split up your configuration into smaller, more manageable files. This feature is only available for + Workspaces on{' '} + + Enterprise plan. - - )} + + )} diff --git a/source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx b/source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx index df994cf5d..2f10ef04c 100644 --- a/source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx +++ b/source/javascripts/components/UpdateConfigurationDialog/UpdateConfigurationDialog.tsx @@ -6,7 +6,6 @@ import YmlNotFoundInRepositoryError from '../common/notifications/YmlNotFoundInR import YmlInRepositoryInvalidError from '../common/notifications/YmlInRepositoryInvalidError'; import { useFormattedYml } from '../common/RepoYmlStorageActions'; import { AppConfig } from '../../models/AppConfig'; -import useFeatureFlag from '../../hooks/useFeatureFlag'; import { segmentTrack } from '../../utils/segmentTracking'; type UpdateConfigurationDialogProps = { @@ -51,8 +50,6 @@ const UpdateConfigurationDialog = (props: UpdateConfigurationDialogProps) => { const toast = useToast(); - const isModularYAMLMentionsEnabled = useFeatureFlag('enable-modular-yaml-mentions'); - const onCopyClick = () => { toast({ title: ' Copied to clipboard', @@ -107,14 +104,10 @@ const UpdateConfigurationDialog = (props: UpdateConfigurationDialogProps) => { - {isModularYAMLMentionsEnabled && ( - <> - - Using multiple configuration files - - You need to re-create the changes in the relevant configuration file on your Git repository. - - )} + + Using multiple configuration files + + You need to re-create the changes in the relevant configuration file on your Git repository. {getAppConfigFromRepoFailed && renderError()} diff --git a/source/javascripts/components/WorkflowEmptyState.tsx b/source/javascripts/components/WorkflowEmptyState.tsx deleted file mode 100644 index c369d0510..000000000 --- a/source/javascripts/components/WorkflowEmptyState.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Button, EmptyState } from '@bitrise/bitkit'; - -type Props = { - onAddWorkflow: () => void; -}; - -const WorkflowEmptyState = ({ onAddWorkflow }: Props) => ( - - - -); - -export default WorkflowEmptyState; diff --git a/source/javascripts/components/YmlEditorHeader/YmlEditorHeader.tsx b/source/javascripts/components/YmlEditorHeader/YmlEditorHeader.tsx index 11cc588f5..db5378b30 100644 --- a/source/javascripts/components/YmlEditorHeader/YmlEditorHeader.tsx +++ b/source/javascripts/components/YmlEditorHeader/YmlEditorHeader.tsx @@ -3,7 +3,6 @@ import { Box, Button, DataWidget, DataWidgetItem, Text, Tooltip, useDisclosure } import { useUserMetaData } from '@/hooks/useUserMetaData'; import { AppConfig } from '@/models/AppConfig'; import ConfigurationYmlSourceDialog from '../ConfigurationYmlSource/ConfigurationYmlSourceDialog'; -import useFeatureFlag from '../../hooks/useFeatureFlag'; import { segmentTrack } from '../../utils/segmentTracking'; import SplitNotification from './SplitNotification'; import GitNotification from './GitNotification'; @@ -63,8 +62,6 @@ const YmlEditorHeader = (props: YmlEditorHeaderProps) => { const isChangeEnabled = repositoryYmlAvailable || initialUsesRepositoryYml === true; - const isModularYAMLMentionsEnabled = useFeatureFlag('enable-modular-yaml-mentions'); - const onYmlSourceChangeClick = () => { onOpen(); segmentTrack('Change Configuration Yml Source Button Clicked', { @@ -127,7 +124,7 @@ const YmlEditorHeader = (props: YmlEditorHeaderProps) => { )} - {isModularYAMLMentionsEnabled && isSplitNotiVisible && ( + {isSplitNotiVisible && ( )} {isGitNotiVisible && } diff --git a/source/javascripts/components/_Step.js b/source/javascripts/components/_Step.js index 06c8e1fd0..ebbb709f4 100644 --- a/source/javascripts/components/_Step.js +++ b/source/javascripts/components/_Step.js @@ -1,267 +1,305 @@ -(function() { - "use strict"; - - angular.module("BitriseWorkflowEditor").factory("Step", function($injector, Variable) { - var MAINTAINER = { - VERIFIED: "verified", OFFICIAL: "bitrise", COMMUNITY: "community" - }; - - var Step = function(cvs, userStepConfig, defaultStepConfig) { - this.cvs = cvs; - this.localPath; - this.gitURL; - this.libraryURL; - this.id; - this.version; - this.info = {}; - - this.userStepConfig = userStepConfig; - if (!this.userStepConfig) { - this.userStepConfig = {}; - } - - this.defaultStepConfig = defaultStepConfig; - }; - - Step.prototype.isStepBundle = function() { - return !!this.cvs && this.cvs.startsWith('bundle::'); - } - - Step.prototype.isWithBlock = function() { - return this.cvs === 'with'; - } - - Step.prototype.displayName = function() { - if (this.isStepBundle()) { - return 'Step bundle: ' + this.cvs.replace('bundle::', ''); - } - if (this.isWithBlock()) { - return 'With group'; - } - if (this.title()) { - return this.title(); - } - - if (this.id) { - return this.id; - } - - return diplayNameFromCvs(this.displayCvs()); - }; - - Step.prototype.displayCvs = function() { - return this.cvs.replace(/^(git|path)::/g, ""); - }; - - Step.prototype.displayTooltip = function() { - return this.displayName() + "\n" + this.displayCvs(); - }; - - function diplayNameFromCvs(cvs) { - var lastDelimiter = cvs.lastIndexOf("/"); - - if (lastDelimiter != -1) { - cvs = cvs.substring(lastDelimiter + 1); - } - - return cvs; - } - - Step.prototype.title = function(newTitle) { - return parameterGetterSetter(this, "title", newTitle); - }; - - Step.isValidTitle = function(title) { - if (title === undefined) { - return undefined; - } - - return title && title.length > 0; - }; - - Step.prototype.summary = function(newSummary) { - return parameterGetterSetter(this, "summary", newSummary); - }; - - Step.prototype.description = function(newDescription) { - return parameterGetterSetter(this, "description", newDescription); - }; - - Step.prototype.sourceURL = function(newSourceURL) { - if (this.gitURL !== undefined) { - if (newSourceURL) { - this.gitURL = newSourceURL; - } - - return this.gitURL; - } - - return parameterGetterSetter(this, "source_code_url", newSourceURL); - }; - - Step.prototype.iconURL = function(newIconURL) { - if (newIconURL !== undefined) { - var regexpForIconType = new RegExp("^.*.(svg|png)"); - var iconType; - var iconTypeKey; - - if (regexpForIconType.test(newIconURL) && regexpForIconType.exec(newIconURL)[1]) { - iconType = regexpForIconType.exec(newIconURL)[1]; - iconTypeKey = "icon." + iconType; - } else { - return this.iconURL(); - } - - if (this.defaultStepConfig && this.defaultStepConfig.asset_urls[iconTypeKey] && this.defaultStepConfig.asset_urls[iconTypeKey] == newIconURL) { - if (this.userStepConfig.asset_urls) { - delete this.userStepConfig.asset_urls[iconTypeKey]; - - if (_.isEmpty(this.userStepConfig.asset_urls)) { - delete this.userStepConfig["asset_urls"]; - } - } - } else { - if (!this.userStepConfig.asset_urls) { - this.userStepConfig.asset_urls = []; - } - - this.userStepConfig.asset_urls[iconTypeKey] = newIconURL; - } - } - - if (this.userStepConfig.asset_urls) { - if (this.userStepConfig.asset_urls["icon.svg"]) { - return this.userStepConfig.asset_urls["icon.svg"]; - } - - if (this.userStepConfig.asset_urls["icon.png"]) { - return this.userStepConfig.asset_urls["icon.png"]; - } - } - - if (this.defaultStepConfig && this.defaultStepConfig.asset_urls) { - if (this.defaultStepConfig.asset_urls["icon.svg"]) { - return this.defaultStepConfig.asset_urls["icon.svg"]; - } - - return this.defaultStepConfig.asset_urls["icon.png"]; - } - - return undefined; - }; - - Step.prototype.typeTags = function(newTypeTags) { - return parameterGetterSetter(this, "type_tags", newTypeTags); - }; - - Step.prototype.projectTypeTags = function(newProjectTypeTags) { - return parameterGetterSetter(this, "project_type_tags", newProjectTypeTags); - }; - - Step.prototype.runIf = function(newRunIf) { - return parameterGetterSetter(this, "run_if", newRunIf); - }; - - Step.prototype.isAlwaysRun = function(newIsAlwaysRun) { - return parameterGetterSetter(this, "is_always_run", newIsAlwaysRun); - }; - - Step.prototype.assetUrls = function(newAssetUrls) { - return parameterGetterSetter(this, "asset_urls", newAssetUrls); - }; - - Step.prototype.isConfigured = function() { - return !!this.defaultStepConfig; - }; - - Step.prototype.isVerified = function() { - return this.info.maintainer === MAINTAINER.VERIFIED && !this.isDeprecated(); - }; - - Step.prototype.isOfficial = function() { - return this.info.maintainer === MAINTAINER.OFFICIAL && !this.isDeprecated(); - }; - - Step.prototype.isDeprecated = function() { - return parameterGetterSetter(this, "is_deprecated"); - }; - - Step.prototype.isLocal = function() { - return !!this.localPath; - }; - - Step.prototype.isLibraryStep = function() { - return !!this.libraryURL; - }; - - Step.prototype.isVCSStep = function() { - return !this.isLocal() && !this.isLibraryStep(); - }; - - Step.prototype.requestedVersion = function() { - if (this.cvs.indexOf("@") == -1) { - return null; - } - - return this.version; - }; - - Step.cvsFromWrappedStepConfig = function(wrappedStepConfig) { - return _.first(_.keys(angular.fromJson(angular.toJson(wrappedStepConfig)))); - }; - - Step.prototype.wrappedUserStepConfig = function() { - var wrappedUserStepConfig = {}; - wrappedUserStepConfig[this.cvs] = this.userStepConfig; - - return wrappedUserStepConfig; - }; - - function parameterGetterSetter(step, parameterKey, parameterValue) { - if (parameterValue === undefined) { - if (step.userStepConfig[parameterKey] !== undefined) { - return step.userStepConfig[parameterKey]; - } - - return step.defaultStepConfig ? step.defaultStepConfig[parameterKey] : undefined; - } - - if (!step.defaultStepConfig || parameterValue != step.defaultStepConfig[parameterKey]) { - step.userStepConfig[parameterKey] = parameterValue; - } else if (step.userStepConfig[parameterKey] !== undefined) { - delete step.userStepConfig[parameterKey]; - } - - return parameterValue; - } - - return Step; - }); - - angular.module("BitriseWorkflowEditor").filter("stepSourceCSSClass", function() { - return function(step) { - if (!step) { - return undefined; - } - - var sourceURL = step.sourceURL(); - - var regexpForGithubStepSourceURL = new RegExp("^(?:https?://)?(?:www.)?github.com/.+"); - if (regexpForGithubStepSourceURL.test(sourceURL)) { - return "github"; - } - - var regexpForBitbucketStepSourceURL = new RegExp("^(?:https?://)?(?:www.)?bitbucket.(?:com|org)/.+"); - if (regexpForBitbucketStepSourceURL.test(sourceURL)) { - return "bitbucket"; - } - - var regexpForGitlabStepSourceURL = new RegExp("^(?:https?://)?(?:www.)?gitlab.com/.+"); - if (regexpForGitlabStepSourceURL.test(sourceURL)) { - return "gitlab"; - } - - return "unknown"; - }; - }); +(function () { + "use strict"; + + angular + .module("BitriseWorkflowEditor") + .factory("Step", function ($injector, Variable) { + var MAINTAINER = { + VERIFIED: "verified", + OFFICIAL: "bitrise", + COMMUNITY: "community", + }; + + var Step = function (cvs, userStepConfig, defaultStepConfig) { + this.cvs = cvs; + this.localPath; + this.gitURL; + this.libraryURL; + this.id; + this.version; + this.info = {}; + + this.userStepConfig = userStepConfig; + if (!this.userStepConfig) { + this.userStepConfig = {}; + } + + this.defaultStepConfig = defaultStepConfig; + }; + + Step.prototype.isStepBundle = function () { + return !!this.cvs && this.cvs.startsWith("bundle::"); + }; + + Step.prototype.isWithBlock = function () { + return this.cvs === "with"; + }; + + Step.prototype.displayName = function () { + if (this.isStepBundle()) { + return "Step bundle: " + this.cvs.replace("bundle::", ""); + } + if (this.isWithBlock()) { + return "With group"; + } + if (this.title()) { + return this.title(); + } + + if (this.id) { + return this.id; + } + + return diplayNameFromCvs(this.displayCvs()); + }; + + Step.prototype.displayCvs = function () { + return this.cvs.replace(/^(git|path)::/g, ""); + }; + + Step.prototype.displayTooltip = function () { + return this.displayName() + "\n" + this.displayCvs(); + }; + + function diplayNameFromCvs(cvs) { + var lastDelimiter = cvs.lastIndexOf("/"); + + if (lastDelimiter != -1) { + cvs = cvs.substring(lastDelimiter + 1); + } + + return cvs; + } + + Step.prototype.title = function (newTitle) { + return parameterGetterSetter(this, "title", newTitle); + }; + + Step.isValidTitle = function (title) { + if (title === undefined) { + return undefined; + } + + return title && title.length > 0; + }; + + Step.prototype.summary = function (newSummary) { + return parameterGetterSetter(this, "summary", newSummary); + }; + + Step.prototype.description = function (newDescription) { + return parameterGetterSetter(this, "description", newDescription); + }; + + Step.prototype.sourceURL = function (newSourceURL) { + if (this.gitURL !== undefined) { + if (newSourceURL) { + this.gitURL = newSourceURL; + } + + return this.gitURL; + } + + return parameterGetterSetter(this, "source_code_url", newSourceURL); + }; + + Step.prototype.iconURL = function (newIconURL) { + if (newIconURL !== undefined) { + var regexpForIconType = new RegExp("^.*.(svg|png)"); + var iconType; + var iconTypeKey; + + if ( + regexpForIconType.test(newIconURL) && + regexpForIconType.exec(newIconURL)[1] + ) { + iconType = regexpForIconType.exec(newIconURL)[1]; + iconTypeKey = "icon." + iconType; + } else { + return this.iconURL(); + } + + if ( + this.defaultStepConfig && + this.defaultStepConfig.asset_urls[iconTypeKey] && + this.defaultStepConfig.asset_urls[iconTypeKey] == newIconURL + ) { + if (this.userStepConfig.asset_urls) { + delete this.userStepConfig.asset_urls[iconTypeKey]; + + if (_.isEmpty(this.userStepConfig.asset_urls)) { + delete this.userStepConfig["asset_urls"]; + } + } + } else { + if (!this.userStepConfig.asset_urls) { + this.userStepConfig.asset_urls = []; + } + + this.userStepConfig.asset_urls[iconTypeKey] = newIconURL; + } + } + + if (this.userStepConfig.asset_urls) { + if (this.userStepConfig.asset_urls["icon.svg"]) { + return this.userStepConfig.asset_urls["icon.svg"]; + } + + if (this.userStepConfig.asset_urls["icon.png"]) { + return this.userStepConfig.asset_urls["icon.png"]; + } + } + + if (this.defaultStepConfig && this.defaultStepConfig.asset_urls) { + if (this.defaultStepConfig.asset_urls["icon.svg"]) { + return this.defaultStepConfig.asset_urls["icon.svg"]; + } + + return this.defaultStepConfig.asset_urls["icon.png"]; + } + + return undefined; + }; + + Step.prototype.typeTags = function (newTypeTags) { + return parameterGetterSetter(this, "type_tags", newTypeTags); + }; + + Step.prototype.projectTypeTags = function (newProjectTypeTags) { + return parameterGetterSetter( + this, + "project_type_tags", + newProjectTypeTags, + ); + }; + + Step.prototype.runIf = function (newRunIf) { + return parameterGetterSetter(this, "run_if", newRunIf); + }; + + Step.prototype.isAlwaysRun = function (newIsAlwaysRun) { + return parameterGetterSetter(this, "is_always_run", newIsAlwaysRun); + }; + + Step.prototype.isSkippable = function (newIsSkippable) { + return parameterGetterSetter(this, "is_skippable", newIsSkippable); + }; + + Step.prototype.assetUrls = function (newAssetUrls) { + return parameterGetterSetter(this, "asset_urls", newAssetUrls); + }; + + Step.prototype.isConfigured = function () { + return !!this.defaultStepConfig; + }; + + Step.prototype.isVerified = function () { + return ( + this.info.maintainer === MAINTAINER.VERIFIED && !this.isDeprecated() + ); + }; + + Step.prototype.isOfficial = function () { + return ( + this.info.maintainer === MAINTAINER.OFFICIAL && !this.isDeprecated() + ); + }; + + Step.prototype.isDeprecated = function () { + return parameterGetterSetter(this, "is_deprecated"); + }; + + Step.prototype.isLocal = function () { + return !!this.localPath; + }; + + Step.prototype.isLibraryStep = function () { + return !!this.libraryURL; + }; + + Step.prototype.isVCSStep = function () { + return !this.isLocal() && !this.isLibraryStep(); + }; + + Step.prototype.requestedVersion = function () { + if (this.cvs.indexOf("@") == -1) { + return null; + } + + return this.version; + }; + + Step.cvsFromWrappedStepConfig = function (wrappedStepConfig) { + return _.first( + _.keys(angular.fromJson(angular.toJson(wrappedStepConfig))), + ); + }; + + Step.prototype.wrappedUserStepConfig = function () { + var wrappedUserStepConfig = {}; + wrappedUserStepConfig[this.cvs] = this.userStepConfig; + + return wrappedUserStepConfig; + }; + + function parameterGetterSetter(step, parameterKey, parameterValue) { + if (parameterValue === undefined) { + if (step.userStepConfig[parameterKey] !== undefined) { + return step.userStepConfig[parameterKey]; + } + + return step.defaultStepConfig + ? step.defaultStepConfig[parameterKey] + : undefined; + } + + if ( + !step.defaultStepConfig || + parameterValue != step.defaultStepConfig[parameterKey] + ) { + step.userStepConfig[parameterKey] = parameterValue; + } else if (step.userStepConfig[parameterKey] !== undefined) { + delete step.userStepConfig[parameterKey]; + } + + return parameterValue; + } + + return Step; + }); + + angular + .module("BitriseWorkflowEditor") + .filter("stepSourceCSSClass", function () { + return function (step) { + if (!step) { + return undefined; + } + + var sourceURL = step.sourceURL(); + + var regexpForGithubStepSourceURL = new RegExp( + "^(?:https?://)?(?:www.)?github.com/.+", + ); + if (regexpForGithubStepSourceURL.test(sourceURL)) { + return "github"; + } + + var regexpForBitbucketStepSourceURL = new RegExp( + "^(?:https?://)?(?:www.)?bitbucket.(?:com|org)/.+", + ); + if (regexpForBitbucketStepSourceURL.test(sourceURL)) { + return "bitbucket"; + } + + var regexpForGitlabStepSourceURL = new RegExp( + "^(?:https?://)?(?:www.)?gitlab.com/.+", + ); + if (regexpForGitlabStepSourceURL.test(sourceURL)) { + return "gitlab"; + } + + return "unknown"; + }; + }); })(); diff --git a/source/javascripts/pages/WorkflowsPage/components.new/CreateWorkflowDialog/CreateWorkflowDialog.stories.tsx b/source/javascripts/components/unified-editor/CreateWorkflowDialog/CreateWorkflowDialog.stories.tsx similarity index 100% rename from source/javascripts/pages/WorkflowsPage/components.new/CreateWorkflowDialog/CreateWorkflowDialog.stories.tsx rename to source/javascripts/components/unified-editor/CreateWorkflowDialog/CreateWorkflowDialog.stories.tsx diff --git a/source/javascripts/pages/WorkflowsPage/components.new/CreateWorkflowDialog/CreateWorkflowDialog.tsx b/source/javascripts/components/unified-editor/CreateWorkflowDialog/CreateWorkflowDialog.tsx similarity index 97% rename from source/javascripts/pages/WorkflowsPage/components.new/CreateWorkflowDialog/CreateWorkflowDialog.tsx rename to source/javascripts/components/unified-editor/CreateWorkflowDialog/CreateWorkflowDialog.tsx index c15b9d5a3..97052510d 100644 --- a/source/javascripts/pages/WorkflowsPage/components.new/CreateWorkflowDialog/CreateWorkflowDialog.tsx +++ b/source/javascripts/components/unified-editor/CreateWorkflowDialog/CreateWorkflowDialog.tsx @@ -61,6 +61,7 @@ const CreateWorkflowDialog = ({ onCreateWorkflow, ...disclosureProps }: Props) = label="Name" placeholder="Workflow name" errorText={errors.workflowId?.message} + inputRef={(ref) => ref?.setAttribute('data-1p-ignore', '')} {...register('workflowId', { validate: (v) => WorkflowService.validateName(v, workflowIds), })} diff --git a/source/javascripts/components/unified-editor/RunWorkflowDialog/RunWorkflowDialog.stories.tsx b/source/javascripts/components/unified-editor/RunWorkflowDialog/RunWorkflowDialog.stories.tsx index 505653cf3..8b12a6784 100644 --- a/source/javascripts/components/unified-editor/RunWorkflowDialog/RunWorkflowDialog.stories.tsx +++ b/source/javascripts/components/unified-editor/RunWorkflowDialog/RunWorkflowDialog.stories.tsx @@ -1,20 +1,28 @@ -import { Meta, StoryObj } from '@storybook/react'; +import { Meta } from '@storybook/react'; +import BuildApiMocks from '@/core/api/BuildApi.mswMocks'; import RunWorkflowDialog from './RunWorkflowDialog'; export default { component: RunWorkflowDialog, args: { isOpen: true, - defaultBranch: 'master', workflowId: 'primary-workflow', }, argTypes: { - isOpen: { type: 'boolean', control: { type: 'boolean' } }, - defaultBranch: { type: 'string' }, workflowId: { type: 'string' }, + isOpen: { type: 'boolean', control: { type: 'boolean' } }, onClose: { type: 'function' }, - onAction: { type: 'function' }, }, } as Meta; -export const Default: StoryObj = {}; +export const Default = { + parameters: { + msw: [BuildApiMocks.startBuild('success')], + }, +}; + +export const Error = { + parameters: { + msw: [BuildApiMocks.startBuild('error')], + }, +}; diff --git a/source/javascripts/components/unified-editor/RunWorkflowDialog/RunWorkflowDialog.tsx b/source/javascripts/components/unified-editor/RunWorkflowDialog/RunWorkflowDialog.tsx index 296727063..bc91b044c 100644 --- a/source/javascripts/components/unified-editor/RunWorkflowDialog/RunWorkflowDialog.tsx +++ b/source/javascripts/components/unified-editor/RunWorkflowDialog/RunWorkflowDialog.tsx @@ -1,22 +1,40 @@ import { useState } from 'react'; -import { Button, Dialog, DialogBody, DialogFooter, Input } from '@bitrise/bitkit'; +import { Button, Dialog, DialogBody, DialogFooter, Input, useToast } from '@bitrise/bitkit'; import { DialogProps } from '@bitrise/bitkit/src/Components/Dialog/Dialog'; +import WindowUtils from '@/core/utils/WindowUtils'; +import useStartBuild from './useStartBuild'; type RunWorkflowDialogProps = Pick & { workflowId: string; - defaultBranch: string; - onAction: (branch: string) => void; }; -const RunWorkflowDialog = ({ isOpen, onClose, defaultBranch, workflowId, onAction }: RunWorkflowDialogProps) => { - const [branch, setBranch] = useState(defaultBranch); +const RunWorkflowDialog = ({ isOpen, onClose, workflowId }: RunWorkflowDialogProps) => { + const [branch, setBranch] = useState(WindowUtils.pageProps()?.project?.defaultBranch || ''); + const toast = useToast(); + const { mutate: startBuild } = useStartBuild(); const handleAction = () => { if (!branch) { return; } - onAction(branch); - onClose(); + startBuild( + { workflowId, branch }, + { + onSuccess: (data) => { + if (data.build_url) { + onClose(); + window.open(data.build_url, '_blank'); + } + }, + onError: (error) => { + toast({ + status: 'error', + title: 'Failed to start build', + description: error.message, + }); + }, + }, + ); }; return ( @@ -24,7 +42,7 @@ const RunWorkflowDialog = ({ isOpen, onClose, defaultBranch, workflowId, onActio title={`Run "${workflowId}"`} isOpen={isOpen} onClose={onClose} - onCloseComplete={() => setBranch(defaultBranch)} + onCloseComplete={() => setBranch(WindowUtils.pageProps()?.project?.defaultBranch || '')} > + BuildApi.startBuild({ + appSlug: WindowUtils.appSlug() ?? '', + workflowId, + branch, + }), + }); +} + +export default useStartBuild; diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.context.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.context.tsx index 07e26fd7b..820fa58c1 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.context.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.context.tsx @@ -1,8 +1,13 @@ -import { createContext, PropsWithChildren, useContext, useMemo } from 'react'; +import { createContext, PropsWithChildren, useContext, useEffect, useMemo } from 'react'; +import { FormProvider, useForm } from 'react-hook-form'; import useStep from '@/hooks/useStep'; +import { FormValues } from './StepConfigDrawer.types'; type Props = { workflowId: string; stepIndex: number }; -type State = { workflowId: string; stepIndex: number } & ReturnType; +type State = { + workflowId: string; + stepIndex: number; +} & ReturnType; const initialState: State = { data: undefined, @@ -14,18 +19,36 @@ const Context = createContext(initialState); const StepConfigDrawerProvider = ({ children, workflowId, stepIndex }: PropsWithChildren) => { const result = useStep(workflowId, stepIndex); - const value = useMemo( - () => - result - ? { - workflowId, - stepIndex, - ...result, - } - : initialState, - [workflowId, stepIndex, result], + const form = useForm({ mode: 'all' }); + + const value = useMemo(() => { + if (!result) return initialState; + return { workflowId, stepIndex, ...result }; + }, [result, workflowId, stepIndex]); + + useEffect(() => { + if (result.isLoading) { + return; + } + + form.reset({ + configuration: { + is_always_run: result?.data?.mergedValues?.is_always_run ?? false, + is_skippable: result?.data?.mergedValues?.is_skippable ?? false, + run_if: result?.data?.mergedValues?.run_if ?? '', + }, + properties: { + name: result?.data?.resolvedInfo?.title ?? '', + version: result?.data?.resolvedInfo?.normalizedVersion ?? '', + }, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [workflowId, stepIndex, result.isLoading]); + return ( + + {children} + ); - return {children}; }; export default StepConfigDrawerProvider; diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.tsx index 8e59797b2..f5410a424 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.tsx @@ -8,6 +8,7 @@ import { DrawerOverlay, UseDisclosureProps, } from '@chakra-ui/react'; +import semver from 'semver'; import { Avatar, Box, @@ -23,10 +24,12 @@ import { Text, useDisclosure, } from '@bitrise/bitkit'; +import { useFormContext } from 'react-hook-form'; import StepBadge from '@/components/StepBadge'; import useBitriseYmlStore from '@/hooks/useBitriseYmlStore'; import defaultIcon from '@/../images/step/icon-default.svg'; import VersionUtils from '@/core/utils/VersionUtils'; +import { FormValues, StepConfigTab } from './StepConfigDrawer.types'; import ConfigurationTab from './tabs/ConfigurationTab'; import PropertiesTab from './tabs/PropertiesTab'; import OutputVariablesTab from './tabs/OutputVariablesTab'; @@ -34,22 +37,33 @@ import StepConfigDrawerProvider, { useStepDrawerContext } from './StepConfigDraw const StepConfigDrawerContent = (props: UseDisclosureProps) => { const { isOpen, onClose } = useDisclosure(props); - const [selectedTab, setSelectedTab] = useState('configuration'); - const { workflowId, stepIndex, data: step } = useStepDrawerContext(); - const { mergedValues, resolvedInfo } = step ?? {}; + const [selectedTab, setSelectedTab] = useState(StepConfigTab.CONFIGURATION); - const { changeStepVersion, cloneStep, deleteStep } = useBitriseYmlStore((s) => ({ + const form = useFormContext(); + const hasChanges = form.formState.isDirty; + + const { workflowId, stepIndex, data } = useStepDrawerContext(); + const { mergedValues, defaultValues, resolvedInfo } = data ?? {}; + const stepHasOutputVariables = Boolean(mergedValues?.outputs?.length ?? 0); + + const [formTitleValue, formVersionValue] = form.watch(['properties.name', 'properties.version']); + const isUpgradable = VersionUtils.hasVersionUpgrade(formVersionValue, resolvedInfo?.versions); + const currentResolvedVersion = VersionUtils.resolveVersion(formVersionValue, resolvedInfo?.versions); + + const { changeStepVersion, updateStep, cloneStep, deleteStep } = useBitriseYmlStore((s) => ({ changeStepVersion: s.changeStepVersion, + updateStep: s.updateStep, cloneStep: s.cloneStep, deleteStep: s.deleteStep, })); - const handleSave = () => { - onClose(); - }; - const handleUpdateStep = () => { - changeStepVersion(workflowId, stepIndex, VersionUtils.normalizeVersion(step?.resolvedInfo?.latestVersion ?? '')); + const updatedVersion = VersionUtils.normalizeVersion(semver.major(resolvedInfo?.latestVersion ?? '').toString()); + form.setValue('properties.version', updatedVersion, { + shouldDirty: true, + shouldTouch: true, + shouldValidate: true, + }); }; const handleCloneStep = () => { @@ -62,15 +76,34 @@ const StepConfigDrawerContent = (props: UseDisclosureProps) => { onClose(); }; - const handleCloseComplete = () => { - setSelectedTab('configuration'); + const handleSave = form.handleSubmit( + ({ properties: { name, version }, configuration: { is_always_run, is_skippable, run_if } }) => { + updateStep(workflowId, stepIndex, { title: name, is_always_run, is_skippable, run_if }, defaultValues || {}); + changeStepVersion(workflowId, stepIndex, version); + onClose(); + }, + ); + + const handleCancel = () => { + onClose(); }; - const stepHasOutputVariables = (mergedValues?.outputs?.length ?? 0) > 0; + const handleCloseComplete = () => { + setSelectedTab(StepConfigTab.CONFIGURATION); + form.reset(); + }; return ( setSelectedTab(tabId)}> - + { { - {resolvedInfo?.title || ''} + {formTitleValue} { - {resolvedInfo?.resolvedVersion || 'Always latest'} - {resolvedInfo?.isUpgradable && ( + {currentResolvedVersion || 'Always latest'} + {isUpgradable && ( <> Newer version available - {resolvedInfo?.latestVersion ? `: ${resolvedInfo.latestVersion}` : ''} + {resolvedInfo?.latestVersion ? `: ${resolvedInfo?.latestVersion}` : ''} )} @@ -126,7 +159,7 @@ const StepConfigDrawerContent = (props: UseDisclosureProps) => { - {resolvedInfo?.isUpgradable && ( + {isUpgradable && ( { - - + diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.types.ts b/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.types.ts new file mode 100644 index 000000000..30d632b79 --- /dev/null +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/StepConfigDrawer.types.ts @@ -0,0 +1,17 @@ +export enum StepConfigTab { + CONFIGURATION = 'configuration', + PROPERTIES = 'properties', + OUTPUTS = 'outputs', +} + +export type FormValues = { + configuration: { + is_always_run: boolean; + is_skippable: boolean; + run_if: string; + }; + properties: { + name: string; + version: string; + }; +}; diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/ConfigurationTab.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/ConfigurationTab.tsx index 13de571c0..d5bec0411 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/ConfigurationTab.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/ConfigurationTab.tsx @@ -1,8 +1,10 @@ import { Box, Divider, ExpandableCard, Text, Toggle } from '@bitrise/bitkit'; +import { Controller, useFormContext } from 'react-hook-form'; import { StepInputVariable } from '@/core/models/Step'; import StepInput from '../components/StepInput'; import { useStepDrawerContext } from '../StepConfigDrawer.context'; import StepInputGroup from '../components/StepInputGroup'; +import { FormValues } from '../StepConfigDrawer.types'; function groupStepInputs(inputs?: StepInputVariable[]) { return inputs?.reduce>((groups, input) => { @@ -15,27 +17,35 @@ function groupStepInputs(inputs?: StepInputVariable[]) { } const ConfigurationTab = () => { - const { data, isLoading } = useStepDrawerContext(); + const { data } = useStepDrawerContext(); const { mergedValues } = data ?? {}; - - // TODO loading state - if (isLoading) { - return <>Loading...; - } + const form = useFormContext(); return ( When to run}> - Run if previous Step(s) failed - + Run even if previous Step(s) failed + } + /> + + + + Continue build even if this Step fails + } + /> diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/OutputVariablesTab.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/OutputVariablesTab.tsx index e6f79051e..1ff4801ee 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/OutputVariablesTab.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/OutputVariablesTab.tsx @@ -7,14 +7,9 @@ import StepHelperText from '../components/StepHelperText'; const OutputVariablesTab = () => { const [, copy] = useCopyToClipboard(); - const { data, isLoading } = useStepDrawerContext(); + const { data } = useStepDrawerContext(); const { mergedValues } = data ?? {}; - // TODO loading state - if (isLoading) { - return <>Loading...; - } - return ( This step will generate these output variables: diff --git a/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/PropertiesTab.tsx b/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/PropertiesTab.tsx index 1e07ddaea..3e77bbd30 100644 --- a/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/PropertiesTab.tsx +++ b/source/javascripts/components/unified-editor/StepConfigDrawer/tabs/PropertiesTab.tsx @@ -10,24 +10,23 @@ import { Text, useDisclosure, } from '@bitrise/bitkit'; +import { useFormContext } from 'react-hook-form'; import StepService from '@/core/models/StepService'; import { useStepDrawerContext } from '../StepConfigDrawer.context'; +import { FormValues } from '../StepConfigDrawer.types'; const PropertiesTab = () => { const { isOpen: showMore, onToggle: toggleShowMore } = useDisclosure(); - const { data, isLoading } = useStepDrawerContext(); - const { cvs, mergedValues, resolvedInfo } = data ?? {}; - - // Todo loading state - if (isLoading) { - return <>Loading...; - } + const { register } = useFormContext(); + const { data } = useStepDrawerContext(); + const { cvs, mergedValues } = data ?? {}; + const { source_code_url: sourceUrl, summary, description } = mergedValues ?? {}; const selectableVersions = StepService.getSelectableVersions(data); return ( - {mergedValues?.source_code_url && ( + {sourceUrl && ( { alignItems="center" colorScheme="purple" rel="noreferrer noopener" - href={mergedValues?.source_code_url} + href={sourceUrl} isExternal > View source code )} - + ref?.setAttribute('data-1p-ignore', '')} + {...register('properties.name')} + />