Commit 9128e784 authored by Filipa Lacerda's avatar Filipa Lacerda Committed by Phil Hughes

Uses Vue app to render part of job show page

parent 88c1cf67
...@@ -12,12 +12,16 @@ ...@@ -12,12 +12,16 @@
type: Object, type: Object,
required: true, required: true,
}, },
iconStatus: {
type: Object,
required: true,
},
}, },
computed: { computed: {
environment() { environment() {
let environmentText; let environmentText;
switch (this.deploymentStatus.status) { switch (this.deploymentStatus.status) {
case 'latest': case 'last':
environmentText = sprintf( environmentText = sprintf(
__('This job is the most recent deployment to %{link}.'), __('This job is the most recent deployment to %{link}.'),
{ link: this.environmentLink }, { link: this.environmentLink },
...@@ -32,7 +36,7 @@ ...@@ -32,7 +36,7 @@
), ),
{ {
environmentLink: this.environmentLink, environmentLink: this.environmentLink,
deploymentLink: this.deploymentLink, deploymentLink: this.deploymentLink(`#${this.lastDeployment.iid}`),
}, },
false, false,
); );
...@@ -56,11 +60,11 @@ ...@@ -56,11 +60,11 @@
if (this.hasLastDeployment) { if (this.hasLastDeployment) {
environmentText = sprintf( 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, environmentLink: this.environmentLink,
deploymentLink: this.deploymentLink, deploymentLink: this.deploymentLink(__('latest deployment')),
}, },
false, false,
); );
...@@ -78,41 +82,57 @@ ...@@ -78,41 +82,57 @@
return environmentText; return environmentText;
}, },
environmentLink() { environmentLink() {
return sprintf( if (this.hasEnvironment) {
'%{startLink}%{name}%{endLink}', return sprintf(
{ '%{startLink}%{name}%{endLink}',
startLink: `<a href="${this.deploymentStatus.environment.path}">`, {
name: _.escape(this.deploymentStatus.environment.name), startLink: `<a href="${
endLink: '</a>', this.deploymentStatus.environment.environment_path
}, }" class="js-environment-link">`,
false, name: _.escape(this.deploymentStatus.environment.name),
); endLink: '</a>',
},
false,
);
}
return '';
}, },
deploymentLink() { hasLastDeployment() {
return this.hasEnvironment && this.deploymentStatus.environment.last_deployment;
},
lastDeployment() {
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( return sprintf(
'%{startLink}%{name}%{endLink}', '%{startLink}%{name}%{endLink}',
{ {
startLink: `<a href="${this.lastDeployment.path}">`, startLink: `<a href="${this.lastDeploymentPath}" class="js-job-deployment-link">`,
name: _.escape(this.lastDeployment.name), name,
endLink: '</a>', endLink: '</a>',
}, },
false, false,
); );
}, },
hasLastDeployment() {
return this.deploymentStatus.environment.last_deployment;
},
lastDeployment() {
return this.deploymentStatus.environment.last_deployment;
},
}, },
}; };
</script> </script>
<template> <template>
<div class="prepend-top-default js-environment-container"> <div class="prepend-top-default js-environment-container">
<div class="environment-information"> <div class="environment-information">
<ci-icon :status="deploymentStatus.icon" /> <ci-icon :status="iconStatus"/>
<p v-html="environment"></p> <p
class="inline append-bottom-0"
v-html="environment"
></p>
</div> </div>
</div> </div>
</template> </template>
<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>
<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>
...@@ -2,7 +2,7 @@ import _ from 'underscore'; ...@@ -2,7 +2,7 @@ import _ from 'underscore';
import { mapState, mapActions } from 'vuex'; import { mapState, mapActions } from 'vuex';
import Vue from 'vue'; import Vue from 'vue';
import Job from '../job'; import Job from '../job';
import JobHeader from './components/header.vue'; import JobApp from './components/job_app.vue';
import Sidebar from './components/sidebar.vue'; import Sidebar from './components/sidebar.vue';
import createStore from './store'; import createStore from './store';
...@@ -22,17 +22,18 @@ export default () => { ...@@ -22,17 +22,18 @@ export default () => {
new Vue({ new Vue({
el: '#js-build-header-vue', el: '#js-build-header-vue',
components: { components: {
JobHeader, JobApp,
}, },
store, store,
computed: { computed: {
...mapState(['job', 'isLoading']), ...mapState(['job', 'isLoading']),
}, },
render(createElement) { render(createElement) {
return createElement('job-header', { return createElement('job-app', {
props: { props: {
isLoading: this.isLoading, isLoading: this.isLoading,
job: this.job, job: this.job,
runnerHelpUrl: dataset.runnerHelpUrl,
}, },
}); });
}, },
......
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 () => {};
...@@ -2,6 +2,7 @@ import Vue from 'vue'; ...@@ -2,6 +2,7 @@ import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import state from './state'; import state from './state';
import * as actions from './actions'; import * as actions from './actions';
import * as getters from './getters';
import mutations from './mutations'; import mutations from './mutations';
Vue.use(Vuex); Vue.use(Vuex);
...@@ -9,5 +10,6 @@ Vue.use(Vuex); ...@@ -9,5 +10,6 @@ Vue.use(Vuex);
export default () => new Vuex.Store({ export default () => new Vuex.Store({
actions, actions,
mutations, mutations,
getters,
state: state(), state: state(),
}); });
...@@ -9,54 +9,6 @@ ...@@ -9,54 +9,6 @@
%div{ class: container_class } %div{ class: container_class }
.build-page.js-build-page .build-page.js-build-page
#js-build-header-vue #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? - if @build.running? || @build.has_trace?
.build-trace-container.prepend-top-default .build-trace-container.prepend-top-default
......
---
title: Renders Job show page in new Vue app
merge_request:
author:
type: other
...@@ -6106,7 +6106,7 @@ msgstr "" ...@@ -6106,7 +6106,7 @@ msgstr ""
msgid "This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}." msgid "This job is an out-of-date deployment to %{environmentLink}. View the most recent deployment %{deploymentLink}."
msgstr "" 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 "" msgstr ""
msgid "This job is creating a deployment to %{environmentLink}." msgid "This job is creating a deployment to %{environmentLink}."
...@@ -7054,6 +7054,9 @@ msgstr "" ...@@ -7054,6 +7054,9 @@ msgstr ""
msgid "issue boards" msgid "issue boards"
msgstr "" msgstr ""
msgid "latest deployment"
msgstr ""
msgid "latest version" msgid "latest version"
msgstr "" msgstr ""
......
...@@ -16,7 +16,9 @@ describe 'User browses a job', :js do ...@@ -16,7 +16,9 @@ describe 'User browses a job', :js do
visit(project_job_path(project, build)) visit(project_job_path(project, build))
end 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_content("Job ##{build.id}")
expect(page).to have_css('#build-trace') expect(page).to have_css('#build-trace')
...@@ -29,9 +31,7 @@ describe 'User browses a job', :js do ...@@ -29,9 +31,7 @@ describe 'User browses a job', :js do
expect(build.artifacts_file.exists?).to be_falsy expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.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')
expect(page).to have_content('Job has been erased')
end
end end
context 'with a failed job' do context 'with a failed job' do
......
...@@ -369,39 +369,167 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do ...@@ -369,39 +369,167 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end end
end end
context 'when job starts environment' do context 'when job starts environment', :js do
let(:environment) { create(:environment, project: project) } let(:environment) { create(:environment, name: 'production', project: project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'job is successfull and has deployment' do context 'job is successful and has deployment' do
let(:deployment) { create(:deployment) } let(:build) { create(:ci_build, :success, :trace_live, environment: environment.name, pipeline: pipeline) }
let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, deployments: [deployment], pipeline: pipeline) } let!(:deployment) { create(:deployment, environment: environment, project: environment.project, deployable: build) }
it 'shows a link for the job' do before 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)"
end
it 'shows a link for the job' do
expect(page).to have_link environment.name expect(page).to have_link environment.name
end 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 end
context 'job is complete and not successful' do 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 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(page).to have_link environment.name
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end end
end end
context 'job creates a new deployment' do context 'deployment still not finished' do
let!(:deployment) { create(:deployment, environment: environment, sha: project.commit.id) } let(:build) { create(:ci_build, :success, environment: environment.name, pipeline: pipeline) }
let(:job) { create(:ci_build, :success, :trace_artifact, environment: environment.name, pipeline: pipeline) }
it 'shows a link to latest deployment' do 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 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_link('latest deployment') expect(page).to have_css(
'.environment-information', text: expected_text)
expect(page).not_to have_css(
'.environment-information', text: 'latest deployment')
end end
end end
end end
......
...@@ -5,19 +5,16 @@ import mountComponent from '../../helpers/vue_mount_component_helper'; ...@@ -5,19 +5,16 @@ import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Environments block', () => { describe('Environments block', () => {
const Component = Vue.extend(component); const Component = Vue.extend(component);
let vm; let vm;
const icon = { const status = {
group: 'success', group: 'success',
icon: 'status_success', icon: 'status_success',
label: 'passed', label: 'passed',
text: 'passed', text: 'passed',
tooltip: 'passed', tooltip: 'passed',
}; };
const deployment = {
path: 'deployment',
name: 'deployment name',
};
const environment = { const environment = {
path: '/environment', environment_path: '/environment',
name: 'environment', name: 'environment',
}; };
...@@ -25,15 +22,14 @@ describe('Environments block', () => { ...@@ -25,15 +22,14 @@ describe('Environments block', () => {
vm.$destroy(); vm.$destroy();
}); });
describe('with latest deployment', () => { describe('with last deployment', () => {
it('renders info for most recent deployment', () => { it('renders info for most recent deployment', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
deploymentStatus: { deploymentStatus: {
status: 'latest', status: 'last',
icon,
deployment,
environment, environment,
}, },
iconStatus: status,
}); });
expect(vm.$el.textContent.trim()).toEqual( expect(vm.$el.textContent.trim()).toEqual(
...@@ -48,17 +44,17 @@ describe('Environments block', () => { ...@@ -48,17 +44,17 @@ describe('Environments block', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
deploymentStatus: { deploymentStatus: {
status: 'out_of_date', status: 'out_of_date',
icon,
deployment,
environment: Object.assign({}, environment, { 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( 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', () => { ...@@ -67,10 +63,9 @@ describe('Environments block', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
deploymentStatus: { deploymentStatus: {
status: 'out_of_date', status: 'out_of_date',
icon,
deployment: null,
environment, environment,
}, },
iconStatus: status,
}); });
expect(vm.$el.textContent.trim()).toEqual( expect(vm.$el.textContent.trim()).toEqual(
...@@ -85,10 +80,9 @@ describe('Environments block', () => { ...@@ -85,10 +80,9 @@ describe('Environments block', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
deploymentStatus: { deploymentStatus: {
status: 'failed', status: 'failed',
icon,
deployment: null,
environment, environment,
}, },
iconStatus: status,
}); });
expect(vm.$el.textContent.trim()).toEqual( expect(vm.$el.textContent.trim()).toEqual(
...@@ -99,21 +93,24 @@ describe('Environments block', () => { ...@@ -99,21 +93,24 @@ describe('Environments block', () => {
describe('creating deployment', () => { describe('creating deployment', () => {
describe('with last 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, { vm = mountComponent(Component, {
deploymentStatus: { deploymentStatus: {
status: 'creating', status: 'creating',
icon,
deployment,
environment: Object.assign({}, environment, { 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( 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', () => { ...@@ -122,10 +119,9 @@ describe('Environments block', () => {
vm = mountComponent(Component, { vm = mountComponent(Component, {
deploymentStatus: { deploymentStatus: {
status: 'creating', status: 'creating',
icon,
deployment: null,
environment, environment,
}, },
iconStatus: status,
}); });
expect(vm.$el.textContent.trim()).toEqual( expect(vm.$el.textContent.trim()).toEqual(
...@@ -133,5 +129,18 @@ describe('Environments block', () => { ...@@ -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();
});
});
}); });
}); });
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');
});
});
});
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();
});
});
});
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);
});
});
});
});
...@@ -18,161 +18,6 @@ describe 'projects/jobs/show' do ...@@ -18,161 +18,6 @@ describe 'projects/jobs/show' do
allow(view).to receive(:can?).and_return(true) allow(view).to receive(:can?).and_return(true)
end 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 context 'when job is running' do
let(:build) { create(:ci_build, :trace_live, :running, pipeline: pipeline) } let(:build) { create(:ci_build, :trace_live, :running, pipeline: pipeline) }
......
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