diff options
Diffstat (limited to 'app/assets/javascripts/pipelines/components/pipelines_list')
28 files changed, 0 insertions, 3471 deletions
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue deleted file mode 100644 index 3bbdfc73e1b..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state.vue +++ /dev/null @@ -1,53 +0,0 @@ -<script> -import { GlEmptyState } from '@gitlab/ui'; -import { s__ } from '~/locale'; -import GitlabExperiment from '~/experimentation/components/gitlab_experiment.vue'; -import PipelinesCiTemplates from './empty_state/pipelines_ci_templates.vue'; -import IosTemplates from './empty_state/ios_templates.vue'; - -export default { - i18n: { - noCiDescription: s__('Pipelines|This project is not currently set up to run pipelines.'), - }, - name: 'PipelinesEmptyState', - components: { - GlEmptyState, - GitlabExperiment, - PipelinesCiTemplates, - IosTemplates, - }, - props: { - emptyStateSvgPath: { - type: String, - required: true, - }, - canSetCi: { - type: Boolean, - required: true, - }, - registrationToken: { - type: String, - required: false, - default: null, - }, - }, -}; -</script> -<template> - <div> - <gitlab-experiment v-if="canSetCi" name="ios_specific_templates"> - <template #control> - <pipelines-ci-templates /> - </template> - <template #candidate> - <ios-templates :registration-token="registrationToken" /> - </template> - </gitlab-experiment> - <gl-empty-state - v-else - title="" - :svg-path="emptyStateSvgPath" - :description="$options.i18n.noCiDescription" - /> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue deleted file mode 100644 index 439dc0eb253..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ci_templates.vue +++ /dev/null @@ -1,106 +0,0 @@ -<script> -import { GlAvatar, GlButton } from '@gitlab/ui'; -import { s__, sprintf } from '~/locale'; -import { mergeUrlParams } from '~/lib/utils/url_utility'; -import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants'; -import Tracking from '~/tracking'; - -export default { - components: { - GlAvatar, - GlButton, - }, - mixins: [Tracking.mixin()], - inject: ['pipelineEditorPath', 'suggestedCiTemplates'], - props: { - disabled: { - type: Boolean, - required: false, - default: false, - }, - filterTemplates: { - type: Array, - required: false, - default: () => [], - }, - }, - data() { - const templates = this.suggestedCiTemplates - .filter( - (template) => !this.filterTemplates.length || this.filterTemplates.includes(template.name), - ) - .map(({ name, logo, title }) => { - return { - name: title || name, - description: sprintf(this.$options.i18n.description, { name: title || name }), - isPng: logo.endsWith('png'), - logo, - link: mergeUrlParams({ template: name }, this.pipelineEditorPath), - }; - }); - - return { - templates, - }; - }, - methods: { - trackEvent(template) { - this.track('template_clicked', { - label: template, - }); - }, - logoStyle(template) { - return template.isPng ? { objectFit: 'contain' } : ''; - }, - }, - i18n: { - description: s__( - 'Pipelines|Continuous integration and deployment template to test and deploy your %{name} project.', - ), - cta: s__('Pipelines|Use template'), - }, - AVATAR_SHAPE_OPTION_RECT, -}; -</script> -<template> - <ul class="gl-list-style-none gl-pl-0"> - <li v-for="template in templates" :key="template.name"> - <div - 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"> - <gl-avatar - :alt="template.name" - class="gl-mr-5 gl-bg-white dark-mode-override" - :class="{ 'gl-p-2': template.isPng }" - :style="logoStyle(template)" - :shape="$options.AVATAR_SHAPE_OPTION_RECT" - :size="48" - :src="template.logo" - data-testid="template-logo" - /> - <div class="gl-flex-direction-row"> - <div class="gl-mb-3"> - <strong class="gl-text-gray-800" data-testid="template-name"> - {{ template.name }} - </strong> - </div> - <p class="gl-mb-0 gl-font-sm" data-testid="template-description"> - {{ template.description }} - </p> - </div> - </div> - <gl-button - :disabled="disabled" - category="primary" - variant="confirm" - :href="template.link" - data-testid="template-link" - @click="trackEvent(template.name)" - > - {{ $options.i18n.cta }} - </gl-button> - </div> - </li> - </ul> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ios_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ios_templates.vue deleted file mode 100644 index 5208f9a3ce7..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/ios_templates.vue +++ /dev/null @@ -1,220 +0,0 @@ -<script> -import { GlButton, GlCard, GlSprintf, GlLink, GlPopover, GlModalDirective } from '@gitlab/ui'; -import { s__ } from '~/locale'; -import { helpPagePath } from '~/helpers/help_page_helper'; -import { mergeUrlParams, DOCS_URL } from '~/lib/utils/url_utility'; -import RunnerInstructionsModal from '~/vue_shared/components/runner_instructions/runner_instructions_modal.vue'; -import apolloProvider from '~/pipelines/graphql/provider'; -import CiTemplates from './ci_templates.vue'; - -export default { - components: { - GlButton, - GlCard, - GlSprintf, - GlLink, - GlPopover, - RunnerInstructionsModal, - CiTemplates, - }, - directives: { - GlModalDirective, - }, - inject: ['pipelineEditorPath', 'iosRunnersAvailable'], - props: { - registrationToken: { - type: String, - required: false, - default: null, - }, - }, - apolloProvider, - iOSTemplateName: 'iOS-Fastlane', - modalId: 'runner-instructions-modal', - runnerDocsLink: `${DOCS_URL}/runner/install/osx`, - whatElseLink: helpPagePath('ci/index.md'), - i18n: { - title: s__('Pipelines|Get started with GitLab CI/CD'), - subtitle: s__('Pipelines|Building for iOS?'), - explanation: s__("Pipelines|We'll walk you through how to deploy to iOS in two easy steps."), - runnerSetupTitle: s__('Pipelines|1. Set up a runner'), - runnerSetupButton: s__('Pipelines|Set up a runner'), - runnerSetupBodyUnfinished: s__( - 'Pipelines|GitLab Runner is an application that works with GitLab CI/CD to run jobs in a pipeline.', - ), - runnerSetupBodyFinished: s__( - 'Pipelines|You have runners available to run your job now. No need to do anything else.', - ), - runnerSetupPopoverTitle: s__( - "Pipelines|Let's get that runner set up! %{emojiStart}tada%{emojiEnd}", - ), - runnerSetupPopoverBodyLine1: s__( - 'Pipelines|Follow these instructions to install GitLab Runner on macOS.', - ), - runnerSetupPopoverBodyLine2: s__( - 'Pipelines|Need more information to set up your runner? %{linkStart}Check out our documentation%{linkEnd}.', - ), - configurePipelineTitle: s__('Pipelines|2. Configure deployment pipeline'), - configurePipelineBody: s__("Pipelines|We'll guide you through a simple pipeline set-up."), - configurePipelineButton: s__('Pipelines|Configure pipeline'), - noWalkthroughTitle: s__("Pipelines|Don't need a guide? Jump in right away with a template."), - noWalkthroughExplanation: s__('Pipelines|Based on your project, we recommend this template:'), - notBuildingForIos: s__( - "Pipelines|Not building for iOS or not what you're looking for? %{linkStart}See what else%{linkEnd} GitLab CI/CD has to offer.", - ), - }, - data() { - return { - isModalShown: false, - isPopoverShown: false, - isRunnerSetupFinished: this.iosRunnersAvailable, - popoverTarget: `${this.$options.modalId}___BV_modal_content_`, - configurePipelineLink: mergeUrlParams( - { template: this.$options.iOSTemplateName }, - this.pipelineEditorPath, - ), - }; - }, - computed: { - runnerSetupBodyText() { - return this.iosRunnersAvailable - ? this.$options.i18n.runnerSetupBodyFinished - : this.$options.i18n.runnerSetupBodyUnfinished; - }, - }, - methods: { - showModal() { - this.isModalShown = true; - }, - hideModal() { - this.togglePopover(); - this.isRunnerSetupFinished = true; - }, - togglePopover() { - this.isPopoverShown = !this.isPopoverShown; - }, - }, -}; -</script> - -<template> - <div> - <h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.i18n.title }}</h2> - <h3 class="gl-font-lg gl-text-gray-900 gl-mt-1">{{ $options.i18n.subtitle }}</h3> - <p>{{ $options.i18n.explanation }}</p> - - <div class="gl-lg-display-flex"> - <div class="gl-lg-display-flex gl-lg-w-25p gl-lg-pr-4 gl-mb-4"> - <gl-card body-class="gl-display-flex gl-flex-grow-1"> - <div - class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column gl-justify-content-space-between gl-align-items-flex-start" - > - <div> - <div class="gl-py-5"> - <gl-emoji - v-show="isRunnerSetupFinished" - class="gl-font-size-h2-xl" - data-name="white_check_mark" - data-testid="runner-setup-marked-completed" - /> - <gl-emoji - v-show="!isRunnerSetupFinished" - class="gl-font-size-h2-xl" - data-name="tools" - data-testid="runner-setup-marked-todo" - /> - </div> - <span class="gl-text-gray-800 gl-font-weight-bold"> - {{ $options.i18n.runnerSetupTitle }} - </span> - <p class="gl-font-sm gl-mt-3">{{ runnerSetupBodyText }}</p> - </div> - - <gl-button - v-if="!iosRunnersAvailable" - v-gl-modal-directive="$options.modalId" - category="primary" - variant="confirm" - @click="showModal" - > - {{ $options.i18n.runnerSetupButton }} - </gl-button> - <runner-instructions-modal - v-if="isModalShown" - :modal-id="$options.modalId" - :registration-token="registrationToken" - default-platform-name="osx" - @shown="togglePopover" - @hide="hideModal" - /> - <gl-popover - v-if="isPopoverShown" - :show="true" - :show-close-button="true" - :target="popoverTarget" - triggers="manual" - placement="left" - fallback-placement="clockwise" - > - <template #title> - <gl-sprintf :message="$options.i18n.runnerSetupPopoverTitle"> - <template #emoji="{ content }"> - <gl-emoji class="gl-ml-2" :data-name="content" /> - </template> - </gl-sprintf> - </template> - <div class="gl-mb-5"> - {{ $options.i18n.runnerSetupPopoverBodyLine1 }} - </div> - <gl-sprintf :message="$options.i18n.runnerSetupPopoverBodyLine2"> - <template #link="{ content }"> - <gl-link :href="$options.runnerDocsLink" target="_blank">{{ content }}</gl-link> - </template> - </gl-sprintf> - </gl-popover> - </div> - </gl-card> - </div> - <div class="gl-lg-display-flex gl-lg-w-25p gl-lg-pr-4 gl-mb-4"> - <gl-card body-class="gl-display-flex gl-flex-grow-1"> - <div - class="gl-display-flex gl-flex-grow-1 gl-flex-direction-column gl-justify-content-space-between gl-align-items-flex-start" - > - <div> - <div class="gl-py-5"><gl-emoji class="gl-font-size-h2-xl" data-name="tools" /></div> - <span class="gl-text-gray-800 gl-font-weight-bold"> - {{ $options.i18n.configurePipelineTitle }} - </span> - <p class="gl-font-sm gl-mt-3">{{ $options.i18n.configurePipelineBody }}</p> - </div> - - <gl-button - :disabled="!isRunnerSetupFinished" - category="primary" - variant="confirm" - data-testid="configure-pipeline-link" - :href="configurePipelineLink" - > - {{ $options.i18n.configurePipelineButton }} - </gl-button> - </div> - </gl-card> - </div> - </div> - <h3 class="gl-font-lg gl-text-gray-900 gl-mt-5">{{ $options.i18n.noWalkthroughTitle }}</h3> - <p>{{ $options.i18n.noWalkthroughExplanation }}</p> - <ci-templates - :filter-templates="/* eslint-disable @gitlab/vue-no-new-non-primitive-in-template */ [ - $options.iOSTemplateName, - ] /* eslint-enable @gitlab/vue-no-new-non-primitive-in-template */" - :disabled="!isRunnerSetupFinished" - /> - <p> - <gl-sprintf :message="$options.i18n.notBuildingForIos"> - <template #link="{ content }"> - <gl-link :href="$options.whatElseLink">{{ content }}</gl-link> - </template> - </gl-sprintf> - </p> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue b/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue deleted file mode 100644 index a6297213402..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/empty_state/pipelines_ci_templates.vue +++ /dev/null @@ -1,79 +0,0 @@ -<script> -import { GlButton, GlCard, GlSprintf } from '@gitlab/ui'; -import { mergeUrlParams } from '~/lib/utils/url_utility'; -import { STARTER_TEMPLATE_NAME, I18N } from '~/ci/pipeline_editor/constants'; -import Tracking from '~/tracking'; -import CiTemplates from './ci_templates.vue'; - -export default { - components: { - GlButton, - GlCard, - GlSprintf, - CiTemplates, - }, - mixins: [Tracking.mixin()], - STARTER_TEMPLATE_NAME, - I18N, - inject: ['pipelineEditorPath'], - data() { - return { - gettingStartedTemplateUrl: mergeUrlParams( - { template: STARTER_TEMPLATE_NAME }, - this.pipelineEditorPath, - ), - tracker: null, - }; - }, - methods: { - trackEvent(template) { - this.track('template_clicked', { - label: template, - }); - }, - }, -}; -</script> -<template> - <div> - <h2 class="gl-font-size-h2 gl-text-gray-900">{{ $options.I18N.title }}</h2> - - <h2 class="gl-font-lg gl-text-gray-900">{{ $options.I18N.learnBasics.title }}</h2> - <p class="gl-text-gray-800 gl-mb-6"> - <gl-sprintf :message="$options.I18N.learnBasics.subtitle"> - <template #code="{ content }"> - <code>{{ content }}</code> - </template> - </gl-sprintf> - </p> - - <div class="gl-lg-w-25p gl-lg-pr-5 gl-mb-8"> - <gl-card> - <div class="gl-flex-direction-row"> - <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.learnBasics.gettingStarted.title }} - </strong> - </div> - <p class="gl-font-sm">{{ $options.I18N.learnBasics.gettingStarted.description }}</p> - </div> - - <gl-button - category="primary" - variant="confirm" - :href="gettingStartedTemplateUrl" - data-testid="test-template-link" - @click="trackEvent($options.STARTER_TEMPLATE_NAME)" - > - {{ $options.I18N.learnBasics.gettingStarted.cta }} - </gl-button> - </gl-card> - </div> - - <h2 class="gl-font-lg gl-text-gray-900">{{ $options.I18N.templates.title }}</h2> - <p class="gl-text-gray-800 gl-mb-6">{{ $options.I18N.templates.subtitle }}</p> - - <ci-templates /> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_job_details.vue b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_job_details.vue deleted file mode 100644 index edf4cc87a87..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_job_details.vue +++ /dev/null @@ -1,165 +0,0 @@ -<script> -import { GlButton, GlIcon, GlLink, GlTooltip } from '@gitlab/ui'; -import { createAlert } from '~/alert'; -import { __, s__, sprintf } from '~/locale'; -import { getIdFromGraphQLId } from '~/graphql_shared/utils'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import SafeHtml from '~/vue_shared/directives/safe_html'; -import { BRIDGE_KIND } from '~/pipelines/components/graph/constants'; -import RetryMrFailedJobMutation from '../../../graphql/mutations/retry_mr_failed_job.mutation.graphql'; - -export default { - components: { - CiIcon, - GlButton, - GlIcon, - GlLink, - GlTooltip, - }, - directives: { - SafeHtml, - }, - props: { - job: { - type: Object, - required: true, - }, - }, - data() { - return { - isHovered: false, - isJobLogVisible: false, - isLoadingAction: false, - }; - }, - computed: { - canReadBuild() { - return this.job.userPermissions.readBuild; - }, - canRetryJob() { - return this.job.retryable && this.job.userPermissions.updateBuild && !this.isBridgeJob; - }, - isBridgeJob() { - return this.job.kind === BRIDGE_KIND; - }, - jobChevronName() { - return this.isJobLogVisible ? 'chevron-down' : 'chevron-right'; - }, - jobTrace() { - if (this.canReadBuild) { - return this.job?.trace?.htmlSummary || this.$options.i18n.noTraceText; - } - - return this.$options.i18n.cannotReadBuild; - }, - parsedJobId() { - return getIdFromGraphQLId(this.job.id); - }, - tooltipErrorText() { - return this.isBridgeJob - ? this.$options.i18n.cannotRetryTrigger - : this.$options.i18n.cannotRetry; - }, - tooltipText() { - return sprintf(this.$options.i18n.jobActionTooltipText, { jobName: this.job.name }); - }, - }, - methods: { - setActiveRow() { - this.isHovered = true; - }, - resetActiveRow() { - this.isHovered = false; - }, - async retryJob() { - try { - this.isLoadingAction = true; - - const { - data: { - jobRetry: { errors }, - }, - } = await this.$apollo.mutate({ - mutation: RetryMrFailedJobMutation, - variables: { id: this.job.id }, - }); - - if (errors.length > 0) { - throw new Error(errors[0]); - } - - this.$emit('job-retried', this.job.name); - } catch (error) { - createAlert({ message: error?.message || this.$options.i18n.retryError }); - } finally { - this.isLoadingAction = false; - } - }, - toggleJobLog(event) { - // Do not toggle the log visibility when clicking on a link - if (event.target.tagName === 'A') { - return; - } - this.isJobLogVisible = !this.isJobLogVisible; - }, - }, - i18n: { - cannotReadBuild: s__("Job|You do not have permission to read this job's log."), - cannotRetry: s__('Job|You do not have permission to run this job again.'), - cannotRetryTrigger: s__('Job|You cannot rerun trigger jobs from this list.'), - jobActionTooltipText: s__('Pipelines|Retry %{jobName} Job'), - noTraceText: s__('Job|No job log'), - retry: __('Retry'), - retryError: __('There was an error while retrying this job'), - }, -}; -</script> -<template> - <div class="container-fluid gl-grid-tpl-rows-auto"> - <div - class="row gl-my-3 gl-cursor-pointer gl-display-flex gl-align-items-center" - :aria-pressed="isJobLogVisible" - role="button" - tabindex="0" - data-testid="widget-row" - @click="toggleJobLog" - @keyup.enter="toggleJobLog" - @keyup.space="toggleJobLog" - @mouseover="setActiveRow" - @mouseout="resetActiveRow" - > - <div class="col-6 gl-text-gray-900 gl-font-weight-bold gl-text-left"> - <gl-icon :name="jobChevronName" /> - <ci-icon :status="job.detailedStatus" /> - {{ job.name }} - </div> - <div class="col-2 gl-text-left">{{ job.stage.name }}</div> - <div class="col-2 gl-text-left"> - <gl-link :href="job.detailedStatus.detailsPath">#{{ parsedJobId }}</gl-link> - </div> - <gl-tooltip v-if="!canRetryJob" :target="() => $refs.retryBtn" placement="top"> - {{ tooltipErrorText }} - </gl-tooltip> - <div class="col-2 gl-text-right"> - <span ref="retryBtn"> - <gl-button - :disabled="!canRetryJob" - icon="retry" - category="tertiary" - :loading="isLoadingAction" - :title="$options.i18n.retry" - :aria-label="$options.i18n.retry" - @click.stop="retryJob" - /> - </span> - </div> - </div> - <div v-if="isJobLogVisible" class="row"> - <pre - v-safe-html="jobTrace" - class="gl-bg-gray-900 gl-text-white gl-w-full" - data-testid="job-log" - ></pre> - </div> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue deleted file mode 100644 index 2c5aa84bc4f..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/failed_jobs_list.vue +++ /dev/null @@ -1,179 +0,0 @@ -<script> -import { GlLoadingIcon } from '@gitlab/ui'; -import { createAlert } from '~/alert'; -import { __, s__, sprintf } from '~/locale'; -import { getQueryHeaders } from '~/pipelines/components/graph/utils'; -import getPipelineFailedJobs from '../../../graphql/queries/get_pipeline_failed_jobs.query.graphql'; -import { graphqlEtagPipelinePath, sortJobsByStatus } from './utils'; -import FailedJobDetails from './failed_job_details.vue'; - -const POLL_INTERVAL = 10000; - -const JOB_ID_HEADER = __('ID'); -const JOB_NAME_HEADER = __('Name'); -const STAGE_HEADER = __('Stage'); - -export default { - components: { - GlLoadingIcon, - FailedJobDetails, - }, - inject: ['graphqlPath'], - props: { - failedJobsCount: { - required: true, - type: Number, - }, - isPipelineActive: { - required: true, - type: Boolean, - }, - pipelineIid: { - type: Number, - required: true, - }, - projectPath: { - type: String, - required: true, - }, - }, - data() { - return { - failedJobs: [], - isActive: false, - isLoadingMore: false, - }; - }, - apollo: { - failedJobs: { - context() { - return getQueryHeaders(this.graphqlResourceEtag); - }, - query: getPipelineFailedJobs, - pollInterval: POLL_INTERVAL, - variables() { - return { - fullPath: this.projectPath, - pipelineIid: this.pipelineIid, - }; - }, - update(data) { - const jobs = data?.project?.pipeline?.jobs?.nodes || []; - return sortJobsByStatus(jobs); - }, - result({ data }) { - const pipeline = data?.project?.pipeline; - - if (pipeline?.jobs?.count) { - this.$emit('failed-jobs-count', pipeline.jobs.count); - this.isActive = pipeline.active; - } - }, - error(e) { - createAlert({ message: e?.message || this.$options.i18n.fetchError, variant: 'danger' }); - }, - }, - }, - computed: { - graphqlResourceEtag() { - return graphqlEtagPipelinePath(this.graphqlPath, this.pipelineIid); - }, - hasFailedJobs() { - return this.failedJobs.length > 0; - }, - isInitialLoading() { - return this.isLoading && !this.isLoadingMore; - }, - isLoading() { - return this.$apollo.queries.failedJobs.loading; - }, - }, - watch: { - isPipelineActive(flag) { - // Turn polling on and off based on REST actions - // By refetching jobs, we will get the graphql `active` - // field to update properly and cascade the polling changes - this.refetchJobs(); - this.handlePolling(flag); - }, - isActive(flag) { - this.handlePolling(flag); - }, - failedJobsCount(count) { - // If the REST data is updated first, we force a refetch - // to keep them in sync - if (this.failedJobs.length !== count) { - this.$apollo.queries.failedJobs.refetch(); - } - }, - }, - mounted() { - if (!this.isActive && !this.isPipelineActive) { - this.handlePolling(false); - } - }, - methods: { - handlePolling(isActive) { - // If the pipeline status has changed and the widget is not expanded, - // We start polling. - if (isActive) { - this.$apollo.queries.failedJobs.startPolling(POLL_INTERVAL); - } else { - this.$apollo.queries.failedJobs.stopPolling(); - } - }, - async retryJob(jobName) { - await this.refetchJobs(); - - this.$toast.show(sprintf(this.$options.i18n.retriedJobsSuccess, { jobName })); - }, - async refetchJobs() { - this.isLoadingMore = true; - - try { - await this.$apollo.queries.failedJobs.refetch(); - } catch { - createAlert(this.$options.i18n.fetchError); - } finally { - this.isLoadingMore = false; - } - }, - }, - columns: [ - { text: JOB_NAME_HEADER, class: 'col-6' }, - { text: STAGE_HEADER, class: 'col-2' }, - { text: JOB_ID_HEADER, class: 'col-2' }, - ], - i18n: { - fetchError: __('There was a problem fetching failed jobs'), - noFailedJobs: s__('Pipeline|No failed jobs in this pipeline 🎉'), - retriedJobsSuccess: __('%{jobName} job is being retried'), - }, -}; -</script> - -<template> - <div> - <gl-loading-icon v-if="isInitialLoading" class="gl-p-4" /> - <div v-else-if="!hasFailedJobs" class="gl-p-4">{{ $options.i18n.noFailedJobs }}</div> - <div v-else class="container-fluid gl-grid-tpl-rows-auto"> - <div class="row gl-my-4 gl-text-gray-900"> - <div - v-for="col in $options.columns" - :key="col.text" - class="gl-font-weight-bold gl-text-left" - :class="col.class" - data-testid="header" - > - {{ col.text }} - </div> - </div> - </div> - <failed-job-details - v-for="job in failedJobs" - :key="job.id" - :job="job" - @job-retried="retryJob" - /> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue deleted file mode 100644 index 60c429459bf..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue +++ /dev/null @@ -1,121 +0,0 @@ -<script> -import { GlButton, GlCard, GlIcon, GlLink, GlPopover, GlSprintf } from '@gitlab/ui'; -import { __, s__, sprintf } from '~/locale'; -import FailedJobsList from './failed_jobs_list.vue'; - -export default { - components: { - GlButton, - GlCard, - GlIcon, - GlLink, - GlPopover, - GlSprintf, - FailedJobsList, - }, - inject: ['fullPath'], - props: { - failedJobsCount: { - required: true, - type: Number, - }, - isPipelineActive: { - required: true, - type: Boolean, - }, - pipelineIid: { - required: true, - type: Number, - }, - pipelinePath: { - required: true, - type: String, - }, - projectPath: { - required: true, - type: String, - }, - }, - data() { - return { - currentFailedJobsCount: this.failedJobsCount, - isActive: false, - isExpanded: false, - }; - }, - computed: { - bodyClasses() { - return this.isExpanded ? '' : 'gl-display-none'; - }, - failedJobsCountText() { - return sprintf(this.$options.i18n.failedJobsLabel, { count: this.currentFailedJobsCount }); - }, - iconName() { - return this.isExpanded ? 'chevron-down' : 'chevron-right'; - }, - popoverId() { - return `popover-${this.pipelineIid}`; - }, - }, - watch: { - failedJobsCount(val) { - this.currentFailedJobsCount = val; - }, - }, - methods: { - setFailedJobsCount(count) { - this.currentFailedJobsCount = count; - }, - toggleWidget() { - this.isExpanded = !this.isExpanded; - }, - }, - i18n: { - additionalInfoPopover: s__( - 'Pipelines|You will see a maximum of 100 jobs in this list. To view all failed jobs, %{linkStart}go to the details page%{linkEnd} of this pipeline.', - ), - additionalInfoTitle: __('Limitation on this view'), - failedJobsLabel: __('Failed jobs (%{count})'), - }, -}; -</script> -<template> - <gl-card - class="gl-new-card" - :class="{ 'gl-border-white gl-hover-border-gray-100': !isExpanded }" - header-class="gl-new-card-header gl-px-3 gl-py-3" - body-class="gl-new-card-body" - data-testid="failed-jobs-card" - :aria-expanded="isExpanded.toString()" - > - <template #header> - <gl-button - variant="link" - class="gl-text-gray-700! gl-font-weight-semibold" - @click="toggleWidget" - > - <gl-icon :name="iconName" /> - {{ failedJobsCountText }} - <gl-icon :id="popoverId" name="information-o" class="gl-ml-2" /> - <gl-popover :target="popoverId" placement="top"> - <template #title> {{ $options.i18n.additionalInfoTitle }} </template> - <slot> - <gl-sprintf :message="$options.i18n.additionalInfoPopover"> - <template #link="{ content }"> - <gl-link class="gl-font-sm" :href="pipelinePath">{{ content }}</gl-link> - </template> - </gl-sprintf> - </slot> - </gl-popover> - </gl-button> - </template> - <failed-jobs-list - v-if="isExpanded" - :failed-jobs-count="failedJobsCount" - :is-pipeline-active="isPipelineActive" - :pipeline-iid="pipelineIid" - :project-path="projectPath" - @failed-jobs-count="setFailedJobsCount" - /> - </gl-card> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/utils.js b/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/utils.js deleted file mode 100644 index 2d0c467c54f..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/failure_widget/utils.js +++ /dev/null @@ -1,19 +0,0 @@ -export const isFailedJob = (job = {}) => { - return job?.detailedStatus?.group === 'failed' || false; -}; - -export const sortJobsByStatus = (jobs = []) => { - const newJobs = [...jobs]; - - return newJobs.sort((a) => { - if (isFailedJob(a)) { - return -1; - } - - return 1; - }); -}; - -export const graphqlEtagPipelinePath = (graphqlPath, pipelineId) => { - return `${graphqlPath}pipelines/id/${pipelineId}`; -}; diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue b/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue deleted file mode 100644 index 235126fea0c..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/nav_controls.vue +++ /dev/null @@ -1,69 +0,0 @@ -<script> -import { GlButton } from '@gitlab/ui'; - -export default { - name: 'PipelineNavControls', - components: { - GlButton, - }, - props: { - newPipelinePath: { - type: String, - required: false, - default: null, - }, - - resetCachePath: { - type: String, - required: false, - default: null, - }, - - ciLintPath: { - type: String, - required: false, - default: null, - }, - - isResetCacheButtonLoading: { - type: Boolean, - required: false, - default: false, - }, - }, - methods: { - onClickResetCache() { - this.$emit('resetRunnersCache', this.resetCachePath); - }, - }, -}; -</script> -<template> - <div class="nav-controls"> - <gl-button - v-if="resetCachePath" - :loading="isResetCacheButtonLoading" - class="js-clear-cache" - data-testid="clear-cache-button" - @click="onClickResetCache" - > - {{ s__('Pipelines|Clear runner caches') }} - </gl-button> - - <gl-button v-if="ciLintPath" :href="ciLintPath" class="js-ci-lint" data-testid="ci-lint-button"> - {{ s__('Pipelines|CI lint') }} - </gl-button> - - <gl-button - v-if="newPipelinePath" - :href="newPipelinePath" - variant="confirm" - category="primary" - class="js-run-pipeline" - data-testid="run-pipeline-button" - data-qa-selector="run_pipeline_button" - > - {{ s__('Pipeline|Run pipeline') }} - </gl-button> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_labels.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_labels.vue deleted file mode 100644 index 40b2454b8c1..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_labels.vue +++ /dev/null @@ -1,170 +0,0 @@ -<script> -import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui'; -import { helpPagePath } from '~/helpers/help_page_helper'; -import { SCHEDULE_ORIGIN } from '../../constants'; - -export default { - components: { - GlBadge, - GlLink, - GlPopover, - GlSprintf, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - inject: { - targetProjectFullPath: { - default: '', - }, - }, - props: { - pipeline: { - type: Object, - required: true, - }, - pipelineScheduleUrl: { - type: String, - required: true, - }, - }, - computed: { - isScheduled() { - return this.pipeline.source === SCHEDULE_ORIGIN; - }, - isInFork() { - return Boolean( - this.targetProjectFullPath && - this.pipeline?.project?.full_path !== `/${this.targetProjectFullPath}`, - ); - }, - autoDevopsTagId() { - return `pipeline-url-autodevops-${this.pipeline.id}`; - }, - autoDevopsHelpPath() { - return helpPagePath('topics/autodevops/index.md'); - }, - }, -}; -</script> -<template> - <div class="label-container gl-mt-1"> - <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 - :title="__('Latest pipeline for the most recent commit on this branch')" - variant="success" - size="sm" - data-testid="pipeline-url-latest" - >{{ __('latest') }}</gl-badge - > - <gl-badge - v-if="pipeline.flags.merge_train_pipeline" - v-gl-tooltip - :title=" - s__( - 'Pipeline|This pipeline ran on the contents of this merge request combined with the contents of all other merge requests queued for merging into the target branch.', - ) - " - variant="info" - size="sm" - data-testid="pipeline-url-train" - >{{ s__('Pipeline|merge train') }}</gl-badge - > - <gl-badge - v-if="pipeline.flags.yaml_errors" - v-gl-tooltip - :title="pipeline.yaml_errors" - variant="danger" - size="sm" - data-testid="pipeline-url-yaml" - >{{ __('yaml invalid') }}</gl-badge - > - <gl-badge - v-if="pipeline.flags.failure_reason" - v-gl-tooltip - :title="pipeline.failure_reason" - variant="danger" - size="sm" - data-testid="pipeline-url-failure" - >{{ __('error') }}</gl-badge - > - <template v-if="pipeline.flags.auto_devops"> - <gl-link - :id="autoDevopsTagId" - tabindex="0" - data-testid="pipeline-url-autodevops" - role="button" - > - <gl-badge variant="info" size="sm"> - {{ __('Auto DevOps') }} - </gl-badge> - </gl-link> - <gl-popover :target="autoDevopsTagId" triggers="focus" placement="top"> - <template #title> - <div class="gl-font-weight-normal gl-line-height-normal"> - <gl-sprintf - :message=" - __( - 'This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}', - ) - " - > - <template #strong="{ content }"> - <b>{{ content }}</b> - </template> - </gl-sprintf> - </div> - </template> - <gl-link - :href="autoDevopsHelpPath" - data-testid="pipeline-url-autodevops-link" - target="_blank" - > - {{ __('Learn more about Auto DevOps') }} - </gl-link> - </gl-popover> - </template> - - <gl-badge - v-if="pipeline.flags.stuck" - variant="warning" - size="sm" - data-testid="pipeline-url-stuck" - >{{ __('stuck') }}</gl-badge - > - <gl-badge - v-if="pipeline.flags.detached_merge_request_pipeline" - v-gl-tooltip - :title=" - s__( - `Pipeline|This pipeline ran on the contents of this merge request's source branch, not the target branch.`, - ) - " - variant="info" - size="sm" - data-testid="pipeline-url-detached" - >{{ s__('Pipeline|merge request') }}</gl-badge - > - <gl-badge - v-if="isInFork" - v-gl-tooltip - :title="__('Pipeline ran in fork of project')" - variant="info" - size="sm" - data-testid="pipeline-url-fork" - >{{ __('fork') }}</gl-badge - > - </div> -</template> 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 deleted file mode 100644 index 747d94d92f2..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_multi_actions.vue +++ /dev/null @@ -1,172 +0,0 @@ -<script> -import { - GlAlert, - GlDropdown, - GlDropdownItem, - GlSearchBoxByType, - GlLoadingIcon, - GlTooltipDirective, -} from '@gitlab/ui'; -import fuzzaldrinPlus from 'fuzzaldrin-plus'; -import axios from '~/lib/utils/axios_utils'; -import { __, s__ } from '~/locale'; -import Tracking from '~/tracking'; -import { TRACKING_CATEGORIES } from '../../constants'; - -export const i18n = { - downloadArtifacts: __('Download artifacts'), - artifactsFetchErrorMessage: s__('Pipelines|Could not load artifacts.'), - artifactsFetchWarningMessage: s__( - 'Pipelines|Failed to update. Please reload page to update the list of artifacts.', - ), - emptyArtifactsMessage: __('No artifacts found'), -}; - -export default { - i18n, - directives: { - GlTooltip: GlTooltipDirective, - }, - components: { - GlAlert, - GlDropdown, - GlDropdownItem, - GlSearchBoxByType, - GlLoadingIcon, - }, - mixins: [Tracking.mixin()], - inject: { - artifactsEndpoint: { - default: '', - }, - artifactsEndpointPlaceholder: { - default: '', - }, - }, - props: { - pipelineId: { - type: Number, - required: true, - }, - }, - data() { - return { - artifacts: [], - hasError: false, - isLoading: false, - searchQuery: '', - isNewPipeline: false, - }; - }, - computed: { - hasArtifacts() { - return this.artifacts.length > 0; - }, - filteredArtifacts() { - return this.searchQuery.length > 0 - ? fuzzaldrinPlus.filter(this.artifacts, this.searchQuery, { key: 'name' }) - : this.artifacts; - }, - }, - watch: { - pipelineId() { - this.isNewPipeline = true; - }, - }, - methods: { - fetchArtifacts() { - // refactor tracking based on action once this dropdown supports - // actions other than artifacts - this.track('click_artifacts_dropdown', { label: TRACKING_CATEGORIES.table }); - - // Preserve the last good list and present it if a request fails - const oldArtifacts = [...this.artifacts]; - this.artifacts = []; - - this.hasError = false; - this.isLoading = true; - - // Replace the placeholder with the ID of the pipeline we are viewing - const endpoint = this.artifactsEndpoint.replace( - this.artifactsEndpointPlaceholder, - this.pipelineId, - ); - return axios - .get(endpoint) - .then(({ data }) => { - this.artifacts = data.artifacts; - this.isNewPipeline = false; - }) - .catch(() => { - this.hasError = true; - if (!this.isNewPipeline) { - this.artifacts = oldArtifacts; - } - }) - .finally(() => { - this.isLoading = false; - }); - }, - handleDropdownShown() { - if (this.hasArtifacts) { - this.$refs.searchInput.focusInput(); - } - }, - }, -}; -</script> -<template> - <gl-dropdown - v-gl-tooltip - :title="$options.i18n.downloadArtifacts" - :text="$options.i18n.downloadArtifacts" - :aria-label="$options.i18n.downloadArtifacts" - :header-text="$options.i18n.downloadArtifacts" - icon="download" - data-testid="pipeline-multi-actions-dropdown" - right - lazy - text-sr-only - @show="fetchArtifacts" - @shown="handleDropdownShown" - > - <gl-alert v-if="hasError && !hasArtifacts" variant="danger" :dismissible="false"> - {{ $options.i18n.artifactsFetchErrorMessage }} - </gl-alert> - - <gl-loading-icon v-else-if="isLoading" size="sm" /> - - <gl-dropdown-item v-else-if="!hasArtifacts" data-testid="artifacts-empty-message"> - {{ $options.i18n.emptyArtifactsMessage }} - </gl-dropdown-item> - - <template #header> - <gl-search-box-by-type v-if="hasArtifacts" ref="searchInput" v-model.trim="searchQuery" /> - </template> - - <gl-dropdown-item - v-for="(artifact, i) in filteredArtifacts" - :key="i" - :href="artifact.path" - rel="nofollow" - download - class="gl-word-break-word" - data-testid="artifact-item" - > - {{ artifact.name }} - </gl-dropdown-item> - - <template #footer> - <gl-dropdown-item - v-if="hasError && hasArtifacts" - class="gl-list-style-none" - disabled - data-testid="artifacts-fetch-warning" - > - <span class="gl-font-sm"> - {{ $options.i18n.artifactsFetchWarningMessage }} - </span> - </gl-dropdown-item> - </template> - </gl-dropdown> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue deleted file mode 100644 index caeee7edefe..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_operations.vue +++ /dev/null @@ -1,113 +0,0 @@ -<script> -import { GlButton, GlTooltipDirective, GlModalDirective } from '@gitlab/ui'; -import Tracking from '~/tracking'; -import eventHub from '../../event_hub'; -import { BUTTON_TOOLTIP_RETRY, BUTTON_TOOLTIP_CANCEL, TRACKING_CATEGORIES } from '../../constants'; -import PipelineMultiActions from './pipeline_multi_actions.vue'; -import PipelinesManualActions from './pipelines_manual_actions.vue'; - -export default { - BUTTON_TOOLTIP_RETRY, - BUTTON_TOOLTIP_CANCEL, - directives: { - GlTooltip: GlTooltipDirective, - GlModalDirective, - }, - components: { - GlButton, - PipelineMultiActions, - PipelinesManualActions, - }, - mixins: [Tracking.mixin()], - props: { - pipeline: { - type: Object, - required: true, - }, - cancelingPipeline: { - type: Number, - required: false, - default: null, - }, - }, - data() { - return { - isRetrying: false, - }; - }, - computed: { - hasActions() { - return ( - this.pipeline?.details?.has_manual_actions || this.pipeline?.details?.has_scheduled_actions - ); - }, - isCancelling() { - return this.cancelingPipeline === this.pipeline.id; - }, - }, - watch: { - pipeline() { - this.isRetrying = false; - }, - }, - methods: { - handleCancelClick() { - this.trackClick('click_cancel_button'); - eventHub.$emit('openConfirmationModal', { - pipeline: this.pipeline, - endpoint: this.pipeline.cancel_path, - }); - }, - handleRetryClick() { - this.isRetrying = true; - this.trackClick('click_retry_button'); - eventHub.$emit('retryPipeline', this.pipeline.retry_path); - }, - trackClick(action) { - this.track(action, { label: TRACKING_CATEGORIES.table }); - }, - }, -}; -</script> - -<template> - <div class="gl-text-right"> - <div class="btn-group"> - <pipelines-manual-actions v-if="hasActions" :iid="pipeline.iid" /> - - <gl-button - v-if="pipeline.flags.retryable" - v-gl-tooltip.hover - :aria-label="$options.BUTTON_TOOLTIP_RETRY" - :title="$options.BUTTON_TOOLTIP_RETRY" - :disabled="isRetrying" - :loading="isRetrying" - class="js-pipelines-retry-button" - data-qa-selector="pipeline_retry_button" - data-testid="pipelines-retry-button" - icon="retry" - variant="default" - category="secondary" - @click="handleRetryClick" - /> - - <gl-button - v-if="pipeline.flags.cancelable" - v-gl-tooltip.hover - v-gl-modal-directive="'confirmation-modal'" - :aria-label="$options.BUTTON_TOOLTIP_CANCEL" - :title="$options.BUTTON_TOOLTIP_CANCEL" - :loading="isCancelling" - :disabled="isCancelling" - icon="cancel" - variant="danger" - category="primary" - class="js-pipelines-cancel-button gl-ml-1" - data-testid="pipelines-cancel-button" - @click="handleCancelClick" - /> - - <pipeline-multi-actions :pipeline-id="pipeline.id" /> - </div> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue deleted file mode 100644 index 9f38be668f2..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_stop_modal.vue +++ /dev/null @@ -1,104 +0,0 @@ -<script> -import { GlLink, GlModal, GlSprintf } from '@gitlab/ui'; -import { isEmpty } from 'lodash'; -import { __, s__, sprintf } from '~/locale'; -import CiIcon from '~/vue_shared/components/ci_icon.vue'; - -/** - * Pipeline Stop Modal. - * - * Renders the modal used to confirm stopping a pipeline. - */ -export default { - components: { - GlModal, - GlLink, - GlSprintf, - CiIcon, - }, - props: { - pipeline: { - type: Object, - required: true, - deep: true, - }, - }, - computed: { - modalTitle() { - return sprintf( - s__('Pipeline|Stop pipeline #%{pipelineId}?'), - { - pipelineId: `${this.pipeline.id}`, - }, - false, - ); - }, - modalText() { - return s__(`Pipeline|You’re about to stop pipeline #%{pipelineId}.`); - }, - hasRef() { - return !isEmpty(this.pipeline.ref); - }, - primaryProps() { - return { - text: s__('Pipeline|Stop pipeline'), - attributes: { variant: 'danger' }, - }; - }, - cancelProps() { - return { - text: __('Cancel'), - }; - }, - }, - methods: { - emitSubmit(event) { - this.$emit('submit', event); - }, - }, -}; -</script> -<template> - <gl-modal - modal-id="confirmation-modal" - :title="modalTitle" - :action-primary="primaryProps" - :action-cancel="cancelProps" - @primary="emitSubmit($event)" - > - <p> - <gl-sprintf :message="modalText"> - <template #pipelineId> - <strong>{{ pipeline.id }}</strong> - </template> - </gl-sprintf> - </p> - - <p v-if="pipeline"> - <ci-icon - v-if="pipeline.details" - :status="pipeline.details.status" - class="vertical-align-middle" - /> - - <span class="font-weight-bold">{{ __('Pipeline') }}</span> - - <a :href="pipeline.path" class="js-pipeline-path link-commit">#{{ pipeline.id }}</a> - <template v-if="hasRef"> - {{ __('from') }} - <a :href="pipeline.ref.path" class="link-commit ref-name">{{ pipeline.ref.name }}</a> - </template> - </p> - - <template v-if="pipeline.commit"> - <p> - <span class="font-weight-bold">{{ __('Commit') }}</span> - - <gl-link :href="pipeline.commit.commit_path" class="js-commit-sha commit-sha link-commit"> - {{ pipeline.commit.short_id }} - </gl-link> - </p> - <p>{{ pipeline.commit.title }}</p> - </template> - </gl-modal> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue deleted file mode 100644 index 2a73795db0a..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_triggerer.vue +++ /dev/null @@ -1,37 +0,0 @@ -<script> -import { GlAvatarLink, GlAvatar, GlTooltipDirective } from '@gitlab/ui'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; - -export default { - components: { - GlAvatarLink, - GlAvatar, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - mixins: [glFeatureFlagMixin()], - props: { - pipeline: { - type: Object, - required: true, - }, - }, - computed: { - user() { - return this.pipeline.user; - }, - }, -}; -</script> -<template> - <div class="pipeline-triggerer" data-testid="pipeline-triggerer"> - <gl-avatar-link v-if="user" v-gl-tooltip :href="user.path" :title="user.name" class="gl-ml-3"> - <gl-avatar :size="32" :src="user.avatar_url" /> - </gl-avatar-link> - - <span v-else class="gl-ml-3"> - {{ s__('Pipelines|API') }} - </span> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue deleted file mode 100644 index ff1a01d5037..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipeline_url.vue +++ /dev/null @@ -1,239 +0,0 @@ -<script> -import { GlIcon, GlLink, GlTooltipDirective } from '@gitlab/ui'; -import { __ } from '~/locale'; -import Tracking from '~/tracking'; -import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate/tooltip_on_truncate.vue'; -import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; -import { ICONS, TRACKING_CATEGORIES } from '../../constants'; -import PipelineLabels from './pipeline_labels.vue'; - -export default { - components: { - GlIcon, - GlLink, - PipelineLabels, - TooltipOnTruncate, - UserAvatarLink, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - mixins: [Tracking.mixin()], - props: { - pipeline: { - type: Object, - required: true, - }, - pipelineScheduleUrl: { - type: String, - required: true, - }, - pipelineKey: { - type: String, - required: true, - }, - refClass: { - type: String, - required: false, - default: '', - }, - }, - computed: { - mergeRequestRef() { - return this.pipeline?.merge_request; - }, - commitRef() { - return this.pipeline?.ref; - }, - commitTag() { - return this.commitRef?.tag; - }, - commitUrl() { - return this.pipeline?.commit?.commit_path; - }, - commitShortSha() { - return this.pipeline?.commit?.short_id; - }, - refUrl() { - return this.commitRef?.ref_url || this.commitRef?.path; - }, - tooltipTitle() { - return this.mergeRequestRef?.title || this.commitRef?.name; - }, - commitAuthor() { - let commitAuthorInformation; - const pipelineCommit = this.pipeline?.commit; - const pipelineCommitAuthor = pipelineCommit?.author; - - if (!pipelineCommit) { - return null; - } - - // 1. person who is an author of a commit might be a GitLab user - if (pipelineCommitAuthor) { - // 2. if person who is an author of a commit is a GitLab user - // they can have a GitLab avatar - if (pipelineCommitAuthor?.avatar_url) { - commitAuthorInformation = pipelineCommitAuthor; - - // 3. If GitLab user does not have avatar, they might have a Gravatar - } else if (pipelineCommit.author_gravatar_url) { - commitAuthorInformation = { - ...pipelineCommitAuthor, - avatar_url: pipelineCommit.author_gravatar_url, - }; - } - // 4. If committer is not a GitLab User, they can have a Gravatar - } else { - commitAuthorInformation = { - avatar_url: pipelineCommit.author_gravatar_url, - path: `mailto:${pipelineCommit.author_email}`, - username: pipelineCommit.author_name, - }; - } - - return commitAuthorInformation; - }, - commitIcon() { - let name = ''; - - if (this.commitTag) { - name = ICONS.TAG; - } else if (this.mergeRequestRef) { - name = ICONS.MR; - } else { - name = ICONS.BRANCH; - } - - return name; - }, - commitIconTooltipTitle() { - switch (this.commitIcon) { - case ICONS.TAG: - return __('Tag'); - case ICONS.MR: - return __('Merge Request'); - default: - return __('Branch'); - } - }, - commitTitle() { - return this.pipeline?.commit?.title; - }, - pipelineName() { - return this.pipeline?.name; - }, - }, - methods: { - trackClick(action) { - this.track(action, { label: TRACKING_CATEGORIES.table }); - }, - }, -}; -</script> -<template> - <div class="pipeline-tags" data-testid="pipeline-url-table-cell"> - <div v-if="pipelineName" class="gl-mb-2" data-testid="pipeline-name-container"> - <span class="gl-display-flex"> - <tooltip-on-truncate - :title="pipelineName" - class="gl-flex-grow-1 gl-text-truncate gl-text-gray-900" - > - <gl-link - :href="pipeline.path" - class="gl-text-blue-600!" - data-testid="pipeline-url-link" - >{{ pipelineName }}</gl-link - > - </tooltip-on-truncate> - </span> - </div> - - <div v-if="!pipelineName" class="commit-title gl-mb-2" data-testid="commit-title-container"> - <span v-if="commitTitle" class="gl-display-flex"> - <tooltip-on-truncate :title="commitTitle" class="gl-flex-grow-1 gl-text-truncate"> - <gl-link - :href="commitUrl" - class="commit-row-message gl-text-blue-600!" - data-testid="commit-title" - @click="trackClick('click_commit_title')" - >{{ commitTitle }}</gl-link - > - </tooltip-on-truncate> - </span> - <span v-else class="gl-text-gray-500">{{ - __("Can't find HEAD commit for this branch") - }}</span> - </div> - <div class="gl-mb-2"> - <gl-link - :href="pipeline.path" - class="gl-mr-1 gl-text-blue-500!" - data-testid="pipeline-url-link" - data-qa-selector="pipeline_url_link" - @click="trackClick('click_pipeline_id')" - >#{{ pipeline[pipelineKey] }}</gl-link - > - <!--Commit row--> - <div class="gl-display-inline-flex gl-rounded-base gl-px-2 gl-bg-gray-50 gl-text-gray-700"> - <tooltip-on-truncate :title="tooltipTitle" truncate-target="child" placement="top"> - <gl-icon - v-gl-tooltip - :name="commitIcon" - :title="commitIconTooltipTitle" - :size="12" - data-testid="commit-icon-type" - /> - <gl-link - v-if="mergeRequestRef" - :href="mergeRequestRef.path" - class="gl-font-sm gl-font-monospace gl-text-gray-700! gl-hover-text-gray-900!" - :class="refClass" - data-testid="merge-request-ref" - @click="trackClick('click_mr_ref')" - >{{ mergeRequestRef.iid }}</gl-link - > - <gl-link - v-else - :href="refUrl" - class="gl-font-sm gl-font-monospace gl-text-gray-700! gl-hover-text-gray-900!" - :class="refClass" - data-testid="commit-ref-name" - @click="trackClick('click_commit_name')" - >{{ commitRef.name }}</gl-link - > - </tooltip-on-truncate> - </div> - <div - class="gl-display-inline-block gl-rounded-base gl-font-sm gl-px-2 gl-bg-gray-50 gl-text-black-normal" - > - <gl-icon - v-gl-tooltip - name="commit" - class="commit-icon gl-mr-1" - :title="__('Commit')" - :size="12" - data-testid="commit-icon" - /> - <gl-link - :href="commitUrl" - class="gl-font-sm gl-font-monospace gl-mr-0 gl-text-gray-700!" - data-testid="commit-short-sha" - @click="trackClick('click_commit_sha')" - >{{ commitShortSha }}</gl-link - > - </div> - <user-avatar-link - v-if="commitAuthor" - :link-href="commitAuthor.path" - :img-src="commitAuthor.avatar_url" - :img-size="16" - :img-alt="commitAuthor.name" - :tooltip-text="commitAuthor.name" - class="gl-ml-1" - /> - <!--End of commit row--> - </div> - <pipeline-labels :pipeline-schedule-url="pipelineScheduleUrl" :pipeline="pipeline" /> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue deleted file mode 100644 index 574d291a767..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines.vue +++ /dev/null @@ -1,449 +0,0 @@ -<!-- eslint-disable vue/multi-word-component-names --> -<script> -import { GlEmptyState, GlIcon, GlLoadingIcon, GlCollapsibleListbox } from '@gitlab/ui'; -import { isEqual } from 'lodash'; -import * as Sentry from '@sentry/browser'; -import { createAlert, VARIANT_INFO, VARIANT_WARNING } from '~/alert'; -import { getParameterByName } from '~/lib/utils/url_utility'; -import { __, s__ } from '~/locale'; -import Tracking from '~/tracking'; -import NavigationTabs from '~/vue_shared/components/navigation_tabs.vue'; -import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; -import { isLoggedIn } from '~/lib/utils/common_utils'; -import setSortPreferenceMutation from '~/issues/list/queries/set_sort_preference.mutation.graphql'; -import { - ANY_TRIGGER_AUTHOR, - RAW_TEXT_WARNING, - FILTER_TAG_IDENTIFIER, - PipelineKeyOptions, - TRACKING_CATEGORIES, -} from '../../constants'; -import PipelinesMixin from '../../mixins/pipelines_mixin'; -import PipelinesService from '../../services/pipelines_service'; -import { validateParams } from '../../utils'; -import EmptyState from './empty_state.vue'; -import NavigationControls from './nav_controls.vue'; -import PipelinesFilteredSearch from './pipelines_filtered_search.vue'; -import PipelinesTableComponent from './pipelines_table.vue'; - -export default { - PipelineKeyOptions, - components: { - EmptyState, - GlCollapsibleListbox, - GlEmptyState, - GlIcon, - GlLoadingIcon, - NavigationTabs, - NavigationControls, - PipelinesFilteredSearch, - PipelinesTableComponent, - TablePagination, - }, - mixins: [PipelinesMixin, Tracking.mixin()], - props: { - store: { - type: Object, - required: true, - }, - // Can be rendered in 3 different places, with some visual differences - // Accepts root | child - // `root` -> main view - // `child` -> rendered inside MR or Commit View - viewType: { - type: String, - required: false, - default: 'root', - }, - endpoint: { - type: String, - required: true, - }, - pipelineScheduleUrl: { - type: String, - required: false, - default: '', - }, - emptyStateSvgPath: { - type: String, - required: true, - }, - errorStateSvgPath: { - type: String, - required: true, - }, - noPipelinesSvgPath: { - type: String, - required: true, - }, - hasGitlabCi: { - type: Boolean, - required: true, - }, - canCreatePipeline: { - type: Boolean, - required: true, - }, - ciLintPath: { - type: String, - required: false, - default: null, - }, - resetCachePath: { - type: String, - required: false, - default: null, - }, - newPipelinePath: { - type: String, - required: false, - default: null, - }, - projectId: { - type: String, - required: true, - }, - defaultBranchName: { - type: String, - required: false, - default: null, - }, - params: { - type: Object, - required: true, - }, - registrationToken: { - type: String, - required: false, - default: null, - }, - defaultVisibilityPipelineIdType: { - type: String, - required: false, - default: null, - }, - }, - data() { - return { - // Start with loading state to avoid a glitch when the empty state will be rendered - isLoading: true, - state: this.store.state, - scope: getParameterByName('scope') || 'all', - page: getParameterByName('page') || '1', - requestData: {}, - isResetCacheButtonLoading: false, - visibilityPipelineIdType: this.defaultVisibilityPipelineIdType, - }; - }, - stateMap: { - // with tabs - loading: 'loading', - tableList: 'tableList', - error: 'error', - emptyTab: 'emptyTab', - - // without tabs - emptyState: 'emptyState', - }, - scopes: { - all: 'all', - finished: 'finished', - branches: 'branches', - tags: 'tags', - }, - computed: { - /** - * `hasGitlabCi` handles both internal and external CI. - * The order on which the checks are made in this method is - * important to guarantee we handle all the corner cases. - */ - stateToRender() { - const { stateMap } = this.$options; - - if (this.isLoading) { - return stateMap.loading; - } - - if (this.hasError) { - return stateMap.error; - } - - if (this.state.pipelines.length) { - return stateMap.tableList; - } - - if ((this.scope !== 'all' && this.scope !== null) || this.hasGitlabCi) { - return stateMap.emptyTab; - } - - return stateMap.emptyState; - }, - /** - * Tabs are rendered in all states except empty state. - * They are not rendered before the first request to avoid a flicker on first load. - */ - shouldRenderTabs() { - const { stateMap } = this.$options; - return ( - this.hasMadeRequest && - [stateMap.loading, stateMap.tableList, stateMap.error, stateMap.emptyTab].includes( - this.stateToRender, - ) - ); - }, - - shouldRenderButtons() { - return ( - (this.newPipelinePath || this.resetCachePath || this.ciLintPath) && this.shouldRenderTabs - ); - }, - - shouldRenderPagination() { - return !this.isLoading && !this.hasError; - }, - - emptyTabMessage() { - if (this.scope === this.$options.scopes.finished) { - return s__('Pipelines|There are currently no finished pipelines.'); - } - - return s__('Pipelines|There are currently no pipelines.'); - }, - - tabs() { - const { count } = this.state; - const { scopes } = this.$options; - - return [ - { - name: __('All'), - scope: scopes.all, - count: count.all, - isActive: this.scope === 'all', - }, - { - name: __('Finished'), - scope: scopes.finished, - isActive: this.scope === 'finished', - }, - { - name: __('Branches'), - scope: scopes.branches, - isActive: this.scope === 'branches', - }, - { - name: __('Tags'), - scope: scopes.tags, - isActive: this.scope === 'tags', - }, - ]; - }, - validatedParams() { - return validateParams(this.params); - }, - selectedPipelineKeyOption() { - return ( - this.$options.PipelineKeyOptions.find((e) => this.visibilityPipelineIdType === e.value) || - this.$options.PipelineKeyOptions[0] - ); - }, - }, - created() { - this.service = new PipelinesService(this.endpoint); - this.requestData = { page: this.page, scope: this.scope, ...this.validatedParams }; - }, - methods: { - onChangeTab(scope) { - if (this.scope === scope) { - return; - } - - let params = { - scope, - page: '1', - }; - - params = this.onChangeWithFilter(params); - - this.updateContent(params); - - this.track('click_filter_tabs', { label: TRACKING_CATEGORIES.tabs, property: scope }); - }, - successCallback(resp) { - // Because we are polling & the user is interacting verify if the response received - // matches the last request made - if (isEqual(resp.config.params, this.requestData)) { - this.store.storeCount(resp.data.count); - this.store.storePagination(resp.headers); - this.setCommonData(resp.data.pipelines); - } - }, - handleResetRunnersCache(endpoint) { - this.isResetCacheButtonLoading = true; - - this.service - .postAction(endpoint) - .then(() => { - this.isResetCacheButtonLoading = false; - createAlert({ - message: s__('Pipelines|Project cache successfully reset.'), - variant: VARIANT_INFO, - }); - }) - .catch(() => { - this.isResetCacheButtonLoading = false; - createAlert({ - message: s__('Pipelines|Something went wrong while cleaning runners cache.'), - }); - }); - }, - resetRequestData() { - this.requestData = { page: this.page, scope: this.scope }; - }, - filterPipelines(filters) { - this.resetRequestData(); - - filters.forEach((filter) => { - // do not add Any for username query param, so we - // can fetch all trigger authors - if ( - filter.type && - filter.value.data !== ANY_TRIGGER_AUTHOR && - filter.type !== FILTER_TAG_IDENTIFIER - ) { - this.requestData[filter.type] = filter.value.data; - } - - if (filter.type === FILTER_TAG_IDENTIFIER) { - this.requestData.ref = filter.value.data; - } - - if (!filter.type) { - createAlert({ - message: RAW_TEXT_WARNING, - variant: VARIANT_WARNING, - }); - } - }); - - if (filters.length === 0) { - this.resetRequestData(); - } - - this.updateContent({ ...this.requestData, page: '1' }); - }, - changeVisibilityPipelineIDType(idType) { - this.visibilityPipelineIdType = idType; - this.saveVisibilityPipelineIDType(idType); - }, - saveVisibilityPipelineIDType(idType) { - if (!isLoggedIn()) return; - - this.$apollo - .mutate({ - mutation: setSortPreferenceMutation, - variables: { input: { visibilityPipelineIdType: idType.toUpperCase() } }, - }) - .then(({ data }) => { - if (data.userPreferencesUpdate.errors.length) { - throw new Error(data.userPreferencesUpdate.errors); - } - }) - .catch((error) => { - Sentry.captureException(error); - }); - }, - }, -}; -</script> -<template> - <div class="pipelines-container"> - <div - v-if="shouldRenderTabs || shouldRenderButtons" - class="top-area scrolling-tabs-container inner-page-scroll-tabs gl-border-none" - > - <div class="fade-left"><gl-icon name="chevron-lg-left" :size="12" /></div> - <div class="fade-right"><gl-icon name="chevron-lg-right" :size="12" /></div> - - <navigation-tabs - v-if="shouldRenderTabs" - :tabs="tabs" - scope="pipelines" - @onChangeTab="onChangeTab" - /> - - <navigation-controls - v-if="shouldRenderButtons" - :new-pipeline-path="newPipelinePath" - :reset-cache-path="resetCachePath" - :ci-lint-path="ciLintPath" - :is-reset-cache-button-loading="isResetCacheButtonLoading" - @resetRunnersCache="handleResetRunnersCache" - /> - </div> - - <div v-if="stateToRender !== $options.stateMap.emptyState" class="gl-display-flex"> - <div class="row-content-block gl-display-flex gl-flex-grow-1 gl-border-b-0"> - <pipelines-filtered-search - class="gl-display-flex gl-flex-grow-1 gl-mr-4" - :project-id="projectId" - :default-branch-name="defaultBranchName" - :params="validatedParams" - @filterPipelines="filterPipelines" - /> - <gl-collapsible-listbox - v-model="visibilityPipelineIdType" - data-testid="pipeline-key-collapsible-box" - :toggle-text="selectedPipelineKeyOption.text" - :items="$options.PipelineKeyOptions" - @select="changeVisibilityPipelineIDType" - /> - </div> - </div> - - <div class="content-list pipelines"> - <gl-loading-icon - v-if="stateToRender === $options.stateMap.loading" - :label="s__('Pipelines|Loading Pipelines')" - size="lg" - class="prepend-top-20" - /> - - <empty-state - v-else-if="stateToRender === $options.stateMap.emptyState" - :empty-state-svg-path="emptyStateSvgPath" - :can-set-ci="canCreatePipeline" - :registration-token="registrationToken" - /> - - <gl-empty-state - v-else-if="stateToRender === $options.stateMap.error" - :svg-path="errorStateSvgPath" - :title="s__('Pipelines|There was an error fetching the pipelines.')" - :description="s__('Pipelines|Try again in a few moments or contact your support team.')" - /> - - <gl-empty-state - v-else-if="stateToRender === $options.stateMap.emptyTab" - :svg-path="noPipelinesSvgPath" - :title="emptyTabMessage" - /> - - <div v-else-if="stateToRender === $options.stateMap.tableList"> - <pipelines-table-component - :pipelines="state.pipelines" - :pipeline-schedule-url="pipelineScheduleUrl" - :update-graph-dropdown="updateGraphDropdown" - :view-type="viewType" - :pipeline-key-option="selectedPipelineKeyOption" - /> - </div> - - <table-pagination - v-if="shouldRenderPagination" - :change="onChangePage" - :page-info="state.pageInfo" - /> - </div> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue deleted file mode 100644 index 4452db64b0a..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_artifacts.vue +++ /dev/null @@ -1,72 +0,0 @@ -<script> -import { GlDisclosureDropdown, GlTooltipDirective } from '@gitlab/ui'; -import { __ } from '~/locale'; - -export const i18n = { - artifacts: __('Artifacts'), - artifactSectionHeader: __('Download artifacts'), -}; - -export default { - i18n, - directives: { - GlTooltip: GlTooltipDirective, - }, - components: { - GlDisclosureDropdown, - }, - inject: { - artifactsEndpoint: { - default: '', - }, - artifactsEndpointPlaceholder: { - default: '', - }, - }, - props: { - pipelineId: { - type: Number, - required: true, - }, - artifacts: { - type: Array, - required: false, - default: () => [], - }, - }, - computed: { - items() { - return [ - { - name: this.$options.i18n.artifactSectionHeader, - items: this.artifacts.map(({ name, path }) => ({ - text: name, - href: path, - extraAttrs: { - download: '', - rel: 'nofollow', - }, - })), - }, - ]; - }, - shouldShowDropdown() { - return this.artifacts?.length; - }, - }, -}; -</script> -<template> - <gl-disclosure-dropdown - v-if="shouldShowDropdown" - v-gl-tooltip - class="build-artifacts js-pipeline-dropdown-download" - :title="$options.i18n.artifacts" - :toggle-text="$options.i18n.artifacts" - :aria-label="$options.i18n.artifacts" - icon="download" - placement="right" - text-sr-only - :items="items" - /> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue deleted file mode 100644 index 7dc1e60610e..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_filtered_search.vue +++ /dev/null @@ -1,130 +0,0 @@ -<script> -import { GlFilteredSearch } from '@gitlab/ui'; -import { map } from 'lodash'; -import { s__ } from '~/locale'; -import Tracking from '~/tracking'; -import { OPERATORS_IS } from '~/vue_shared/components/filtered_search_bar/constants'; -import { TRACKING_CATEGORIES } from '../../constants'; -import PipelineBranchNameToken from './tokens/pipeline_branch_name_token.vue'; -import PipelineSourceToken from './tokens/pipeline_source_token.vue'; -import PipelineStatusToken from './tokens/pipeline_status_token.vue'; -import PipelineTagNameToken from './tokens/pipeline_tag_name_token.vue'; -import PipelineTriggerAuthorToken from './tokens/pipeline_trigger_author_token.vue'; - -export default { - userType: 'username', - branchType: 'ref', - tagType: 'tag', - statusType: 'status', - sourceType: 'source', - defaultTokensLength: 1, - components: { - GlFilteredSearch, - }, - mixins: [Tracking.mixin()], - props: { - projectId: { - type: String, - required: true, - }, - defaultBranchName: { - type: String, - required: false, - default: null, - }, - params: { - type: Object, - required: true, - }, - }, - data() { - return { - internalValue: [], - }; - }, - computed: { - selectedTypes() { - return this.value.map((i) => i.type); - }, - tokens() { - return [ - { - type: this.$options.userType, - icon: 'user', - title: s__('Pipeline|Trigger author'), - unique: true, - token: PipelineTriggerAuthorToken, - operators: OPERATORS_IS, - projectId: this.projectId, - }, - { - type: this.$options.branchType, - icon: 'branch', - title: s__('Pipeline|Branch name'), - unique: true, - token: PipelineBranchNameToken, - operators: OPERATORS_IS, - projectId: this.projectId, - defaultBranchName: this.defaultBranchName, - disabled: this.selectedTypes.includes(this.$options.tagType), - }, - { - type: this.$options.tagType, - icon: 'tag', - title: s__('Pipeline|Tag name'), - unique: true, - token: PipelineTagNameToken, - operators: OPERATORS_IS, - projectId: this.projectId, - disabled: this.selectedTypes.includes(this.$options.branchType), - }, - { - type: this.$options.statusType, - icon: 'status', - title: s__('Pipeline|Status'), - unique: true, - token: PipelineStatusToken, - operators: OPERATORS_IS, - }, - { - type: this.$options.sourceType, - icon: 'trigger-source', - title: s__('Pipeline|Source'), - unique: true, - token: PipelineSourceToken, - operators: OPERATORS_IS, - }, - ]; - }, - parsedParams() { - return map(this.params, (val, key) => ({ - type: key, - value: { data: val, operator: '=' }, - })); - }, - value: { - get() { - return this.internalValue.length > 0 ? this.internalValue : this.parsedParams; - }, - set(value) { - this.internalValue = value; - }, - }, - }, - methods: { - onSubmit(filters) { - this.track('click_filtered_search', { label: TRACKING_CATEGORIES.search }); - this.$emit('filterPipelines', filters); - }, - }, -}; -</script> - -<template> - <gl-filtered-search - v-model="value" - :placeholder="__('Filter pipelines')" - :available-tokens="tokens" - @submit="onSubmit" - /> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue deleted file mode 100644 index 262e82677a7..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_manual_actions.vue +++ /dev/null @@ -1,159 +0,0 @@ -<script> -import { GlDropdown, GlDropdownItem, GlIcon, GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui'; -import { createAlert } from '~/alert'; -import axios from '~/lib/utils/axios_utils'; -import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; -import { s__, __, sprintf } from '~/locale'; -import Tracking from '~/tracking'; -import GlCountdown from '~/vue_shared/components/gl_countdown.vue'; -import eventHub from '../../event_hub'; -import { TRACKING_CATEGORIES } from '../../constants'; -import getPipelineActionsQuery from '../../graphql/queries/get_pipeline_actions.query.graphql'; - -export default { - name: 'PipelinesManualActions', - directives: { - GlTooltip: GlTooltipDirective, - }, - components: { - GlCountdown, - GlDropdown, - GlDropdownItem, - GlIcon, - GlLoadingIcon, - }, - mixins: [Tracking.mixin()], - inject: ['fullPath', 'manualActionsLimit'], - props: { - iid: { - type: Number, - required: true, - }, - }, - apollo: { - actions: { - query: getPipelineActionsQuery, - variables() { - return { - fullPath: this.fullPath, - iid: this.iid, - limit: this.manualActionsLimit, - }; - }, - skip() { - return !this.hasDropdownBeenShown; - }, - update({ project }) { - return project?.pipeline?.jobs?.nodes || []; - }, - }, - }, - data() { - return { - isLoading: false, - actions: [], - hasDropdownBeenShown: false, - }; - }, - computed: { - isActionsLoading() { - return this.$apollo.queries.actions.loading; - }, - isDropdownLimitReached() { - return this.actions.length === this.manualActionsLimit; - }, - }, - methods: { - async onClickAction(action) { - if (action.scheduledAt) { - const confirmationMessage = sprintf( - s__( - 'DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after its timer finishes.', - ), - { jobName: action.name }, - ); - - const confirmed = await confirmAction(confirmationMessage); - - if (!confirmed) { - return; - } - } - - this.isLoading = true; - - /** - * Ideally, the component would not make an api call directly. - * However, in order to use the eventhub and know when to - * toggle back the `isLoading` property we'd need an ID - * to track the request with a watcher - since this component - * is rendered at least 20 times in the same page, moving the - * api call directly here is the most performant solution - */ - axios - .post(`${action.playPath}.json`) - .then(() => { - this.isLoading = false; - eventHub.$emit('updateTable'); - }) - .catch(() => { - this.isLoading = false; - createAlert({ message: __('An error occurred while making the request.') }); - }); - }, - fetchActions() { - this.hasDropdownBeenShown = true; - - this.$apollo.queries.actions.refetch(); - - this.trackClick(); - }, - trackClick() { - this.track('click_manual_actions', { label: TRACKING_CATEGORIES.table }); - }, - }, -}; -</script> -<template> - <gl-dropdown - v-gl-tooltip - :title="__('Run manual or delayed jobs')" - :loading="isLoading" - data-testid="pipelines-manual-actions-dropdown" - right - lazy - icon="play" - @shown="fetchActions" - > - <gl-dropdown-item v-if="isActionsLoading"> - <div class="gl-display-flex"> - <gl-loading-icon class="mr-2" /> - <span>{{ __('Loading...') }}</span> - </div> - </gl-dropdown-item> - - <gl-dropdown-item - v-for="action in actions" - v-else - :key="action.id" - :disabled="!action.canPlayJob" - @click="onClickAction(action)" - > - <div class="gl-display-flex gl-justify-content-space-between gl-flex-wrap"> - {{ action.name }} - <span v-if="action.scheduledAt"> - <gl-icon name="clock" /> - <gl-countdown :end-date-string="action.scheduledAt" /> - </span> - </div> - </gl-dropdown-item> - - <template #footer> - <gl-dropdown-item v-if="isDropdownLimitReached"> - <span class="gl-font-sm gl-text-gray-300!" data-testid="limit-reached-msg"> - {{ __('Showing first 50 actions.') }} - </span> - </gl-dropdown-item> - </template> - </gl-dropdown> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue deleted file mode 100644 index 00ab8a25ca1..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_status_badge.vue +++ /dev/null @@ -1,50 +0,0 @@ -<script> -import { CHILD_VIEW, TRACKING_CATEGORIES } from '~/pipelines/constants'; -import CiBadgeLink from '~/vue_shared/components/ci_badge_link.vue'; -import Tracking from '~/tracking'; -import PipelinesTimeago from './time_ago.vue'; - -export default { - components: { - CiBadgeLink, - PipelinesTimeago, - }, - mixins: [Tracking.mixin()], - props: { - pipeline: { - type: Object, - required: true, - }, - viewType: { - type: String, - required: true, - }, - }, - computed: { - pipelineStatus() { - return this.pipeline?.details?.status ?? {}; - }, - isChildView() { - return this.viewType === CHILD_VIEW; - }, - }, - methods: { - trackClick() { - this.track('click_ci_status_badge', { label: TRACKING_CATEGORIES.table }); - }, - }, -}; -</script> - -<template> - <div> - <ci-badge-link - class="gl-mb-3" - :status="pipelineStatus" - :show-text="!isChildView" - data-qa-selector="pipeline_commit_status" - @ciStatusBadgeClick="trackClick" - /> - <pipelines-timeago :pipeline="pipeline" /> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue deleted file mode 100644 index c03085e6419..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/pipelines_table.vue +++ /dev/null @@ -1,240 +0,0 @@ -<script> -import { GlTableLite, GlTooltipDirective } from '@gitlab/ui'; -import { cleanLeadingSeparator } from '~/lib/utils/url_utility'; -import { s__, __ } from '~/locale'; -import Tracking from '~/tracking'; -import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; -import { keepLatestDownstreamPipelines } from '~/pipelines/components/parsing_utils'; -import LegacyPipelineMiniGraph from '~/pipelines/components/pipeline_mini_graph/legacy_pipeline_mini_graph.vue'; -import PipelineFailedJobsWidget from '~/pipelines/components/pipelines_list/failure_widget/pipeline_failed_jobs_widget.vue'; -import eventHub from '../../event_hub'; -import { TRACKING_CATEGORIES } from '../../constants'; -import PipelineOperations from './pipeline_operations.vue'; -import PipelineStopModal from './pipeline_stop_modal.vue'; -import PipelineTriggerer from './pipeline_triggerer.vue'; -import PipelineUrl from './pipeline_url.vue'; -import PipelinesStatusBadge from './pipelines_status_badge.vue'; - -const HIDE_TD_ON_MOBILE = 'gl-display-none! gl-lg-display-table-cell!'; -const DEFAULT_TH_CLASSES = - 'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!'; - -export default { - components: { - GlTableLite, - LegacyPipelineMiniGraph, - PipelineFailedJobsWidget, - PipelineOperations, - PipelinesStatusBadge, - PipelineStopModal, - PipelineTriggerer, - PipelineUrl, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - mixins: [Tracking.mixin(), glFeatureFlagMixin()], - inject: { - withFailedJobsDetails: { - default: false, - }, - }, - props: { - pipelines: { - type: Array, - required: true, - }, - pipelineScheduleUrl: { - type: String, - required: false, - default: '', - }, - updateGraphDropdown: { - type: Boolean, - required: false, - default: false, - }, - viewType: { - type: String, - required: true, - }, - pipelineKeyOption: { - type: Object, - required: true, - }, - }, - data() { - return { - pipelineId: 0, - pipeline: {}, - endpoint: '', - cancelingPipeline: null, - }; - }, - computed: { - showFailedJobsWidget() { - return this.glFeatures.ciJobFailuresInMr; - }, - tableFields() { - return [ - { - key: 'status', - label: s__('Pipeline|Status'), - thClass: DEFAULT_TH_CLASSES, - columnClass: 'gl-w-15p', - tdClass: this.tdClasses, - thAttr: { 'data-testid': 'status-th' }, - }, - { - key: 'pipeline', - label: __('Pipeline'), - thClass: DEFAULT_TH_CLASSES, - tdClass: `${this.tdClasses}`, - columnClass: 'gl-w-30p', - thAttr: { 'data-testid': 'pipeline-th' }, - }, - { - key: 'triggerer', - label: s__('Pipeline|Triggerer'), - thClass: DEFAULT_TH_CLASSES, - tdClass: `${this.tdClasses} ${HIDE_TD_ON_MOBILE}`, - columnClass: 'gl-w-10p', - thAttr: { 'data-testid': 'triggerer-th' }, - }, - { - key: 'stages', - label: s__('Pipeline|Stages'), - thClass: DEFAULT_TH_CLASSES, - tdClass: this.tdClasses, - columnClass: 'gl-w-quarter', - thAttr: { 'data-testid': 'stages-th' }, - }, - { - key: 'actions', - thClass: DEFAULT_TH_CLASSES, - tdClass: this.tdClasses, - columnClass: 'gl-w-15p', - thAttr: { 'data-testid': 'actions-th' }, - }, - ]; - }, - tdClasses() { - return this.withFailedJobsDetails ? 'gl-pb-0! gl-border-none!' : 'pl-p-5!'; - }, - pipelinesWithDetails() { - if (this.withFailedJobsDetails) { - return this.pipelines.map((p) => { - return { ...p, _showDetails: true }; - }); - } - - return this.pipelines; - }, - }, - watch: { - pipelines() { - this.cancelingPipeline = null; - }, - }, - created() { - eventHub.$on('openConfirmationModal', this.setModalData); - }, - beforeDestroy() { - eventHub.$off('openConfirmationModal', this.setModalData); - }, - methods: { - getDownstreamPipelines(pipeline) { - const downstream = pipeline.triggered; - return keepLatestDownstreamPipelines(downstream); - }, - getProjectPath(item) { - return cleanLeadingSeparator(item.project.full_path); - }, - failedJobsCount(pipeline) { - return pipeline?.failed_builds?.length || 0; - }, - setModalData(data) { - this.pipelineId = data.pipeline.id; - this.pipeline = data.pipeline; - this.endpoint = data.endpoint; - }, - onSubmit() { - eventHub.$emit('postAction', this.endpoint); - this.cancelingPipeline = this.pipelineId; - }, - trackPipelineMiniGraph() { - this.track('click_minigraph', { label: TRACKING_CATEGORIES.table }); - }, - }, - TBODY_TR_ATTR: { - 'data-testid': 'pipeline-table-row', - 'data-qa-selector': 'pipeline_row_container', - }, -}; -</script> -<template> - <div class="ci-table"> - <gl-table-lite - :fields="tableFields" - :items="pipelinesWithDetails" - :tbody-tr-attr="$options.TBODY_TR_ATTR" - stacked="lg" - fixed - > - <template #head(actions)> - <span class="gl-display-block gl-lg-display-none!">{{ s__('Pipeline|Actions') }}</span> - <slot name="table-header-actions"></slot> - </template> - - <template #table-colgroup="{ fields }"> - <col v-for="field in fields" :key="field.key" :class="field.columnClass" /> - </template> - - <template #cell(status)="{ item }"> - <pipelines-status-badge :pipeline="item" :view-type="viewType" /> - </template> - - <template #cell(pipeline)="{ item }"> - <pipeline-url - :pipeline="item" - :pipeline-schedule-url="pipelineScheduleUrl" - :pipeline-key="pipelineKeyOption.value" - ref-color="gl-text-black-normal" - /> - </template> - - <template #cell(triggerer)="{ item }"> - <pipeline-triggerer :pipeline="item" /> - </template> - - <template #cell(stages)="{ item }"> - <legacy-pipeline-mini-graph - :downstream-pipelines="getDownstreamPipelines(item)" - :pipeline-path="item.path" - :stages="item.details.stages" - :update-dropdown="updateGraphDropdown" - :upstream-pipeline="item.triggered_by" - @miniGraphStageClick="trackPipelineMiniGraph" - /> - </template> - - <template #cell(actions)="{ item }"> - <pipeline-operations :pipeline="item" :canceling-pipeline="cancelingPipeline" /> - </template> - - <template #row-details="{ item }"> - <pipeline-failed-jobs-widget - v-if="showFailedJobsWidget" - :failed-jobs-count="failedJobsCount(item)" - :is-pipeline-active="item.active" - :pipeline-iid="item.iid" - :pipeline-path="item.path" - :project-path="getProjectPath(item)" - class="gl-ml-n4 gl-mt-n3 gl-mb-n1" - /> - </template> - </gl-table-lite> - - <pipeline-stop-modal :pipeline="pipeline" @submit="onSubmit" /> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue deleted file mode 100644 index 70343544638..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue +++ /dev/null @@ -1,61 +0,0 @@ -<script> -import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; -import { formatTime } from '~/lib/utils/datetime_utility'; -import timeagoMixin from '~/vue_shared/mixins/timeago'; - -export default { - directives: { - GlTooltip: GlTooltipDirective, - }, - components: { GlIcon }, - mixins: [timeagoMixin], - props: { - pipeline: { - type: Object, - required: true, - }, - fontSize: { - type: String, - required: false, - default: 'gl-font-sm', - validator: (fontSize) => ['gl-font-sm', 'gl-font-md'].includes(fontSize), - }, - }, - computed: { - duration() { - return this.pipeline?.details?.duration; - }, - durationFormatted() { - return formatTime(this.duration * 1000); - }, - finishedTime() { - return this.pipeline?.details?.finished_at || this.pipeline?.finishedAt; - }, - }, -}; -</script> -<template> - <div - class="gl-display-flex gl-flex-direction-column gl-align-items-flex-end gl-lg-align-items-flex-start" - :class="fontSize" - > - <p v-if="duration" class="duration gl-display-inline-flex gl-align-items-center"> - <gl-icon name="timer" class="gl-mr-2" :size="12" /> - {{ durationFormatted }} - </p> - - <p v-if="finishedTime" class="finished-at gl-display-inline-flex gl-align-items-center"> - <gl-icon name="calendar" class="gl-mr-2" :size="12" data-testid="calendar-icon" /> - - <time - v-gl-tooltip - :title="tooltipTitle(finishedTime)" - :datetime="finishedTime" - data-placement="top" - data-container="body" - > - {{ timeFormatted(finishedTime) }} - </time> - </p> - </div> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/constants.js b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/constants.js deleted file mode 100644 index d8f15cfde91..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/constants.js +++ /dev/null @@ -1,52 +0,0 @@ -import { s__ } from '~/locale'; - -export const PIPELINE_SOURCES = [ - { - text: s__('PipelineSource|Push'), - value: 'push', - }, - { - text: s__('PipelineSource|Web'), - value: 'web', - }, - { - text: s__('PipelineSource|Trigger'), - value: 'trigger', - }, - { - text: s__('PipelineSource|Schedule'), - value: 'schedule', - }, - { - text: s__('PipelineSource|API'), - value: 'api', - }, - { - text: s__('PipelineSource|External'), - value: 'external', - }, - { - text: s__('PipelineSource|Pipeline'), - value: 'pipeline', - }, - { - text: s__('PipelineSource|Chat'), - value: 'chat', - }, - { - text: s__('PipelineSource|Web IDE'), - value: 'webide', - }, - { - text: s__('PipelineSource|Merge Request'), - value: 'merge_request_event', - }, - { - text: s__('PipelineSource|External Pull Request'), - value: 'external_pull_request_event', - }, - { - text: s__('PipelineSource|Parent Pipeline'), - value: 'parent_pipeline', - }, -]; 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 deleted file mode 100644 index 81f46d5f2f9..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_branch_name_token.vue +++ /dev/null @@ -1,82 +0,0 @@ -<script> -import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui'; -import { debounce } from 'lodash'; -import Api from '~/api'; -import { createAlert } from '~/alert'; -import { FETCH_BRANCH_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants'; - -export default { - components: { - GlFilteredSearchToken, - GlFilteredSearchSuggestion, - GlLoadingIcon, - }, - props: { - config: { - type: Object, - required: true, - }, - value: { - type: Object, - required: true, - }, - }, - data() { - return { - branches: null, - loading: true, - }; - }, - created() { - this.fetchBranches(); - }, - methods: { - fetchBranches(searchterm) { - Api.branches(this.config.projectId, searchterm) - .then(({ data }) => { - this.branches = data.map((branch) => branch.name); - if (!searchterm && this.config.defaultBranchName) { - // Shift the default branch to the top of the list - this.branches = this.branches.filter( - (branch) => branch !== this.config.defaultBranchName, - ); - this.branches.unshift(this.config.defaultBranchName); - } - this.loading = false; - }) - .catch((err) => { - createAlert({ - message: FETCH_BRANCH_ERROR_MESSAGE, - }); - this.loading = false; - throw err; - }); - }, - searchBranches: debounce(function debounceSearch({ data }) { - this.fetchBranches(data); - }, FILTER_PIPELINES_SEARCH_DELAY), - }, -}; -</script> - -<template> - <gl-filtered-search-token - :config="config" - v-bind="{ ...$props, ...$attrs }" - v-on="$listeners" - @input="searchBranches" - > - <template #suggestions> - <gl-loading-icon v-if="loading" size="sm" /> - <template v-else> - <gl-filtered-search-suggestion - v-for="(branch, index) in branches" - :key="index" - :value="branch" - > - {{ branch }} - </gl-filtered-search-suggestion> - </template> - </template> - </gl-filtered-search-token> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue deleted file mode 100644 index 9643ddfbd21..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_source_token.vue +++ /dev/null @@ -1,47 +0,0 @@ -<script> -import { GlFilteredSearchToken, GlFilteredSearchSuggestion } from '@gitlab/ui'; -import { PIPELINE_SOURCES } from 'ee_else_ce/pipelines/components/pipelines_list/tokens/constants'; - -export default { - PIPELINE_SOURCES, - components: { - GlFilteredSearchToken, - GlFilteredSearchSuggestion, - }, - props: { - config: { - type: Object, - required: true, - }, - value: { - type: Object, - required: true, - }, - }, - computed: { - activeSource() { - return PIPELINE_SOURCES.find((source) => source.value === this.value.data); - }, - }, -}; -</script> - -<template> - <gl-filtered-search-token v-bind="{ ...$props, ...$attrs }" v-on="$listeners"> - <template #view> - <div class="gl-display-flex gl-align-items-center"> - <span>{{ activeSource.text }}</span> - </div> - </template> - - <template #suggestions> - <gl-filtered-search-suggestion - v-for="source in $options.PIPELINE_SOURCES" - :key="source.value" - :value="source.value" - > - {{ source.text }} - </gl-filtered-search-suggestion> - </template> - </gl-filtered-search-token> -</template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue b/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue deleted file mode 100644 index 020a08b8cee..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_status_token.vue +++ /dev/null @@ -1,104 +0,0 @@ -<script> -import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlIcon } from '@gitlab/ui'; -import { s__ } from '~/locale'; - -export default { - components: { - GlFilteredSearchToken, - GlFilteredSearchSuggestion, - GlIcon, - }, - props: { - config: { - type: Object, - required: true, - }, - value: { - type: Object, - required: true, - }, - }, - computed: { - statuses() { - return [ - { - class: 'ci-status-icon-canceled', - icon: 'status_canceled', - text: s__('Pipeline|Canceled'), - value: 'canceled', - }, - { - class: 'ci-status-icon-created', - icon: 'status_created', - text: s__('Pipeline|Created'), - value: 'created', - }, - { - class: 'ci-status-icon-failed', - icon: 'status_failed', - text: s__('Pipeline|Failed'), - value: 'failed', - }, - { - class: 'ci-status-icon-manual', - icon: 'status_manual', - text: s__('Pipeline|Manual'), - value: 'manual', - }, - { - class: 'ci-status-icon-success', - icon: 'status_success', - text: s__('Pipeline|Passed'), - value: 'success', - }, - { - class: 'ci-status-icon-pending', - icon: 'status_pending', - text: s__('Pipeline|Pending'), - value: 'pending', - }, - { - class: 'ci-status-icon-running', - icon: 'status_running', - text: s__('Pipeline|Running'), - value: 'running', - }, - { - class: 'ci-status-icon-skipped', - icon: 'status_skipped', - text: s__('Pipeline|Skipped'), - value: 'skipped', - }, - ]; - }, - findActiveStatus() { - return this.statuses.find((status) => status.value === this.value.data); - }, - }, -}; -</script> - -<template> - <gl-filtered-search-token v-bind="{ ...$props, ...$attrs }" v-on="$listeners"> - <template #view> - <div class="gl-display-flex gl-align-items-center"> - <div :class="findActiveStatus.class"> - <gl-icon :name="findActiveStatus.icon" class="gl-mr-2 gl-display-block" /> - </div> - <span>{{ findActiveStatus.text }}</span> - </div> - </template> - <template #suggestions> - <gl-filtered-search-suggestion - v-for="(status, index) in statuses" - :key="index" - :value="status.value" - > - <div class="gl-display-flex" :class="status.class"> - <gl-icon :name="status.icon" class="gl-mr-3" /> - <span>{{ status.text }}</span> - </div> - </gl-filtered-search-suggestion> - </template> - </gl-filtered-search-token> -</template> 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 deleted file mode 100644 index b32f5de2d7e..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_tag_name_token.vue +++ /dev/null @@ -1,66 +0,0 @@ -<script> -import { GlFilteredSearchToken, GlFilteredSearchSuggestion, GlLoadingIcon } from '@gitlab/ui'; -import { debounce } from 'lodash'; -import Api from '~/api'; -import { createAlert } from '~/alert'; -import { FETCH_TAG_ERROR_MESSAGE, FILTER_PIPELINES_SEARCH_DELAY } from '../../../constants'; - -export default { - components: { - GlFilteredSearchToken, - GlFilteredSearchSuggestion, - GlLoadingIcon, - }, - props: { - config: { - type: Object, - required: true, - }, - value: { - type: Object, - required: true, - }, - }, - data() { - return { - tags: null, - loading: true, - }; - }, - created() { - this.fetchTags(); - }, - methods: { - fetchTags(searchTerm) { - Api.tags(this.config.projectId, searchTerm) - .then(({ data }) => { - this.tags = data.map((tag) => tag.name); - this.loading = false; - }) - .catch((err) => { - createAlert({ - message: FETCH_TAG_ERROR_MESSAGE, - }); - this.loading = false; - throw err; - }); - }, - searchTags: debounce(function debounceSearch({ data }) { - this.fetchTags(data); - }, FILTER_PIPELINES_SEARCH_DELAY), - }, -}; -</script> - -<template> - <gl-filtered-search-token v-bind="{ ...$props, ...$attrs }" v-on="$listeners" @input="searchTags"> - <template #suggestions> - <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 }} - </gl-filtered-search-suggestion> - </template> - </template> - </gl-filtered-search-token> -</template> 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 deleted file mode 100644 index a89354c671a..00000000000 --- a/app/assets/javascripts/pipelines/components/pipelines_list/tokens/pipeline_trigger_author_token.vue +++ /dev/null @@ -1,113 +0,0 @@ -<script> -import { - GlFilteredSearchToken, - GlAvatar, - GlFilteredSearchSuggestion, - GlDropdownDivider, - GlLoadingIcon, -} from '@gitlab/ui'; -import { debounce } from 'lodash'; -import Api from '~/api'; -import { createAlert } from '~/alert'; -import { - ANY_TRIGGER_AUTHOR, - FETCH_AUTHOR_ERROR_MESSAGE, - FILTER_PIPELINES_SEARCH_DELAY, -} from '../../../constants'; - -export default { - anyTriggerAuthor: ANY_TRIGGER_AUTHOR, - components: { - GlFilteredSearchToken, - GlAvatar, - GlFilteredSearchSuggestion, - GlDropdownDivider, - GlLoadingIcon, - }, - props: { - config: { - type: Object, - required: true, - }, - value: { - type: Object, - required: true, - }, - }, - data() { - return { - users: [], - loading: true, - }; - }, - computed: { - currentValue() { - return this.value.data.toLowerCase(); - }, - activeUser() { - return this.users.find((user) => { - return user.username.toLowerCase() === this.currentValue; - }); - }, - }, - created() { - this.fetchProjectUsers(); - }, - methods: { - fetchProjectUsers(searchTerm) { - Api.projectUsers(this.config.projectId, searchTerm) - .then((users) => { - this.users = users; - this.loading = false; - }) - .catch((err) => { - createAlert({ - message: FETCH_AUTHOR_ERROR_MESSAGE, - }); - this.loading = false; - throw err; - }); - }, - searchAuthors: debounce(function debounceSearch({ data }) { - this.fetchProjectUsers(data); - }, FILTER_PIPELINES_SEARCH_DELAY), - }, -}; -</script> - -<template> - <gl-filtered-search-token - :config="config" - v-bind="{ ...$props, ...$attrs }" - v-on="$listeners" - @input="searchAuthors" - > - <template #view="{ inputValue }"> - <gl-avatar v-if="activeUser" :size="16" :src="activeUser.avatar_url" class="gl-mr-2" /> - <span>{{ activeUser ? activeUser.name : inputValue }}</span> - </template> - <template #suggestions> - <gl-filtered-search-suggestion :value="$options.anyTriggerAuthor">{{ - $options.anyTriggerAuthor - }}</gl-filtered-search-suggestion> - <gl-dropdown-divider /> - - <gl-loading-icon v-if="loading" size="sm" /> - <template v-else> - <gl-filtered-search-suggestion - v-for="user in users" - :key="user.username" - :value="user.username" - > - <div class="d-flex"> - <gl-avatar :size="32" :src="user.avatar_url" /> - <div> - <div>{{ user.name }}</div> - <div>@{{ user.username }}</div> - </div> - </div> - </gl-filtered-search-suggestion> - </template> - </template> - </gl-filtered-search-token> -</template> |