Uses Vue app to render part of job show page

This commit is contained in:
Filipa Lacerda 2018-10-03 15:29:07 +00:00 committed by Phil Hughes
parent 88c1cf676c
commit 9128e7849d
16 changed files with 691 additions and 472 deletions

View file

@ -12,12 +12,16 @@
type: Object,
required: true,
},
iconStatus: {
type: Object,
required: true,
},
},
computed: {
environment() {
let environmentText;
switch (this.deploymentStatus.status) {
case 'latest':
case 'last':
environmentText = sprintf(
__('This job is the most recent deployment to %{link}.'),
{ link: this.environmentLink },
@ -32,7 +36,7 @@
),
{
environmentLink: this.environmentLink,
deploymentLink: this.deploymentLink,
deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
},
false,
);
@ -56,11 +60,11 @@
if (this.hasLastDeployment) {
environmentText = sprintf(
__(
'This job is creating a deployment to %{environmentLink} and will overwrite the last %{deploymentLink}.',
'This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}.',
),
{
environmentLink: this.environmentLink,
deploymentLink: this.deploymentLink,
deploymentLink: this.deploymentLink(__('latest deployment')),
},
false,
);
@ -78,32 +82,45 @@
return environmentText;
},
environmentLink() {
return sprintf(
'%{startLink}%{name}%{endLink}',
{
startLink: `<a href="${this.deploymentStatus.environment.path}">`,
name: _.escape(this.deploymentStatus.environment.name),
endLink: '</a>',
},
false,
);
},
deploymentLink() {
return sprintf(
'%{startLink}%{name}%{endLink}',
{
startLink: `<a href="${this.lastDeployment.path}">`,
name: _.escape(this.lastDeployment.name),
endLink: '</a>',
},
false,
);
if (this.hasEnvironment) {
return sprintf(
'%{startLink}%{name}%{endLink}',
{
startLink: `<a href="${
this.deploymentStatus.environment.environment_path
}" class="js-environment-link">`,
name: _.escape(this.deploymentStatus.environment.name),
endLink: '</a>',
},
false,
);
}
return '';
},
hasLastDeployment() {
return this.deploymentStatus.environment.last_deployment;
return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
},
lastDeployment() {
return this.deploymentStatus.environment.last_deployment;
return this.hasLastDeployment ? this.deploymentStatus.environment.last_deployment : {};
},
hasEnvironment() {
return !_.isEmpty(this.deploymentStatus.environment);
},
lastDeploymentPath() {
return !_.isEmpty(this.lastDeployment.deployable) ? this.lastDeployment.deployable.build_path : '';
},
},
methods: {
deploymentLink(name) {
return sprintf(
'%{startLink}%{name}%{endLink}',
{
startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
name,
endLink: '</a>',
},
false,
);
},
},
};
@ -111,8 +128,11 @@
<template>
<div class="prepend-top-default js-environment-container">
<div class="environment-information">
<ci-icon :status="deploymentStatus.icon" />
<p v-html="environment"></p>
<ci-icon :status="iconStatus"/>
<p
class="inline append-bottom-0"
v-html="environment"
></p>
</div>
</div>
</template>

View file

