Commit 755fdd44 authored by Adam Hegyi's avatar Adam Hegyi

Remove CI builds for test and staging stages in VSA

Remove special case for test and staging stages in Value Stream
Analytics. This also fixes the cross-join with the CI tables.

Changelog: changed
parent 70a62f99
...@@ -17,8 +17,6 @@ import { ...@@ -17,8 +17,6 @@ import {
PAGINATION_SORT_FIELD_DURATION, PAGINATION_SORT_FIELD_DURATION,
PAGINATION_SORT_DIRECTION_ASC, PAGINATION_SORT_DIRECTION_ASC,
PAGINATION_SORT_DIRECTION_DESC, PAGINATION_SORT_DIRECTION_DESC,
STAGE_TITLE_STAGING,
STAGE_TITLE_TEST,
} from '../constants'; } from '../constants';
import TotalTime from './total_time_component.vue'; import TotalTime from './total_time_component.vue';
...@@ -107,28 +105,12 @@ export default { ...@@ -107,28 +105,12 @@ export default {
emptyStateTitleText() { emptyStateTitleText() {
return this.emptyStateTitle || NOT_ENOUGH_DATA_ERROR; return this.emptyStateTitle || NOT_ENOUGH_DATA_ERROR;
}, },
isDefaultTestStage() {
const { selectedStage } = this;
return (
!selectedStage.custom && selectedStage.title?.toLowerCase().trim() === STAGE_TITLE_TEST
);
},
isDefaultStagingStage() {
const { selectedStage } = this;
return (
!selectedStage.custom && selectedStage.title?.toLowerCase().trim() === STAGE_TITLE_STAGING
);
},
isMergeRequestStage() { isMergeRequestStage() {
const [firstEvent] = this.stageEvents; const [firstEvent] = this.stageEvents;
return this.isMrLink(firstEvent.url); return this.isMrLink(firstEvent.url);
}, },
workflowTitle() { workflowTitle() {
if (this.isDefaultTestStage) { if (this.isMergeRequestStage) {
return WORKFLOW_COLUMN_TITLES.jobs;
} else if (this.isDefaultStagingStage) {
return WORKFLOW_COLUMN_TITLES.deployments;
} else if (this.isMergeRequestStage) {
return WORKFLOW_COLUMN_TITLES.mergeRequests; return WORKFLOW_COLUMN_TITLES.mergeRequests;
} }
return WORKFLOW_COLUMN_TITLES.issues; return WORKFLOW_COLUMN_TITLES.issues;
...@@ -209,22 +191,6 @@ export default { ...@@ -209,22 +191,6 @@ export default {
<div data-testid="vsa-stage-event"> <div data-testid="vsa-stage-event">
<div v-if="item.id" data-testid="vsa-stage-content"> <div v-if="item.id" data-testid="vsa-stage-content">
<p class="gl-m-0"> <p class="gl-m-0">
<template v-if="isDefaultTestStage">
<span
class="icon-build-status gl-vertical-align-middle gl-text-green-500"
data-testid="vsa-stage-event-build-status"
>
<gl-icon name="status_success" :size="14" />
</span>
<gl-link
class="gl-text-black-normal item-build-name"
data-testid="vsa-stage-event-build-name"
:href="item.url"
>
{{ item.name }}
</gl-link>
&middot;
</template>
<gl-link class="gl-text-black-normal pipeline-id" :href="item.url" <gl-link class="gl-text-black-normal pipeline-id" :href="item.url"
>#{{ item.id }}</gl-link >#{{ item.id }}</gl-link
> >
...@@ -246,12 +212,7 @@ export default { ...@@ -246,12 +212,7 @@ export default {
> >
</p> </p>
<p class="gl-m-0"> <p class="gl-m-0">
<span v-if="isDefaultTestStage" data-testid="vsa-stage-event-build-status-date"> <span data-testid="vsa-stage-event-build-author-and-date">
<gl-link class="gl-text-black-normal issue-date" :href="item.url">{{
item.date
}}</gl-link>
</span>
<span v-else data-testid="vsa-stage-event-build-author-and-date">
<gl-link class="gl-text-black-normal build-date" :href="item.url">{{ <gl-link class="gl-text-black-normal build-date" :href="item.url">{{
item.date item.date
}}</gl-link> }}</gl-link>
......
...@@ -20,9 +20,6 @@ export const PAGINATION_SORT_FIELD_DURATION = 'duration'; ...@@ -20,9 +20,6 @@ export const PAGINATION_SORT_FIELD_DURATION = 'duration';
export const PAGINATION_SORT_DIRECTION_DESC = 'desc'; export const PAGINATION_SORT_DIRECTION_DESC = 'desc';
export const PAGINATION_SORT_DIRECTION_ASC = 'asc'; export const PAGINATION_SORT_DIRECTION_ASC = 'asc';
export const STAGE_TITLE_STAGING = 'staging';
export const STAGE_TITLE_TEST = 'test';
export const I18N_VSA_ERROR_STAGES = __( export const I18N_VSA_ERROR_STAGES = __(
'There was an error fetching value stream analytics stages.', 'There was an error fetching value stream analytics stages.',
); );
......
...@@ -38,22 +38,6 @@ module Gitlab ...@@ -38,22 +38,6 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def serialized_records def serialized_records
strong_memoize(:serialized_records) do strong_memoize(:serialized_records) do
# special case (legacy): 'Test' and 'Staging' stages should show Ci::Build records
if default_test_stage? || default_staging_stage?
ci_build_join = mr_metrics_table
.join(build_table)
.on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id]))
.join_sources
records = ordered_and_limited_query
.joins(ci_build_join)
.select(build_table[:id], *time_columns)
yield records if block_given?
ci_build_records = preload_ci_build_associations(records)
AnalyticsBuildSerializer.new.represent(ci_build_records.map { |e| e['build'] })
else
records = ordered_and_limited_query.select(*columns, *time_columns) records = ordered_and_limited_query.select(*columns, *time_columns)
yield records if block_given? yield records if block_given?
...@@ -70,7 +54,6 @@ module Gitlab ...@@ -70,7 +54,6 @@ module Gitlab
end end
end end
end end
end
# rubocop: enable CodeReuse/ActiveRecord # rubocop: enable CodeReuse/ActiveRecord
private private
...@@ -83,26 +66,10 @@ module Gitlab ...@@ -83,26 +66,10 @@ module Gitlab
end end
end end
def default_test_stage?
stage.matches_with_stage_params?(Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_test_stage)
end
def default_staging_stage?
stage.matches_with_stage_params?(Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_staging_stage)
end
def serializer def serializer
MAPPINGS.fetch(subject_class).fetch(:serializer_class).new MAPPINGS.fetch(subject_class).fetch(:serializer_class).new
end end
# rubocop: disable CodeReuse/ActiveRecord
def preload_ci_build_associations(records)
results = records.map(&:attributes)
Gitlab::CycleAnalytics::Updater.update!(results, from: 'id', to: 'build', klass: ::Ci::Build.includes({ project: [:namespace], user: [], pipeline: [] }))
end
# rubocop: enable CodeReuse/ActiveRecord
def ordered_and_limited_query def ordered_and_limited_query
strong_memoize(:ordered_and_limited_query) do strong_memoize(:ordered_and_limited_query) do
order_by(query, sort, direction, columns).page(page).per(per_page).without_count order_by(query, sort, direction, columns).page(page).per(per_page).without_count
......
...@@ -77,13 +77,13 @@ RSpec.describe 'Value Stream Analytics', :js do ...@@ -77,13 +77,13 @@ RSpec.describe 'Value Stream Analytics', :js do
expect_merge_request_to_be_present expect_merge_request_to_be_present
click_stage('Test') click_stage('Test')
expect_build_to_be_present expect_merge_request_to_be_present
click_stage('Review') click_stage('Review')
expect_merge_request_to_be_present expect_merge_request_to_be_present
click_stage('Staging') click_stage('Staging')
expect_build_to_be_present expect_merge_request_to_be_present
end end
context "when I change the time period observed" do context "when I change the time period observed" do
...@@ -160,12 +160,6 @@ RSpec.describe 'Value Stream Analytics', :js do ...@@ -160,12 +160,6 @@ RSpec.describe 'Value Stream Analytics', :js do
expect(find(stage_table_selector)).to have_content("##{issue.iid}") expect(find(stage_table_selector)).to have_content("##{issue.iid}")
end end
def expect_build_to_be_present
expect(find(stage_table_selector)).to have_content(@build.ref)
expect(find(stage_table_selector)).to have_content(@build.short_sha)
expect(find(stage_table_selector)).to have_content("##{@build.id}")
end
def expect_merge_request_to_be_present def expect_merge_request_to_be_present
expect(find(stage_table_selector)).to have_content(mr.title) expect(find(stage_table_selector)).to have_content(mr.title)
expect(find(stage_table_selector)).to have_content(mr.author.name) expect(find(stage_table_selector)).to have_content(mr.author.name)
......
...@@ -132,8 +132,6 @@ export const convertedData = { ...@@ -132,8 +132,6 @@ export const convertedData = {
export const rawIssueEvents = stageFixtures.issue; export const rawIssueEvents = stageFixtures.issue;
export const issueEvents = deepCamelCase(rawIssueEvents); export const issueEvents = deepCamelCase(rawIssueEvents);
export const reviewEvents = deepCamelCase(stageFixtures.review); export const reviewEvents = deepCamelCase(stageFixtures.review);
export const testEvents = deepCamelCase(stageFixtures.test);
export const stagingEvents = deepCamelCase(stageFixtures.staging);
export const pathNavIssueMetric = 172800; export const pathNavIssueMetric = 172800;
......
...@@ -4,16 +4,7 @@ import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; ...@@ -4,16 +4,7 @@ import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import StageTable from '~/cycle_analytics/components/stage_table.vue'; import StageTable from '~/cycle_analytics/components/stage_table.vue';
import { PAGINATION_SORT_FIELD_DURATION } from '~/cycle_analytics/constants'; import { PAGINATION_SORT_FIELD_DURATION } from '~/cycle_analytics/constants';
import { import { issueEvents, issueStage, reviewStage, reviewEvents } from './mock_data';
stagingEvents,
stagingStage,
issueEvents,
issueStage,
testEvents,
testStage,
reviewStage,
reviewEvents,
} from './mock_data';
let wrapper = null; let wrapper = null;
let trackingSpy = null; let trackingSpy = null;
...@@ -22,12 +13,8 @@ const noDataSvgPath = 'path/to/no/data'; ...@@ -22,12 +13,8 @@ const noDataSvgPath = 'path/to/no/data';
const emptyStateTitle = 'Too much data'; const emptyStateTitle = 'Too much data';
const notEnoughDataError = "We don't have enough data to show this stage."; const notEnoughDataError = "We don't have enough data to show this stage.";
const issueEventItems = issueEvents.events; const issueEventItems = issueEvents.events;
const stagingEventItems = stagingEvents.events;
const testEventItems = testEvents.events;
const reviewEventItems = reviewEvents.events; const reviewEventItems = reviewEvents.events;
const [firstIssueEvent] = issueEventItems; const [firstIssueEvent] = issueEventItems;
const [firstStagingEvent] = stagingEventItems;
const [firstTestEvent] = testEventItems;
const [firstReviewEvent] = reviewEventItems; const [firstReviewEvent] = reviewEventItems;
const pagination = { page: 1, hasNextPage: true }; const pagination = { page: 1, hasNextPage: true };
...@@ -156,99 +143,6 @@ describe('StageTable', () => { ...@@ -156,99 +143,6 @@ describe('StageTable', () => {
}); });
}); });
describe('staging event', () => {
beforeEach(() => {
wrapper = createComponent({
stageEvents: [{ ...firstStagingEvent }],
selectedStage: { ...stagingStage, custom: false },
});
});
it('will set the workflow title to "Deployments"', () => {
expect(findTableHead().text()).toContain('Deployments');
expect(findTableHead().text()).not.toContain('Issues');
});
it('will not render the event title', () => {
expect(wrapper.findByTestId('vsa-stage-event-title').exists()).toBe(false);
});
it('will render the fork icon', () => {
expect(findIcon('fork').exists()).toBe(true);
});
it('will render the branch icon', () => {
expect(findIcon('commit').exists()).toBe(true);
});
it('will render the total time', () => {
expect(findStageTime().text()).toBe('2 mins');
});
it('will render the build shortSha', () => {
expect(wrapper.findByTestId('vsa-stage-event-build-sha').text()).toBe(
firstStagingEvent.shortSha,
);
});
it('will render the author and date', () => {
const content = wrapper.findByTestId('vsa-stage-event-build-author-and-date').text();
expect(content).toContain(firstStagingEvent.author.name);
expect(content).toContain(firstStagingEvent.date);
});
});
describe('test event', () => {
beforeEach(() => {
wrapper = createComponent({
stageEvents: [{ ...firstTestEvent }],
selectedStage: { ...testStage, custom: false },
});
});
it('will set the workflow title to "Jobs"', () => {
expect(findTableHead().text()).toContain('Jobs');
expect(findTableHead().text()).not.toContain('Issues');
});
it('will not render the event title', () => {
expect(wrapper.findByTestId('vsa-stage-event-title').exists()).toBe(false);
});
it('will render the fork icon', () => {
expect(findIcon('fork').exists()).toBe(true);
});
it('will render the branch icon', () => {
expect(findIcon('commit').exists()).toBe(true);
});
it('will render the total time', () => {
expect(findStageTime().text()).toBe('2 mins');
});
it('will render the build shortSha', () => {
expect(wrapper.findByTestId('vsa-stage-event-build-sha').text()).toBe(
firstTestEvent.shortSha,
);
});
it('will render the build pipeline success icon', () => {
expect(wrapper.findByTestId('status_success-icon').exists()).toBe(true);
});
it('will render the build date', () => {
const content = wrapper.findByTestId('vsa-stage-event-build-status-date').text();
expect(content).toContain(firstTestEvent.date);
});
it('will render the build event name', () => {
expect(wrapper.findByTestId('vsa-stage-event-build-name').text()).toContain(
firstTestEvent.name,
);
});
});
describe('isLoading = true', () => { describe('isLoading = true', () => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ isLoading: true }, true); wrapper = createComponent({ isLoading: true }, true);
......
...@@ -79,56 +79,6 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do ...@@ -79,56 +79,6 @@ RSpec.describe Gitlab::Analytics::CycleAnalytics::RecordsFetcher do
include_context 'when records are loaded by maintainer' include_context 'when records are loaded by maintainer'
end end
describe 'special case' do
let(:mr1) { create(:merge_request, source_project: project, allow_broken: true, created_at: 20.days.ago) }
let(:mr2) { create(:merge_request, source_project: project, allow_broken: true, created_at: 19.days.ago) }
let(:ci_build1) { create(:ci_build) }
let(:ci_build2) { create(:ci_build) }
let(:default_stages) { Gitlab::Analytics::CycleAnalytics::DefaultStages }
let(:stage) { build(:cycle_analytics_project_stage, default_stages.params_for_test_stage.merge(project: project)) }
before do
mr1.metrics.update!({
merged_at: 5.days.ago,
first_deployed_to_production_at: 1.day.ago,
latest_build_started_at: 5.days.ago,
latest_build_finished_at: 1.day.ago,
pipeline: ci_build1.pipeline
})
mr2.metrics.update!({
merged_at: 10.days.ago,
first_deployed_to_production_at: 5.days.ago,
latest_build_started_at: 9.days.ago,
latest_build_finished_at: 7.days.ago,
pipeline: ci_build2.pipeline
})
project.add_user(user, Gitlab::Access::MAINTAINER)
end
context 'returns build records' do
shared_examples 'orders build records by `latest_build_finished_at`' do
it 'orders by `latest_build_finished_at`' do
build_ids = subject.map { |item| item[:id] }
expect(build_ids).to eq([ci_build1.id, ci_build2.id])
end
end
context 'when requesting records for default test stage' do
include_examples 'orders build records by `latest_build_finished_at`'
end
context 'when requesting records for default staging stage' do
before do
stage.assign_attributes(default_stages.params_for_staging_stage)
end
include_examples 'orders build records by `latest_build_finished_at`'
end
end
end
end end
describe 'pagination' do describe 'pagination' do
......
...@@ -8,6 +8,9 @@ RSpec.describe 'value stream analytics events' do ...@@ -8,6 +8,9 @@ RSpec.describe 'value stream analytics events' do
let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
describe 'GET /:namespace/:project/value_stream_analytics/events/issues' do describe 'GET /:namespace/:project/value_stream_analytics/events/issues' do
let(:first_issue_iid) { project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s }
let(:first_mr_iid) { project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s }
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -25,8 +28,6 @@ RSpec.describe 'value stream analytics events' do ...@@ -25,8 +28,6 @@ RSpec.describe 'value stream analytics events' do
it 'lists the issue events' do it 'lists the issue events' do
get project_cycle_analytics_issue_path(project, format: :json) get project_cycle_analytics_issue_path(project, format: :json)
first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
expect(json_response['events']).not_to be_empty expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['iid']).to eq(first_issue_iid) expect(json_response['events'].first['iid']).to eq(first_issue_iid)
end end
...@@ -34,8 +35,6 @@ RSpec.describe 'value stream analytics events' do ...@@ -34,8 +35,6 @@ RSpec.describe 'value stream analytics events' do
it 'lists the plan events' do it 'lists the plan events' do
get project_cycle_analytics_plan_path(project, format: :json) get project_cycle_analytics_plan_path(project, format: :json)
first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
expect(json_response['events']).not_to be_empty expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['iid']).to eq(first_issue_iid) expect(json_response['events'].first['iid']).to eq(first_issue_iid)
end end
...@@ -45,8 +44,6 @@ RSpec.describe 'value stream analytics events' do ...@@ -45,8 +44,6 @@ RSpec.describe 'value stream analytics events' do
expect(json_response['events']).not_to be_empty expect(json_response['events']).not_to be_empty
first_mr_iid = project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
expect(json_response['events'].first['iid']).to eq(first_mr_iid) expect(json_response['events'].first['iid']).to eq(first_mr_iid)
end end
...@@ -54,15 +51,15 @@ RSpec.describe 'value stream analytics events' do ...@@ -54,15 +51,15 @@ RSpec.describe 'value stream analytics events' do
get project_cycle_analytics_test_path(project, format: :json) get project_cycle_analytics_test_path(project, format: :json)
expect(json_response['events']).not_to be_empty expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['date']).not_to be_empty
expect(json_response['events'].first['iid']).to eq(first_mr_iid)
end end
it 'lists the review events' do it 'lists the review events' do
get project_cycle_analytics_review_path(project, format: :json) get project_cycle_analytics_review_path(project, format: :json)
first_mr_iid = project.merge_requests.sort_by_attribute(:created_desc).pluck(:iid).first.to_s
expect(json_response['events']).not_to be_empty expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['iid']).to eq(first_mr_iid) expect(json_response['events'].first['iid']).to eq(first_mr_iid)
end end
...@@ -70,7 +67,8 @@ RSpec.describe 'value stream analytics events' do ...@@ -70,7 +67,8 @@ RSpec.describe 'value stream analytics events' do
get project_cycle_analytics_staging_path(project, format: :json) get project_cycle_analytics_staging_path(project, format: :json)
expect(json_response['events']).not_to be_empty expect(json_response['events']).not_to be_empty
expect(json_response['events'].first['date']).not_to be_empty
expect(json_response['events'].first['iid']).to eq(first_issue_iid)
end end
context 'with private project and builds' do context 'with private project and builds' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment