Commit b0ad78db authored by Peter Hegman's avatar Peter Hegman

Merge branch 'afontaine/new-environments-folder' into 'master'

Add new environment folder component

See merge request gitlab-org/gitlab!73487
parents ad7de5d9 e1e7d0d7
<script>
import { GlCollapse, GlIcon, GlBadge, GlLink } from '@gitlab/ui';
import folderQuery from '../graphql/queries/folder.query.graphql';
export default {
components: {
GlCollapse,
GlIcon,
GlBadge,
GlLink,
},
props: {
nestedEnvironment: {
type: Object,
required: true,
},
},
data() {
return { visible: false };
},
apollo: {
folder: {
query: folderQuery,
variables() {
return { environment: this.nestedEnvironment.latest };
},
},
},
computed: {
icons() {
return this.visible
? { caret: 'angle-down', folder: 'folder-open' }
: { caret: 'angle-right', folder: 'folder-o' };
},
count() {
return this.folder?.availableCount ?? 0;
},
folderClass() {
return { 'gl-font-weight-bold': this.visible };
},
folderPath() {
return this.nestedEnvironment.latest.folderPath;
},
},
methods: {
toggleCollapse() {
this.visible = !this.visible;
},
},
};
</script>
<template>
<div class="gl-border-b-solid gl-border-gray-100 gl-border-1 gl-px-3 gl-pt-3 gl-pb-5">
<div class="gl-w-full gl-display-flex gl-align-items-center" @click="toggleCollapse">
<gl-icon
class="gl-mr-2 gl-fill-current-color gl-text-gray-500"
:name="icons.caret"
:size="12"
/>
<gl-icon class="gl-mr-2 gl-fill-current-color gl-text-gray-500" :name="icons.folder" />
<div class="gl-mr-2 gl-text-gray-500" :class="folderClass">
{{ nestedEnvironment.name }}
</div>
<gl-badge size="sm" class="gl-mr-auto">{{ count }}</gl-badge>
<gl-link v-if="visible" :href="folderPath">{{ s__('Environments|Show all') }}</gl-link>
</div>
<gl-collapse :visible="visible" />
</div>
</template>
<script>
export default {};
import { GlBadge, GlTab, GlTabs } from '@gitlab/ui';
import environmentAppQuery from '../graphql/queries/environmentApp.query.graphql';
import EnvironmentFolder from './new_environment_folder.vue';
export default {
components: {
EnvironmentFolder,
GlBadge,
GlTab,
GlTabs,
},
apollo: {
environmentApp: {
query: environmentAppQuery,
},
},
computed: {
folders() {
return this.environmentApp?.environments.filter((e) => e.size > 1) ?? [];
},
availableCount() {
return this.environmentApp?.availableCount;
},
},
};
</script>
<template>
<div></div>
<div>
<gl-tabs>
<gl-tab>
<template #title>
<span>{{ __('Available') }}</span>
<gl-badge size="sm" class="gl-tab-counter-badge">
{{ availableCount }}
</gl-badge>
</template>
<environment-folder
v-for="folder in folders"
:key="folder.name"
class="gl-mb-3"
:nested-environment="folder"
/>
</gl-tab>
</gl-tabs>
</div>
</template>
export const environmentsApp = {
environments: [
{
name: 'review',
size: 2,
latest: {
id: 42,
global_id: 'gid://gitlab/Environment/42',
name: 'review/goodbye',
state: 'available',
external_url: 'https://example.org',
environment_type: 'review',
name_without_type: 'goodbye',
last_deployment: null,
has_stop_action: false,
rollout_status: null,
environment_path: '/h5bp/html5-boilerplate/-/environments/42',
stop_path: '/h5bp/html5-boilerplate/-/environments/42/stop',
cancel_auto_stop_path: '/h5bp/html5-boilerplate/-/environments/42/cancel_auto_stop',
delete_path: '/api/v4/projects/8/environments/42',
folder_path: '/h5bp/html5-boilerplate/-/environments/folders/review',
created_at: '2021-10-04T19:27:20.639Z',
updated_at: '2021-10-04T19:27:20.639Z',
can_stop: true,
logs_path: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fgoodbye',
logs_api_path: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fgoodbye',
enable_advanced_logs_querying: false,
can_delete: false,
has_opened_alert: false,
},
},
{
name: 'production',
size: 1,
......@@ -134,35 +163,6 @@ export const environmentsApp = {
has_opened_alert: false,
},
},
{
name: 'review',
size: 2,
latest: {
id: 42,
global_id: 'gid://gitlab/Environment/42',
name: 'review/goodbye',
state: 'available',
external_url: 'https://example.org',
environment_type: 'review',
name_without_type: 'goodbye',
last_deployment: null,
has_stop_action: false,
rollout_status: null,
environment_path: '/h5bp/html5-boilerplate/-/environments/42',
stop_path: '/h5bp/html5-boilerplate/-/environments/42/stop',
cancel_auto_stop_path: '/h5bp/html5-boilerplate/-/environments/42/cancel_auto_stop',
delete_path: '/api/v4/projects/8/environments/42',
folder_path: '/h5bp/html5-boilerplate/-/environments/folders/review',
created_at: '2021-10-04T19:27:20.639Z',
updated_at: '2021-10-04T19:27:20.639Z',
can_stop: true,
logs_path: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fgoodbye',
logs_api_path: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fgoodbye',
enable_advanced_logs_querying: false,
can_delete: false,
has_opened_alert: false,
},
},
{
name: 'staging',
size: 1,
......@@ -206,6 +206,36 @@ export const environmentsApp = {
export const resolvedEnvironmentsApp = {
availableCount: 4,
environments: [
{
name: 'review',
size: 2,
latest: {
id: 42,
globalId: 'gid://gitlab/Environment/42',
name: 'review/goodbye',
state: 'available',
externalUrl: 'https://example.org',
environmentType: 'review',
nameWithoutType: 'goodbye',
lastDeployment: null,
hasStopAction: false,
rolloutStatus: null,
environmentPath: '/h5bp/html5-boilerplate/-/environments/42',
stopPath: '/h5bp/html5-boilerplate/-/environments/42/stop',
cancelAutoStopPath: '/h5bp/html5-boilerplate/-/environments/42/cancel_auto_stop',
deletePath: '/api/v4/projects/8/environments/42',
folderPath: '/h5bp/html5-boilerplate/-/environments/folders/review',
createdAt: '2021-10-04T19:27:20.639Z',
updatedAt: '2021-10-04T19:27:20.639Z',
canStop: true,
logsPath: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fgoodbye',
logsApiPath: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fgoodbye',
enableAdvancedLogsQuerying: false,
canDelete: false,
hasOpenedAlert: false,
},
__typename: 'NestedEnvironment',
},
{
name: 'production',
size: 1,
......@@ -340,36 +370,6 @@ export const resolvedEnvironmentsApp = {
},
__typename: 'NestedEnvironment',
},
{
name: 'review',
size: 2,
latest: {
id: 42,
globalId: 'gid://gitlab/Environment/42',
name: 'review/goodbye',
state: 'available',
externalUrl: 'https://example.org',
environmentType: 'review',
nameWithoutType: 'goodbye',
lastDeployment: null,
hasStopAction: false,
rolloutStatus: null,
environmentPath: '/h5bp/html5-boilerplate/-/environments/42',
stopPath: '/h5bp/html5-boilerplate/-/environments/42/stop',
cancelAutoStopPath: '/h5bp/html5-boilerplate/-/environments/42/cancel_auto_stop',
deletePath: '/api/v4/projects/8/environments/42',
folderPath: '/h5bp/html5-boilerplate/-/environments/folders/review',
createdAt: '2021-10-04T19:27:20.639Z',
updatedAt: '2021-10-04T19:27:20.639Z',
canStop: true,
logsPath: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fgoodbye',
logsApiPath: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fgoodbye',
enableAdvancedLogsQuerying: false,
canDelete: false,
hasOpenedAlert: false,
},
__typename: 'NestedEnvironment',
},
{
name: 'staging',
size: 1,
......
import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { GlCollapse, GlIcon } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import { s__ } from '~/locale';
import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data';
Vue.use(VueApollo);
describe('~/environments/components/new_environments_folder.vue', () => {
let wrapper;
let environmentFolderMock;
let nestedEnvironment;
let folderName;
const findLink = () => wrapper.findByRole('link', { name: s__('Environments|Show all') });
const createApolloProvider = () => {
const mockResolvers = { Query: { folder: environmentFolderMock } };
return createMockApollo([], mockResolvers);
};
const createWrapper = (propsData, apolloProvider) =>
mountExtended(EnvironmentsFolder, { apolloProvider, propsData });
beforeEach(() => {
environmentFolderMock = jest.fn();
[nestedEnvironment] = resolvedEnvironmentsApp.environments;
environmentFolderMock.mockReturnValue(resolvedFolder);
wrapper = createWrapper({ nestedEnvironment }, createApolloProvider());
folderName = wrapper.findByText(nestedEnvironment.name);
});
afterEach(() => {
wrapper?.destroy();
});
it('displays the name of the folder', () => {
expect(folderName.text()).toBe(nestedEnvironment.name);
});
describe('collapse', () => {
let icons;
let collapse;
beforeEach(() => {
collapse = wrapper.findComponent(GlCollapse);
icons = wrapper.findAllComponents(GlIcon);
});
it('is collapsed by default', () => {
const link = findLink();
expect(collapse.attributes('visible')).toBeUndefined();
expect(icons.wrappers.map((i) => i.props('name'))).toEqual(['angle-right', 'folder-o']);
expect(folderName.classes('gl-font-weight-bold')).toBe(false);
expect(link.exists()).toBe(false);
});
it('opens on click', async () => {
await folderName.trigger('click');
const link = findLink();
expect(collapse.attributes('visible')).toBe('true');
expect(icons.wrappers.map((i) => i.props('name'))).toEqual(['angle-down', 'folder-open']);
expect(folderName.classes('gl-font-weight-bold')).toBe(true);
expect(link.attributes('href')).toBe(nestedEnvironment.latest.folderPath);
});
});
});
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { mount } from '@vue/test-utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import EnvironmentsApp from '~/environments/components/new_environments_app.vue';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data';
Vue.use(VueApollo);
describe('~/environments/components/new_environments_app.vue', () => {
let wrapper;
let environmentAppMock;
let environmentFolderMock;
const createApolloProvider = () => {
const mockResolvers = {
Query: { environmentApp: environmentAppMock, folder: environmentFolderMock },
};
return createMockApollo([], mockResolvers);
};
const createWrapper = (apolloProvider) => mount(EnvironmentsApp, { apolloProvider });
beforeEach(() => {
environmentAppMock = jest.fn();
environmentFolderMock = jest.fn();
});
afterEach(() => {
wrapper?.destroy();
});
it('should show all the folders that are fetched', async () => {
environmentAppMock.mockReturnValue(resolvedEnvironmentsApp);
environmentFolderMock.mockReturnValue(resolvedFolder);
const apolloProvider = createApolloProvider();
wrapper = createWrapper(apolloProvider);
await waitForPromises();
await Vue.nextTick();
const text = wrapper.findAllComponents(EnvironmentsFolder).wrappers.map((w) => w.text());
expect(text).toContainEqual(expect.stringMatching('review'));
expect(text).not.toContainEqual(expect.stringMatching('production'));
});
});
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