@ -1,95 +0,0 @@
<script>
import ciHeader from '../../vue_shared/components/header_ci_component.vue';
import callout from '../../vue_shared/components/callout.vue';
export default {
name: 'JobHeaderSection',
components: {
ciHeader,
callout,
},
props: {
job: {
type: Object,
required: true,
},
isLoading: {
type: Boolean,
required: true,
},
},
data() {
return {
actions: this.getActions(),
};
},
computed: {
status() {
return this.job && this.job.status;
},
shouldRenderContent() {
return !this.isLoading && Object.keys(this.job).length;
},
shouldRenderReason() {
return !!(this.job.status && this.job.callout_message);
},
/**
* When job has not started the key will be `false`
* When job started the key will be a string with a date.
*/
jobStarted() {
return !this.job.started === false;
},
headerTime() {
return this.jobStarted ? this.job.started : this.job.created_at;
},
},
watch: {
job() {
this.actions = this.getActions();
},
},
methods: {
getActions() {
const actions = [];
if (this.job.new_issue_path) {
actions.push({
label: 'New issue',
path: this.job.new_issue_path,
cssClass: 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
type: 'link',
});
}
return actions;
},
},
};
</script>
<template>
<header>
<div class="js-build-header build-header top-area">
<ci-header
v-if="shouldRenderContent"
:status="status"
:item-id="job.id"
:time="headerTime"
:user="job.user"
:actions="actions"
:has-sidebar-button="true"
:should-render-triggered-label="jobStarted"
item-name="Job"
/>
<gl-loading-icon
v-if="isLoading"
:size="2"
class="prepend-top-default append-bottom-default"
/>
</div>
<callout
v-if="shouldRenderReason"
:message="job.callout_message"
/>
</header>
</template>

View file

@ -0,0 +1,99 @@
<script>
import { mapGetters, mapState } from 'vuex';
import CiHeader from '~/vue_shared/components/header_ci_component.vue';
import Callout from '~/vue_shared/components/callout.vue';
import EnvironmentsBlock from './environments_block.vue';
import ErasedBlock from './erased_block.vue';
import StuckBlock from './stuck_block.vue';
export default {
name: 'JobPageApp',
components: {
CiHeader,
Callout,
EnvironmentsBlock,
ErasedBlock,
StuckBlock,
},
props: {
runnerHelpUrl: {
type: String,
required: false,
default: null,
},
},
computed: {
...mapState(['isLoading', 'job']),
...mapGetters([
'headerActions',
'headerTime',
'shouldRenderCalloutMessage',
'jobHasStarted',
'hasEnvironment',
'isJobStuck',
]),
},
};
</script>
<template>
<div>
<gl-loading-icon
v-if="isLoading"
:size="2"
class="prepend-top-20"
/>
<template v-else>
<!-- Header Section -->
<header>
<div class="js-build-header build-header top-area">
<ci-header
:status="job.status"
:item-id="job.id"
:time="headerTime"
:user="job.user"
:actions="headerActions"
:has-sidebar-button="true"
:should-render-triggered-label="jobHasStarted"
:item-name="__('Job')"
/>
</div>
<callout
v-if="shouldRenderCalloutMessage"
:message="job.callout_message"
/>
</header>
<!-- EO Header Section -->
<!-- Body Section -->
<stuck-block
v-if="isJobStuck"
class="js-job-stuck"
:has-no-runners-for-project="job.runners.available"
:tags="job.tags"
:runners-path="runnerHelpUrl"
/>
<environments-block
v-if="hasEnvironment"
:deployment-status="job.deployment_status"
:icon-status="job.status"
/>
<erased-block
v-if="job.erased"
:user="job.erased_by"
:erased-at="job.erased_at"
/>
<!--job log -->
<!-- EO job log -->
<!--empty state -->
<!-- EO empty state -->
<!-- EO Body Section -->
</template>
</div>
</template>

View file

