Commit a4dbeeb0 authored by Thong Kuah's avatar Thong Kuah

Merge branch 'ee-55095-show-cluster-on-job' into 'master'

EE: Show cluster link on job page

See merge request gitlab-org/gitlab-ee!16361
parents 01e12b05 fdc89367
...@@ -79,7 +79,9 @@ export default { ...@@ -79,7 +79,9 @@ export default {
default: default:
break; break;
} }
return environmentText; return environmentText && this.hasCluster
? `${environmentText} ${this.clusterText}`
: environmentText;
}, },
environmentLink() { environmentLink() {
if (this.hasEnvironment) { if (this.hasEnvironment) {
...@@ -109,6 +111,37 @@ export default { ...@@ -109,6 +111,37 @@ export default {
? this.lastDeployment.deployable.build_path ? this.lastDeployment.deployable.build_path
: ''; : '';
}, },
hasCluster() {
return this.hasLastDeployment && this.lastDeployment.cluster;
},
clusterNameOrLink() {
if (!this.hasCluster) {
return '';
}
const { name, path } = this.lastDeployment.cluster;
const escapedName = _.escape(name);
const escapedPath = _.escape(path);
if (!escapedPath) {
return escapedName;
}
return sprintf(
'%{startLink}%{name}%{endLink}',
{
startLink: `<a href="${escapedPath}" class="js-job-cluster-link">`,
name: escapedName,
endLink: '</a>',
},
false,
);
},
clusterText() {
return this.hasCluster
? sprintf(__('Cluster %{cluster} was used.'), { cluster: this.clusterNameOrLink }, false)
: '';
},
}, },
methods: { methods: {
deploymentLink(name) { deploymentLink(name) {
......
# frozen_string_literal: true
class ClusterBasicEntity < Grape::Entity
include RequestAwareEntity
expose :name
expose :path, if: -> (cluster) { can?(request.current_user, :read_cluster, cluster) } do |cluster|
cluster.present(current_user: request.current_user).show_path
end
end
...@@ -38,6 +38,8 @@ class DeploymentEntity < Grape::Entity ...@@ -38,6 +38,8 @@ class DeploymentEntity < Grape::Entity
expose :manual_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? } expose :manual_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
expose :scheduled_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? } expose :scheduled_actions, using: JobEntity, if: -> (*) { include_details? && can_create_deployment? }
expose :cluster, using: ClusterBasicEntity
private private
def include_details? def include_details?
......
---
title: Show link to cluster used on job page
merge_request: 32446
author:
type: added
...@@ -3184,6 +3184,9 @@ msgstr "" ...@@ -3184,6 +3184,9 @@ msgstr ""
msgid "Closes this %{quick_action_target}." msgid "Closes this %{quick_action_target}."
msgstr "" msgstr ""
msgid "Cluster %{cluster} was used."
msgstr ""
msgid "Cluster Health" msgid "Cluster Health"
msgstr "" msgstr ""
......
...@@ -265,7 +265,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -265,7 +265,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
let(:job) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) } let(:job) { create(:ci_build, :running, environment: environment.name, pipeline: pipeline) }
before do before do
create(:deployment, :success, environment: environment, project: project) create(:deployment, :success, :on_cluster, environment: environment, project: project)
project.add_maintainer(user) # Need to be a maintianer to view cluster.path
end end
it 'exposes the deployment information' do it 'exposes the deployment information' do
...@@ -276,8 +277,9 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do ...@@ -276,8 +277,9 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(json_response.dig('deployment_status', 'status')).to eq 'creating' expect(json_response.dig('deployment_status', 'status')).to eq 'creating'
expect(json_response.dig('deployment_status', 'environment')).not_to be_nil expect(json_response.dig('deployment_status', 'environment')).not_to be_nil
expect(json_response.dig('deployment_status', 'environment', 'last_deployment')).not_to be_nil expect(json_response.dig('deployment_status', 'environment', 'last_deployment')).not_to be_nil
expect(json_response.dig('deployment_status', 'environment', 'last_deployment')) expect(json_response.dig('deployment_status', 'environment', 'last_deployment')).not_to include('commit')
.not_to include('commit') expect(json_response.dig('deployment_status', 'environment', 'last_deployment', 'cluster', 'name')).to eq('test-cluster')
expect(json_response.dig('deployment_status', 'environment', 'last_deployment', 'cluster', 'path')).to be_present
end end
end end
......
...@@ -17,6 +17,10 @@ FactoryBot.define do ...@@ -17,6 +17,10 @@ FactoryBot.define do
unless deployment.project.repository_exists? unless deployment.project.repository_exists?
allow(deployment.project.repository).to receive(:create_ref) allow(deployment.project.repository).to receive(:create_ref)
end end
if deployment.cluster && deployment.cluster.project_type? && deployment.cluster.project.nil?
deployment.cluster.projects << deployment.project
end
end end
trait :review_app do trait :review_app do
......
...@@ -534,9 +534,32 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do ...@@ -534,9 +534,32 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end end
it 'shows deployment message' do it 'shows deployment message' do
expect(page).to have_content 'This job is the most recent deployment' expect(page).to have_content 'This job is the most recent deployment to production'
expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}") expect(find('.js-environment-link')['href']).to match("environments/#{environment.id}")
end end
context 'when there is a cluster used for the deployment' do
let(:cluster) { create(:cluster, name: 'the-cluster') }
let(:deployment) { create(:deployment, :success, cluster: cluster, environment: environment, project: environment.project) }
let(:user_access_level) { :maintainer }
it 'shows a link to the cluster' do
expect(page).to have_link 'the-cluster'
end
it 'shows the name of the cluster' do
expect(page).to have_content 'Cluster the-cluster was used'
end
context 'when the user is not able to view the cluster' do
let(:user_access_level) { :developer }
it 'includes only the name of the cluster without a link' do
expect(page).to have_content 'Cluster the-cluster was used'
expect(page).not_to have_link 'the-cluster'
end
end
end
end end
context 'job is complete and not successful' do context 'job is complete and not successful' do
......
{
"type": "object",
"required": [
"name"
],
"properties": {
"name": { "type": "string" },
"path": {
"oneOf": [
{ "type": "null" },
{ "type": "string" }
]
}
},
"additionalProperties": false
}
...@@ -47,6 +47,12 @@ ...@@ -47,6 +47,12 @@
{ "$ref": "job/job.json" } { "$ref": "job/job.json" }
] ]
}, },
"cluster": {
"oneOf": [
{ "type": "null" },
{ "$ref": "cluster_basic.json" }
]
},
"manual_actions": { "manual_actions": {
"type": "array", "type": "array",
"items": { "$ref": "job/job.json" } "items": { "$ref": "job/job.json" }
......
...@@ -28,6 +28,8 @@ ...@@ -28,6 +28,8 @@
"created_at": { "type": "string", "format": "date-time" }, "created_at": { "type": "string", "format": "date-time" },
"updated_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" },
"can_stop": { "type": "boolean" }, "can_stop": { "type": "boolean" },
"cluster_type": { "type": "types/nullable_string.json" },
"terminal_path": { "type": "types/nullable_string.json" },
"last_deployment": { "last_deployment": {
"oneOf": [ "oneOf": [
{ "type": "null" }, { "type": "null" },
......
...@@ -18,6 +18,8 @@ describe('Environments block', () => { ...@@ -18,6 +18,8 @@ describe('Environments block', () => {
name: 'environment', name: 'environment',
}; };
const lastDeployment = { iid: 'deployment', deployable: { build_path: 'bar' } };
afterEach(() => { afterEach(() => {
vm.$destroy(); vm.$destroy();
}); });
...@@ -45,7 +47,7 @@ describe('Environments block', () => { ...@@ -45,7 +47,7 @@ describe('Environments block', () => {
deploymentStatus: { deploymentStatus: {
status: 'out_of_date', status: 'out_of_date',
environment: Object.assign({}, environment, { environment: Object.assign({}, environment, {
last_deployment: { iid: 'deployment', deployable: { build_path: 'bar' } }, last_deployment: lastDeployment,
}), }),
}, },
iconStatus: status, iconStatus: status,
...@@ -99,10 +101,7 @@ describe('Environments block', () => { ...@@ -99,10 +101,7 @@ describe('Environments block', () => {
deploymentStatus: { deploymentStatus: {
status: 'creating', status: 'creating',
environment: Object.assign({}, environment, { environment: Object.assign({}, environment, {
last_deployment: { last_deployment: lastDeployment,
iid: 'deployment',
deployable: { build_path: 'foo' },
},
}), }),
}, },
iconStatus: status, iconStatus: status,
...@@ -112,7 +111,7 @@ describe('Environments block', () => { ...@@ -112,7 +111,7 @@ describe('Environments block', () => {
'This job is creating a deployment to environment and will overwrite the latest 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'); expect(vm.$el.querySelector('.js-job-deployment-link').getAttribute('href')).toEqual('bar');
}); });
}); });
...@@ -146,4 +145,71 @@ describe('Environments block', () => { ...@@ -146,4 +145,71 @@ describe('Environments block', () => {
}); });
}); });
}); });
describe('with a cluster', () => {
it('renders the cluster link', () => {
const cluster = {
name: 'the-cluster',
path: '/the-cluster-path',
};
vm = mountComponent(Component, {
deploymentStatus: {
status: 'last',
environment: Object.assign({}, environment, {
last_deployment: {
...lastDeployment,
cluster,
},
}),
},
iconStatus: status,
});
expect(vm.$el.textContent.trim()).toContain('Cluster the-cluster was used.');
expect(vm.$el.querySelector('.js-job-cluster-link').getAttribute('href')).toEqual(
'/the-cluster-path',
);
});
describe('when the cluster is missing the path', () => {
it('renders the name without a link', () => {
const cluster = {
name: 'the-cluster',
};
vm = mountComponent(Component, {
deploymentStatus: {
status: 'last',
environment: Object.assign({}, environment, {
last_deployment: {
...lastDeployment,
cluster,
},
}),
},
iconStatus: status,
});
expect(vm.$el.textContent.trim()).toContain('Cluster the-cluster was used.');
expect(vm.$el.querySelector('.js-job-cluster-link')).toBeNull();
});
});
});
describe('without a cluster', () => {
it('does not render a cluster link', () => {
vm = mountComponent(Component, {
deploymentStatus: {
status: 'last',
environment: Object.assign({}, environment, {
last_deployment: lastDeployment,
}),
},
iconStatus: status,
});
expect(vm.$el.querySelector('.js-job-cluster-link')).toBeNull();
});
});
}); });
require 'spec_helper'
describe ClusterBasicEntity do
describe '#as_json' do
subject { described_class.new(cluster, request: request).as_json }
let(:maintainer) { create(:user) }
let(:developer) { create(:user) }
let(:current_user) { maintainer }
let(:request) { double(:request, current_user: current_user) }
let(:project) { create(:project) }
let(:cluster) { create(:cluster, name: 'the-cluster', projects: [project]) }
before do
project.add_maintainer(maintainer)
project.add_developer(developer)
end
it 'matches cluster_basic entity schema' do
expect(subject.as_json).to match_schema('cluster_basic')
end
it 'exposes the cluster details' do
expect(subject[:name]).to eq('the-cluster')
expect(subject[:path]).to eq("/#{project.full_path}/clusters/#{cluster.id}")
end
context 'when the user does not have permission to view the cluster' do
let(:current_user) { developer }
it 'does not include the path' do
expect(subject[:path]).to be_nil
end
end
end
end
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