Commit 60cb91d4 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch...

Merge branch 'ee-46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual' into 'master'

Port "Sort Environments by Last Updated"

See merge request gitlab-org/gitlab-ee!9581
parents f4bbf8e5 1ca43312
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
* Render environments table. * Render environments table.
*/ */
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import _ from 'underscore';
import environmentItem from './environment_item.vue'; // eslint-disable-line import/order import environmentItem from './environment_item.vue'; // eslint-disable-line import/order
// ee-only start // ee-only start
...@@ -60,6 +61,15 @@ export default { ...@@ -60,6 +61,15 @@ export default {
}, },
// ee-only end // ee-only end
}, },
computed: {
sortedEnvironments() {
return this.sortEnvironments(this.environments).map(env =>
this.shouldRenderFolderContent(env)
? { ...env, children: this.sortEnvironments(env.children) }
: env,
);
},
},
methods: { methods: {
folderUrl(model) { folderUrl(model) {
return `${window.location.pathname}/folders/${model.folderName}`; return `${window.location.pathname}/folders/${model.folderName}`;
...@@ -67,6 +77,30 @@ export default { ...@@ -67,6 +77,30 @@ export default {
shouldRenderFolderContent(env) { shouldRenderFolderContent(env) {
return env.isFolder && env.isOpen && env.children && env.children.length > 0; return env.isFolder && env.isOpen && env.children && env.children.length > 0;
}, },
sortEnvironments(environments) {
/*
* The sorting algorithm should sort in the following priorities:
*
* 1. folders first,
* 2. last updated descending,
* 3. by name ascending,
*
* the sorting algorithm must:
*
* 1. Sort by name ascending,
* 2. Reverse (sort by name descending),
* 3. Sort by last deployment ascending,
* 4. Reverse (last deployment descending, name ascending),
* 5. Put folders first.
*/
return _.chain(environments)
.sortBy(env => (env.isFolder ? env.folderName : env.name))
.reverse()
.sortBy(env => (env.last_deployment ? env.last_deployment.created_at : '0000'))
.reverse()
.sortBy(env => (env.isFolder ? -1 : 1))
.value();
},
// ee-only start // ee-only start
shouldShowCanaryCallout(env) { shouldShowCanaryCallout(env) {
return env.showCanaryCallout && this.showCanaryDeploymentCallout; return env.showCanaryCallout && this.showCanaryDeploymentCallout;
...@@ -94,7 +128,7 @@ export default { ...@@ -94,7 +128,7 @@ export default {
{{ s__('Environments|Updated') }} {{ s__('Environments|Updated') }}
</div> </div>
</div> </div>
<template v-for="(model, i) in environments" :model="model"> <template v-for="(model, i) in sortedEnvironments" :model="model">
<div <div
is="environment-item" is="environment-item"
:key="`environment-item-${i}`" :key="`environment-item-${i}`"
......
---
title: Sort Environments by Last Updated
merge_request: 25260
author:
type: added
import Vue from 'vue';
import environmentTableComp from '~/environments/components/environments_table.vue';
import eventHub from '~/environments/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { deployBoardMockData } from 'spec/environments/mock_data';
describe('Environment table', () => {
let Component;
let vm;
beforeEach(() => {
Component = Vue.extend(environmentTableComp);
});
afterEach(() => {
vm.$destroy();
});
it('Should render a table', () => {
const mockItem = {
name: 'review',
folderName: 'review',
size: 3,
isFolder: true,
environment_path: 'url',
};
vm = mountComponent(Component, {
environments: [mockItem],
canReadEnvironment: true,
// ee-only start
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
// ee-only end
});
expect(vm.$el.getAttribute('class')).toContain('ci-table');
});
it('should render deploy board container when data is provided', () => {
const mockItem = {
name: 'review',
size: 1,
environment_path: 'url',
id: 1,
hasDeployBoard: true,
deployBoardData: deployBoardMockData,
isDeployBoardVisible: true,
isLoadingDeployBoard: false,
isEmptyDeployBoard: false,
};
vm = mountComponent(Component, {
environments: [mockItem],
canCreateDeployment: false,
canReadEnvironment: true,
// ee-only start
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
// ee-only end
});
expect(vm.$el.querySelector('.js-deploy-board-row')).toBeDefined();
expect(vm.$el.querySelector('.deploy-board-icon')).not.toBeNull();
});
it('should toggle deploy board visibility when arrow is clicked', done => {
const mockItem = {
name: 'review',
size: 1,
environment_path: 'url',
id: 1,
hasDeployBoard: true,
deployBoardData: {
instances: [{ status: 'ready', tooltip: 'foo' }],
abort_url: 'url',
rollback_url: 'url',
completion: 100,
is_completed: true,
},
isDeployBoardVisible: false,
};
eventHub.$on('toggleDeployBoard', env => {
expect(env.id).toEqual(mockItem.id);
done();
});
vm = mountComponent(Component, {
environments: [mockItem],
canReadEnvironment: true,
// ee-only start
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
// ee-only end
});
vm.$el.querySelector('.deploy-board-icon').click();
});
// ee-only start
it('should render canary callout', () => {
const mockItem = {
name: 'review',
folderName: 'review',
size: 3,
isFolder: true,
environment_path: 'url',
showCanaryCallout: true,
};
vm = mountComponent(Component, {
environments: [mockItem],
canCreateDeployment: false,
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
});
expect(vm.$el.querySelector('.canary-deployment-callout')).not.toBeNull();
});
// ee-only end
});
import Vue from 'vue'; import Vue from 'vue';
import environmentTableComp from '~/environments/components/environments_table.vue'; import environmentTableComp from '~/environments/components/environments_table.vue';
import eventHub from '~/environments/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper'; import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { deployBoardMockData } from './mock_data';
describe('Environment table', () => { describe('Environment table', () => {
let Component; let Component;
let vm; let vm;
// ee-only start
let eeOnlyProps;
// ee-only end
beforeEach(() => { beforeEach(() => {
Component = Vue.extend(environmentTableComp); Component = Vue.extend(environmentTableComp);
// ee-only start
eeOnlyProps = {
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
};
// ee-only end
}); });
afterEach(() => { afterEach(() => {
...@@ -19,117 +29,256 @@ describe('Environment table', () => { ...@@ -19,117 +29,256 @@ describe('Environment table', () => {
it('Should render a table', () => { it('Should render a table', () => {
const mockItem = { const mockItem = {
name: 'review', name: 'review',
folderName: 'review',
size: 3, size: 3,
isFolder: true, isFolder: true,
environment_path: 'url', latest: {
environment_path: 'url',
},
}; };
vm = mountComponent(Component, { vm = mountComponent(Component, {
environments: [mockItem], environments: [mockItem],
canReadEnvironment: true, canReadEnvironment: true,
// ee-only start // ee-only start
canaryDeploymentFeatureId: 'canary_deployment', ...eeOnlyProps,
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
// ee-only end // ee-only end
}); });
expect(vm.$el.getAttribute('class')).toContain('ci-table'); expect(vm.$el.getAttribute('class')).toContain('ci-table');
}); });
it('should render deploy board container when data is provided', () => { describe('sortEnvironments', () => {
const mockItem = { it('should sort environments by last updated', () => {
name: 'review', const mockItems = [
size: 1, {
environment_path: 'url', name: 'old',
id: 1, size: 3,
hasDeployBoard: true, isFolder: false,
deployBoardData: deployBoardMockData, last_deployment: {
isDeployBoardVisible: true, created_at: new Date(2019, 0, 5).toISOString(),
isLoadingDeployBoard: false, },
isEmptyDeployBoard: false, },
}; {
name: 'new',
size: 3,
isFolder: false,
last_deployment: {
created_at: new Date(2019, 1, 5).toISOString(),
},
},
{
name: 'older',
size: 3,
isFolder: false,
last_deployment: {
created_at: new Date(2018, 0, 5).toISOString(),
},
},
{
name: 'an environment with no deployment',
},
];
vm = mountComponent(Component, { vm = mountComponent(Component, {
environments: [mockItem], environments: mockItems,
canCreateDeployment: false, canReadEnvironment: true,
canReadEnvironment: true, // ee-only start
// ee-only start ...eeOnlyProps,
canaryDeploymentFeatureId: 'canary_deployment', // ee-only end
showCanaryDeploymentCallout: true, });
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', const [old, newer, older, noDeploy] = mockItems;
helpCanaryDeploymentsPath: 'help/canary-deployments',
// ee-only end expect(vm.sortEnvironments(mockItems)).toEqual([newer, old, older, noDeploy]);
}); });
expect(vm.$el.querySelector('.js-deploy-board-row')).toBeDefined(); it('should push environments with no deployments to the bottom', () => {
expect(vm.$el.querySelector('.deploy-board-icon')).not.toBeNull(); const mockItems = [
}); {
name: 'production',
size: 1,
id: 2,
state: 'available',
external_url: 'https://google.com/production',
environment_type: null,
last_deployment: null,
has_stop_action: false,
environment_path: '/Commit451/lab-coat/environments/2',
stop_path: '/Commit451/lab-coat/environments/2/stop',
folder_path: '/Commit451/lab-coat/environments/folders/production',
created_at: '2019-01-17T16:26:10.064Z',
updated_at: '2019-01-17T16:27:37.717Z',
can_stop: true,
},
{
name: 'review/225addcibuildstatus',
size: 2,
isFolder: true,
isLoadingFolderContent: false,
folderName: 'review',
isOpen: false,
children: [],
id: 12,
state: 'available',
external_url: 'https://google.com/review/225addcibuildstatus',
environment_type: 'review',
last_deployment: null,
has_stop_action: false,
environment_path: '/Commit451/lab-coat/environments/12',
stop_path: '/Commit451/lab-coat/environments/12/stop',
folder_path: '/Commit451/lab-coat/environments/folders/review',
created_at: '2019-01-17T16:27:37.877Z',
updated_at: '2019-01-17T16:27:37.883Z',
can_stop: true,
},
{
name: 'staging',
size: 1,
id: 1,
state: 'available',
external_url: 'https://google.com/staging',
environment_type: null,
last_deployment: {
created_at: '2019-01-17T16:26:15.125Z',
scheduled_actions: [],
},
},
];
it('should toggle deploy board visibility when arrow is clicked', done => { vm = mountComponent(Component, {
const mockItem = { environments: mockItems,
name: 'review', canReadEnvironment: true,
size: 1, // ee-only start
environment_path: 'url', ...eeOnlyProps,
id: 1, // ee-only end
hasDeployBoard: true, });
deployBoardData: {
instances: [{ status: 'ready', tooltip: 'foo' }], const [prod, review, staging] = mockItems;
abort_url: 'url',
rollback_url: 'url',
completion: 100,
is_completed: true,
},
isDeployBoardVisible: false,
};
eventHub.$on('toggleDeployBoard', env => { expect(vm.sortEnvironments(mockItems)).toEqual([review, staging, prod]);
expect(env.id).toEqual(mockItem.id);
done();
}); });
vm = mountComponent(Component, { it('should sort environments by folder first', () => {
environments: [mockItem], const mockItems = [
canReadEnvironment: true, {
// ee-only start name: 'old',
canaryDeploymentFeatureId: 'canary_deployment', size: 3,
showCanaryDeploymentCallout: true, isFolder: false,
userCalloutsPath: '/callouts', last_deployment: {
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg', created_at: new Date(2019, 0, 5).toISOString(),
helpCanaryDeploymentsPath: 'help/canary-deployments', },
// ee-only end },
{
name: 'new',
size: 3,
isFolder: false,
last_deployment: {
created_at: new Date(2019, 1, 5).toISOString(),
},
},
{
name: 'older',
size: 3,
isFolder: true,
children: [],
},
];
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
// ee-only start
...eeOnlyProps,
// ee-only end
});
const [old, newer, older] = mockItems;
expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
}); });
vm.$el.querySelector('.deploy-board-icon').click(); it('should break ties by name', () => {
}); const mockItems = [
{
name: 'old',
isFolder: false,
},
{
name: 'new',
isFolder: false,
},
{
folderName: 'older',
isFolder: true,
},
];
// ee-only start vm = mountComponent(Component, {
it('should render canary callout', () => { environments: mockItems,
const mockItem = { canReadEnvironment: true,
name: 'review', // ee-only start
folderName: 'review', ...eeOnlyProps,
size: 3, // ee-only end
isFolder: true, });
environment_path: 'url',
showCanaryCallout: true,
};
vm = mountComponent(Component, { const [old, newer, older] = mockItems;
environments: [mockItem],
canCreateDeployment: false, expect(vm.sortEnvironments(mockItems)).toEqual([older, newer, old]);
canReadEnvironment: true,
canaryDeploymentFeatureId: 'canary_deployment',
showCanaryDeploymentCallout: true,
userCalloutsPath: '/callouts',
lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
helpCanaryDeploymentsPath: 'help/canary-deployments',
}); });
});
describe('sortedEnvironments', () => {
it('it should sort children as well', () => {
const mockItems = [
{
name: 'production',
last_deployment: null,
},
{
name: 'review/225addcibuildstatus',
isFolder: true,
folderName: 'review',
isOpen: true,
children: [
{
name: 'review/225addcibuildstatus',
last_deployment: {
created_at: '2019-01-17T16:26:15.125Z',
},
},
{
name: 'review/master',
last_deployment: {
created_at: '2019-02-17T16:26:15.125Z',
},
},
],
},
{
name: 'staging',
last_deployment: {
created_at: '2019-01-17T16:26:15.125Z',
},
},
];
const [production, review, staging] = mockItems;
const [addcibuildstatus, master] = mockItems[1].children;
expect(vm.$el.querySelector('.canary-deployment-callout')).not.toBeNull(); vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
// ee-only start
...eeOnlyProps,
// ee-only end
});
expect(vm.sortedEnvironments.map(env => env.name)).toEqual([
review.name,
staging.name,
production.name,
]);
expect(vm.sortedEnvironments[0].children).toEqual([master, addcibuildstatus]);
});
}); });
// ee-only 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