@ -2,7 +2,7 @@ import _ from 'underscore';
import { mapState, mapActions } from 'vuex';
import Vue from 'vue';
import Job from '../job';
import JobHeader from './components/header.vue';
import JobApp from './components/job_app.vue';
import Sidebar from './components/sidebar.vue';
import createStore from './store';
@ -22,17 +22,18 @@ export default () => {
new Vue({
el: '#js-build-header-vue',
components: {
JobHeader,
JobApp,
},
store,
computed: {
...mapState(['job', 'isLoading']),
},
render(createElement) {
return createElement('job-header', {
return createElement('job-app', {
props: {
isLoading: this.isLoading,
job: this.job,
runnerHelpUrl: dataset.runnerHelpUrl,
},
});
},

View file

@ -0,0 +1,42 @@
import _ from 'underscore';
import { __ } from '~/locale';
export const headerActions = state => {
if (state.job.new_issue_path) {
return [
{
label: __('New issue'),
path: state.job.new_issue_path,
cssClass:
'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
type: 'link',
},
];
}
return [];
};
export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
export const shouldRenderCalloutMessage = state =>
!_.isEmpty(state.job.status) && !_.isEmpty(state.job.callout_message);
/**
* When job has not started the key will be `false`
* When job started the key will be a string with a date.
*/
export const jobHasStarted = state => !(state.job.started === false);
export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status);
/**
* When the job is pending and there are no available runners
* we need to render the stuck block;
*
* @returns {Boolean}
*/
export const isJobStuck = state =>
state.job.status.group === 'pending' && state.job.runners && state.job.runners.available === false;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};

View file

@ -2,6 +2,7 @@ import Vue from 'vue';
import Vuex from 'vuex';
import state from './state';
import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations';
Vue.use(Vuex);
@ -9,5 +10,6 @@ Vue.use(Vuex);
export default () => new Vuex.Store({
actions,
mutations,
getters,
state: state(),
});

View file

@ -9,54 +9,6 @@
%div{ class: container_class }
.build-page.js-build-page
#js-build-header-vue
- if @build.stuck?
- unless @build.any_runners_online?
.bs-callout.bs-callout-warning.js-build-stuck
%p
- if @project.any_runners?
This job is stuck, because the project doesn't have any runners online assigned to it.
- elsif @build.tags.any?
This job is stuck, because you don't have any active runners online with any of these tags assigned to them:
- @build.tags.each do |tag|
%span.badge.badge-primary
= tag
- else
This job is stuck, because you don't have any active runners that can run this job.
%br
Go to
= link_to project_runners_path(@build.project, anchor: 'js-runners-settings') do
Runners page
- if @build.starts_environment?
.prepend-top-default.js-environment-container
.environment-information
- if @build.outdated_deployment?
= ci_icon_for_status('success_with_warnings')
- else
= ci_icon_for_status(@build.status)
- environment = environment_for_build(@build.project, @build)
- if @build.success? && @build.last_deployment.present?
- if @build.last_deployment.last?
This job is the most recent deployment to #{environment_link_for_build(@build.project, @build)}.
- else
This job is an out-of-date deployment to #{environment_link_for_build(@build.project, @build)}.
View the most recent deployment #{deployment_link(environment.last_deployment)}.
- elsif @build.complete? && !@build.success?
The deployment of this job to #{environment_link_for_build(@build.project, @build)} did not succeed.
- else
This job is creating a deployment to #{environment_link_for_build(@build.project, @build)}
- if environment.try(:last_deployment)
and will overwrite the #{deployment_link(environment.last_deployment, text: 'latest deployment')}
- if @build.erased?
.prepend-top-default.js-build-erased
.erased.alert.alert-warning
- if @build.erased_by_user?
Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)}
- else
Job has been erased #{time_ago_with_tooltip(@build.erased_at)}
- if @build.running? || @build.has_trace?
.build-trace-container.prepend-top-default

View file

@ -0,0 +1,5 @@
---
title: Renders Job show page in new Vue app
merge_request:
author:
type: other

View file

@ -6106,7 +6106,7 @@ msgstr ""
msgid "This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}."
msgstr ""
msgid "This job is creating a deployment to %{environmentLink} and will overwrite the last %{deploymentLink}."
msgid "This job is creating a deployment to %{environmentLink} and will overwrite the %{deploymentLink}."
msgstr ""
msgid "This job is creating a deployment to %{environmentLink}."
@ -7054,6 +7054,9 @@ msgstr ""
msgid "issue boards"
msgstr ""
msgid "latest deployment"
msgstr ""
msgid "latest version"
msgstr ""

View file

@ -16,7 +16,9 @@ describe 'User browses a job', :js do
visit(project_job_path(project, build))
end
it 'erases the job log' do
it 'erases the job log', :js do
wait_for_requests
expect(page).to have_content("Job ##{build.id}")
expect(page).to have_css('#build-trace')
@ -29,9 +31,7 @@ describe 'User browses a job', :js do
expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.exists?).to be_falsy
page.within('.erased') do
expect(page).to have_content('Job has been erased')
end
expect(page).to have_content('Job has been erased')
end
context 'with a failed job' do

