diff options
Diffstat (limited to 'app/assets/javascripts/pipelines')
27 files changed, 162 insertions, 210 deletions
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 71ec81b8969..ea45b5e3ec7 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -101,9 +101,6 @@ export default { showJobLinks() { return !this.isStageView && this.showLinks; }, - shouldShowStageName() { - return !this.isStageView; - }, // The show downstream check prevents showing redundant linked columns showDownstreamPipelines() { return ( @@ -165,8 +162,10 @@ export default { <div class="js-pipeline-graph"> <div ref="mainPipelineContainer" - class="gl-display-flex gl-position-relative gl-bg-gray-10 gl-white-space-nowrap gl-border-t-solid gl-border-t-1 gl-border-gray-100" - :class="{ 'gl-pipeline-min-h gl-py-5 gl-overflow-auto': !isLinkedPipeline }" + class="gl-display-flex gl-position-relative gl-bg-gray-10 gl-white-space-nowrap" + :class="{ + 'gl-pipeline-min-h gl-py-5 gl-overflow-auto gl-border-t-solid gl-border-t-1 gl-border-gray-100': !isLinkedPipeline, + }" > <linked-graph-wrapper> <template #upstream> @@ -202,11 +201,12 @@ export default { :groups="column.groups" :action="column.status.action" :highlighted-jobs="highlightedJobs" - :show-stage-name="shouldShowStageName" + :is-stage-view="isStageView" :job-hovered="hoveredJobName" :source-job-hovered="hoveredSourceJobName" :pipeline-expanded="pipelineExpanded" :pipeline-id="pipeline.id" + :user-permissions="pipeline.userPermissions" @refreshPipelineGraph="$emit('refreshPipelineGraph')" @jobHover="setJob" @updateMeasurements="getMeasurements" diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue index fb45738f8d1..a948a57c144 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue @@ -105,7 +105,7 @@ export default { return this.pipeline; } - return unwrapPipelineData(this.pipelineProjectPath, data); + return unwrapPipelineData(this.pipelineProjectPath, JSON.parse(JSON.stringify(data))); }, error(err) { this.reportFailure({ type: LOAD_FAILURE, skipSentry: true }); @@ -114,7 +114,7 @@ export default { this.$options.name, `| type: ${LOAD_FAILURE} , info: ${serializeLoadErrors(err)}`, { - projectPath: this.projectPath, + projectPath: this.pipelineProjectPath, pipelineIid: this.pipelineIid, pipelineStages: this.pipeline?.stages?.length || 0, nbOfDownstreams: this.pipeline?.downstream?.length || 0, diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue index b3c5af5418f..dd8a354511a 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipeline.vue @@ -161,7 +161,7 @@ export default { :size="24" css-classes="gl-top-0 gl-pr-2" /> - <div v-else class="gl-pr-2"><gl-loading-icon inline /></div> + <div v-else class="gl-pr-2"><gl-loading-icon size="sm" inline /></div> <div class="gl-display-flex gl-flex-direction-column gl-w-13"> <span class="gl-text-truncate" data-testid="downstream-title"> {{ downstreamTitle }} diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue index 45113ecff41..52ee40bd982 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue @@ -118,7 +118,7 @@ export default { return this.currentPipeline; } - return unwrapPipelineData(projectPath, data); + return unwrapPipelineData(projectPath, JSON.parse(JSON.stringify(data))); }, result() { this.loadingPipelineId = null; diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index 81d59f1ef65..d34ae8036ed 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -40,6 +40,11 @@ export default { required: false, default: () => [], }, + isStageView: { + type: Boolean, + required: false, + default: false, + }, jobHovered: { type: String, required: false, @@ -50,16 +55,15 @@ export default { required: false, default: () => ({}), }, - showStageName: { - type: Boolean, - required: false, - default: false, - }, sourceJobHovered: { type: String, required: false, default: '', }, + userPermissions: { + type: Object, + required: true, + }, }, titleClasses: [ 'gl-font-weight-bold', @@ -69,20 +73,11 @@ export default { 'gl-pl-3', ], computed: { - /* - currentGroups and filteredGroups are part of - a test to hunt down a bug - (see: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/57142). - - They should be removed when the bug is rectified. - */ - currentGroups() { - return this.glFeatures.pipelineFilterJobs ? this.filteredGroups : this.groups; + canUpdatePipeline() { + return this.userPermissions.updatePipeline; }, - filteredGroups() { - return this.groups.map((group) => { - return { ...group, jobs: group.jobs.filter(Boolean) }; - }); + columnSpacingClass() { + return this.isStageView ? 'gl-px-6' : 'gl-px-9'; }, formattedTitle() { return capitalize(escape(this.name)); @@ -90,6 +85,9 @@ export default { hasAction() { return !isEmpty(this.action); }, + showStageName() { + return !this.isStageView; + }, }, errorCaptured(err, _vm, info) { reportToSentry('stage_column_component', `error: ${err}, info: ${info}`); @@ -123,7 +121,7 @@ export default { }; </script> <template> - <main-graph-wrapper class="gl-px-6" data-testid="stage-column"> + <main-graph-wrapper :class="columnSpacingClass" data-testid="stage-column"> <template #stages> <div data-testid="stage-column-title" @@ -132,7 +130,7 @@ export default { > <div>{{ formattedTitle }}</div> <action-component - v-if="hasAction" + v-if="hasAction && canUpdatePipeline" :action-icon="action.icon" :tooltip-text="action.title" :link="action.path" @@ -143,7 +141,7 @@ export default { </template> <template #jobs> <div - v-for="group in currentGroups" + v-for="group in groups" :id="groupId(group)" :key="getGroupId(group)" data-testid="stage-column-group" diff --git a/app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js b/app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js index 7c62acbe8de..83f2466f0bf 100644 --- a/app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js +++ b/app/assets/javascripts/pipelines/components/graph_shared/drawing_utils.js @@ -75,11 +75,11 @@ export const generateLinksData = ({ links }, containerID, modifier = '') => { // until we can safely draw the bezier to look nice. // The adjustment number here is a magic number to make things // look nice and should change if the padding changes. This goes well - // with gl-px-6. gl-px-8 is more like 100. - const straightLineDestinationX = targetNodeX - 60; + // with gl-px-9 which we translate with 100px here. + const straightLineDestinationX = targetNodeX - 100; const controlPointX = straightLineDestinationX + (targetNodeX - straightLineDestinationX) / 2; - if (straightLineDestinationX > 0) { + if (straightLineDestinationX > firstPointCoordinateX) { path.lineTo(straightLineDestinationX, sourceNodeY); } diff --git a/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue b/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue index d19215e7895..efad43ddd4f 100644 --- a/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue +++ b/app/assets/javascripts/pipelines/components/jobs_shared/action_component.vue @@ -99,7 +99,7 @@ export default { class="js-ci-action gl-ci-action-icon-container ci-action-icon-container ci-action-icon-wrapper gl-display-flex gl-align-items-center gl-justify-content-center" @click.stop="onClickAction" > - <gl-loading-icon v-if="isLoading" class="js-action-icon-loading" /> + <gl-loading-icon v-if="isLoading" size="sm" class="js-action-icon-loading" /> <gl-icon v-else :name="actionIcon" class="gl-mr-0!" :aria-label="actionIcon" /> </gl-button> </template> diff --git a/app/assets/javascripts/pipelines/components/parsing_utils.js b/app/assets/javascripts/pipelines/components/parsing_utils.js index f1d9ced807b..b36c9c0d049 100644 --- a/app/assets/javascripts/pipelines/components/parsing_utils.js +++ b/app/assets/javascripts/pipelines/components/parsing_utils.js @@ -1,4 +1,4 @@ -import { isEqual, memoize, uniqWith } from 'lodash'; +import { memoize } from 'lodash'; import { createSankey } from './dag/drawing_utils'; /* @@ -113,11 +113,24 @@ export const filterByAncestors = (links, nodeDict) => return !allAncestors.includes(source); }); +/* + A peformant alternative to lodash's isEqual. Because findIndex always finds + the first instance of a match, if the found index is not the first, we know + it is in fact a duplicate. +*/ +const deduplicate = (item, itemIndex, arr) => { + const foundIdx = arr.findIndex((test) => { + return test.source === item.source && test.target === item.target; + }); + + return foundIdx === itemIndex; +}; + export const parseData = (nodes) => { const nodeDict = createNodeDict(nodes); const allLinks = makeLinksFromNodes(nodes, nodeDict); const filteredLinks = filterByAncestors(allLinks, nodeDict); - const links = uniqWith(filteredLinks, isEqual); + const links = filteredLinks.filter(deduplicate); return { nodes, links }; }; diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue index 01baf0a42d5..836333c8bde 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/job_pill.vue @@ -14,7 +14,7 @@ export default { type: Number, required: true, }, - isHighlighted: { + isHovered: { type: Boolean, required: false, default: false, @@ -42,7 +42,7 @@ export default { jobPillClasses() { return [ { 'gl-opacity-3': this.isFadedOut }, - this.isHighlighted ? 'gl-shadow-blue-200-x0-y0-b4-s2' : 'gl-inset-border-2-green-400', + { 'gl-bg-gray-50 gl-inset-border-1-gray-200': this.isHovered }, ]; }, }, @@ -57,15 +57,17 @@ export default { }; </script> <template> - <tooltip-on-truncate :title="jobName" truncate-target="child" placement="top"> - <div - :id="id" - class="gl-w-15 gl-bg-white gl-text-center gl-text-truncate gl-rounded-pill gl-mb-3 gl-px-5 gl-py-2 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease" - :class="jobPillClasses" - @mouseover="onMouseEnter" - @mouseleave="onMouseLeave" - > - {{ jobName }} - </div> - </tooltip-on-truncate> + <div class="gl-w-full"> + <tooltip-on-truncate :title="jobName" truncate-target="child" placement="top"> + <div + :id="id" + class="gl-bg-white gl-inset-border-1-gray-100 gl-text-center gl-text-truncate gl-rounded-6 gl-mb-3 gl-px-5 gl-py-3 gl-relative gl-z-index-1 gl-transition-duration-slow gl-transition-timing-function-ease" + :class="jobPillClasses" + @mouseover="onMouseEnter" + @mouseleave="onMouseLeave" + > + {{ jobName }} + </div> + </tooltip-on-truncate> + </div> </template> diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue index 3ba0d7d0120..78771b6a072 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/pipeline_graph.vue @@ -4,14 +4,14 @@ import { __ } from '~/locale'; import { DRAW_FAILURE, DEFAULT } from '../../constants'; import LinksLayer from '../graph_shared/links_layer.vue'; import JobPill from './job_pill.vue'; -import StagePill from './stage_pill.vue'; +import StageName from './stage_name.vue'; export default { components: { GlAlert, JobPill, LinksLayer, - StagePill, + StageName, }, CONTAINER_REF: 'PIPELINE_GRAPH_CONTAINER_REF', BASE_CONTAINER_ID: 'pipeline-graph-container', @@ -21,6 +21,11 @@ export default { [DRAW_FAILURE]: __('Could not draw the lines for job relationships'), [DEFAULT]: __('An unknown error occurred.'), }, + // The combination of gl-w-full gl-min-w-full and gl-max-w-15 is necessary. + // The max width and the width make sure the ellipsis to work and the min width + // is for when there is less text than the stage column width (which the width 100% does not fix) + jobWrapperClasses: + 'gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full gl-px-8 gl-min-w-full gl-max-w-15', props: { pipelineData: { required: true, @@ -85,23 +90,8 @@ export default { height: this.$refs[this.$options.CONTAINER_REF].scrollHeight, }; }, - getStageBackgroundClasses(index) { - const { length } = this.pipelineStages; - // It's possible for a graph to have only one stage, in which - // case we concatenate both the left and right rounding classes - if (length === 1) { - return 'gl-rounded-bottom-left-6 gl-rounded-top-left-6 gl-rounded-bottom-right-6 gl-rounded-top-right-6'; - } - - if (index === 0) { - return 'gl-rounded-bottom-left-6 gl-rounded-top-left-6'; - } - - if (index === length - 1) { - return 'gl-rounded-bottom-right-6 gl-rounded-top-right-6'; - } - - return ''; + isFadedOut(jobName) { + return this.highlightedJobs.length > 1 && !this.isJobHighlighted(jobName); }, isJobHighlighted(jobName) { return this.highlightedJobs.includes(jobName); @@ -137,7 +127,12 @@ export default { > {{ failure.text }} </gl-alert> - <div :id="containerId" :ref="$options.CONTAINER_REF" data-testid="graph-container"> + <div + :id="containerId" + :ref="$options.CONTAINER_REF" + class="gl-bg-gray-10 gl-overflow-auto" + data-testid="graph-container" + > <links-layer :pipeline-data="pipelineStages" :pipeline-id="$options.PIPELINE_ID" @@ -152,23 +147,17 @@ export default { :key="`${stage.name}-${index}`" class="gl-flex-direction-column" > - <div - class="gl-display-flex gl-align-items-center gl-bg-white gl-w-full gl-px-8 gl-py-4 gl-mb-5" - :class="getStageBackgroundClasses(index)" - data-testid="stage-background" - > - <stage-pill :stage-name="stage.name" :is-empty="stage.groups.length === 0" /> + <div class="gl-display-flex gl-align-items-center gl-w-full gl-px-9 gl-py-4 gl-mb-5"> + <stage-name :stage-name="stage.name" /> </div> - <div - class="gl-display-flex gl-flex-direction-column gl-align-items-center gl-w-full gl-px-8" - > + <div :class="$options.jobWrapperClasses"> <job-pill v-for="group in stage.groups" :key="group.name" :job-name="group.name" :pipeline-id="$options.PIPELINE_ID" - :is-highlighted="hasHighlightedJob && isJobHighlighted(group.name)" - :is-faded-out="hasHighlightedJob && !isJobHighlighted(group.name)" + :is-hovered="highlightedJob === group.name" + :is-faded-out="isFadedOut(group.name)" @on-mouse-enter="setHoveredJob" @on-mouse-leave="removeHoveredJob" /> diff --git a/app/assets/javascripts/pipelines/components/pipeline_graph/stage_pill.vue b/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue index df48426f24e..367a18af248 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_graph/stage_pill.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_graph/stage_name.vue @@ -1,4 +1,5 @@ <script> +import { capitalize, escape } from 'lodash'; import tooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; export default { @@ -10,26 +11,18 @@ export default { type: String, required: true, }, - isEmpty: { - type: Boolean, - required: false, - default: false, - }, }, computed: { - emptyClass() { - return this.isEmpty ? 'gl-bg-gray-200' : 'gl-bg-gray-600'; + formattedTitle() { + return capitalize(escape(this.stageName)); }, }, }; </script> <template> <tooltip-on-truncate :title="stageName" truncate-target="child" placement="top"> - <div - class="gl-px-5 gl-py-2 gl-text-white gl-text-center gl-text-truncate gl-rounded-pill gl-w-20" - :class="emptyClass" - > - {{ stageName }} + <div class="gl-py-2 gl-text-truncate gl-font-weight-bold gl-w-20"> + {{ formattedTitle }} </div> </tooltip-on-truncate> </template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue index 104a3caab4c..1ce6654e0e9 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue @@ -16,7 +16,6 @@ export default { consuming tasks, so you can spend more time creating.`), aboutRunnersBtnText: s__('Pipelines|Learn about Runners'), installRunnersBtnText: s__('Pipelines|Install GitLab Runners'), - getStartedBtnText: s__('Pipelines|Get started with CI/CD'), codeQualityTitle: s__('Pipelines|Improve code quality with GitLab CI/CD'), codeQualityDescription: s__(`Pipelines|To keep your codebase simple, readable, and accessible to contributors, use GitLab CI/CD @@ -55,9 +54,6 @@ export default { ciHelpPagePath() { return helpPagePath('ci/quick_start/index.md'); }, - isPipelineEmptyStateTemplatesExperimentActive() { - return this.canSetCi && Boolean(getExperimentData('pipeline_empty_state_templates')); - }, isCodeQualityExperimentActive() { return this.canSetCi && Boolean(getExperimentData('code_quality_walkthrough')); }, @@ -81,37 +77,8 @@ export default { </script> <template> <div> - <gitlab-experiment - v-if="isPipelineEmptyStateTemplatesExperimentActive" - name="pipeline_empty_state_templates" - > - <template #control> - <gl-empty-state - :title="$options.i18n.title" - :svg-path="emptyStateSvgPath" - :description="$options.i18n.description" - :primary-button-text="$options.i18n.getStartedBtnText" - :primary-button-link="ciHelpPagePath" - /> - </template> - <template #candidate> - <pipelines-ci-templates /> - </template> - </gitlab-experiment> - <gitlab-experiment v-else-if="isCodeQualityExperimentActive" name="code_quality_walkthrough"> - <template #control> - <gl-empty-state - :title="$options.i18n.title" - :svg-path="emptyStateSvgPath" - :description="$options.i18n.description" - > - <template #actions> - <gl-button :href="ciHelpPagePath" variant="confirm" @click="trackClick()"> - {{ $options.i18n.getStartedBtnText }} - </gl-button> - </template> - </gl-empty-state> - </template> + <gitlab-experiment v-if="isCodeQualityExperimentActive" name="code_quality_walkthrough"> + <template #control><pipelines-ci-templates /></template> <template #candidate> <gl-empty-state :title="$options.i18n.codeQualityTitle" @@ -127,23 +94,7 @@ export default { </template> </gitlab-experiment> <gitlab-experiment v-else-if="isCiRunnerTemplatesExperimentActive" name="ci_runner_templates"> - <template #control> - <gl-empty-state - :title="$options.i18n.title" - :svg-path="emptyStateSvgPath" - :description="$options.i18n.description" - > - <template #actions> - <gl-button - :href="ciHelpPagePath" - variant="confirm" - @click="trackCiRunnerTemplatesClick('get_started_button_clicked')" - > - {{ $options.i18n.getStartedBtnText }} - </gl-button> - </template> - </gl-empty-state> - </template> + <template #control><pipelines-ci-templates /></template> <template #candidate> <gl-empty-state :title="$options.i18n.title" @@ -169,14 +120,7 @@ export default { </gl-empty-state> </template> </gitlab-experiment> - <gl-empty-state - v-else-if="canSetCi" - :title="$options.i18n.title" - :svg-path="emptyStateSvgPath" - :description="$options.i18n.description" - :primary-button-text="$options.i18n.getStartedBtnText" - :primary-button-link="ciHelpPagePath" - /> + <pipelines-ci-templates v-else-if="canSetCi" /> <gl-empty-state v-else title="" diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue index d7bd2d731b1..5e18f636b52 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue @@ -97,7 +97,7 @@ export default { {{ $options.i18n.artifactsFetchErrorMessage }} </gl-alert> - <gl-loading-icon v-if="isLoading" /> + <gl-loading-icon v-if="isLoading" size="sm" /> <gl-dropdown-item v-for="(artifact, i) in artifacts" diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue index bf992b84387..7552ddb61dc 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stage.vue @@ -13,7 +13,7 @@ */ import { GlDropdown, GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui'; -import { deprecatedCreateFlash as Flash } from '~/flash'; +import createFlash from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import eventHub from '../../event_hub'; @@ -83,7 +83,9 @@ export default { this.$refs.dropdown.hide(); this.isLoading = false; - Flash(__('Something went wrong on our end.')); + createFlash({ + message: __('Something went wrong on our end.'), + }); }); }, isDropdownOpen() { @@ -118,7 +120,7 @@ export default { <gl-icon :name="borderlessIcon" /> </span> </template> - <gl-loading-icon v-if="isLoading" /> + <gl-loading-icon v-if="isLoading" size="sm" /> <ul v-else class="js-builds-dropdown-list scrollable-menu" diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue index 52c8ef2cf26..fc8f31c5b7e 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue @@ -60,19 +60,20 @@ export default { data-testid="pipeline-url-link" data-qa-selector="pipeline_url_link" > - <span class="pipeline-id">#{{ pipeline.id }}</span> + #{{ pipeline.id }} </gl-link> <div class="label-container"> - <gl-link v-if="isScheduled" :href="pipelineScheduleUrl" target="__blank"> - <gl-badge - v-gl-tooltip - :title="__('This pipeline was triggered by a schedule.')" - variant="info" - size="sm" - data-testid="pipeline-url-scheduled" - >{{ __('Scheduled') }}</gl-badge - > - </gl-link> + <gl-badge + v-if="isScheduled" + v-gl-tooltip + :href="pipelineScheduleUrl" + target="__blank" + :title="__('This pipeline was triggered by a schedule.')" + variant="info" + size="sm" + data-testid="pipeline-url-scheduled" + >{{ __('Scheduled') }}</gl-badge + > <gl-badge v-if="pipeline.flags.latest" v-gl-tooltip diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue index 8bb2657c161..e3373178239 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue @@ -2,7 +2,7 @@ import { GlEmptyState, GlIcon, GlLoadingIcon } from '@gitlab/ui'; import { isEqual } from 'lodash'; import createFlash from '~/flash'; -import { getParameterByName } from '~/lib/utils/common_utils'; +import { getParameterByName } from '~/lib/utils/url_utility'; import { __, s__ } from '~/locale'; import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue index 147fff52101..36629d9f1f1 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue @@ -96,7 +96,7 @@ export default { {{ $options.i18n.artifactsFetchErrorMessage }} </gl-alert> - <gl-loading-icon v-if="isLoading" /> + <gl-loading-icon v-if="isLoading" size="sm" /> <gl-alert v-else-if="!hasArtifacts" variant="info" :dismissible="false"> {{ $options.i18n.noArtifacts }} diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue index c2ec8c57fd7..c6c81d5253b 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_ci_templates.vue @@ -1,17 +1,19 @@ <script> -import { GlButton, GlCard, GlSprintf } from '@gitlab/ui'; -import ExperimentTracking from '~/experimentation/experiment_tracking'; +import { GlAvatar, GlButton, GlCard, GlSprintf } from '@gitlab/ui'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import { s__, sprintf } from '~/locale'; -import { HELLO_WORLD_TEMPLATE_KEY } from '../../constants'; +import { STARTER_TEMPLATE_NAME } from '~/pipeline_editor/constants'; +import Tracking from '~/tracking'; export default { components: { + GlAvatar, GlButton, GlCard, GlSprintf, }, - HELLO_WORLD_TEMPLATE_KEY, + mixins: [Tracking.mixin()], + STARTER_TEMPLATE_NAME, i18n: { cta: s__('Pipelines|Use template'), testTemplates: { @@ -19,10 +21,10 @@ export default { subtitle: s__( 'Pipelines|Use a sample %{codeStart}.gitlab-ci.yml%{codeEnd} template file to explore how CI/CD works.', ), - helloWorld: { - title: s__('Pipelines|“Hello world” with GitLab CI/CD'), + gettingStarted: { + title: s__('Pipelines|Get started with GitLab CI/CD'), description: s__( - 'Pipelines|Get familiar with GitLab CI/CD syntax by starting with a simple pipeline that runs a “Hello world” script.', + 'Pipelines|Get familiar with GitLab CI/CD syntax by starting with a basic 3 stage CI/CD pipeline.', ), }, }, @@ -34,31 +36,30 @@ export default { description: s__('Pipelines|CI/CD template to test and deploy your %{name} project.'), }, }, - inject: ['addCiYmlPath', 'suggestedCiTemplates'], + inject: ['pipelineEditorPath', 'suggestedCiTemplates'], data() { const templates = this.suggestedCiTemplates.map(({ name, logo }) => { return { name, logo, - link: mergeUrlParams({ template: name }, this.addCiYmlPath), + link: mergeUrlParams({ template: name }, this.pipelineEditorPath), description: sprintf(this.$options.i18n.templates.description, { name }), }; }); return { templates, - helloWorldTemplateUrl: mergeUrlParams( - { template: HELLO_WORLD_TEMPLATE_KEY }, - this.addCiYmlPath, + gettingStartedTemplateUrl: mergeUrlParams( + { template: STARTER_TEMPLATE_NAME }, + this.pipelineEditorPath, ), }; }, methods: { trackEvent(template) { - const tracking = new ExperimentTracking('pipeline_empty_state_templates', { + this.track('template_clicked', { label: template, }); - tracking.event('template_clicked'); }, }, }; @@ -81,18 +82,18 @@ export default { <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="wave" /></div> <div class="gl-mb-3"> <strong class="gl-text-gray-800 gl-mb-2">{{ - $options.i18n.testTemplates.helloWorld.title + $options.i18n.testTemplates.gettingStarted.title }}</strong> </div> - <p class="gl-font-sm">{{ $options.i18n.testTemplates.helloWorld.description }}</p> + <p class="gl-font-sm">{{ $options.i18n.testTemplates.gettingStarted.description }}</p> </div> <gl-button category="primary" variant="confirm" - :href="helloWorldTemplateUrl" + :href="gettingStartedTemplateUrl" data-testid="test-template-link" - @click="trackEvent($options.HELLO_WORLD_TEMPLATE_KEY)" + @click="trackEvent($options.STARTER_TEMPLATE_NAME)" > {{ $options.i18n.cta }} </gl-button> @@ -109,11 +110,12 @@ export default { class="gl-display-flex gl-align-items-center gl-justify-content-space-between gl-border-b-solid gl-border-b-1 gl-border-b-gray-100 gl-pb-3 gl-pt-3" > <div class="gl-display-flex gl-flex-direction-row gl-align-items-center"> - <img - width="64" - height="64" + <gl-avatar :src="template.logo" - class="gl-mr-6" + :size="64" + class="gl-mr-6 gl-bg-white dark-mode-override" + shape="rect" + :alt="template.name" data-testid="template-logo" /> <div class="gl-flex-direction-row"> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue index 15ff7da35e1..5409e68cdc4 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue @@ -60,7 +60,7 @@ export default { @input="searchBranches" > <template #suggestions> - <gl-loading-icon v-if="loading" /> + <gl-loading-icon v-if="loading" size="sm" /> <template v-else> <gl-filtered-search-suggestion v-for="(branch, index) in branches" diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue index af62c492748..afcdd63b664 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue @@ -55,7 +55,7 @@ export default { <template> <gl-filtered-search-token v-bind="{ ...$props, ...$attrs }" v-on="$listeners" @input="searchTags"> <template #suggestions> - <gl-loading-icon v-if="loading" /> + <gl-loading-icon v-if="loading" size="sm" /> <template v-else> <gl-filtered-search-suggestion v-for="(tag, index) in tags" :key="index" :value="tag"> {{ tag }} diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue index bc661f37493..33115d72b9c 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue @@ -98,7 +98,7 @@ export default { }}</gl-filtered-search-suggestion> <gl-dropdown-divider /> - <gl-loading-icon v-if="loading" /> + <gl-loading-icon v-if="loading" size="sm" /> <template v-else> <gl-filtered-search-suggestion v-for="user in users" diff --git a/app/assets/javascripts/pipelines/constants.js b/app/assets/javascripts/pipelines/constants.js index 01705e7726f..21b114825a6 100644 --- a/app/assets/javascripts/pipelines/constants.js +++ b/app/assets/javascripts/pipelines/constants.js @@ -35,6 +35,3 @@ export const POST_FAILURE = 'post_failure'; export const UNSUPPORTED_DATA = 'unsupported_data'; export const CHILD_VIEW = 'child'; - -// The key of the template is the same as the filename -export const HELLO_WORLD_TEMPLATE_KEY = 'Hello-World'; diff --git a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js index 9f15b6c4ae3..5c34f4e4f7e 100644 --- a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js +++ b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js @@ -1,4 +1,4 @@ -import { deprecatedCreateFlash as flash } from '~/flash'; +import createFlash from '~/flash'; import { __ } from '~/locale'; export default { @@ -13,7 +13,9 @@ export default { }) .catch(() => { this.mediator.store.toggleLoading(pipeline); - flash(__('An error occurred while fetching the pipeline.')); + createFlash({ + message: __('An error occurred while fetching the pipeline.'), + }); }); }, /** @@ -53,9 +55,11 @@ export default { requestRefreshPipelineGraph() { // When an action is clicked // (whether in the dropdown or in the main nodes, we refresh the big graph) - this.mediator - .refreshPipeline() - .catch(() => flash(__('An error occurred while making the request.'))); + this.mediator.refreshPipeline().catch(() => + createFlash({ + message: __('An error occurred while making the request.'), + }), + ); }, }, }; diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js index 9ab4753fec8..e8d5ed175ba 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js +++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js @@ -1,5 +1,5 @@ import Vue from 'vue'; -import { deprecatedCreateFlash as Flash } from '~/flash'; +import createFlash from '~/flash'; import { parseBoolean } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; import Translate from '~/vue_shared/translate'; @@ -96,14 +96,18 @@ export default async function initPipelineDetailsBundle() { try { createPipelineHeaderApp(SELECTORS.PIPELINE_HEADER, apolloProvider, dataset.graphqlResourceEtag); } catch { - Flash(__('An error occurred while loading a section of this page.')); + createFlash({ + message: __('An error occurred while loading a section of this page.'), + }); } if (canShowNewPipelineDetails) { try { createPipelinesDetailApp(SELECTORS.PIPELINE_GRAPH, apolloProvider, dataset); } catch { - Flash(__('An error occurred while loading the pipeline.')); + createFlash({ + message: __('An error occurred while loading the pipeline.'), + }); } } else { const { default: PipelinesMediator } = await import( diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js index 09637c25654..72c4fedc64c 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js +++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js @@ -1,5 +1,5 @@ import Visibility from 'visibilityjs'; -import { deprecatedCreateFlash as Flash } from '../flash'; +import createFlash from '~/flash'; import Poll from '../lib/utils/poll'; import { __ } from '../locale'; import PipelineService from './services/pipeline_service'; @@ -47,7 +47,9 @@ export default class pipelinesMediator { errorCallback() { this.state.isLoading = false; - Flash(__('An error occurred while fetching the pipeline.')); + createFlash({ + message: __('An error occurred while fetching the pipeline.'), + }); } refreshPipeline() { diff --git a/app/assets/javascripts/pipelines/pipeline_shared_client.js b/app/assets/javascripts/pipelines/pipeline_shared_client.js index c3be487caae..7a922acd0b3 100644 --- a/app/assets/javascripts/pipelines/pipeline_shared_client.js +++ b/app/assets/javascripts/pipelines/pipeline_shared_client.js @@ -5,6 +5,7 @@ export const apolloProvider = new VueApollo({ defaultClient: createDefaultClient( {}, { + assumeImmutableResults: true, useGet: true, }, ), diff --git a/app/assets/javascripts/pipelines/pipelines_index.js b/app/assets/javascripts/pipelines/pipelines_index.js index 925a96ea1aa..c4c2b5f2927 100644 --- a/app/assets/javascripts/pipelines/pipelines_index.js +++ b/app/assets/javascripts/pipelines/pipelines_index.js @@ -29,7 +29,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { errorStateSvgPath, noPipelinesSvgPath, newPipelinePath, - addCiYmlPath, + pipelineEditorPath, suggestedCiTemplates, canCreatePipeline, hasGitlabCi, @@ -44,7 +44,7 @@ export const initPipelinesIndex = (selector = '#pipelines-list-vue') => { return new Vue({ el, provide: { - addCiYmlPath, + pipelineEditorPath, artifactsEndpoint, artifactsEndpointPlaceholder, suggestedCiTemplates: JSON.parse(suggestedCiTemplates), |