Commit aced918d authored by Andrew Fontaine's avatar Andrew Fontaine

Make Environment Stop work with GraphQL

The new environments page is built using local GraphQL state management,
and so the ability to stop environments needs to rely on it instead of
the event hub.

To ensure everything still works until I am ready to delete the old
environments page, the new opt-in graphql prop is added to enable this
functionality.
parent b4bfb098
...@@ -8,6 +8,8 @@ import { GlTooltipDirective, GlButton, GlModalDirective } from '@gitlab/ui'; ...@@ -8,6 +8,8 @@ import { GlTooltipDirective, GlButton, GlModalDirective } from '@gitlab/ui';
import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants'; import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import setEnvironmentToStopMutation from '../graphql/mutations/set_environment_to_stop.mutation.graphql';
import isEnvironmentStoppingQuery from '../graphql/queries/is_environment_stopping.query.graphql';
export default { export default {
components: { components: {
...@@ -22,6 +24,19 @@ export default { ...@@ -22,6 +24,19 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
graphql: {
type: Boolean,
required: false,
default: false,
},
},
apollo: {
isEnvironmentStopping: {
query: isEnvironmentStoppingQuery,
variables() {
return { environment: this.environment };
},
},
}, },
i18n: { i18n: {
title: s__('Environments|Stop environment'), title: s__('Environments|Stop environment'),
...@@ -30,6 +45,7 @@ export default { ...@@ -30,6 +45,7 @@ export default {
data() { data() {
return { return {
isLoading: false, isLoading: false,
isEnvironmentStopping: false,
}; };
}, },
mounted() { mounted() {
...@@ -41,7 +57,14 @@ export default { ...@@ -41,7 +57,14 @@ export default {
methods: { methods: {
onClick() { onClick() {
this.$root.$emit(BV_HIDE_TOOLTIP, this.$options.stopEnvironmentTooltipId); this.$root.$emit(BV_HIDE_TOOLTIP, this.$options.stopEnvironmentTooltipId);
eventHub.$emit('requestStopEnvironment', this.environment); if (this.graphql) {
this.$apollo.mutate({
mutation: setEnvironmentToStopMutation,
variables: { environment: this.environment },
});
} else {
eventHub.$emit('requestStopEnvironment', this.environment);
}
}, },
onStopEnvironment(environment) { onStopEnvironment(environment) {
if (this.environment.id === environment.id) { if (this.environment.id === environment.id) {
...@@ -56,7 +79,7 @@ export default { ...@@ -56,7 +79,7 @@ export default {
<gl-button <gl-button
v-gl-tooltip="{ id: $options.stopEnvironmentTooltipId }" v-gl-tooltip="{ id: $options.stopEnvironmentTooltipId }"
v-gl-modal-directive="'stop-environment-modal'" v-gl-modal-directive="'stop-environment-modal'"
:loading="isLoading" :loading="isLoading || isEnvironmentStopping"
:title="$options.i18n.title" :title="$options.i18n.title"
:aria-label="$options.i18n.title" :aria-label="$options.i18n.title"
icon="stop" icon="stop"
......
...@@ -5,8 +5,10 @@ import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_util ...@@ -5,8 +5,10 @@ import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_util
import environmentAppQuery from '../graphql/queries/environment_app.query.graphql'; import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql'; import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
import pageInfoQuery from '../graphql/queries/page_info.query.graphql'; import pageInfoQuery from '../graphql/queries/page_info.query.graphql';
import environmentToStopQuery from '../graphql/queries/environment_to_stop.query.graphql';
import EnvironmentFolder from './new_environment_folder.vue'; import EnvironmentFolder from './new_environment_folder.vue';
import EnableReviewAppModal from './enable_review_app_modal.vue'; import EnableReviewAppModal from './enable_review_app_modal.vue';
import StopEnvironmentModal from './stop_environment_modal.vue';
export default { export default {
components: { components: {
...@@ -16,6 +18,7 @@ export default { ...@@ -16,6 +18,7 @@ export default {
GlPagination, GlPagination,
GlTab, GlTab,
GlTabs, GlTabs,
StopEnvironmentModal,
}, },
apollo: { apollo: {
environmentApp: { environmentApp: {
...@@ -36,6 +39,9 @@ export default { ...@@ -36,6 +39,9 @@ export default {
pageInfo: { pageInfo: {
query: pageInfoQuery, query: pageInfoQuery,
}, },
environmentToStop: {
query: environmentToStopQuery,
},
}, },
inject: ['newEnvironmentPath', 'canCreateEnvironment'], inject: ['newEnvironmentPath', 'canCreateEnvironment'],
i18n: { i18n: {
...@@ -57,6 +63,7 @@ export default { ...@@ -57,6 +63,7 @@ export default {
isReviewAppModalVisible: false, isReviewAppModalVisible: false,
page: parseInt(page, 10), page: parseInt(page, 10),
scope, scope,
environmentToStop: {},
}; };
}, },
computed: { computed: {
...@@ -157,6 +164,7 @@ export default { ...@@ -157,6 +164,7 @@ export default {
:modal-id="$options.modalId" :modal-id="$options.modalId"
data-testid="enable-review-app-modal" data-testid="enable-review-app-modal"
/> />
<stop-environment-modal :environment="environmentToStop" graphql />
<gl-tabs <gl-tabs
:action-secondary="addEnvironment" :action-secondary="addEnvironment"
:action-primary="openReviewAppModal" :action-primary="openReviewAppModal"
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
import { GlSprintf, GlTooltipDirective, GlModal } from '@gitlab/ui'; import { GlSprintf, GlTooltipDirective, GlModal } from '@gitlab/ui';
import { __, s__ } from '~/locale'; import { __, s__ } from '~/locale';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import stopEnvironmentMutation from '../graphql/mutations/stop_environment.mutation.graphql';
export default { export default {
id: 'stop-environment-modal', id: 'stop-environment-modal',
...@@ -21,6 +22,11 @@ export default { ...@@ -21,6 +22,11 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
graphql: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
...@@ -39,7 +45,14 @@ export default { ...@@ -39,7 +45,14 @@ export default {
methods: { methods: {
onSubmit() { onSubmit() {
eventHub.$emit('stopEnvironment', this.environment); if (this.graphql) {
this.$apollo.mutate({
mutation: stopEnvironmentMutation,
variables: { environment: this.environment },
});
} else {
eventHub.$emit('stopEnvironment', this.environment);
}
}, },
}, },
}; };
......
mutation SetEnvironmentToStop($environment: LocalEnvironmentInput) {
setEnvironmentToStop(environment: $environment) @client
}
query isEnvironmentStopping($environment: LocalEnvironment) {
isEnvironmentStopping(environment: $environment) @client
}
...@@ -8,6 +8,7 @@ import { ...@@ -8,6 +8,7 @@ import {
import pollIntervalQuery from './queries/poll_interval.query.graphql'; import pollIntervalQuery from './queries/poll_interval.query.graphql';
import environmentToRollbackQuery from './queries/environment_to_rollback.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 environmentToDeleteQuery from './queries/environment_to_delete.query.graphql';
import pageInfoQuery from './queries/page_info.query.graphql'; import pageInfoQuery from './queries/page_info.query.graphql';
...@@ -108,6 +109,12 @@ export const resolvers = (endpoint) => ({ ...@@ -108,6 +109,12 @@ export const resolvers = (endpoint) => ({
]); ]);
}); });
}, },
setEnvironmentToStop(_, { environment }, { client }) {
client.writeQuery({
query: environmentToStopQuery,
data: { environmentToStop: environment },
});
},
setEnvironmentToDelete(_, { environment }, { client }) { setEnvironmentToDelete(_, { environment }, { client }) {
client.writeQuery({ client.writeQuery({
query: environmentToDeleteQuery, query: environmentToDeleteQuery,
......
...@@ -68,6 +68,8 @@ extend type Query { ...@@ -68,6 +68,8 @@ extend type Query {
environmentToDelete: LocalEnvironment environmentToDelete: LocalEnvironment
pageInfo: LocalPageInfo pageInfo: LocalPageInfo
environmentToRollback: LocalEnvironment environmentToRollback: LocalEnvironment
environmentToStop: LocalEnvironment
isEnvironmentStopping(environment: LocalEnvironmentInput): Boolean
isLastDeployment: Boolean isLastDeployment: Boolean
} }
...@@ -78,4 +80,5 @@ extend type Mutation { ...@@ -78,4 +80,5 @@ extend type Mutation {
cancelAutoStop(environment: LocalEnvironmentInput): LocalErrors cancelAutoStop(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToDelete(environment: LocalEnvironmentInput): LocalErrors setEnvironmentToDelete(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToStop(environment: LocalEnvironmentInput): LocalErrors
} }
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import $ from 'jquery'; import Vue from 'vue';
import VueApollo from 'vue-apollo';
import setEnvironmentToStopMutation from '~/environments/graphql/mutations/set_environment_to_stop.mutation.graphql';
import isEnvironmentStoppingQuery from '~/environments/graphql/queries/is_environment_stopping.query.graphql';
import StopComponent from '~/environments/components/environment_stop.vue'; import StopComponent from '~/environments/components/environment_stop.vue';
import eventHub from '~/environments/event_hub'; import eventHub from '~/environments/event_hub';
import createMockApollo from 'helpers/mock_apollo_helper';
$.fn.tooltip = () => {}; import { resolvedEnvironment } from './graphql/mock_data';
describe('Stop Component', () => { describe('Stop Component', () => {
let wrapper; let wrapper;
const createWrapper = () => { const createWrapper = (props = {}, options = {}) => {
wrapper = shallowMount(StopComponent, { wrapper = shallowMount(StopComponent, {
propsData: { propsData: {
environment: {}, environment: {},
...props,
}, },
...options,
}); });
}; };
const findButton = () => wrapper.find(GlButton); const findButton = () => wrapper.find(GlButton);
beforeEach(() => { describe('eventHub', () => {
jest.spyOn(window, 'confirm'); beforeEach(() => {
createWrapper();
});
createWrapper(); it('should render a button to stop the environment', () => {
}); expect(findButton().exists()).toBe(true);
expect(wrapper.attributes('title')).toEqual('Stop environment');
});
it('should render a button to stop the environment', () => { it('emits requestStopEnvironment in the event hub when button is clicked', () => {
expect(findButton().exists()).toBe(true); jest.spyOn(eventHub, '$emit');
expect(wrapper.attributes('title')).toEqual('Stop environment'); findButton().vm.$emit('click');
expect(eventHub.$emit).toHaveBeenCalledWith('requestStopEnvironment', wrapper.vm.environment);
});
}); });
it('emits requestStopEnvironment in the event hub when button is clicked', () => { describe('graphql', () => {
jest.spyOn(eventHub, '$emit'); Vue.use(VueApollo);
findButton().vm.$emit('click'); let mockApollo;
expect(eventHub.$emit).toHaveBeenCalledWith('requestStopEnvironment', wrapper.vm.environment);
beforeEach(() => {
mockApollo = createMockApollo();
mockApollo.clients.defaultClient.writeQuery({
query: isEnvironmentStoppingQuery,
variables: { environment: resolvedEnvironment },
data: { isEnvironmentStopping: true },
});
createWrapper(
{ graphql: true, environment: resolvedEnvironment },
{ apolloProvider: mockApollo },
);
});
it('should render a button to stop the environment', () => {
expect(findButton().exists()).toBe(true);
expect(wrapper.attributes('title')).toEqual('Stop environment');
});
it('sets the environment to stop on click', () => {
jest.spyOn(mockApollo.defaultClient, 'mutate');
findButton().vm.$emit('click');
expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({
mutation: setEnvironmentToStopMutation,
variables: { environment: resolvedEnvironment },
});
});
it('should show a loading icon if the environment is currently stopping', async () => {
expect(findButton().props('loading')).toBe(true);
});
}); });
}); });
...@@ -3,6 +3,7 @@ import axios from '~/lib/utils/axios_utils'; ...@@ -3,6 +3,7 @@ import axios from '~/lib/utils/axios_utils';
import { resolvers } from '~/environments/graphql/resolvers'; import { resolvers } from '~/environments/graphql/resolvers';
import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql'; import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql';
import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql'; import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql';
import environmentToStopQuery from '~/environments/graphql/queries/environment_to_stop.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql'; import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql';
import pageInfoQuery from '~/environments/graphql/queries/page_info.query.graphql'; import pageInfoQuery from '~/environments/graphql/queries/page_info.query.graphql';
...@@ -210,4 +211,19 @@ describe('~/frontend/environments/graphql/resolvers', () => { ...@@ -210,4 +211,19 @@ describe('~/frontend/environments/graphql/resolvers', () => {
}); });
}); });
}); });
describe('setEnvironmentToStop', () => {
it('should write the given environment to the cache', () => {
localState.client.writeQuery = jest.fn();
mockResolvers.Mutation.setEnvironmentToStop(
null,
{ environment: resolvedEnvironment },
localState,
);
expect(localState.client.writeQuery).toHaveBeenCalledWith({
query: environmentToStopQuery,
data: { environmentToStop: resolvedEnvironment },
});
});
});
}); });
...@@ -8,7 +8,8 @@ import setWindowLocation from 'helpers/set_window_location_helper'; ...@@ -8,7 +8,8 @@ import setWindowLocation from 'helpers/set_window_location_helper';
import { sprintf, __, s__ } from '~/locale'; import { sprintf, __, s__ } from '~/locale';
import EnvironmentsApp from '~/environments/components/new_environments_app.vue'; import EnvironmentsApp from '~/environments/components/new_environments_app.vue';
import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue'; import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue';
import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data'; import StopEnvironmentModal from '~/environments/components/stop_environment_modal.vue';
import { resolvedEnvironmentsApp, resolvedFolder, resolvedEnvironment } from './graphql/mock_data';
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -17,6 +18,7 @@ describe('~/environments/components/new_environments_app.vue', () => { ...@@ -17,6 +18,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
let environmentAppMock; let environmentAppMock;
let environmentFolderMock; let environmentFolderMock;
let paginationMock; let paginationMock;
let environmentToStopMock;
const createApolloProvider = () => { const createApolloProvider = () => {
const mockResolvers = { const mockResolvers = {
...@@ -24,6 +26,7 @@ describe('~/environments/components/new_environments_app.vue', () => { ...@@ -24,6 +26,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentApp: environmentAppMock, environmentApp: environmentAppMock,
folder: environmentFolderMock, folder: environmentFolderMock,
pageInfo: paginationMock, pageInfo: paginationMock,
environmentToStop: environmentToStopMock,
}, },
}; };
...@@ -45,6 +48,7 @@ describe('~/environments/components/new_environments_app.vue', () => { ...@@ -45,6 +48,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
provide = {}, provide = {},
environmentsApp, environmentsApp,
folder, folder,
environmentToStop = {},
pageInfo = { pageInfo = {
total: 20, total: 20,
perPage: 5, perPage: 5,
...@@ -58,6 +62,7 @@ describe('~/environments/components/new_environments_app.vue', () => { ...@@ -58,6 +62,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
environmentAppMock.mockReturnValue(environmentsApp); environmentAppMock.mockReturnValue(environmentsApp);
environmentFolderMock.mockReturnValue(folder); environmentFolderMock.mockReturnValue(folder);
paginationMock.mockReturnValue(pageInfo); paginationMock.mockReturnValue(pageInfo);
environmentToStopMock.mockReturnValue(environmentToStop);
const apolloProvider = createApolloProvider(); const apolloProvider = createApolloProvider();
wrapper = createWrapper({ apolloProvider, provide }); wrapper = createWrapper({ apolloProvider, provide });
...@@ -68,6 +73,7 @@ describe('~/environments/components/new_environments_app.vue', () => { ...@@ -68,6 +73,7 @@ describe('~/environments/components/new_environments_app.vue', () => {
beforeEach(() => { beforeEach(() => {
environmentAppMock = jest.fn(); environmentAppMock = jest.fn();
environmentFolderMock = jest.fn(); environmentFolderMock = jest.fn();
environmentToStopMock = jest.fn();
paginationMock = jest.fn(); paginationMock = jest.fn();
}); });
...@@ -175,6 +181,20 @@ describe('~/environments/components/new_environments_app.vue', () => { ...@@ -175,6 +181,20 @@ describe('~/environments/components/new_environments_app.vue', () => {
}); });
}); });
describe('modals', () => {
it('should pass the environment to stop to the stop environment modal', async () => {
await createWrapperWithMocked({
environmentsApp: resolvedEnvironmentsApp,
folder: resolvedFolder,
environmentToStop: resolvedEnvironment,
});
const modal = wrapper.findComponent(StopEnvironmentModal);
expect(modal.props('environment')).toMatchObject(resolvedEnvironment);
});
});
describe('pagination', () => { describe('pagination', () => {
it('should sync page from query params on load', async () => { it('should sync page from query params on load', async () => {
await createWrapperWithMocked({ await createWrapperWithMocked({
......
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