View file

@ -369,39 +369,167 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
context 'when job starts environment' do
let(:environment) { create(:environment, project: project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when job starts environment', :js do
let(:environment) { create(:environment, name: 'production', project: project) }
context 'job is successfull and has deployment' do
let(:deployment) { create(:deployment) }
let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, deployments: [deployment], pipeline: pipeline) }
context 'job is successful and has deployment' do
let(:build) { create(:ci_build, :success, :trace_live, environment: environment.name, pipeline: pipeline) }
let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) }
before do
visit project_job_path(project, build)
wait_for_requests
# scroll to the top of the page first
execute_script "window.scrollTo(0,0)"
end
it 'shows a link for the job' do
visit project_job_path(project, job)
expect(page).to have_link environment.name
end
it 'shows deployment message' do
expect(page).to have_content 'This job is the most recent deployment'
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
end
context 'job is complete and not successful' do
let(:job) { create(:ci_build, :failed, :trace_artifact, environment: environment.name, pipeline: pipeline) }
let(:build) { create(:ci_build, :failed, :trace_artifact, environment: environment.name, pipeline: pipeline) }
it 'shows a link for the job' do
visit project_job_path(project, job)
visit project_job_path(project, build)
wait_for_requests
# scroll to the top of the page first
execute_script "window.scrollTo(0,0)"
expect(page).to have_link environment.name
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
end
context 'job creates a new deployment' do
let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) }
let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, pipeline: pipeline) }
context 'deployment still not finished' do
let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
it 'shows a link to latest deployment' do
visit project_job_path(project, job)
visit project_job_path(project, build)
wait_for_all_requests
# scroll to the top of the page first
execute_script "window.scrollTo(0,0)"
expect(page).to have_link('latest deployment')
expect(page).to have_link environment.name
expect(page).to have_content 'This job is creating a deployment'
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
end
end
describe 'environment info in job view', :js do
before do
visit project_job_path(project, job)
wait_for_requests
# scroll to the top of the page first
execute_script "window.scrollTo(0,0)"
end
context 'job with outdated deployment' do
let(:job) { create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline) }
let(:second_build) { create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline) }
let(:environment) { create(:environment, name: 'staging', project: project) }
let!(:first_deployment) { create(:deployment, environment: environment, deployable: job) }
let!(:second_deployment) { create(:deployment, environment: environment, deployable: second_build) }
it 'shows deployment message' do
expected_text = 'This job is an out-of-date deployment ' \
"to staging. View the most recent deployment ##{second_deployment.iid}."
expect(page).to have_css('.environment-information', text: expected_text)
end
it 'renders a link to the most recent deployment' do
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
expect(find('.js-job-deployment-link')['href']).to include(second_deployment.deployable.project.path, second_deployment.deployable_id.to_s)
end
end
context 'job failed to deploy' do
let(:job) { create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline) }
let!(:environment) { create(:environment, name: 'staging', project: project) }
it 'shows deployment message' do
expected_text = 'The deployment of this job to staging did not succeed.'
expect(page).to have_css(
'.environment-information', text: expected_text)
end
end
context 'job will deploy' do
let(:job) { create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline) }
context 'when environment exists' do
let!(:environment) { create(:environment, name: 'staging', project: project) }
it 'shows deployment message' do
expected_text = 'This job is creating a deployment to staging'
expect(page).to have_css(
'.environment-information', text: expected_text)
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
context 'when it has deployment' do
let!(:deployment) { create(:deployment, environment: environment) }
it 'shows that deployment will be overwritten' do
expected_text = 'This job is creating a deployment to staging'
expect(page).to have_css(
'.environment-information', text: expected_text)
expect(page).to have_css(
'.environment-information', text: 'latest deployment')
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
end
end
context 'when environment does not exist' do
let!(:environment) { create(:environment, name: 'staging', project: project) }
it 'shows deployment message' do
expected_text = 'This job is creating a deployment to staging'
expect(page).to have_css(
'.environment-information', text: expected_text)
expect(page).not_to have_css(
'.environment-information', text: 'latest deployment')
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end
end
end
context 'job that failed to deploy and environment has not been created' do
let(:job) { create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline) }
let!(:environment) { create(:environment, name: 'staging', project: project) }
it 'shows deployment message' do
expected_text = 'The deployment of this job to staging did not succeed'
expect(page).to have_css(
'.environment-information', text: expected_text)
end
end
context 'job that will deploy and environment has not been created' do
let(:job) { create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline) }
let!(:environment) { create(:environment, name: 'staging', project: project) }
it 'shows deployment message' do
expected_text = 'This job is creating a deployment to staging'
expect(page).to have_css(
'.environment-information', text: expected_text)
expect(page).not_to have_css(
'.environment-information', text: 'latest deployment')
end
end
end

