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'; ...@@ -7,6 +7,7 @@ import { escape } from 'lodash';
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
import { __, s__, sprintf } from '~/locale'; import { __, s__, sprintf } from '~/locale';
import rollbackEnvironment from '../graphql/mutations/rollback_environment.mutation.graphql';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
export default { export default {
...@@ -40,10 +41,15 @@ export default { ...@@ -40,10 +41,15 @@ export default {
required: false, required: false,
default: null, default: null,
}, },
graphql: {
type: Boolean,
required: false,
default: false,
},
}, },
computed: { computed: {
modalTitle() { modalTitle() {
const title = this.environment.isLastDeployment const title = this.isLastDeployment
? s__('Environments|Re-deploy environment %{name}?') ? s__('Environments|Re-deploy environment %{name}?')
: s__('Environments|Rollback environment %{name}?'); : s__('Environments|Rollback environment %{name}?');
...@@ -53,6 +59,11 @@ export default { ...@@ -53,6 +59,11 @@ export default {
}, },
commitShortSha() { commitShortSha() {
if (this.hasMultipleCommits) { if (this.hasMultipleCommits) {
if (this.graphql) {
const { lastDeployment } = this.environment;
return this.commitData(lastDeployment, 'shortId');
}
const { last_deployment } = this.environment; const { last_deployment } = this.environment;
return this.commitData(last_deployment, 'short_id'); return this.commitData(last_deployment, 'short_id');
} }
...@@ -61,6 +72,11 @@ export default { ...@@ -61,6 +72,11 @@ export default {
}, },
commitUrl() { commitUrl() {
if (this.hasMultipleCommits) { if (this.hasMultipleCommits) {
if (this.graphql) {
const { lastDeployment } = this.environment;
return this.commitData(lastDeployment, 'commitPath');
}
const { last_deployment } = this.environment; const { last_deployment } = this.environment;
return this.commitData(last_deployment, 'commit_path'); return this.commitData(last_deployment, 'commit_path');
} }
...@@ -68,9 +84,7 @@ export default { ...@@ -68,9 +84,7 @@ export default {
return this.environment.commitUrl; return this.environment.commitUrl;
}, },
modalActionText() { modalActionText() {
return this.environment.isLastDeployment return this.isLastDeployment ? s__('Environments|Re-deploy') : s__('Environments|Rollback');
? s__('Environments|Re-deploy')
: s__('Environments|Rollback');
}, },
primaryProps() { primaryProps() {
let attributes = [{ variant: 'danger' }]; let attributes = [{ variant: 'danger' }];
...@@ -84,20 +98,27 @@ export default { ...@@ -84,20 +98,27 @@ export default {
attributes, attributes,
}; };
}, },
isLastDeployment() {
// eslint-disable-next-line @gitlab/require-i18n-strings
return this.environment?.isLastDeployment || this.environment?.lastDeployment?.['last?'];
},
}, },
methods: { methods: {
handleChange(event) { handleChange(event) {
this.$emit('change', event); this.$emit('change', event);
}, },
onOk() { onOk() {
eventHub.$emit('rollbackEnvironment', this.environment); if (this.graphql) {
this.$apollo.mutate({
mutation: rollbackEnvironment,
variables: { environment: this.environment },
});
} else {
eventHub.$emit('rollbackEnvironment', this.environment);
}
}, },
commitData(lastDeployment, key) { commitData(lastDeployment, key) {
if (lastDeployment && lastDeployment.commit) { return lastDeployment?.commit?.[key] ?? '';
return lastDeployment.commit[key];
}
return '';
}, },
}, },
csrf, csrf,
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
import { GlModalDirective, GlDropdownItem } from '@gitlab/ui'; import { GlModalDirective, GlDropdownItem } from '@gitlab/ui';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import eventHub from '../event_hub'; import eventHub from '../event_hub';
import setEnvironmentToRollback from '../graphql/mutations/set_environment_to_rollback.mutation.graphql';
export default { export default {
components: { components: {
...@@ -32,11 +33,12 @@ export default { ...@@ -32,11 +33,12 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
},
data() { graphql: {
return { type: Boolean,
isLoading: false, required: false,
}; default: false,
},
}, },
computed: { computed: {
...@@ -49,16 +51,18 @@ export default { ...@@ -49,16 +51,18 @@ export default {
methods: { methods: {
onClick() { onClick() {
eventHub.$emit('requestRollbackEnvironment', { if (this.graphql) {
...this.environment, this.$apollo.mutate({
retryUrl: this.retryUrl, mutation: setEnvironmentToRollback,
isLastDeployment: this.isLastDeployment, variables: { environment: this.environment },
}); });
eventHub.$on('rollbackEnvironment', (environment) => { } else {
if (environment.id === this.environment.id) { eventHub.$emit('requestRollbackEnvironment', {
this.isLoading = true; ...this.environment,
} retryUrl: this.retryUrl,
}); isLastDeployment: this.isLastDeployment,
});
}
}, },
}, },
}; };
......
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'; ...@@ -2,6 +2,7 @@ import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale'; import { s__ } from '~/locale';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
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';
const buildErrors = (errors = []) => ({ const buildErrors = (errors = []) => ({
errors, errors,
...@@ -84,6 +85,12 @@ export const resolvers = (endpoint) => ({ ...@@ -84,6 +85,12 @@ export const resolvers = (endpoint) => ({
]); ]);
}); });
}, },
setEnvironmentToRollback(_, { environment }, { client }) {
client.writeQuery({
query: environmentToRollbackQuery,
data: { environmentToRollback: environment },
});
},
cancelAutoStop(_, { environment: { autoStopPath } }) { cancelAutoStop(_, { environment: { autoStopPath } }) {
return axios return axios
.post(autoStopPath) .post(autoStopPath)
......
...@@ -58,6 +58,7 @@ type LocalErrors { ...@@ -58,6 +58,7 @@ type LocalErrors {
extend type Query { extend type Query {
environmentApp: LocalEnvironmentApp environmentApp: LocalEnvironmentApp
folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder
environmentToRollback: LocalEnvironment
isLastDeployment: Boolean isLastDeployment: Boolean
} }
...@@ -66,4 +67,5 @@ extend type Mutation { ...@@ -66,4 +67,5 @@ extend type Mutation {
deleteEnvironment(environment: LocalEnvironmentInput): LocalErrors deleteEnvironment(environment: LocalEnvironmentInput): LocalErrors
rollbackEnvironment(environment: LocalEnvironmentInput): LocalErrors rollbackEnvironment(environment: LocalEnvironmentInput): LocalErrors
cancelAutoStop(environment: LocalEnvironmentInput): LocalErrors cancelAutoStop(environment: LocalEnvironmentInput): LocalErrors
setEnvironmentToRollback(environment: LocalEnvironmentInput): LocalErrors
} }
import { GlModal, GlSprintf } from '@gitlab/ui'; import { GlModal, GlSprintf } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; 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 ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import eventHub from '~/environments/event_hub'; import eventHub from '~/environments/event_hub';
describe('Confirm Rollback Modal Component', () => { describe('Confirm Rollback Modal Component', () => {
...@@ -17,6 +20,17 @@ describe('Confirm Rollback Modal Component', () => { ...@@ -17,6 +20,17 @@ describe('Confirm Rollback Modal Component', () => {
modalId: 'test', modalId: 'test',
}; };
const envWithLastDeploymentGraphql = {
name: 'test',
lastDeployment: {
commit: {
shortId: 'abc0123',
},
'last?': true,
},
modalId: 'test',
};
const envWithoutLastDeployment = { const envWithoutLastDeployment = {
name: 'test', name: 'test',
modalId: 'test', modalId: 'test',
...@@ -26,7 +40,7 @@ describe('Confirm Rollback Modal Component', () => { ...@@ -26,7 +40,7 @@ describe('Confirm Rollback Modal Component', () => {
const retryPath = 'test/-/jobs/123/retry'; const retryPath = 'test/-/jobs/123/retry';
const createComponent = (props = {}) => { const createComponent = (props = {}, options = {}) => {
component = shallowMount(ConfirmRollbackModal, { component = shallowMount(ConfirmRollbackModal, {
propsData: { propsData: {
...props, ...props,
...@@ -34,6 +48,7 @@ describe('Confirm Rollback Modal Component', () => { ...@@ -34,6 +48,7 @@ describe('Confirm Rollback Modal Component', () => {
stubs: { stubs: {
GlSprintf, GlSprintf,
}, },
...options,
}); });
}; };
...@@ -101,4 +116,121 @@ describe('Confirm Rollback Modal Component', () => { ...@@ -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 { GlDropdownItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import RollbackComponent from '~/environments/components/environment_rollback.vue'; import RollbackComponent from '~/environments/components/environment_rollback.vue';
import eventHub from '~/environments/event_hub'; 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', () => { describe('Rollback Component', () => {
const retryUrl = 'https://gitlab.com/retry'; const retryUrl = 'https://gitlab.com/retry';
...@@ -50,4 +54,29 @@ describe('Rollback Component', () => { ...@@ -50,4 +54,29 @@ describe('Rollback Component', () => {
name: 'test', 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 = { ...@@ -469,6 +469,33 @@ export const folder = {
stopped_count: 0, 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 = { export const resolvedFolder = {
availableCount: 2, availableCount: 2,
environments: [ environments: [
......
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils'; 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 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 { TEST_HOST } from 'helpers/test_constants'; 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`; const ENDPOINT = `${TEST_HOST}/environments`;
describe('~/frontend/environments/graphql/resolvers', () => { describe('~/frontend/environments/graphql/resolvers', () => {
let mockResolvers; let mockResolvers;
let mock; let mock;
let mockApollo;
let localState;
beforeEach(() => { beforeEach(() => {
mockResolvers = resolvers(ENDPOINT); mockResolvers = resolvers(ENDPOINT);
mock = new MockAdapter(axios); mock = new MockAdapter(axios);
mockApollo = createMockApollo();
localState = mockApollo.defaultClient.localState;
}); });
afterEach(() => { afterEach(() => {
...@@ -108,4 +120,19 @@ describe('~/frontend/environments/graphql/resolvers', () => { ...@@ -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