Commit bacc60c6 authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'afontaine/enacpsulate-deployments-list-mr-widget' into 'master'

Encapsulate Deployment List in Own Component

See merge request gitlab-org/gitlab!55793
parents 2d02e88b 7b74c999
<script>
import { GlSprintf } from '@gitlab/ui';
import { n__ } from '~/locale';
import MrCollapsibleExtension from '../mr_collapsible_extension.vue';
export default {
components: {
Deployment: () => import('./deployment.vue'),
GlSprintf,
MrCollapsibleExtension,
},
props: {
deployments: {
type: Array,
required: true,
},
deploymentClass: {
type: String,
required: true,
},
hasDeploymentMetrics: {
type: Boolean,
required: true,
},
visualReviewAppMeta: {
type: Object,
required: false,
default: () => ({
sourceProjectId: '',
sourceProjectPath: '',
mergeRequestId: '',
appUrl: '',
}),
},
showVisualReviewAppLink: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
showCollapsedDeployments() {
return this.deployments.length > 3;
},
multipleDeploymentsTitle() {
return n__(
'Deployments|%{deployments} environment impacted.',
'Deployments|%{deployments} environments impacted.',
this.deployments.length,
);
},
},
};
</script>
<template>
<mr-collapsible-extension
v-if="showCollapsedDeployments"
:title="__('View all environments.')"
data-testid="mr-collapsed-deployments"
>
<template #header>
<div class="gl-mr-3 gl-line-height-normal">
<gl-sprintf :message="multipleDeploymentsTitle">
<template #deployments>
<span class="gl-font-weight-bold gl-mr-2">{{ deployments.length }}</span>
</template>
</gl-sprintf>
</div>
</template>
<deployment
v-for="deployment in deployments"
:key="deployment.id"
:class="deploymentClass"
class="gl-bg-gray-50"
:deployment="deployment"
:show-metrics="hasDeploymentMetrics"
:show-visual-review-app="showVisualReviewAppLink"
:visual-review-app-meta="visualReviewAppMeta"
/>
</mr-collapsible-extension>
<div v-else class="mr-widget-extension">
<deployment
v-for="deployment in deployments"
:key="deployment.id"
:class="deploymentClass"
:deployment="deployment"
:show-metrics="hasDeploymentMetrics"
:show-visual-review-app="showVisualReviewAppLink"
:visual-review-app-meta="visualReviewAppMeta"
/>
</div>
</template>
<script> <script>
import { GlSprintf } from '@gitlab/ui';
import { isNumber } from 'lodash'; import { isNumber } from 'lodash';
import { sanitize } from '~/lib/dompurify'; import { sanitize } from '~/lib/dompurify';
import { n__ } from '~/locale'; import { n__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import MergeRequestStore from '../stores/mr_widget_store'; import MergeRequestStore from '../stores/mr_widget_store';
import ArtifactsApp from './artifacts_list_app.vue'; import ArtifactsApp from './artifacts_list_app.vue';
import MrCollapsibleExtension from './mr_collapsible_extension.vue'; import DeploymentList from './deployment/deployment_list.vue';
import MrWidgetContainer from './mr_widget_container.vue'; import MrWidgetContainer from './mr_widget_container.vue';
import MrWidgetPipeline from './mr_widget_pipeline.vue'; import MrWidgetPipeline from './mr_widget_pipeline.vue';
...@@ -22,9 +21,7 @@ export default { ...@@ -22,9 +21,7 @@ export default {
name: 'MrWidgetPipelineContainer', name: 'MrWidgetPipelineContainer',
components: { components: {
ArtifactsApp, ArtifactsApp,
Deployment: () => import('./deployment/deployment.vue'), DeploymentList,
GlSprintf,
MrCollapsibleExtension,
MrWidgetContainer, MrWidgetContainer,
MrWidgetPipeline, MrWidgetPipeline,
MergeTrainPositionIndicator: () => MergeTrainPositionIndicator: () =>
...@@ -70,7 +67,9 @@ export default { ...@@ -70,7 +67,9 @@ export default {
return this.isPostMerge ? this.mr.mergePipeline : this.mr.pipeline; return this.isPostMerge ? this.mr.mergePipeline : this.mr.pipeline;
}, },
showVisualReviewAppLink() { showVisualReviewAppLink() {
return this.mr.visualReviewAppAvailable && this.glFeatures.anonymousVisualReviewFeedback; return Boolean(
this.mr.visualReviewAppAvailable && this.glFeatures.anonymousVisualReviewFeedback,
);
}, },
showMergeTrainPositionIndicator() { showMergeTrainPositionIndicator() {
return isNumber(this.mr.mergeTrainIndex); return isNumber(this.mr.mergeTrainIndex);
...@@ -116,44 +115,14 @@ export default { ...@@ -116,44 +115,14 @@ export default {
<div v-if="mr.exposedArtifactsPath" class="js-exposed-artifacts"> <div v-if="mr.exposedArtifactsPath" class="js-exposed-artifacts">
<artifacts-app :endpoint="mr.exposedArtifactsPath" /> <artifacts-app :endpoint="mr.exposedArtifactsPath" />
</div> </div>
<template v-if="deployments.length"> <deployment-list
<mr-collapsible-extension v-if="deployments.length"
v-if="showCollapsedDeployments" :deployments="deployments"
:title="__('View all environments.')" :deployment-class="deploymentClass"
data-testid="mr-collapsed-deployments" :has-deployment-metrics="hasDeploymentMetrics"
> :visual-review-app-meta="visualReviewAppMeta"
<template #header> :show-visual-review-app-link="showVisualReviewAppLink"
<div class="gl-mr-3 gl-line-height-normal"> />
<gl-sprintf :message="multipleDeploymentsTitle">
<template #deployments>
<span class="gl-font-weight-bold gl-mr-2">{{ deployments.length }}</span>
</template>
</gl-sprintf>
</div>
</template>
<deployment
v-for="deployment in deployments"
:key="deployment.id"
:class="deploymentClass"
class="gl-bg-gray-50"
:deployment="deployment"
:show-metrics="hasDeploymentMetrics"
:show-visual-review-app="showVisualReviewAppLink"
:visual-review-app-meta="visualReviewAppMeta"
/>
</mr-collapsible-extension>
<div v-else class="mr-widget-extension">
<deployment
v-for="deployment in deployments"
:key="deployment.id"
:class="deploymentClass"
:deployment="deployment"
:show-metrics="hasDeploymentMetrics"
:show-visual-review-app="showVisualReviewAppLink"
:visual-review-app-meta="visualReviewAppMeta"
/>
</div>
</template>
<merge-train-position-indicator <merge-train-position-indicator
v-if="showMergeTrainPositionIndicator" v-if="showMergeTrainPositionIndicator"
class="mr-widget-extension" class="mr-widget-extension"
......
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { trimText } from 'helpers/text_helper';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import ArtifactsApp from '~/vue_merge_request_widget/components/artifacts_list_app.vue'; import ArtifactsApp from '~/vue_merge_request_widget/components/artifacts_list_app.vue';
import Deployment from '~/vue_merge_request_widget/components/deployment/deployment.vue'; import DeploymentList from '~/vue_merge_request_widget/components/deployment/deployment_list.vue';
import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue'; import MrWidgetPipeline from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import MrWidgetPipelineContainer from '~/vue_merge_request_widget/components/mr_widget_pipeline_container.vue'; import MrWidgetPipelineContainer from '~/vue_merge_request_widget/components/mr_widget_pipeline_container.vue';
import { mockStore } from '../mock_data'; import { mockStore } from '../mock_data';
...@@ -30,6 +29,8 @@ describe('MrWidgetPipelineContainer', () => { ...@@ -30,6 +29,8 @@ describe('MrWidgetPipelineContainer', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findDeploymentList = () => wrapper.findComponent(DeploymentList);
describe('when pre merge', () => { describe('when pre merge', () => {
beforeEach(() => { beforeEach(() => {
factory(); factory();
...@@ -57,6 +58,9 @@ describe('MrWidgetPipelineContainer', () => { ...@@ -57,6 +58,9 @@ describe('MrWidgetPipelineContainer', () => {
const deployments = wrapper.findAll('.mr-widget-extension .js-pre-deployment'); const deployments = wrapper.findAll('.mr-widget-extension .js-pre-deployment');
expect(findDeploymentList().exists()).toBe(true);
expect(findDeploymentList().props('deployments')).toBe(mockStore.deployments);
expect(deployments.wrappers.map((x) => x.props())).toEqual(expectedProps); expect(deployments.wrappers.map((x) => x.props())).toEqual(expectedProps);
}); });
}); });
...@@ -102,6 +106,8 @@ describe('MrWidgetPipelineContainer', () => { ...@@ -102,6 +106,8 @@ describe('MrWidgetPipelineContainer', () => {
const deployments = wrapper.findAll('.mr-widget-extension .js-post-deployment'); const deployments = wrapper.findAll('.mr-widget-extension .js-post-deployment');
expect(findDeploymentList().exists()).toBe(true);
expect(findDeploymentList().props('deployments')).toBe(mockStore.postMergeDeployments);
expect(deployments.wrappers.map((x) => x.props())).toEqual(expectedProps); expect(deployments.wrappers.map((x) => x.props())).toEqual(expectedProps);
}); });
}); });
...@@ -113,50 +119,4 @@ describe('MrWidgetPipelineContainer', () => { ...@@ -113,50 +119,4 @@ describe('MrWidgetPipelineContainer', () => {
expect(wrapper.find(ArtifactsApp).isVisible()).toBe(true); expect(wrapper.find(ArtifactsApp).isVisible()).toBe(true);
}); });
}); });
describe('with many deployments', () => {
let deployments;
let collapsibleExtension;
beforeEach(() => {
deployments = [
...mockStore.deployments,
...mockStore.deployments.map((deployment) => ({
...deployment,
id: deployment.id + mockStore.deployments.length,
})),
];
factory({
mr: {
...mockStore,
deployments,
},
});
collapsibleExtension = wrapper.find('[data-testid="mr-collapsed-deployments"]');
});
it('renders them collapsed', () => {
expect(collapsibleExtension.exists()).toBe(true);
expect(trimText(collapsibleExtension.text())).toBe(
`${deployments.length} environments impacted. View all environments.`,
);
});
it('shows them when clicked', async () => {
const expectedProps = deployments.map((dep) =>
expect.objectContaining({
deployment: dep,
showMetrics: false,
}),
);
await collapsibleExtension.find('button').trigger('click');
const deploymentWrappers = collapsibleExtension.findAllComponents(Deployment);
expect(deploymentWrappers.wrappers.map((x) => x.props())).toEqual(expectedProps);
deploymentWrappers.wrappers.forEach((x) => {
expect(x.text()).toEqual(expect.any(String));
expect(x.text()).not.toBe('');
});
});
});
}); });
import { mount } from '@vue/test-utils';
import { zip } from 'lodash';
import { trimText } from 'helpers/text_helper';
import Deployment from '~/vue_merge_request_widget/components/deployment/deployment.vue';
import DeploymentList from '~/vue_merge_request_widget/components/deployment/deployment_list.vue';
import MrCollapsibleExtension from '~/vue_merge_request_widget/components/mr_collapsible_extension.vue';
import { mockStore } from '../mock_data';
const DEFAULT_PROPS = {
showVisualReviewAppLink: false,
hasDeploymentMetrics: false,
deploymentClass: 'js-pre-deployment',
};
describe('~/vue_merge_request_widget/components/deployment/deployment_list.vue', () => {
let wrapper;
let propsData;
const factory = (props = {}) => {
propsData = {
...DEFAULT_PROPS,
deployments: mockStore.deployments,
...props,
};
wrapper = mount(DeploymentList, {
propsData,
});
};
afterEach(() => {
wrapper?.destroy?.();
wrapper = null;
});
describe('with few deployments', () => {
beforeEach(() => {
factory();
});
it('shows all deployments', () => {
const deploymentWrappers = wrapper.findAllComponents(Deployment);
expect(wrapper.findComponent(MrCollapsibleExtension).exists()).toBe(false);
expect(deploymentWrappers).toHaveLength(propsData.deployments.length);
zip(deploymentWrappers.wrappers, propsData.deployments).forEach(
([deploymentWrapper, deployment]) => {
expect(deploymentWrapper.props('deployment')).toEqual(deployment);
expect(deploymentWrapper.props()).toMatchObject({
showVisualReviewApp: DEFAULT_PROPS.showVisualReviewAppLink,
showMetrics: DEFAULT_PROPS.hasDeploymentMetrics,
});
expect(deploymentWrapper.classes(DEFAULT_PROPS.deploymentClass)).toBe(true);
expect(deploymentWrapper.text()).toEqual(expect.any(String));
expect(deploymentWrapper.text()).not.toBe('');
},
);
});
});
describe('with many deployments', () => {
let deployments;
let collapsibleExtension;
beforeEach(() => {
deployments = [
...mockStore.deployments,
...mockStore.deployments.map((deployment) => ({
...deployment,
id: deployment.id + mockStore.deployments.length,
})),
];
factory({ deployments });
collapsibleExtension = wrapper.findComponent(MrCollapsibleExtension);
});
it('shows collapsed deployments', () => {
expect(collapsibleExtension.exists()).toBe(true);
expect(trimText(collapsibleExtension.text())).toBe(
`${deployments.length} environments impacted. View all environments.`,
);
});
it('shows all deployments on click', async () => {
await collapsibleExtension.find('button').trigger('click');
const deploymentWrappers = wrapper.findAllComponents(Deployment);
expect(deploymentWrappers).toHaveLength(deployments.length);
zip(deploymentWrappers.wrappers, propsData.deployments).forEach(
([deploymentWrapper, deployment]) => {
expect(deploymentWrapper.props('deployment')).toEqual(deployment);
expect(deploymentWrapper.props()).toMatchObject({
showVisualReviewApp: DEFAULT_PROPS.showVisualReviewAppLink,
showMetrics: DEFAULT_PROPS.hasDeploymentMetrics,
});
expect(deploymentWrapper.classes(DEFAULT_PROPS.deploymentClass)).toBe(true);
expect(deploymentWrapper.text()).toEqual(expect.any(String));
expect(deploymentWrapper.text()).not.toBe('');
},
);
});
});
});
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