View file

@ -5,19 +5,16 @@ import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Environments block', () => {
const Component = Vue.extend(component);
let vm;
const icon = {
const status = {
group: 'success',
icon: 'status_success',
label: 'passed',
text: 'passed',
tooltip: 'passed',
};
const deployment = {
path: 'deployment',
name: 'deployment name',
};
const environment = {
path: '/environment',
environment_path: '/environment',
name: 'environment',
};
@ -25,15 +22,14 @@ describe('Environments block', () => {
vm.$destroy();
});
describe('with latest deployment', () => {
describe('with last deployment', () => {
it('renders info for most recent deployment', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'latest',
icon,
deployment,
status: 'last',
environment,
},
iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
@ -48,17 +44,17 @@ describe('Environments block', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'out_of_date',
icon,
deployment,
environment: Object.assign({}, environment, {
last_deployment: { name: 'deployment', path: 'last_deployment' },
last_deployment: { iid: 'deployment', deployable: { build_path: 'bar' } },
}),
},
iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
'This job is an out-of-date deployment to environment. View the most recent deployment deployment.',
'This job is an out-of-date deployment to environment. View the most recent deployment #deployment.',
);
expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('bar');
});
});
@ -67,10 +63,9 @@ describe('Environments block', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'out_of_date',
icon,
deployment: null,
environment,
},
iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
@ -85,10 +80,9 @@ describe('Environments block', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'failed',
icon,
deployment: null,
environment,
},
iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
@ -99,21 +93,24 @@ describe('Environments block', () => {
describe('creating deployment', () => {
describe('with last deployment', () => {
it('renders info about creating deployment and overriding lastest deployment', () => {
it('renders info about creating deployment and overriding latest deployment', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'creating',
icon,
deployment,
environment: Object.assign({}, environment, {
last_deployment: { name: 'deployment', path: 'last_deployment' },
last_deployment: {
iid: 'deployment',
deployable: { build_path: 'foo' },
},
}),
},
iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
'This job is creating a deployment to environment and will overwrite the last deployment.',
'This job is creating a deployment to environment and will overwrite the latest deployment.',
);
expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('foo');
});
});
@ -122,10 +119,9 @@ describe('Environments block', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'creating',
icon,
deployment: null,
environment,
},
iconStatus: status,
});
expect(vm.$el.textContent.trim()).toEqual(
@ -133,5 +129,18 @@ describe('Environments block', () => {
);
});
});
describe('without environment', () => {
it('does not render environment link', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'creating',
environment: null,
},
iconStatus: status,
});
expect(vm.$el.querySelector('.js-environment-link')).toBeNull();
});
});
});
});

View file

