Merge branch '35779-realtime-update-of-pipeline-status-in-files-view' into 'master'
Realtime update of pipeline status in Files view Closes #35779 See merge request gitlab-org/gitlab-ce!16523
This commit is contained in:
commit
72be759af1
|
@ -1,3 +1,5 @@
|
|||
import Vue from 'vue';
|
||||
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
|
||||
import TreeView from '../../../../tree';
|
||||
import ShortcutsNavigation from '../../../../shortcuts_navigation';
|
||||
import BlobViewer from '../../../../blob/viewer';
|
||||
|
@ -11,5 +13,25 @@ export default () => {
|
|||
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
|
||||
$('#tree-slider').waitForImages(() =>
|
||||
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
|
||||
|
||||
const commitPipelineStatusEl = document.getElementById('commit-pipeline-status');
|
||||
const statusLink = document.querySelector('.commit-actions .ci-status-link');
|
||||
if (statusLink != null) {
|
||||
statusLink.remove();
|
||||
// eslint-disable-next-line no-new
|
||||
new Vue({
|
||||
el: commitPipelineStatusEl,
|
||||
components: {
|
||||
commitPipelineStatus,
|
||||
},
|
||||
render(createElement) {
|
||||
return createElement('commit-pipeline-status', {
|
||||
props: {
|
||||
endpoint: commitPipelineStatusEl.dataset.endpoint,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
<script>
|
||||
import Visibility from 'visibilityjs';
|
||||
import ciIcon from '~/vue_shared/components/ci_icon.vue';
|
||||
import loadingIcon from '~/vue_shared/components/loading_icon.vue';
|
||||
import Poll from '~/lib/utils/poll';
|
||||
import Flash from '~/flash';
|
||||
import { s__, sprintf } from '~/locale';
|
||||
import tooltip from '~/vue_shared/directives/tooltip';
|
||||
import CommitPipelineService from '../services/commit_pipeline_service';
|
||||
|
||||
export default {
|
||||
directives: {
|
||||
tooltip,
|
||||
},
|
||||
components: {
|
||||
ciIcon,
|
||||
loadingIcon,
|
||||
},
|
||||
props: {
|
||||
endpoint: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
/* This prop can be used to replace some of the `render_commit_status`
|
||||
used across GitLab, this way we could use this vue component and add a
|
||||
realtime status where it makes sense
|
||||
realtime: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
}, */
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
ciStatus: {},
|
||||
isLoading: true,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
statusTitle() {
|
||||
return sprintf(s__('Commits|Commit: %{commitText}'), { commitText: this.ciStatus.text });
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.service = new CommitPipelineService(this.endpoint);
|
||||
this.initPolling();
|
||||
},
|
||||
methods: {
|
||||
successCallback(res) {
|
||||
const pipelines = res.data.pipelines;
|
||||
if (pipelines.length > 0) {
|
||||
// The pipeline entity always keeps the latest pipeline info on the `details.status`
|
||||
this.ciStatus = pipelines[0].details.status;
|
||||
}
|
||||
this.isLoading = false;
|
||||
},
|
||||
errorCallback() {
|
||||
this.ciStatus = {
|
||||
text: 'not found',
|
||||
icon: 'status_notfound',
|
||||
group: 'notfound',
|
||||
};
|
||||
this.isLoading = false;
|
||||
Flash(s__('Something went wrong on our end'));
|
||||
},
|
||||
initPolling() {
|
||||
this.poll = new Poll({
|
||||
resource: this.service,
|
||||
method: 'fetchData',
|
||||
successCallback: response => this.successCallback(response),
|
||||
errorCallback: this.errorCallback,
|
||||
});
|
||||
|
||||
if (!Visibility.hidden()) {
|
||||
this.isLoading = true;
|
||||
this.poll.makeRequest();
|
||||
} else {
|
||||
this.fetchPipelineCommitData();
|
||||
}
|
||||
|
||||
Visibility.change(() => {
|
||||
if (!Visibility.hidden()) {
|
||||
this.poll.restart();
|
||||
} else {
|
||||
this.poll.stop();
|
||||
}
|
||||
});
|
||||
},
|
||||
fetchPipelineCommitData() {
|
||||
this.service.fetchData()
|
||||
.then(this.successCallback)
|
||||
.catch(this.errorCallback);
|
||||
},
|
||||
},
|
||||
destroy() {
|
||||
this.poll.stop();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<loading-icon
|
||||
label="Loading pipeline status"
|
||||
size="3"
|
||||
v-if="isLoading"
|
||||
/>
|
||||
<a
|
||||
v-else
|
||||
:href="ciStatus.details_path"
|
||||
>
|
||||
<ci-icon
|
||||
v-tooltip
|
||||
:title="statusTitle"
|
||||
:aria-label="statusTitle"
|
||||
data-container="body"
|
||||
:status="ciStatus"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,11 @@
|
|||
import axios from '~/lib/utils/axios_utils';
|
||||
|
||||
export default class CommitPipelineService {
|
||||
constructor(endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
fetchData() {
|
||||
return axios.get(this.endpoint);
|
||||
}
|
||||
}
|
|
@ -195,6 +195,18 @@
|
|||
.commit-actions {
|
||||
@media (min-width: $screen-sm-min) {
|
||||
font-size: 0;
|
||||
|
||||
div {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.fa-spinner {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.ci-status-link {
|
||||
|
@ -219,6 +231,11 @@
|
|||
font-size: 14px;
|
||||
font-weight: $gl-font-weight-bold;
|
||||
}
|
||||
|
||||
.ci-status-icon {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.commit,
|
||||
|
|
|
@ -51,6 +51,7 @@
|
|||
- if commit.status(ref)
|
||||
= render_commit_status(commit, ref: ref)
|
||||
|
||||
#commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } }
|
||||
= link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link"
|
||||
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"))
|
||||
= link_to_browse_code(project, commit)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add realtime ci status for the repository -> files view
|
||||
merge_request: 16523
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,104 @@
|
|||
import Vue from 'vue';
|
||||
import MockAdapter from 'axios-mock-adapter';
|
||||
import axios from '~/lib/utils/axios_utils';
|
||||
import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue';
|
||||
import mountComponent from '../helpers/vue_mount_component_helper';
|
||||
|
||||
describe('Commit pipeline status component', () => {
|
||||
let vm;
|
||||
let Component;
|
||||
let mock;
|
||||
const mockCiStatus = {
|
||||
details_path: '/root/hello-world/pipelines/1',
|
||||
favicon: 'canceled.ico',
|
||||
group: 'canceled',
|
||||
has_details: true,
|
||||
icon: 'status_canceled',
|
||||
label: 'canceled',
|
||||
text: 'canceled',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
Component = Vue.extend(commitPipelineStatus);
|
||||
});
|
||||
|
||||
describe('While polling pipeline data succesfully', () => {
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet('/dummy/endpoint').reply(() => {
|
||||
const res = Promise.resolve([200, {
|
||||
pipelines: [
|
||||
{
|
||||
details: {
|
||||
status: mockCiStatus,
|
||||
},
|
||||
},
|
||||
],
|
||||
}]);
|
||||
return res;
|
||||
});
|
||||
vm = mountComponent(Component, {
|
||||
endpoint: '/dummy/endpoint',
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.poll.stop();
|
||||
vm.$destroy();
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('shows the loading icon when polling is starting', (done) => {
|
||||
expect(vm.$el.querySelector('.loading-container')).not.toBe(null);
|
||||
setTimeout(() => {
|
||||
expect(vm.$el.querySelector('.loading-container')).toBe(null);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('contains a ciStatus when the polling is succesful ', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(vm.ciStatus).toEqual(mockCiStatus);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('contains a ci-status icon when polling is succesful', (done) => {
|
||||
setTimeout(() => {
|
||||
expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null);
|
||||
expect(vm.$el.querySelector('.ci-status-icon').classList).toContain(`ci-status-icon-${mockCiStatus.group}`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('When polling data was not succesful', () => {
|
||||
beforeEach(() => {
|
||||
mock = new MockAdapter(axios);
|
||||
mock.onGet('/dummy/endpoint').reply(() => {
|
||||
const res = Promise.reject([502, { }]);
|
||||
return res;
|
||||
});
|
||||
vm = new Component({
|
||||
props: {
|
||||
endpoint: '/dummy/endpoint',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.poll.stop();
|
||||
vm.$destroy();
|
||||
mock.restore();
|
||||
});
|
||||
|
||||
it('calls an errorCallback', (done) => {
|
||||
spyOn(vm, 'errorCallback').and.callThrough();
|
||||
vm.$mount();
|
||||
setTimeout(() => {
|
||||
expect(vm.errorCallback.calls.count()).toEqual(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue