Commit d99aa3c8 authored by Andrew Fontaine's avatar Andrew Fontaine

Add GraphQL to the Environment Rollback Components

This adds the necessary local queries and mutations required to trigger
a rollback/re-deploy via the new local GraphQL state management set-up.
This will be used as part of the new environments page. It pulls the use
of isLastDeployment out of the environment object as well, and relies on
it being passed in as a prop.
parent 60f4f450
......@@ -7,6 +7,7 @@ import { escape } from 'lodash';
import csrf from '~/lib/utils/csrf';
import { __, s__, sprintf } from '~/locale';
import rollbackEnvironment from '../graphql/mutations/rollback_environment.mutation.graphql';
import eventHub from '../event_hub';
export default {
......@@ -40,10 +41,15 @@ export default {
required: false,
default: null,
},
graphql: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
modalTitle() {
const title = this.environment.isLastDeployment
const title = this.isLastDeployment
? s__('Environments|Re-deploy environment %{name}?')
: s__('Environments|Rollback environment %{name}?');
......@@ -53,6 +59,11 @@ export default {
},
commitShortSha() {
if (this.hasMultipleCommits) {
if (this.graphql) {
const { lastDeployment } = this.environment;
return this.commitData(lastDeployment, 'shortId');
}
const { last_deployment } = this.environment;
return this.commitData(last_deployment, 'short_id');
}
......@@ -61,6 +72,11 @@ export default {
},
commitUrl() {
if (this.hasMultipleCommits) {
if (this.graphql) {
const { lastDeployment } = this.environment;
return this.commitData(lastDeployment, 'commitPath');
}
const { last_deployment } = this.environment;
return this.commitData(last_deployment, 'commit_path');
}
......@@ -68,9 +84,7 @@ export default {
return this.environment.commitUrl;
},
modalActionText() {
return this.environment.isLastDeployment
? s__('Environments|Re-deploy')
: s__('Environments|Rollback');
return this.isLastDeployment ? s__('Environments|Re-deploy') : s__('Environments|Rollback');
},
primaryProps() {
let attributes = [{ variant: 'danger' }];
......@@ -84,20 +98,27 @@ export default {
attributes,
};
},
isLastDeployment() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return this.environment?.isLastDeployment || this.environment?.lastDeployment?.['last?'];
},
},
methods: {
handleChange(event) {
this.$emit('change', event);
},
onOk() {
if (this.graphql) {
this.$apollo.mutate({
mutation: rollbackEnvironment,
variables: { environment: this.environment },
});
} else {
eventHub.$emit('rollbackEnvironment', this.environment);
}
},
commitData(lastDeployment, key) {
if (lastDeployment && lastDeployment.commit) {
return lastDeployment.commit[key];
}
return '';
return lastDeployment?.commit?.[key] ?? '';
},
},
csrf,
......
......@@ -8,6 +8,7 @@
import { GlModalDirective, GlDropdownItem } from '@gitlab/ui';
import { s__ } from '~/locale';
import eventHub from '../event_hub';
import setEnvironmentToRollback from '../graphql/mutations/set_environment_to_rollback.mutation.graphql';
export default {
components: {
......@@ -32,11 +33,12 @@ export default {
type: String,
required: true,
},
graphql: {
type: Boolean,
required: false,
default: false,
},
data() {
return {
isLoading: false,
};
},
computed: {
......@@ -49,16 +51,18 @@ export default {
methods: {
onClick() {
if (this.graphql) {
this.$apollo.mutate({
mutation: setEnvironmentToRollback,
variables: { environment: this.environment },
});
} else {
eventHub.$emit('requestRollbackEnvironment', {
...this.environment,
retryUrl: this.retryUrl,
isLastDeployment: this.isLastDeployment,
});
eventHub.$on('rollbackEnvironment', (environment) => {
if (environment.id === this.environment.id) {
this.isLoading = true;
}
});
},
},
};
......
mutation SetEnvironmentToRollback($environment: Environment) {
setEnvironmentToRollback(environment: $environment) @client
}
query environmentToRollback {
environmentToRollback @client {
id
name
lastDeployment
}
}
......@@ -2,6 +2,7 @@ import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import pollIntervalQuery from './queries/poll_interval.query.graphql';
import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql';
const buildErrors = (errors = []) => ({
errors,
......@@ -84,6 +85,12 @@ export const resolvers = (endpoint) => ({
]);
});
},
setEnvironmentToRollback(_, { environment }, { client }) {
client.writeQuery({
query: environmentToRollbackQuery,
data: { environmentToRollback: environment },
});
},
cancelAutoStop(_, { environment: { autoStopPath } }) {
return axios
.post(autoStopPath)
......
......@@ -58,6 +58,7 @@ type LocalErrors {
extend type Query {
environmentApp: LocalEnvironmentApp
folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder
environmentToRollback: LocalEnvironment
isLastDeployment: Boolean
}
......@@ -66,4 +67,5 @@ extend type Mutation {
deleteEnvironment(environment: LocalEnvironmentInput): LocalErrors
rollbackEnvironment(environment: LocalEnvironmentInput): LocalErrors
cancelAutoStop(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors
}
import { GlModal, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import eventHub from '~/environments/event_hub';
describe('Confirm Rollback Modal Component', () => {
......@@ -17,6 +20,17 @@ describe('Confirm Rollback Modal Component', () => {
modalId: 'test',
};
const envWithLastDeploymentGraphql = {
name: 'test',
lastDeployment: {
commit: {
shortId: 'abc0123',
},
'last?': true,
},
modalId: 'test',
};
const envWithoutLastDeployment = {
name: 'test',
modalId: 'test',
......@@ -26,7 +40,7 @@ describe('Confirm Rollback Modal Component', () => {
const retryPath = 'test/-/jobs/123/retry';
const createComponent = (props = {}) => {
const createComponent = (props = {}, options = {}) => {
component = shallowMount(ConfirmRollbackModal, {
propsData: {
...props,
......@@ -34,6 +48,7 @@ describe('Confirm Rollback Modal Component', () => {
stubs: {
GlSprintf,
},
...options,
});
};
......@@ -101,4 +116,121 @@ describe('Confirm Rollback Modal Component', () => {
});
},
);
describe('graphql', () => {
describe.each`
hasMultipleCommits | environmentData | retryUrl | primaryPropsAttrs
${true} | ${envWithLastDeploymentGraphql} | ${null} | ${[{ variant: 'danger' }]}
${false} | ${envWithoutLastDeployment} | ${retryPath} | ${[{ variant: 'danger' }, { 'data-method': 'post' }, { href: retryPath }]}
`(
'when hasMultipleCommits=$hasMultipleCommits',
({ hasMultipleCommits, environmentData, retryUrl, primaryPropsAttrs }) => {
Vue.use(VueApollo);
let apolloProvider;
let rollbackResolver;
beforeEach(() => {
rollbackResolver = jest.fn();
apolloProvider = createMockApollo([], {
Mutation: { rollbackEnvironment: rollbackResolver },
});
environment = environmentData;
});
it('should set contain the commit hash and ask for confirmation', () => {
createComponent(
{
environment: {
...environment,
lastDeployment: {
...environment.lastDeployment,
'last?': false,
},
},
hasMultipleCommits,
retryUrl,
graphql: true,
},
{ apolloProvider },
);
const modal = component.find(GlModal);
expect(modal.text()).toContain('commit abc0123');
expect(modal.text()).toContain('Are you sure you want to continue?');
});
it('should show "Rollback" when isLastDeployment is false', () => {
createComponent(
{
environment: {
...environment,
lastDeployment: {
...environment.lastDeployment,
'last?': false,
},
},
hasMultipleCommits,
retryUrl,
graphql: true,
},
{ apolloProvider },
);
const modal = component.find(GlModal);
expect(modal.attributes('title')).toContain('Rollback');
expect(modal.attributes('title')).toContain('test');
expect(modal.props('actionPrimary').text).toBe('Rollback');
expect(modal.props('actionPrimary').attributes).toEqual(primaryPropsAttrs);
});
it('should show "Re-deploy" when isLastDeployment is true', () => {
createComponent(
{
environment: {
...environment,
lastDeployment: {
...environment.lastDeployment,
'last?': true,
},
},
hasMultipleCommits,
graphql: true,
},
{ apolloProvider },
);
const modal = component.find(GlModal);
expect(modal.attributes('title')).toContain('Re-deploy');
expect(modal.attributes('title')).toContain('test');
expect(modal.props('actionPrimary').text).toBe('Re-deploy');
});
it('should commit the "rollback" mutation when "ok" is clicked', async () => {
const env = { ...environmentData, isLastDeployment: true };
createComponent(
{
environment: env,
hasMultipleCommits,
graphql: true,
},
{ apolloProvider },
);
const modal = component.find(GlModal);
modal.vm.$emit('ok');
await nextTick();
expect(rollbackResolver).toHaveBeenCalledWith(
expect.anything(),
{ environment: env },
expect.anything(),
expect.anything(),
);
});
},
);
});
});
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import RollbackComponent from '~/environments/components/environment_rollback.vue';
import eventHub from '~/environments/event_hub';
import setEnvironmentToRollback from '~/environments/graphql/mutations/set_environment_to_rollback.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
describe('Rollback Component', () => {
const retryUrl = 'https://gitlab.com/retry';
......@@ -50,4 +54,29 @@ describe('Rollback Component', () => {
name: 'test',
});
});
it('should trigger a graphql mutation when graphql is enabled', () => {
Vue.use(VueApollo);
const apolloProvider = createMockApollo();
jest.spyOn(apolloProvider.defaultClient, 'mutate');
const environment = {
name: 'test',
};
const wrapper = shallowMount(RollbackComponent, {
propsData: {
retryUrl,
graphql: true,
environment,
},
apolloProvider,
});
const button = wrapper.find(GlDropdownItem);
button.vm.$emit('click');
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({
mutation: setEnvironmentToRollback,
variables: { environment },
});
});
});
......@@ -469,6 +469,33 @@ export const folder = {
stopped_count: 0,
};
export const resolvedEnvironment = {
id: 41,
globalId: 'gid://gitlab/Environment/41',
name: 'review/hello',
state: 'available',
externalUrl: 'https://example.org',
environmentType: 'review',
nameWithoutType: 'hello',
lastDeployment: null,
hasStopAction: false,
rolloutStatus: null,
environmentPath: '/h5bp/html5-boilerplate/-/environments/41',
stopPath: '/h5bp/html5-boilerplate/-/environments/41/stop',
cancelAutoStopPath: '/h5bp/html5-boilerplate/-/environments/41/cancel_auto_stop',
deletePath: '/api/v4/projects/8/environments/41',
folderPath: '/h5bp/html5-boilerplate/-/environments/folders/review',
createdAt: '2021-10-04T19:27:00.527Z',
updatedAt: '2021-10-04T19:27:00.527Z',
canStop: true,
logsPath: '/h5bp/html5-boilerplate/-/logs?environment_name=review%2Fhello',
logsApiPath: '/h5bp/html5-boilerplate/-/logs/k8s.json?environment_name=review%2Fhello',
enableAdvancedLogsQuerying: false,
canDelete: false,
hasOpenedAlert: false,
__typename: 'LocalEnvironment',
};
export const resolvedFolder = {
availableCount: 2,
environments: [
......
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { resolvers } from '~/environments/graphql/resolvers';
import environmentToRollback from '~/environments/graphql/queries/environment_to_rollback.query.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql';
import { TEST_HOST } from 'helpers/test_constants';
import { environmentsApp, resolvedEnvironmentsApp, folder, resolvedFolder } from './mock_data';
import {
environmentsApp,
resolvedEnvironmentsApp,
resolvedEnvironment,
folder,
resolvedFolder,
} from './mock_data';
const ENDPOINT = `${TEST_HOST}/environments`;
describe('~/frontend/environments/graphql/resolvers', () => {
let mockResolvers;
let mock;
let mockApollo;
let localState;
beforeEach(() => {
mockResolvers = resolvers(ENDPOINT);
mock = new MockAdapter(axios);
mockApollo = createMockApollo();
localState = mockApollo.defaultClient.localState;
});
afterEach(() => {
......@@ -108,4 +120,19 @@ describe('~/frontend/environments/graphql/resolvers', () => {
);
});
});
describe('setEnvironmentToRollback', () => {
it('should write the given environment to the cache', () => {
localState.client.writeQuery = jest.fn();
mockResolvers.Mutation.setEnvironmentToRollback(
null,
{ environment: resolvedEnvironment },
localState,
);
expect(localState.client.writeQuery).toHaveBeenCalledWith({
query: environmentToRollback,
data: { environmentToRollback: resolvedEnvironment },
});
});
});
});
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