@ -1,98 +0,0 @@
import Vue from 'vue';
import headerComponent from '~/jobs/components/header.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Job details header', () => {
let HeaderComponent;
let vm;
let props;
beforeEach(() => {
HeaderComponent = Vue.extend(headerComponent);
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
const twoDaysAgo = new Date();
twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
props = {
job: {
status: {
group: 'failed',
icon: 'status_failed',
label: 'failed',
text: 'failed',
details_path: 'path',
},
id: 123,
created_at: threeWeeksAgo.toISOString(),
user: {
web_url: 'path',
name: 'Foo',
username: 'foobar',
email: 'foo@bar.com',
avatar_url: 'link',
},
started: twoDaysAgo.toISOString(),
new_issue_path: 'path',
},
isLoading: false,
};
});
afterEach(() => {
vm.$destroy();
});
describe('job reason', () => {
it('should not render the reason when reason is absent', () => {
vm = mountComponent(HeaderComponent, props);
expect(vm.shouldRenderReason).toBe(false);
});
it('should render the reason when reason is present', () => {
props.job.callout_message = 'There is an unknown failure, please try again';
vm = mountComponent(HeaderComponent, props);
expect(vm.shouldRenderReason).toBe(true);
});
});
describe('triggered job', () => {
beforeEach(() => {
vm = mountComponent(HeaderComponent, props);
});
it('should render provided job information', () => {
expect(
vm.$el
.querySelector('.header-main-content')
.textContent.replace(/\s+/g, ' ')
.trim(),
).toEqual('failed Job #123 triggered 2 days ago by Foo');
});
it('should render new issue link', () => {
expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
props.job.new_issue_path,
);
});
});
describe('created job', () => {
it('should render created key', () => {
props.job.started = false;
vm = mountComponent(HeaderComponent, props);
expect(
vm.$el
.querySelector('.header-main-content')
.textContent.replace(/\s+/g, ' ')
.trim(),
).toEqual('failed Job #123 created 3 weeks ago by Foo');
});
});
});

View file

@ -0,0 +1,185 @@
import Vue from 'vue';
import jobApp from '~/jobs/components/job_app.vue';
import createStore from '~/jobs/store';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
describe('Job App ', () => {
const Component = Vue.extend(jobApp);
let store;
let vm;
const threeWeeksAgo = new Date();
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21);
const twoDaysAgo = new Date();
twoDaysAgo.setDate(twoDaysAgo.getDate() - 2);
const job = {
status: {
group: 'failed',
icon: 'status_failed',
label: 'failed',
text: 'failed',
details_path: 'path',
},
id: 123,
created_at: threeWeeksAgo.toISOString(),
user: {
web_url: 'path',
name: 'Foo',
username: 'foobar',
email: 'foo@bar.com',
avatar_url: 'link',
},
started: twoDaysAgo.toISOString(),
new_issue_path: 'path',
runners: {
available: false,
},
tags: ['docker'],
};
const props = {
runnerHelpUrl: 'help/runners',
};
beforeEach(() => {
store = createStore();
});
afterEach(() => {
vm.$destroy();
});
describe('Header section', () => {
describe('job callout message', () => {
it('should not render the reason when reason is absent', () => {
store.dispatch('receiveJobSuccess', job);
vm = mountComponentWithStore(Component, {
props,
store,
});
expect(vm.shouldRenderCalloutMessage).toBe(false);
});
it('should render the reason when reason is present', () => {
store.dispatch(
'receiveJobSuccess',
Object.assign({}, job, {
callout_message: 'There is an unknown failure, please try again',
}),
);
vm = mountComponentWithStore(Component, {
props,
store,
});
expect(vm.shouldRenderCalloutMessage).toBe(true);
});
});
describe('triggered job', () => {
beforeEach(() => {
store.dispatch('receiveJobSuccess', job);
vm = mountComponentWithStore(Component, {
props,
store,
});
});
it('should render provided job information', () => {
expect(
vm.$el
.querySelector('.header-main-content')
.textContent.replace(/\s+/g, ' ')
.trim(),
).toEqual('failed Job #123 triggered 2 days ago by Foo');
});
it('should render new issue link', () => {
expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
job.new_issue_path,
);
});
});
describe('created job', () => {
it('should render created key', () => {
store.dispatch('receiveJobSuccess', Object.assign({}, job, { started: false }));
vm = mountComponentWithStore(Component, {
props,
store,
});
expect(
vm.$el
.querySelector('.header-main-content')
.textContent.replace(/\s+/g, ' ')
.trim(),
).toEqual('failed Job #123 created 3 weeks ago by Foo');
});
});
});
describe('stuck block', () => {
it('renders stuck block when there are no runners', () => {
store.dispatch(
'receiveJobSuccess',
Object.assign({}, job, {
status: {
group: 'pending',
icon: 'status_pending',
label: 'pending',
text: 'pending',
details_path: 'path',
},
}),
);
vm = mountComponentWithStore(Component, {
props,
store,
});
expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
});
it('renders tags in stuck block when there are no runners', () => {
store.dispatch(
'receiveJobSuccess',
Object.assign({}, job, {
status: {
group: 'pending',
icon: 'status_pending',
label: 'pending',
text: 'pending',
details_path: 'path',
},
}),
);
vm = mountComponentWithStore(Component, {
props,
store,
});
expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
});
it(' does not renders stuck block when there are no runners', () => {
store.dispatch('receiveJobSuccess', Object.assign({}, job, { runners: { available: true } }));
vm = mountComponentWithStore(Component, {
props,
store,
});
expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
});
});
});

