Commit 508829cd authored by Brandon Labuschagne's avatar Brandon Labuschagne

Merge branch 'afontaine/new-deploy-boards' into 'master'

Integrate deploy boards into new environments page

See merge request gitlab-org/gitlab!79759
parents 70b4da99 7214a994
......@@ -17,6 +17,11 @@ export default {
required: true,
type: Object,
},
graphql: {
required: false,
type: Boolean,
default: false,
},
},
ingressOptions: Array(100 / 5 + 1)
.fill(0)
......@@ -47,11 +52,17 @@ export default {
canaryWeightId() {
return uniqueId('canary-weight-');
},
weight() {
if (this.graphql) {
return this.canaryIngress.canaryWeight;
}
return this.canaryIngress.canary_weight;
},
stableWeight() {
return (100 - this.canaryIngress.canary_weight).toString();
return (100 - this.weight).toString();
},
canaryWeight() {
return this.canaryIngress.canary_weight.toString();
return this.weight.toString();
},
},
methods: {
......
......@@ -71,7 +71,7 @@ export default {
mutation: updateCanaryIngress,
variables: {
input: {
id: this.environment.global_id,
id: this.environment.global_id || this.environment.globalId,
weight: this.weight,
},
},
......
<script>
/* eslint-disable @gitlab/vue-require-i18n-strings */
/**
* Renders a deploy board.
*
......@@ -17,11 +16,11 @@ import {
GlTooltip,
GlTooltipDirective,
GlSafeHtmlDirective as SafeHtml,
GlSprintf,
} from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { n__ } from '~/locale';
import { s__, n__ } from '~/locale';
import instanceComponent from '~/vue_shared/components/deployment_instance.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { STATUS_MAP, CANARY_STATUS } from '../constants';
import CanaryIngress from './canary_ingress.vue';
......@@ -32,13 +31,13 @@ export default {
GlIcon,
GlLoadingIcon,
GlLink,
GlSprintf,
GlTooltip,
},
directives: {
GlTooltip: GlTooltipDirective,
SafeHtml,
},
mixins: [glFeatureFlagsMixin()],
props: {
deployBoardData: {
type: Object,
......@@ -57,6 +56,11 @@ export default {
required: false,
default: '',
},
graphql: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
canRenderDeployBoard() {
......@@ -65,8 +69,15 @@ export default {
canRenderEmptyState() {
return this.isEmpty;
},
canaryIngress() {
if (this.graphql) {
return this.deployBoardData.canaryIngress;
}
return this.deployBoardData.canary_ingress;
},
canRenderCanaryWeight() {
return !isEmpty(this.deployBoardData.canary_ingress);
return !isEmpty(this.canaryIngress);
},
instanceCount() {
const { instances } = this.deployBoardData;
......@@ -90,8 +101,20 @@ export default {
deployBoardSvg() {
return deployBoardSvg;
},
rollbackUrl() {
if (this.graphql) {
return this.deployBoardData.rollbackUrl;
}
return this.deployBoardData.rollback_url;
},
abortUrl() {
if (this.graphql) {
return this.deployBoardData.abortUrl;
}
return this.deployBoardData.abort_url;
},
deployBoardActions() {
return this.deployBoardData.rollback_url || this.deployBoardData.abort_url;
return this.rollbackUrl || this.abortUrl;
},
statuses() {
// Canary is not a pod status but it needs to be in the legend.
......@@ -106,7 +129,17 @@ export default {
changeCanaryWeight(weight) {
this.$emit('changeCanaryWeight', weight);
},
podName(instance) {
if (this.graphql) {
return instance.podName;
}
return instance.pod_name;
},
},
emptyStateText: s__(
'DeployBoards|To see deployment progress for your environments, make sure you are deploying to %{codeStart}$KUBE_NAMESPACE%{codeEnd} and annotating with %{codeStart}app.gitlab.com/app=$CI_PROJECT_PATH_SLUG%{codeEnd} and %{codeStart}app.gitlab.com/env=$CI_ENVIRONMENT_SLUG%{codeEnd}.',
),
};
</script>
<template>
......@@ -152,7 +185,7 @@ export default {
:key="i"
:status="instance.status"
:tooltip-text="instance.tooltip"
:pod-name="instance.pod_name"
:pod-name="podName(instance)"
:logs-path="logsPath"
:stable="instance.stable"
/>
......@@ -163,22 +196,23 @@ export default {
<canary-ingress
v-if="canRenderCanaryWeight"
class="deploy-board-canary-ingress"
:canary-ingress="deployBoardData.canary_ingress"
:canary-ingress="canaryIngress"
:graphql="graphql"
@change="changeCanaryWeight"
/>
<section v-if="deployBoardActions" class="deploy-board-actions">
<gl-link
v-if="deployBoardData.rollback_url"
:href="deployBoardData.rollback_url"
v-if="rollbackUrl"
:href="rollbackUrl"
class="btn"
data-method="post"
rel="nofollow"
>{{ __('Rollback') }}</gl-link
>
<gl-link
v-if="deployBoardData.abort_url"
:href="deployBoardData.abort_url"
v-if="abortUrl"
:href="abortUrl"
class="btn btn-danger btn-inverted"
data-method="post"
rel="nofollow"
......@@ -196,11 +230,11 @@ export default {
__('Kubernetes deployment not found')
}}</span>
<span>
To see deployment progress for your environments, make sure you are deploying to
<code>$KUBE_NAMESPACE</code> and annotating with
<code>app.gitlab.com/app=$CI_PROJECT_PATH_SLUG</code>
and
<code>app.gitlab.com/env=$CI_ENVIRONMENT_SLUG</code>.
<gl-sprintf :message="$options.emptyStateText">
<template #code="{ content }">
<code>{{ content }}</code>
</template>
</gl-sprintf>
</span>
</section>
</div>
......
<script>
import { GlCollapse, GlButton } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import setEnvironmentToChangeCanaryMutation from '../graphql/mutations/set_environment_to_change_canary.mutation.graphql';
import DeployBoard from './deploy_board.vue';
export default {
components: {
DeployBoard,
GlButton,
GlCollapse,
},
props: {
rolloutStatus: {
required: true,
type: Object,
},
environment: {
required: true,
type: Object,
},
},
data() {
return { visible: false };
},
computed: {
icon() {
return this.visible ? 'angle-down' : 'angle-right';
},
label() {
return this.visible ? this.$options.i18n.collapse : this.$options.i18n.expand;
},
isLoading() {
return this.rolloutStatus.status === 'loading';
},
isEmpty() {
return this.rolloutStatus.status === 'not_found';
},
},
methods: {
toggleCollapse() {
this.visible = !this.visible;
},
changeCanaryWeight(weight) {
this.$apollo.mutate({
mutation: setEnvironmentToChangeCanaryMutation,
variables: {
environment: this.environment,
weight,
},
});
},
},
i18n: {
collapse: __('Collapse'),
expand: __('Expand'),
pods: s__('DeployBoard|Kubernetes Pods'),
},
};
</script>
<template>
<div>
<div>
<gl-button
class="gl-mr-4 gl-min-w-fit-content"
:icon="icon"
:aria-label="label"
size="small"
category="tertiary"
@click="toggleCollapse"
/>
<span>{{ $options.i18n.pods }}</span>
</div>
<gl-collapse :visible="visible">
<deploy-board
:deploy-board-data="rolloutStatus"
:is-loading="isLoading"
:is-empty="isEmpty"
:environment="environment"
graphql
class="gl-reset-bg!"
@changeCanaryWeight="changeCanaryWeight"
/>
</gl-collapse>
</div>
</template>
......@@ -20,6 +20,7 @@ import Monitoring from './environment_monitoring.vue';
import Terminal from './environment_terminal_button.vue';
import Delete from './environment_delete.vue';
import Deployment from './deployment.vue';
import DeployBoardWrapper from './deploy_board_wrapper.vue';
export default {
components: {
......@@ -30,6 +31,7 @@ export default {
GlSprintf,
Actions,
Deployment,
DeployBoardWrapper,
ExternalUrl,
StopComponent,
Rollback,
......@@ -145,6 +147,9 @@ export default {
displayName() {
return truncate(this.name, 80);
},
rolloutStatus() {
return this.environment?.rolloutStatus;
},
},
methods: {
toggleCollapse() {
......@@ -159,6 +164,14 @@ export default {
'gl-md-pl-7',
'gl-bg-gray-10',
],
deployBoardClasses: [
'gl-border-gray-100',
'gl-border-t-solid',
'gl-border-1',
'gl-py-4',
'gl-md-pl-7',
'gl-bg-gray-10',
],
};
</script>
<template>
......@@ -298,6 +311,14 @@ export default {
</template>
</gl-sprintf>
</div>
<div v-if="rolloutStatus" :class="$options.deployBoardClasses">
<deploy-board-wrapper
:rollout-status="rolloutStatus"
:environment="environment"
:class="{ 'gl-ml-7': inFolder }"
class="gl-pl-4"
/>
</div>
</gl-collapse>
</div>
</template>
......@@ -8,16 +8,19 @@ import pageInfoQuery from '../graphql/queries/page_info.query.graphql';
import environmentToDeleteQuery from '../graphql/queries/environment_to_delete.query.graphql';
import environmentToRollbackQuery from '../graphql/queries/environment_to_rollback.query.graphql';
import environmentToStopQuery from '../graphql/queries/environment_to_stop.query.graphql';
import environmentToChangeCanaryQuery from '../graphql/queries/environment_to_change_canary.query.graphql';
import EnvironmentFolder from './new_environment_folder.vue';
import EnableReviewAppModal from './enable_review_app_modal.vue';
import StopEnvironmentModal from './stop_environment_modal.vue';
import EnvironmentItem from './new_environment_item.vue';
import ConfirmRollbackModal from './confirm_rollback_modal.vue';
import DeleteEnvironmentModal from './delete_environment_modal.vue';
import CanaryUpdateModal from './canary_update_modal.vue';
export default {
components: {
DeleteEnvironmentModal,
CanaryUpdateModal,
ConfirmRollbackModal,
EnvironmentFolder,
EnableReviewAppModal,
......@@ -56,6 +59,12 @@ export default {
environmentToStop: {
query: environmentToStopQuery,
},
environmentToChangeCanary: {
query: environmentToChangeCanaryQuery,
},
weight: {
query: environmentToChangeCanaryQuery,
},
},
inject: ['newEnvironmentPath', 'canCreateEnvironment'],
i18n: {
......@@ -80,6 +89,8 @@ export default {
environmentToDelete: {},
environmentToRollback: {},
environmentToStop: {},
environmentToChangeCanary: {},
weight: 0,
};
},
computed: {
......@@ -186,6 +197,7 @@ export default {
<delete-environment-modal :environment="environmentToDelete" graphql />
<stop-environment-modal :environment="environmentToStop" graphql />
<confirm-rollback-modal :environment="environmentToRollback" graphql />
<canary-update-modal :environment="environmentToChangeCanary" :weight="weight" />
<gl-tabs
:action-secondary="addEnvironment"
:action-primary="openReviewAppModal"
......
mutation SetEnvironmentToChangeCanary($environment: LocalEnvironmentInput, $weight: Int!) {
setEnvironmentToChangeCanary(environment: $environment, weight: $weight) @client
}
query environmentToChangeCanary {
environmentToChangeCanary @client
weight @client
}
......@@ -10,6 +10,7 @@ import pollIntervalQuery from './queries/poll_interval.query.graphql';
import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql';
import environmentToStopQuery from './queries/environment_to_stop.query.graphql';
import environmentToDeleteQuery from './queries/environment_to_delete.query.graphql';
import environmentToChangeCanaryQuery from './queries/environment_to_change_canary.query.graphql';
import pageInfoQuery from './queries/page_info.query.graphql';
const buildErrors = (errors = []) => ({
......@@ -134,6 +135,12 @@ export const resolvers = (endpoint) => ({
data: { environmentToRollback: environment },
});
},
setEnvironmentToChangeCanary(_, { environment, weight }, { client }) {
client.writeQuery({
query: environmentToChangeCanaryQuery,
data: { environmentToChangeCanary: environment, weight },
});
},
cancelAutoStop(_, { autoStopUrl }) {
return axios
.post(autoStopUrl)
......
......@@ -81,5 +81,6 @@ extend type Mutation {
setEnvironmentToDelete(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToStop(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToChangeCanary(environment: LocalEnvironmentInput, weight: Int): LocalErrors
action(environment: LocalEnvironmentInput): LocalErrors
}
- page_title _("Environments")
- add_page_specific_style 'page_bundles/environments'
- if Feature.enabled?(:new_environments_table)
#environments-table{ data: { endpoint: project_environments_path(@project, format: :json),
......@@ -9,7 +10,6 @@
"project-path" => @project.full_path,
"default-branch-name" => @project.default_branch_or_main } }
- else
- add_page_specific_style 'page_bundles/environments'
#environments-list-view{ data: { environments_data: environments_list_data,
"can-read-environment" => can?(current_user, :read_environment, @project).to_s,
"can-create-environment" => can?(current_user, :create_environment, @project).to_s,
......
......@@ -11948,6 +11948,12 @@ msgstr ""
msgid "Deploy to..."
msgstr ""
msgid "DeployBoards|To see deployment progress for your environments, make sure you are deploying to %{codeStart}$KUBE_NAMESPACE%{codeEnd} and annotating with %{codeStart}app.gitlab.com/app=$CI_PROJECT_PATH_SLUG%{codeEnd} and %{codeStart}app.gitlab.com/env=$CI_ENVIRONMENT_SLUG%{codeEnd}."
msgstr ""
msgid "DeployBoard|Kubernetes Pods"
msgstr ""
msgid "DeployFreeze|Add a freeze period to prevent unintended releases during a period of time for a given environment. You must update the deployment jobs in %{filename} according to the deploy freezes added here. %{freeze_period_link_start}Learn more.%{freeze_period_link_end}"
msgstr ""
......
......@@ -3,6 +3,7 @@ import { mount } from '@vue/test-utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import CanaryIngress from '~/environments/components/canary_ingress.vue';
import { CANARY_UPDATE_MODAL } from '~/environments/constants';
import { rolloutStatus } from './graphql/mock_data';
describe('/environments/components/canary_ingress.vue', () => {
let wrapper;
......@@ -13,16 +14,18 @@ describe('/environments/components/canary_ingress.vue', () => {
.at(x / 5)
.vm.$emit('click');
const createComponent = () => {
const createComponent = (props = {}, options = {}) => {
wrapper = mount(CanaryIngress, {
propsData: {
canaryIngress: {
canary_weight: 60,
},
...props,
},
directives: {
GlModal: createMockDirective(),
},
...options,
});
};
......@@ -94,9 +97,25 @@ describe('/environments/components/canary_ingress.vue', () => {
});
it('is set to open the change modal', () => {
const options = canaryWeightDropdown.findAll(GlDropdownItem);
expect(options).toHaveLength(21);
options.wrappers.forEach((w, i) => expect(w.text()).toBe((i * 5).toString()));
canaryWeightDropdown
.findAll(GlDropdownItem)
.wrappers.forEach((w) =>
expect(getBinding(w.element, 'gl-modal')).toMatchObject({ value: CANARY_UPDATE_MODAL }),
);
});
});
describe('graphql', () => {
beforeEach(() => {
createComponent({
graphql: true,
canaryIngress: rolloutStatus.canaryIngress,
});
});
it('shows the correct weight', () => {
const canaryWeightDropdown = wrapper.find('[data-testid="canary-weight"]');
expect(canaryWeightDropdown.props('text')).toBe('50');
});
});
});
......@@ -4,6 +4,7 @@ import Vue, { nextTick } from 'vue';
import CanaryIngress from '~/environments/components/canary_ingress.vue';
import DeployBoard from '~/environments/components/deploy_board.vue';
import { deployBoardMockData, environment } from './mock_data';
import { rolloutStatus } from './graphql/mock_data';
const logsPath = `gitlab-org/gitlab-test/-/logs?environment_name=${environment.name}`;
......@@ -28,7 +29,7 @@ describe('Deploy Board', () => {
});
it('should render percentage with completion value provided', () => {
expect(wrapper.vm.$refs.percentage.innerText).toEqual(`${deployBoardMockData.completion}%`);
expect(wrapper.find({ ref: 'percentage' }).text()).toBe(`${deployBoardMockData.completion}%`);
});
it('should render total instance count', () => {
......@@ -57,20 +58,74 @@ describe('Deploy Board', () => {
it('sets up a tooltip for the legend', () => {
const iconSpan = wrapper.find('[data-testid="legend-tooltip-target"]');
const tooltip = wrapper.find(GlTooltip);
const icon = iconSpan.find(GlIcon);
const tooltip = wrapper.findComponent(GlTooltip);
const icon = iconSpan.findComponent(GlIcon);
expect(tooltip.props('target')()).toBe(iconSpan.element);
expect(icon.props('name')).toBe('question');
});
it('renders the canary weight selector', () => {
const canary = wrapper.find(CanaryIngress);
const canary = wrapper.findComponent(CanaryIngress);
expect(canary.exists()).toBe(true);
expect(canary.props('canaryIngress')).toEqual({ canary_weight: 50 });
});
});
describe('with new valid data', () => {
beforeEach(async () => {
wrapper = createComponent({
graphql: true,
deployBoardData: rolloutStatus,
});
await nextTick();
});
it('should render percentage with completion value provided', () => {
expect(wrapper.find({ ref: 'percentage' }).text()).toBe(`${rolloutStatus.completion}%`);
});
it('should render total instance count', () => {
const renderedTotal = wrapper.find('.deploy-board-instances-text');
const actualTotal = rolloutStatus.instances.length;
const output = `${actualTotal > 1 ? 'Instances' : 'Instance'} (${actualTotal})`;
expect(renderedTotal.text()).toEqual(output);
});
it('should render all instances', () => {
const instances = wrapper.findAll('.deploy-board-instances-container a');
expect(instances).toHaveLength(rolloutStatus.instances.length);
expect(
instances.at(1).classes(`deployment-instance-${rolloutStatus.instances[2].status}`),
).toBe(true);
});
it('should render an abort and a rollback button with the provided url', () => {
const buttons = wrapper.findAll('.deploy-board-actions a');
expect(buttons.at(0).attributes('href')).toEqual(rolloutStatus.rollbackUrl);
expect(buttons.at(1).attributes('href')).toEqual(rolloutStatus.abortUrl);
});
it('sets up a tooltip for the legend', () => {
const iconSpan = wrapper.find('[data-testid="legend-tooltip-target"]');
const tooltip = wrapper.findComponent(GlTooltip);
const icon = iconSpan.findComponent(GlIcon);
expect(tooltip.props('target')()).toBe(iconSpan.element);
expect(icon.props('name')).toBe('question');
});
it('renders the canary weight selector', () => {
const canary = wrapper.findComponent(CanaryIngress);
expect(canary.exists()).toBe(true);
expect(canary.props('canaryIngress')).toEqual({ canaryWeight: 50 });
expect(canary.props('graphql')).toBe(true);
});
});
describe('with empty state', () => {
beforeEach((done) => {
wrapper = createComponent({
......@@ -102,7 +157,7 @@ describe('Deploy Board', () => {
});
it('should render loading spinner', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlCollapse, GlIcon } from '@gitlab/ui';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { stubTransition } from 'helpers/stub_transition';
import createMockApollo from 'helpers/mock_apollo_helper';
import { __, s__ } from '~/locale';
import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue';
import DeployBoard from '~/environments/components/deploy_board.vue';
import setEnvironmentToChangeCanaryMutation from '~/environments/graphql/mutations/set_environment_to_change_canary.mutation.graphql';
import { resolvedEnvironment, rolloutStatus } from './graphql/mock_data';
Vue.use(VueApollo);
describe('~/environments/components/deploy_board_wrapper.vue', () => {
let wrapper;
let mockApollo;
const findDeployBoard = () => wrapper.findComponent(DeployBoard);
const createWrapper = ({ propsData = {} } = {}) => {
mockApollo = createMockApollo();
return mountExtended(DeployBoardWrapper, {
propsData: { environment: resolvedEnvironment, rolloutStatus, ...propsData },
provide: { helpPagePath: '/help' },
stubs: { transition: stubTransition() },
apolloProvider: mockApollo,
});
};
const expandCollapsedSection = async () => {
const button = wrapper.findByRole('button', { name: __('Expand') });
await button.trigger('click');
return button;
};
afterEach(() => {
wrapper?.destroy();
});
it('is labeled Kubernetes Pods', () => {
wrapper = createWrapper();
expect(wrapper.findByText(s__('DeployBoard|Kubernetes Pods')).exists()).toBe(true);
});
describe('collapse', () => {
let icon;
let collapse;
beforeEach(() => {
wrapper = createWrapper();
collapse = wrapper.findComponent(GlCollapse);
icon = wrapper.findComponent(GlIcon);
});
it('is collapsed by default', () => {
expect(collapse.attributes('visible')).toBeUndefined();
expect(icon.props('name')).toBe('angle-right');
});
it('opens on click', async () => {
const button = await expandCollapsedSection();
expect(button.attributes('aria-label')).toBe(__('Collapse'));
expect(collapse.attributes('visible')).toBe('visible');
expect(icon.props('name')).toBe('angle-down');
const deployBoard = findDeployBoard();
expect(deployBoard.exists()).toBe(true);
});
});
describe('deploy board', () => {
it('passes the rollout status on and sets graphql to true', async () => {
wrapper = createWrapper();
await expandCollapsedSection();
const deployBoard = findDeployBoard();
expect(deployBoard.props('deployBoardData')).toEqual(rolloutStatus);
expect(deployBoard.props('graphql')).toBe(true);
});
it('sets the update to the canary via graphql', () => {
wrapper = createWrapper();
jest.spyOn(mockApollo.defaultClient, 'mutate');
const deployBoard = findDeployBoard();
deployBoard.vm.$emit('changeCanaryWeight', 15);
expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({
mutation: setEnvironmentToChangeCanaryMutation,
variables: { environment: resolvedEnvironment, weight: 15 },
});
});
describe('is loading', () => {
it('should set the loading prop', async () => {
wrapper = createWrapper({
propsData: { rolloutStatus: { ...rolloutStatus, status: 'loading' } },
});
await expandCollapsedSection();
const deployBoard = findDeployBoard();
expect(deployBoard.props('isLoading')).toBe(true);
});
});
describe('is empty', () => {
it('should set the empty prop', async () => {
wrapper = createWrapper({
propsData: { rolloutStatus: { ...rolloutStatus, status: 'not_found' } },
});
await expandCollapsedSection();
const deployBoard = findDeployBoard();
expect(deployBoard.props('isEmpty')).toBe(true);
});
});
});
});
export const rolloutStatus = {
instances: [
{
status: 'succeeded',
tooltip: 'tanuki-2334 Finished',
podName: 'production-tanuki-1',
stable: false,
},
{
status: 'succeeded',
tooltip: 'tanuki-2335 Finished',
podName: 'production-tanuki-1',
stable: false,
},
{
status: 'succeeded',
tooltip: 'tanuki-2336 Finished',
podName: 'production-tanuki-1',
stable: false,
},
{
status: 'succeeded',
tooltip: 'tanuki-2337 Finished',
podName: 'production-tanuki-1',
stable: false,
},
{
status: 'succeeded',
tooltip: 'tanuki-2338 Finished',
podName: 'production-tanuki-1',
stable: false,
},
{
status: 'succeeded',
tooltip: 'tanuki-2339 Finished',
podName: 'production-tanuki-1',
stable: false,
},
{ status: 'succeeded', tooltip: 'tanuki-2340 Finished', podName: 'production-tanuki-1' },
{ status: 'succeeded', tooltip: 'tanuki-2334 Finished', podName: 'production-tanuki-1' },
{ status: 'succeeded', tooltip: 'tanuki-2335 Finished', podName: 'production-tanuki-1' },
{ status: 'succeeded', tooltip: 'tanuki-2336 Finished', podName: 'production-tanuki-1' },
{ status: 'succeeded', tooltip: 'tanuki-2337 Finished', podName: 'production-tanuki-1' },
{ status: 'succeeded', tooltip: 'tanuki-2338 Finished', podName: 'production-tanuki-1' },
{ status: 'succeeded', tooltip: 'tanuki-2339 Finished', podName: 'production-tanuki-1' },
{ status: 'succeeded', tooltip: 'tanuki-2340 Finished', podName: 'production-tanuki-1' },
{ status: 'running', tooltip: 'tanuki-2341 Deploying', podName: 'production-tanuki-1' },
{ status: 'running', tooltip: 'tanuki-2342 Deploying', podName: 'production-tanuki-1' },
{ status: 'running', tooltip: 'tanuki-2343 Deploying', podName: 'production-tanuki-1' },
{ status: 'failed', tooltip: 'tanuki-2344 Failed', podName: 'production-tanuki-1' },
{ status: 'unknown', tooltip: 'tanuki-2345 Ready', podName: 'production-tanuki-1' },
{ status: 'unknown', tooltip: 'tanuki-2346 Ready', podName: 'production-tanuki-1' },
{ status: 'pending', tooltip: 'tanuki-2348 Preparing', podName: 'production-tanuki-1' },
{ status: 'pending', tooltip: 'tanuki-2349 Preparing', podName: 'production-tanuki-1' },
{ status: 'pending', tooltip: 'tanuki-2350 Preparing', podName: 'production-tanuki-1' },
{ status: 'pending', tooltip: 'tanuki-2353 Preparing', podName: 'production-tanuki-1' },
{ status: 'pending', tooltip: 'tanuki-2354 waiting', podName: 'production-tanuki-1' },
{ status: 'pending', tooltip: 'tanuki-2355 waiting', podName: 'production-tanuki-1' },
{ status: 'pending', tooltip: 'tanuki-2356 waiting', podName: 'production-tanuki-1' },
],
abortUrl: 'url',
rollbackUrl: 'url',
completion: 100,
status: 'found',
canaryIngress: { canaryWeight: 50 },
};
export const environmentsApp = {
environments: [
{
......
......@@ -8,7 +8,8 @@ import { formatDate, getTimeago } from '~/lib/utils/datetime_utility';
import { __, s__, sprintf } from '~/locale';
import EnvironmentItem from '~/environments/components/new_environment_item.vue';
import Deployment from '~/environments/components/deployment.vue';
import { resolvedEnvironment } from './graphql/mock_data';
import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue';
import { resolvedEnvironment, rolloutStatus } from './graphql/mock_data';
Vue.use(VueApollo);
......@@ -455,4 +456,35 @@ describe('~/environments/components/new_environment_item.vue', () => {
expect(emptyState.exists()).toBe(false);
});
});
describe('deploy boards', () => {
it('should show a deploy board if the environment has a rollout status', async () => {
const environment = {
...resolvedEnvironment,
rolloutStatus,
};
wrapper = createWrapper({
propsData: { environment },
apolloProvider: createApolloProvider(),
});
await expandCollapsedSection();
const deployBoard = wrapper.findComponent(DeployBoardWrapper);
expect(deployBoard.exists()).toBe(true);
expect(deployBoard.props('rolloutStatus')).toBe(rolloutStatus);
});
it('should not show a deploy board if the environment has no rollout status', async () => {
wrapper = createWrapper({
apolloProvider: createApolloProvider(),
});
await expandCollapsedSection();
const deployBoard = wrapper.findComponent(DeployBoardWrapper);
expect(deployBoard.exists()).toBe(false);
});
});
});
......@@ -10,6 +10,7 @@ import EnvironmentsApp from '~/environments/components/new_environments_app.vue'
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import EnvironmentsItem from '~/environments/components/new_environment_item.vue';
import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue';
import CanaryUpdateModal from '~/environments/components/canary_update_modal.vue';
import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data';
Vue.use(VueApollo);
......@@ -20,6 +21,8 @@ describe('~/environments/components/new_environments_app.vue', () => {
let environmentFolderMock;
let paginationMock;
let environmentToStopMock;
let environmentToChangeCanaryMock;
let weightMock;
const createApolloProvider = () => {
const mockResolvers = {
......@@ -30,6 +33,8 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentToStop: environmentToStopMock,
environmentToDelete: jest.fn().mockResolvedValue(resolvedEnvironment),
environmentToRollback: jest.fn().mockResolvedValue(resolvedEnvironment),
environmentToChangeCanary: environmentToChangeCanaryMock,
weight: weightMock,
},
};
......@@ -53,6 +58,8 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentsApp,
folder,
environmentToStop = {},
environmentToChangeCanary = {},
weight = 0,
pageInfo = {
total: 20,
perPage: 5,
......@@ -67,6 +74,8 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentFolderMock.mockReturnValue(folder);
paginationMock.mockReturnValue(pageInfo);
environmentToStopMock.mockReturnValue(environmentToStop);
environmentToChangeCanaryMock.mockReturnValue(environmentToChangeCanary);
weightMock.mockReturnValue(weight);
const apolloProvider = createApolloProvider();
wrapper = createWrapper({ apolloProvider, provide });
......@@ -78,6 +87,8 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentAppMock = jest.fn();
environmentFolderMock = jest.fn();
environmentToStopMock = jest.fn();
environmentToChangeCanaryMock = jest.fn();
weightMock = jest.fn();
paginationMock = jest.fn();
});
......@@ -207,6 +218,19 @@ describe('~/environments/components/new_environments_app.vue', () => {
expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
});
it('should pass the environment to change canary to the canary update modal', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
environmentToChangeCanary: resolvedEnvironment,
weight: 10,
});
const modal = wrapper.findComponent(CanaryUpdateModal);
expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
});
});
describe('pagination', () => {
......
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