diff --git a/app/assets/javascripts/ci/pipelines_page/components/pipeline_url.vue b/app/assets/javascripts/ci/pipelines_page/components/pipeline_url.vue index 1173d137a1b625a2c1689c6d37c0dccf5b5178ff..ee299ef46f66ada137497c843af6b9b43005a7fc 100644 --- a/app/assets/javascripts/ci/pipelines_page/components/pipeline_url.vue +++ b/app/assets/javascripts/ci/pipelines_page/components/pipeline_url.vue @@ -120,8 +120,24 @@ export default { commitTitle() { return this.pipeline?.commit?.title; }, - pipelineName() { - return this.pipeline?.name; + pipelineIdentifier() { + const { name, path, pipeline_schedule: pipelineSchedule } = this.pipeline || {}; + + if (pipelineSchedule) { + return { + text: pipelineSchedule.description, + link: pipelineSchedule.path, + }; + } + + if (name) { + return { + text: name, + link: path, + }; + } + + return false; }, }, methods: { @@ -133,17 +149,27 @@ export default { </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"> + <div v-if="pipelineIdentifier" class="gl-mb-2" data-testid="pipeline-identifier-container"> <span class="gl-flex"> - <tooltip-on-truncate :title="pipelineName" class="gl-grow gl-truncate gl-text-gray-900"> - <gl-link :href="pipeline.path" class="!gl-text-link" data-testid="pipeline-url-link">{{ - pipelineName - }}</gl-link> + <tooltip-on-truncate + :title="pipelineIdentifier.text" + class="gl-grow gl-truncate gl-text-gray-900" + > + <gl-link + :href="pipelineIdentifier.link" + class="!gl-text-link" + data-testid="pipeline-identifier-link" + >{{ pipelineIdentifier.text }}</gl-link + > </tooltip-on-truncate> </span> </div> - <div v-if="!pipelineName" class="commit-title gl-mb-2" data-testid="commit-title-container"> + <div + v-if="!pipelineIdentifier" + class="commit-title gl-mb-2" + data-testid="commit-title-container" + > <span v-if="commitTitle" class="gl-flex"> <tooltip-on-truncate :title="commitTitle" @@ -162,6 +188,7 @@ export default { __("Can't find HEAD commit for this branch") }}</span> </div> + <div class="gl-mb-2"> <gl-link :href="pipeline.path" @@ -200,6 +227,7 @@ export default { > </tooltip-on-truncate> </div> + <div class="gl-inline-block gl-rounded-base gl-bg-gray-50 gl-px-2 gl-text-sm gl-text-default"> <gl-icon v-gl-tooltip @@ -217,6 +245,7 @@ export default { >{{ commitShortSha }}</gl-link > </div> + <user-avatar-link v-if="commitAuthor" :link-href="commitAuthor.path" diff --git a/app/serializers/ci/pipeline_entity.rb b/app/serializers/ci/pipeline_entity.rb index 10a3330b923312afb5a0c1c349a62e161a7dcdf7..70cc499386ddd72a5e6a83de85e448dd9a584705 100644 --- a/app/serializers/ci/pipeline_entity.rb +++ b/app/serializers/ci/pipeline_entity.rb @@ -96,6 +96,8 @@ class Ci::PipelineEntity < Grape::Entity pipeline.failed_builds.size end + expose :pipeline_schedule, using: Ci::PipelineScheduleEntity + private alias_method :pipeline, :object diff --git a/app/serializers/ci/pipeline_schedule_entity.rb b/app/serializers/ci/pipeline_schedule_entity.rb new file mode 100644 index 0000000000000000000000000000000000000000..b68730ddfe4c2101c1aaccab90b52167c7fc6689 --- /dev/null +++ b/app/serializers/ci/pipeline_schedule_entity.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Ci + class PipelineScheduleEntity < Grape::Entity + include RequestAwareEntity + + expose :id + expose :description + expose :path do |schedule| + pipeline_schedules_path(schedule.project) + end + end +end diff --git a/app/serializers/pipeline_serializer.rb b/app/serializers/pipeline_serializer.rb index 0a14988b08d522645e215267cc736fbfe3eb14c3..4a42eeaed5e372070711c28cacc3d6483a28dd97 100644 --- a/app/serializers/pipeline_serializer.rb +++ b/app/serializers/pipeline_serializer.rb @@ -33,6 +33,7 @@ def represent_stages(resource) def preloaded_relations(preload_statuses: true, preload_downstream_statuses: true, **) [ :pipeline_metadata, + :pipeline_schedule, :cancelable_statuses, :retryable_builds, :stages, diff --git a/spec/frontend/ci/pipelines_page/components/pipeline_url_spec.js b/spec/frontend/ci/pipelines_page/components/pipeline_url_spec.js index 46e836cc101ff0bf52b8ee822f99301bf7bf55b0..d9afaad784c4c0a85230afbe21d1da3337597e53 100644 --- a/spec/frontend/ci/pipelines_page/components/pipeline_url_spec.js +++ b/spec/frontend/ci/pipelines_page/components/pipeline_url_spec.js @@ -8,7 +8,7 @@ import { mockPipeline, mockPipelineBranch, mockPipelineTag, -} from 'jest/ci/pipeline_details/mock_data'; +} from '../../pipeline_details/mock_data'; const projectPath = 'test/test'; @@ -18,6 +18,7 @@ describe('Pipeline Url Component', () => { const findTableCell = () => wrapper.findByTestId('pipeline-url-table-cell'); const findPipelineUrlLink = () => wrapper.findByTestId('pipeline-url-link'); + const findPipelineIdentifierLink = () => wrapper.findByTestId('pipeline-identifier-link'); const findRefName = () => wrapper.findByTestId('merge-request-ref'); const findCommitShortSha = () => wrapper.findByTestId('commit-short-sha'); const findCommitIcon = () => wrapper.findByTestId('commit-icon'); @@ -25,7 +26,8 @@ describe('Pipeline Url Component', () => { const findCommitRefName = () => wrapper.findByTestId('commit-ref-name'); const findCommitTitleContainer = () => wrapper.findByTestId('commit-title-container'); - const findPipelineNameContainer = () => wrapper.findByTestId('pipeline-name-container'); + const findPipelineIdentifierContainer = () => + wrapper.findByTestId('pipeline-identifier-container'); const findCommitTitle = (commitWrapper) => commitWrapper.find('[data-testid="commit-title"]'); const defaultProps = { ...mockPipeline(projectPath), refClass: 'gl-text-black' }; @@ -46,20 +48,43 @@ describe('Pipeline Url Component', () => { createComponent(); expect(findPipelineUrlLink().attributes('href')).toBe('foo'); - expect(findPipelineUrlLink().text()).toBe('#1'); }); - it('should render the pipeline name instead of commit title', () => { - createComponent(merge(mockPipeline(projectPath), { pipeline: { name: 'Build pipeline' } })); + it('should render the pipeline schedule identifier instead of pipeline name', () => { + createComponent( + merge(mockPipeline(projectPath), { + pipeline: { + name: 'Build pipeline', + pipeline_schedule: { id: 1, description: 'Schedule', path: 'schedule/path' }, + }, + }), + ); + + expect(findCommitTitleContainer().exists()).toBe(false); + expect(findPipelineIdentifierContainer().exists()).toBe(true); + expect(findRefName().exists()).toBe(true); + expect(findCommitShortSha().exists()).toBe(true); + expect(findPipelineIdentifierLink().text()).toBe('Schedule'); + expect(findPipelineIdentifierLink().attributes('href')).toBe('schedule/path'); + }); + + it('should render the pipeline name identifier instead of commit title', () => { + createComponent( + merge(mockPipeline(projectPath), { + pipeline: { name: 'Build pipeline', pipeline_schedule: null }, + }), + ); expect(findCommitTitleContainer().exists()).toBe(false); - expect(findPipelineNameContainer().exists()).toBe(true); + expect(findPipelineIdentifierContainer().exists()).toBe(true); expect(findRefName().exists()).toBe(true); expect(findCommitShortSha().exists()).toBe(true); + expect(findPipelineIdentifierLink().text()).toBe('Build pipeline'); + expect(findPipelineIdentifierLink().attributes('href')).toBe('foo'); }); - it('should render the commit title when pipeline has no name', () => { + it('should render the commit title when pipeline has no identifier', () => { createComponent(); const commitWrapper = findCommitTitleContainer(); @@ -67,7 +92,8 @@ describe('Pipeline Url Component', () => { expect(findCommitTitle(commitWrapper).exists()).toBe(true); expect(findRefName().exists()).toBe(true); expect(findCommitShortSha().exists()).toBe(true); - expect(findPipelineNameContainer().exists()).toBe(false); + expect(findPipelineIdentifierContainer().exists()).toBe(false); + expect(findPipelineIdentifierLink().exists()).toBe(false); }); it('should pass the refClass prop to merge request link', () => { diff --git a/spec/serializers/ci/pipeline_entity_spec.rb b/spec/serializers/ci/pipeline_entity_spec.rb index e4ac8488c8c3b387439b7aac17053404bd010065..0cff0c6a8962776ba224471a6a984da7dd0cebf2 100644 --- a/spec/serializers/ci/pipeline_entity_spec.rb +++ b/spec/serializers/ci/pipeline_entity_spec.rb @@ -300,5 +300,26 @@ expect(subject[:coverage]).to eq('35.00') end end + + context 'when pipeline has a schedule' do + let_it_be(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) } + let_it_be(:pipeline) { create(:ci_pipeline, pipeline_schedule: pipeline_schedule, project: project) } + + it 'exposes the schedule' do + expect(subject[:pipeline_schedule]).to eq({ + id: pipeline_schedule.id, + description: pipeline_schedule.description, + path: pipeline_schedules_path(pipeline_schedule.project) + }) + end + end + + context 'when pipeline has no schedule' do + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + + it 'is nil' do + expect(subject[:pipeline_schedule]).to be_nil + end + end end end diff --git a/spec/serializers/ci/pipeline_schedule_entity_spec.rb b/spec/serializers/ci/pipeline_schedule_entity_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..21d0d510bd0fd24ae6e635a1b4682215c0c502b5 --- /dev/null +++ b/spec/serializers/ci/pipeline_schedule_entity_spec.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Ci::PipelineScheduleEntity, feature_category: :continuous_integration do + include Gitlab::Routing + + let_it_be(:project) { build_stubbed(:project) } + let_it_be(:pipeline_schedule) { build_stubbed(:ci_pipeline_schedule, :nightly, project: project) } + + let(:request) { instance_double(ActionDispatch::Request) } + let(:entity) { described_class.new(pipeline_schedule, request: request) } + + subject(:data) { entity.as_json } + + it { is_expected.to include(:id) } + it { is_expected.to include(:description) } + it { is_expected.to include(:path) } + + it { expect(data[:id]).to eq(pipeline_schedule.id) } + it { expect(data[:description]).to eq(pipeline_schedule.description) } + it { expect(data[:path]).to eq(pipeline_schedules_path(pipeline_schedule.project)) } +end