View file

@ -0,0 +1,121 @@
import * as getters from '~/jobs/store/getters';
import state from '~/jobs/store/state';
describe('Job Store Getters', () => {
let localState;
beforeEach(() => {
localState = state();
});
describe('headerActions', () => {
describe('with new issue path', () => {
it('returns an array with action to create a new issue', () => {
localState.job.new_issue_path = 'issues/new';
expect(getters.headerActions(localState)).toEqual([
{
label: 'New issue',
path: localState.job.new_issue_path,
cssClass:
'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
type: 'link',
},
]);
});
});
describe('without new issue path', () => {
it('returns an empty array', () => {
expect(getters.headerActions(localState)).toEqual([]);
});
});
});
describe('headerTime', () => {
describe('when the job has started key', () => {
it('returns started key', () => {
const started = '2018-08-31T16:20:49.023Z';
localState.job.started = started;
expect(getters.headerTime(localState)).toEqual(started);
});
});
describe('when the job does not have started key', () => {
it('returns created_at key', () => {
const created = '2018-08-31T16:20:49.023Z';
localState.job.created_at = created;
expect(getters.headerTime(localState)).toEqual(created);
});
});
});
describe('shouldRenderCalloutMessage', () => {
describe('with status and callout message', () => {
it('returns true', () => {
localState.job.callout_message = 'Callout message';
localState.job.status = { icon: 'passed' };
expect(getters.shouldRenderCalloutMessage(localState)).toEqual(true);
});
});
describe('without status & with callout message', () => {
it('returns false', () => {
localState.job.callout_message = 'Callout message';
expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
});
});
describe('with status & without callout message', () => {
it('returns false', () => {
localState.job.status = { icon: 'passed' };
expect(getters.shouldRenderCalloutMessage(localState)).toEqual(false);
});
});
});
describe('jobHasStarted', () => {
describe('when started equals false', () => {
it('returns false', () => {
localState.job.started = false;
expect(getters.jobHasStarted(localState)).toEqual(false);
});
});
describe('when started equals string', () => {
it('returns true', () => {
localState.job.started = '2018-08-31T16:20:49.023Z';
expect(getters.jobHasStarted(localState)).toEqual(true);
});
});
});
describe('hasEnvironment', () => {
describe('without `deployment_status`', () => {
it('returns false', () => {
expect(getters.hasEnvironment(localState)).toEqual(false);
});
});
describe('with an empty object for `deployment_status`', () => {
it('returns false', () => {
localState.job.deployment_status = {};
expect(getters.hasEnvironment(localState)).toEqual(false);
});
});
describe('when `deployment_status` is defined and not empty', () => {
it('returns true', () => {
localState.job.deployment_status = {
status: 'creating',
environment: {
last_deployment: {},
},
};
expect(getters.hasEnvironment(localState)).toEqual(true);
});
});
});
});

