Commit 34722c93 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch '21425-commit-box-pipeline-status-vue' into 'master'

Add polling to commit box pipeline status

See merge request gitlab-org/gitlab!84201
parents 7d80c5de be0aac05
......@@ -10,7 +10,7 @@ import {
import { formatStages } from '../utils';
import getLinkedPipelinesQuery from '../graphql/queries/get_linked_pipelines.query.graphql';
import getPipelineStagesQuery from '../graphql/queries/get_pipeline_stages.query.graphql';
import { PIPELINE_STAGES_POLL_INTERVAL } from '../constants';
import { COMMIT_BOX_POLL_INTERVAL } from '../constants';
export default {
i18n: {
......@@ -65,7 +65,7 @@ export default {
return getQueryHeaders(this.graphqlResourceEtag);
},
query: getPipelineStagesQuery,
pollInterval: PIPELINE_STAGES_POLL_INTERVAL,
pollInterval: COMMIT_BOX_POLL_INTERVAL,
variables() {
return {
fullPath: this.fullPath,
......
<script>
import { GlLoadingIcon, GlLink } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import createFlash from '~/flash';
import {
getQueryHeaders,
toggleQueryPollingByVisibility,
} from '~/pipelines/components/graph/utils';
import getLatestPipelineStatusQuery from '../graphql/queries/get_latest_pipeline_status.query.graphql';
import { COMMIT_BOX_POLL_INTERVAL, PIPELINE_STATUS_FETCH_ERROR } from '../constants';
export default {
PIPELINE_STATUS_FETCH_ERROR,
components: {
CiIcon,
GlLoadingIcon,
GlLink,
},
inject: {
fullPath: {
default: '',
},
iid: {
default: '',
},
graphqlResourceEtag: {
default: '',
},
},
apollo: {
pipelineStatus: {
context() {
return getQueryHeaders(this.graphqlResourceEtag);
},
query: getLatestPipelineStatusQuery,
pollInterval: COMMIT_BOX_POLL_INTERVAL,
variables() {
return {
fullPath: this.fullPath,
iid: this.iid,
};
},
update({ project }) {
return project?.pipeline?.detailedStatus || {};
},
error() {
createFlash({ message: this.$options.PIPELINE_STATUS_FETCH_ERROR });
},
},
},
data() {
return {
pipelineStatus: {},
};
},
computed: {
loading() {
return this.$apollo.queries.pipelineStatus.loading;
},
},
mounted() {
toggleQueryPollingByVisibility(this.$apollo.queries.pipelineStatus);
},
};
</script>
<template>
<div class="gl-display-inline-block gl-vertical-align-middle gl-mr-2">
<gl-loading-icon v-if="loading" />
<gl-link v-else :href="pipelineStatus.detailsPath">
<ci-icon :status="pipelineStatus" :size="24" />
</gl-link>
</div>
</template>
export const PIPELINE_STAGES_POLL_INTERVAL = 10000;
import { __ } from '~/locale';
export const COMMIT_BOX_POLL_INTERVAL = 10000;
export const PIPELINE_STATUS_FETCH_ERROR = __(
'There was a problem fetching the latest pipeline status.',
);
query getLatestPipelineStatus($fullPath: ID!, $iid: ID!) {
project(fullPath: $fullPath) {
id
pipeline(iid: $iid) {
id
detailedStatus {
id
detailsPath
icon
group
}
}
}
}
......@@ -2,6 +2,7 @@ import { fetchCommitMergeRequests } from '~/commit_merge_requests';
import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph';
import { initDetailsButton } from './init_details_button';
import { loadBranches } from './load_branches';
import initCommitPipelineStatus from './init_commit_pipeline_status';
export const initCommitBoxInfo = () => {
// Display commit related branches
......@@ -14,4 +15,6 @@ export const initCommitBoxInfo = () => {
initCommitPipelineMiniGraph();
initDetailsButton();
initCommitPipelineStatus();
};
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import CommitBoxPipelineStatus from './components/commit_box_pipeline_status.vue';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient({}, { useGet: true }),
});
export default (selector = '.js-commit-pipeline-status') => {
const el = document.querySelector(selector);
if (!el) {
return;
}
const { fullPath, iid, graphqlResourceEtag } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
el,
apolloProvider,
provide: {
fullPath,
iid,
graphqlResourceEtag,
},
render(createElement) {
return createElement(CommitBoxPipelineStatus);
},
});
};
......@@ -47,9 +47,7 @@
- if can?(current_user, :read_pipeline, @last_pipeline)
.well-segment.pipeline-info
.status-icon-container
= link_to project_pipeline_path(@project, @last_pipeline.id), class: "ci-status-icon-#{@last_pipeline.status}" do
= ci_icon_for_status(@last_pipeline.status)
.js-commit-pipeline-status{ data: { full_path: @project.full_path, iid: @last_pipeline.iid, graphql_resource_etag: graphql_etag_pipeline_path(@last_pipeline) } }
#{ _('Pipeline') }
= link_to "##{@last_pipeline.id}", project_pipeline_path(@project, @last_pipeline.id)
= ci_label_for_status(@last_pipeline.status)
......
......@@ -7,7 +7,7 @@ import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import CommitBoxPipelineMiniGraph from '~/projects/commit_box/info/components/commit_box_pipeline_mini_graph.vue';
import { PIPELINE_STAGES_POLL_INTERVAL } from '~/projects/commit_box/info/constants';
import { COMMIT_BOX_POLL_INTERVAL } from '~/projects/commit_box/info/constants';
import getLinkedPipelinesQuery from '~/projects/commit_box/info/graphql/queries/get_linked_pipelines.query.graphql';
import getPipelineStagesQuery from '~/projects/commit_box/info/graphql/queries/get_pipeline_stages.query.graphql';
import * as graphQlUtils from '~/pipelines/components/graph/utils';
......@@ -42,7 +42,7 @@ describe('Commit box pipeline mini graph', () => {
const findDownstream = () => wrapper.findByTestId('commit-box-mini-graph-downstream');
const advanceToNextFetch = () => {
jest.advanceTimersByTime(PIPELINE_STAGES_POLL_INTERVAL);
jest.advanceTimersByTime(COMMIT_BOX_POLL_INTERVAL);
};
const createMockApolloProvider = (handler = downstreamHandler) => {
......@@ -161,7 +161,7 @@ describe('Commit box pipeline mini graph', () => {
const expectedInterval = wrapper.vm.$apollo.queries.pipelineStages.options.pollInterval;
expect(expectedInterval).toBe(PIPELINE_STAGES_POLL_INTERVAL);
expect(expectedInterval).toBe(COMMIT_BOX_POLL_INTERVAL);
});
it('polls for stages', async () => {
......
......@@ -37990,6 +37990,9 @@ msgstr ""
msgid "There was a problem fetching the keep latest artifacts setting."
msgstr ""
msgid "There was a problem fetching the latest pipeline status."
msgstr ""
msgid "There was a problem fetching the pipeline stages."
msgstr ""
......
import { GlLoadingIcon, GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import CommitBoxPipelineStatus from '~/projects/commit_box/info/components/commit_box_pipeline_status.vue';
import {
COMMIT_BOX_POLL_INTERVAL,
PIPELINE_STATUS_FETCH_ERROR,
} from '~/projects/commit_box/info/constants';
import getLatestPipelineStatusQuery from '~/projects/commit_box/info/graphql/queries/get_latest_pipeline_status.query.graphql';
import * as graphQlUtils from '~/pipelines/components/graph/utils';
import { mockPipelineStatusResponse } from '../mock_data';
const mockProvide = {
fullPath: 'root/ci-project',
iid: '46',
graphqlResourceEtag: '/api/graphql:pipelines/id/320',
};
Vue.use(VueApollo);
jest.mock('~/flash');
describe('Commit box pipeline status', () => {
let wrapper;
const statusSuccessHandler = jest.fn().mockResolvedValue(mockPipelineStatusResponse);
const failedHandler = jest.fn().mockRejectedValue(new Error('GraphQL error'));
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findStatusIcon = () => wrapper.findComponent(CiIcon);
const findPipelineLink = () => wrapper.findComponent(GlLink);
const advanceToNextFetch = () => {
jest.advanceTimersByTime(COMMIT_BOX_POLL_INTERVAL);
};
const createMockApolloProvider = (handler) => {
const requestHandlers = [[getLatestPipelineStatusQuery, handler]];
return createMockApollo(requestHandlers);
};
const createComponent = (handler = statusSuccessHandler) => {
wrapper = shallowMount(CommitBoxPipelineStatus, {
provide: {
...mockProvide,
},
apolloProvider: createMockApolloProvider(handler),
});
};
afterEach(() => {
wrapper.destroy();
});
describe('loading state', () => {
it('should display loading state when loading', () => {
createComponent();
expect(findLoadingIcon().exists()).toBe(true);
expect(findStatusIcon().exists()).toBe(false);
});
});
describe('loaded state', () => {
beforeEach(async () => {
createComponent();
await waitForPromises();
});
it('should display pipeline status after the query is resolved successfully', async () => {
expect(findStatusIcon().exists()).toBe(true);
expect(findLoadingIcon().exists()).toBe(false);
expect(createFlash).toHaveBeenCalledTimes(0);
});
it('should link to the latest pipeline', () => {
const {
data: {
project: {
pipeline: {
detailedStatus: { detailsPath },
},
},
},
} = mockPipelineStatusResponse;
expect(findPipelineLink().attributes('href')).toBe(detailsPath);
});
});
describe('error state', () => {
it('createFlash should show if there is an error fetching the pipeline status', async () => {
createComponent(failedHandler);
await waitForPromises();
expect(createFlash).toHaveBeenCalledWith({
message: PIPELINE_STATUS_FETCH_ERROR,
});
});
});
describe('polling', () => {
it('polling interval is set for pipeline stages', () => {
createComponent();
const expectedInterval = wrapper.vm.$apollo.queries.pipelineStatus.options.pollInterval;
expect(expectedInterval).toBe(COMMIT_BOX_POLL_INTERVAL);
});
it('polls for pipeline status', async () => {
createComponent();
await waitForPromises();
expect(statusSuccessHandler).toHaveBeenCalledTimes(1);
advanceToNextFetch();
await waitForPromises();
expect(statusSuccessHandler).toHaveBeenCalledTimes(2);
advanceToNextFetch();
await waitForPromises();
expect(statusSuccessHandler).toHaveBeenCalledTimes(3);
});
it('toggles pipelineStatus polling with visibility check', async () => {
jest.spyOn(graphQlUtils, 'toggleQueryPollingByVisibility');
createComponent();
await waitForPromises();
expect(graphQlUtils.toggleQueryPollingByVisibility).toHaveBeenCalledWith(
wrapper.vm.$apollo.queries.pipelineStatus,
);
});
});
});
......@@ -141,3 +141,23 @@ export const mockPipelineStagesQueryResponse = {
},
},
};
export const mockPipelineStatusResponse = {
data: {
project: {
id: 'gid://gitlab/Project/20',
pipeline: {
id: 'gid://gitlab/Ci::Pipeline/320',
detailedStatus: {
id: 'pending-320-320',
detailsPath: '/root/ci-project/-/pipelines/320',
icon: 'status_pending',
group: 'pending',
__typename: 'DetailedStatus',
},
__typename: 'Pipeline',
},
__typename: 'Project',
},
},
};
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