Commit 4d5b521e authored by Mike Greiling's avatar Mike Greiling

Merge branch 'app-label-warning' into 'master'

[EE] App label warning for deploy boards

See merge request gitlab-org/gitlab-ee!14103
parents 0c1e8060 a8b2fb45
......@@ -9,16 +9,17 @@
* [Mockup](https://gitlab.com/gitlab-org/gitlab-ce/uploads/2f655655c0eadf655d0ae7467b53002a/environments__deploy-graphic.png)
*/
import _ from 'underscore';
import { n__ } from '~/locale';
import { n__, s__, sprintf } from '~/locale';
import tooltip from '~/vue_shared/directives/tooltip';
import deployBoardSvg from 'ee_empty_states/icons/_deploy_board.svg';
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlLink } from '@gitlab/ui';
import instanceComponent from './deploy_board_instance_component.vue';
export default {
components: {
instanceComponent,
GlLoadingIcon,
GlLink,
},
directives: {
tooltip,
......@@ -28,6 +29,11 @@ export default {
type: Object,
required: true,
},
deployBoardsHelpPath: {
type: String,
required: false,
default: '',
},
isLoading: {
type: Boolean,
required: true,
......@@ -41,13 +47,29 @@ export default {
required: false,
default: '',
},
hasLegacyAppLabel: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
canRenderDeployBoard() {
return !this.isLoading && !this.isEmpty && !_.isEmpty(this.deployBoardData);
return !this.isEmpty && !_.isEmpty(this.deployBoardData);
},
legacyLabelWarningMessage() {
return sprintf(
s__(
'DeployBoard|Matching on the %{appLabel} label has been removed for deploy boards. To see all instances on your board, you must update your chart and redeploy.',
),
{
appLabel: '<code>app</code>',
},
false,
);
},
canRenderEmptyState() {
return !this.isLoading && this.isEmpty;
return this.isEmpty;
},
instanceCount() {
const { instances } = this.deployBoardData;
......@@ -80,72 +102,81 @@ export default {
<template>
<div class="js-deploy-board deploy-board">
<gl-loading-icon v-if="isLoading" />
<template v-else>
<div v-if="hasLegacyAppLabel" class="bs-callout bs-callout-warning mb-0 mt-0">
<span v-html="legacyLabelWarningMessage"></span>
<gl-link target="blank" :href="deployBoardsHelpPath">
<strong>{{ __('More Information') }}</strong>
</gl-link>
</div>
<div v-if="canRenderDeployBoard">
<section class="deploy-board-information">
<span v-tooltip :title="instanceIsCompletedText">
<span class="percentage text-center text-plain">{{ deployBoardData.completion }}%</span>
<span class="text text-center text-secondary">Complete</span>
</span>
</section>
<div v-if="canRenderDeployBoard" class="deploy-board-information">
<section class="deploy-board-status">
<span v-tooltip :title="instanceIsCompletedText">
<span class="percentage text-center text-plain">{{ deployBoardData.completion }}%</span>
<span class="text text-center text-secondary">Complete</span>
</span>
</section>
<section class="deploy-board-instances">
<p class="deploy-board-instances-text text-secondary">
<span>{{ instanceTitle }}</span>
<span class="total-instances">({{ instanceCount }})</span>
</p>
<section class="deploy-board-instances">
<p class="deploy-board-instances-text text-secondary">
<span>{{ instanceTitle }}</span>
<span class="total-instances">({{ instanceCount }})</span>
</p>
<div class="deploy-board-instances-container d-flex flex-wrap flex-row">
<template v-for="(instance, i) in deployBoardData.instances">
<instance-component
:key="i"
:status="instance.status"
:tooltip-text="instance.tooltip"
:pod-name="instance.pod_name"
:logs-path="logsPath"
:stable="instance.stable"
/>
</template>
</div>
</section>
<section
v-if="deployBoardData.rollback_url || deployBoardData.abort_url"
class="deploy-board-actions"
>
<a
v-if="deployBoardData.rollback_url"
:href="deployBoardData.rollback_url"
class="btn"
data-method="post"
rel="nofollow"
>
Rollback
</a>
<div class="deploy-board-instances-container d-flex flex-wrap flex-row">
<template v-for="(instance, i) in deployBoardData.instances">
<instance-component
:key="i"
:status="instance.status"
:tooltip-text="instance.tooltip"
:pod-name="instance.pod_name"
:logs-path="logsPath"
:stable="instance.stable"
/>
</template>
</div>
</section>
<a
v-if="deployBoardData.abort_url"
:href="deployBoardData.abort_url"
class="btn btn-red btn-inverted"
data-method="post"
rel="nofollow"
<section
v-if="deployBoardData.rollback_url || deployBoardData.abort_url"
class="deploy-board-actions"
>
Abort
</a>
</section>
</div>
<a
v-if="deployBoardData.rollback_url"
:href="deployBoardData.rollback_url"
class="btn"
data-method="post"
rel="nofollow"
>
Rollback
</a>
<a
v-if="deployBoardData.abort_url"
:href="deployBoardData.abort_url"
class="btn btn-red btn-inverted"
data-method="post"
rel="nofollow"
>
Abort
</a>
</section>
</div>
<div v-if="canRenderEmptyState">
<section class="deploy-board-empty-state-svg" v-html="deployBoardSvg"></section>
<div v-if="canRenderEmptyState" class="deploy-board-empty">
<section class="deploy-board-empty-state-svg" v-html="deployBoardSvg"></section>
<section class="deploy-board-empty-state-text">
<span class="deploy-board-empty-state-title d-flex">Kubernetes deployment not found</span>
<span>
To see deployment progress for your environments, make sure your deployments are in
Kubernetes namespace <code>{{ projectName }}</code> and labeled with
<code>app=$CI_ENVIRONMENT_SLUG</code>.
</span>
</section>
</div>
<section class="deploy-board-empty-state-text">
<span class="deploy-board-empty-state-title d-flex">Kubernetes deployment not found</span>
<span>
To see deployment progress for your environments, make sure your deployments are in
Kubernetes namespace <code>{{ projectName }}</code
>, and annotated with
<code>app.gitlab.com/app=$CI_PROJECT_PATH_SLUG</code>
and <code>app.gitlab.com/env=$CI_ENVIRONMENT_SLUG</code>.
</span>
</section>
</div>
</template>
</div>
</template>
......@@ -20,5 +20,10 @@ export default {
type: String,
required: true,
},
deployBoardsHelpPath: {
type: String,
required: false,
default: '',
},
},
};
......@@ -24,6 +24,7 @@ export const setDeployBoard = (oldEnvironmentState, environment) => {
environment.rollout_status.status === 'found' ? environment.rollout_status : {},
isLoadingDeployBoard: environment.rollout_status.status === 'loading',
isEmptyDeployBoard: environment.rollout_status.status === 'not_found',
hasLegacyAppLabel: environment.rollout_status.has_legacy_app_label,
});
}
return parsedEnvironment;
......
......@@ -2,15 +2,20 @@
* Deploy boards
*/
.deploy-board {
padding: 10px;
background-color: $gray-light;
min-height: 20px;
> div {
> .loading-container,
> .deploy-board-empty,
> .deploy-board-information {
padding: 10px;
}
> .deploy-board-information {
display: flex;
justify-content: space-between;
.deploy-board-information {
.deploy-board-status {
order: 1;
display: flex;
width: 70px;
......
......@@ -13,7 +13,9 @@ module EE
deployments = filter_by_project_environment(data[:deployments], project.full_path_slug, environment.slug)
pods = filter_by_project_environment(data[:pods], project.full_path_slug, environment.slug) if data[:pods]&.any?
::Gitlab::Kubernetes::RolloutStatus.from_deployments(*deployments, pods: pods)
legacy_deployments = filter_by_legacy_label(data[:deployments], project.full_path_slug, environment.slug)
::Gitlab::Kubernetes::RolloutStatus.from_deployments(*deployments, pods: pods, legacy_deployments: legacy_deployments)
end
result || ::Gitlab::Kubernetes::RolloutStatus.loading
end
......
......@@ -4,6 +4,7 @@ class RolloutStatusEntity < Grape::Entity
include RequestAwareEntity
expose :status, as: :status
expose :has_legacy_app_label?, as: :has_legacy_app_label
expose :instances, if: -> (rollout_status, _) { rollout_status.found? }
expose :completion, if: -> (rollout_status, _) { rollout_status.found? }
......
---
title: Show warning for deploy boards if legacy app label is used
merge_request: 14103
author:
type: other
......@@ -30,26 +30,31 @@ module Gitlab
@status == :not_found
end
def has_legacy_app_label?
legacy_deployments.present?
end
def found?
@status == :found
end
def self.from_deployments(*deployments, pods: {})
return new([], status: :not_found) if deployments.empty?
def self.from_deployments(*deployments, pods: {}, legacy_deployments: [])
return new([], status: :not_found, legacy_deployments: legacy_deployments) if deployments.empty?
deployments = deployments.map { |deploy| ::Gitlab::Kubernetes::Deployment.new(deploy, pods: pods) }
deployments.sort_by!(&:order)
new(deployments)
new(deployments, legacy_deployments: legacy_deployments)
end
def self.loading
new([], status: :loading)
end
def initialize(deployments, status: :found)
def initialize(deployments, status: :found, legacy_deployments: [])
@status = status
@deployments = deployments
@instances = deployments.flat_map(&:instances)
@legacy_deployments = legacy_deployments
@completion =
if @instances.empty?
......@@ -60,6 +65,10 @@ module Gitlab
(finished / @instances.count.to_f * 100).to_i
end
end
private
attr_reader :legacy_deployments
end
end
end
......@@ -14,6 +14,9 @@
"is_completed": {
"type": "boolean"
},
"has_legacy_app_label": {
"type": "boolean"
},
"instances": {
"type": "array",
"items": {
......
......@@ -99,4 +99,29 @@ describe('Deploy Board', () => {
expect(component.$el.querySelector('.fa-spin')).toBeDefined();
});
});
describe('with hasLegacyAppLabel equal true', () => {
let component;
beforeEach(() => {
component = new DeployBoardComponent({
propsData: {
isLoading: false,
isEmpty: false,
logsPath: environment.log_path,
hasLegacyAppLabel: true,
deployBoardData: {},
},
}).$mount();
});
it('should render legacy label warning message', () => {
const warningMessage = component.$el.querySelector('.bs-callout-warning');
expect(warningMessage).toBeTruthy();
expect(warningMessage.innerText).toContain(
'Matching on the app label has been removed for deploy boards.',
);
});
});
});
......@@ -60,6 +60,26 @@ describe('Store', () => {
expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData);
});
it('should set hasLegacyAppLabel property', () => {
expect(store.state.environments[0].deployBoardData).toEqual(deployBoardMockData);
const environment = {
name: 'foo',
size: 1,
latest: {
id: 1,
},
rollout_status: {
...deployBoardMockData,
status: 'not_found',
has_legacy_app_label: true,
},
};
store.storeEnvironments([environment]);
expect(store.state.environments[0].hasLegacyAppLabel).toBe(true);
});
});
describe('canaryCallout', () => {
......
......@@ -5,7 +5,7 @@ describe Gitlab::Kubernetes::RolloutStatus do
let(:track) { nil }
let(:specs) { specs_all_finished }
let(:specs_none) { [] }
let(:legacy_deployments) { [] }
let(:pods) do
create_pods(name: "one", count: 3, track: 'stable') + create_pods(name: "two", count: 3, track: "canary")
......@@ -25,7 +25,26 @@ describe Gitlab::Kubernetes::RolloutStatus do
]
end
subject(:rollout_status) { described_class.from_deployments(*specs, pods: pods) }
subject(:rollout_status) { described_class.from_deployments(*specs, pods: pods, legacy_deployments: legacy_deployments) }
describe '#has_legacy_app_label?' do
let(:specs) { [] }
let(:pods) { [] }
context 'no legacy deployments' do
it { is_expected.not_to be_has_legacy_app_label }
end
context 'with legacy deployment' do
let(:legacy_deployments) do
[
kube_deployment(name: 'legacy')
]
end
it { is_expected.to be_has_legacy_app_label }
end
end
describe '#deployments' do
it 'stores the deployments' do
......@@ -125,7 +144,7 @@ describe Gitlab::Kubernetes::RolloutStatus do
end
context 'when list of specs is empty' do
let(:specs) { specs_none }
let(:specs) { [] }
it { is_expected.to be_not_found }
end
......@@ -137,7 +156,7 @@ describe Gitlab::Kubernetes::RolloutStatus do
end
context 'when list of specs is empty' do
let(:specs) { specs_none }
let(:specs) { [] }
it { is_expected.not_to be_found }
end
......
......@@ -43,6 +43,10 @@ describe KubernetesService, models: true, use_clean_rails_memory_store_caching:
expect(rollout_status.deployments).to eq([])
end
it 'has the has_legacy_app_label flag' do
expect(rollout_status).to be_has_legacy_app_label
end
end
context 'new deployment based on annotations' do
......@@ -62,6 +66,40 @@ describe KubernetesService, models: true, use_clean_rails_memory_store_caching:
expect(rollout_status.deployments.map(&:name)).to contain_exactly('matched-deployment')
end
it 'does have the has_legacy_app_label flag' do
expect(rollout_status).to be_has_legacy_app_label
end
end
context 'deployment with app label not matching the environment' do
let(:other_deployment) do
kube_deployment(name: 'other-deployment').tap do |deployment|
deployment['metadata']['annotations'].delete('app.gitlab.com/env')
deployment['metadata']['annotations'].delete('app.gitlab.com/app')
deployment['metadata']['labels']['app'] = 'helm-app-label'
end
end
let(:other_pod) do
kube_pod(name: 'other-pod').tap do |pod|
pod['metadata']['annotations'].delete('app.gitlab.com/env')
pod['metadata']['annotations'].delete('app.gitlab.com/app')
pod['metadata']['labels']['app'] = environment.slug
end
end
before do
stub_reactive_cache(
service,
deployments: [other_deployment],
pods: [other_pod]
)
end
it 'does not have the has_legacy_app_label flag' do
expect(rollout_status).not_to be_has_legacy_app_label
end
end
end
......
......@@ -3,19 +3,23 @@ require 'spec_helper'
describe RolloutStatusEntity do
include KubernetesHelpers
let(:rollout_status) { kube_deployment_rollout_status }
let(:entity) do
described_class.new(rollout_status, request: double)
end
subject { entity.as_json }
context 'when kube deployment is valid' do
let(:rollout_status) { kube_deployment_rollout_status }
it "exposes status" do
is_expected.to include(:status)
end
it "exposes status" do
is_expected.to include(:status)
end
it 'exposes has_legacy_app_label' do
is_expected.to include(:has_legacy_app_label)
end
context 'when kube deployment is valid' do
it "exposes deployment data" do
is_expected.to include(:instances, :completion, :is_completed)
end
......
......@@ -4164,6 +4164,9 @@ msgstr ""
msgid "Deploy key was successfully updated."
msgstr ""
msgid "DeployBoard|Matching on the %{appLabel} label has been removed for deploy boards. To see all instances on your board, you must update your chart and redeploy."
msgstr ""
msgid "DeployKeys|+%{count} others"
msgstr ""
......@@ -8539,6 +8542,9 @@ msgstr ""
msgid "More"
msgstr ""
msgid "More Information"
msgstr ""
msgid "More actions"
msgstr ""
......
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