View file

@ -18,161 +18,6 @@ describe 'projects/jobs/show' do
allow(view).to receive(:can?).and_return(true)
end
describe 'environment info in job view' do
context 'job with latest deployment' do
let(:build) do
create(:ci_build, :success, :trace_artifact, environment: 'staging')
end
before do
create(:environment, name: 'staging')
create(:deployment, deployable: build)
end
it 'shows deployment message' do
expected_text = 'This job is the most recent deployment'
render
expect(rendered).to have_css(
'.environment-information', text: expected_text)
end
end
context 'job with outdated deployment' do
let(:build) do
create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline)
end
let(:second_build) do
create(:ci_build, :success, :trace_artifact, environment: 'staging', pipeline: pipeline)
end
let(:environment) do
create(:environment, name: 'staging', project: project)
end
let!(:first_deployment) do
create(:deployment, environment: environment, deployable: build)
end
let!(:second_deployment) do
create(:deployment, environment: environment, deployable: second_build)
end
it 'shows deployment message' do
expected_text = 'This job is an out-of-date deployment ' \
"to staging.\nView the most recent deployment ##{second_deployment.iid}."
render
expect(rendered).to have_css('.environment-information', text: expected_text)
end
end
context 'job failed to deploy' do
let(:build) do
create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline)
end
let!(:environment) do
create(:environment, name: 'staging', project: project)
end
it 'shows deployment message' do
expected_text = 'The deployment of this job to staging did not succeed.'
render
expect(rendered).to have_css(
'.environment-information', text: expected_text)
end
end
context 'job will deploy' do
let(:build) do
create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline)
end
context 'when environment exists' do
let!(:environment) do
create(:environment, name: 'staging', project: project)
end
it 'shows deployment message' do
expected_text = 'This job is creating a deployment to staging'
render
expect(rendered).to have_css(
'.environment-information', text: expected_text)
end
context 'when it has deployment' do
let!(:deployment) do
create(:deployment, environment: environment)
end
it 'shows that deployment will be overwritten' do
expected_text = 'This job is creating a deployment to staging'
render
expect(rendered).to have_css(
'.environment-information', text: expected_text)
expect(rendered).to have_css(
'.environment-information', text: 'latest deployment')
end
end
end
context 'when environment does not exist' do
it 'shows deployment message' do
expected_text = 'This job is creating a deployment to staging'
render
expect(rendered).to have_css(
'.environment-information', text: expected_text)
expect(rendered).not_to have_css(
'.environment-information', text: 'latest deployment')
end
end
end
context 'job that failed to deploy and environment has not been created' do
let(:build) do
create(:ci_build, :failed, :trace_artifact, environment: 'staging', pipeline: pipeline)
end
let!(:environment) do
create(:environment, name: 'staging', project: project)
end
it 'shows deployment message' do
expected_text = 'The deployment of this job to staging did not succeed'
render
expect(rendered).to have_css(
'.environment-information', text: expected_text)
end
end
context 'job that will deploy and environment has not been created' do
let(:build) do
create(:ci_build, :running, :trace_live, environment: 'staging', pipeline: pipeline)
end
let!(:environment) do
create(:environment, name: 'staging', project: project)
end
it 'shows deployment message' do
expected_text = 'This job is creating a deployment to staging'
render
expect(rendered).to have_css(
'.environment-information', text: expected_text)
expect(rendered).not_to have_css(
'.environment-information', text: 'latest deployment')
end
end
end
context 'when job is running' do
let(:build) { create(:ci_build, :trace_live, :running, pipeline: pipeline) }