Commit 7b3b0024 authored by Natalia Tepluhina's avatar Natalia Tepluhina

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

Move Environments App Query to Use Polling

See merge request gitlab-org/gitlab!75259
parents c4a14b1e fafaf43f
<script>
import { GlBadge, GlTab, GlTabs } from '@gitlab/ui';
import environmentAppQuery from '../graphql/queries/environmentApp.query.graphql';
import environmentAppQuery from '../graphql/queries/environment_app.query.graphql';
import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql';
import EnvironmentFolder from './new_environment_folder.vue';
export default {
......@@ -13,7 +14,16 @@ export default {
apollo: {
environmentApp: {
query: environmentAppQuery,
pollInterval() {
return this.interval;
},
},
interval: {
query: pollIntervalQuery,
},
},
data() {
return { interval: undefined };
},
computed: {
folders() {
......
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
import environmentApp from './queries/environmentApp.query.graphql';
import environmentApp from './queries/environment_app.query.graphql';
import { resolvers } from './resolvers';
import typeDefs from './typedefs.graphql';
......
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';
const buildErrors = (errors = []) => ({
errors,
__typename: 'LocalEnvironmentErrors',
});
const mapNestedEnvironment = (env) => ({
...convertObjectPropsToCamelCase(env, { deep: true }),
......@@ -12,17 +19,27 @@ const mapEnvironment = (env) => ({
export const resolvers = (endpoint) => ({
Query: {
environmentApp() {
return axios.get(endpoint, { params: { nested: true } }).then((res) => ({
availableCount: res.data.available_count,
environments: res.data.environments.map(mapNestedEnvironment),
reviewApp: {
...convertObjectPropsToCamelCase(res.data.review_app),
__typename: 'ReviewApp',
},
stoppedCount: res.data.stopped_count,
__typename: 'LocalEnvironmentApp',
}));
environmentApp(_context, _variables, { cache }) {
return axios.get(endpoint, { params: { nested: true } }).then((res) => {
const interval = res.headers['poll-interval'];
if (interval) {
cache.writeQuery({ query: pollIntervalQuery, data: { interval } });
} else {
cache.writeQuery({ query: pollIntervalQuery, data: { interval: undefined } });
}
return {
availableCount: res.data.available_count,
environments: res.data.environments.map(mapNestedEnvironment),
reviewApp: {
...convertObjectPropsToCamelCase(res.data.review_app),
__typename: 'ReviewApp',
},
stoppedCount: res.data.stopped_count,
__typename: 'LocalEnvironmentApp',
};
});
},
folder(_, { environment: { folderPath } }) {
return axios.get(folderPath, { params: { per_page: 3 } }).then((res) => ({
......@@ -32,19 +49,51 @@ export const resolvers = (endpoint) => ({
__typename: 'LocalEnvironmentFolder',
}));
},
isLastDeployment(_, { environment }) {
// eslint-disable-next-line @gitlab/require-i18n-strings
return environment?.lastDeployment?.['last?'];
},
},
Mutations: {
stopEnvironment(_, { environment: { stopPath } }) {
return axios.post(stopPath);
Mutation: {
stopEnvironment(_, { environment }) {
return axios
.post(environment.stopPath)
.then(() => buildErrors())
.catch(() => {
return buildErrors([
s__('Environments|An error occurred while stopping the environment, please try again'),
]);
});
},
deleteEnvironment(_, { environment: { deletePath } }) {
return axios.delete(deletePath);
},
rollbackEnvironment(_, { environment: { retryUrl } }) {
return axios.post(retryUrl);
rollbackEnvironment(_, { environment, isLastDeployment }) {
return axios
.post(environment?.retryUrl)
.then(() => buildErrors())
.catch(() => {
buildErrors([
isLastDeployment
? s__(
'Environments|An error occurred while re-deploying the environment, please try again',
)
: s__(
'Environments|An error occurred while rolling back the environment, please try again',
),
]);
});
},
cancelAutoStop(_, { environment: { autoStopPath } }) {
return axios.post(autoStopPath);
return axios
.post(autoStopPath)
.then(() => buildErrors())
.catch((err) =>
buildErrors([
err?.response?.data?.message ||
s__('Environments|An error occurred while canceling the auto stop, please try again'),
]),
);
},
},
});
......@@ -33,3 +33,20 @@ type LocalEnvironmentApp {
environments: [NestedLocalEnvironment!]!
reviewApp: ReviewApp!
}
type LocalErrors {
errors: [String!]!
}
extend type Query {
environmentApp: LocalEnvironmentApp
folder(environment: NestedLocalEnvironment): LocalEnvironmentFolder
isLastDeployment: Boolean
}
extend type Mutation {
stopEnvironment(environment: LocalEnvironment): LocalErrors
deleteEnvironment(environment: LocalEnvironment): LocalErrors
rollbackEnvironment(environment: LocalEnvironment): LocalErrors
cancelAutoStop(environment: LocalEnvironment): LocalErrors
}
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { resolvers } from '~/environments/graphql/resolvers';
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';
......@@ -21,10 +22,27 @@ describe('~/frontend/environments/graphql/resolvers', () => {
describe('environmentApp', () => {
it('should fetch environments and map them to frontend data', async () => {
mock.onGet(ENDPOINT, { params: { nested: true } }).reply(200, environmentsApp);
const cache = { writeQuery: jest.fn() };
mock.onGet(ENDPOINT, { params: { nested: true } }).reply(200, environmentsApp, {});
const app = await mockResolvers.Query.environmentApp();
const app = await mockResolvers.Query.environmentApp(null, null, { cache });
expect(app).toEqual(resolvedEnvironmentsApp);
expect(cache.writeQuery).toHaveBeenCalledWith({
query: pollIntervalQuery,
data: { interval: undefined },
});
});
it('should set the poll interval when there is one', async () => {
const cache = { writeQuery: jest.fn() };
mock
.onGet(ENDPOINT, { params: { nested: true } })
.reply(200, environmentsApp, { 'poll-interval': 3000 });
await mockResolvers.Query.environmentApp(null, null, { cache });
expect(cache.writeQuery).toHaveBeenCalledWith({
query: pollIntervalQuery,
data: { interval: 3000 },
});
});
});
describe('folder', () => {
......@@ -42,7 +60,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
it('should post to the stop environment path', async () => {
mock.onPost(ENDPOINT).reply(200);
await mockResolvers.Mutations.stopEnvironment(null, { environment: { stopPath: ENDPOINT } });
await mockResolvers.Mutation.stopEnvironment(null, { environment: { stopPath: ENDPOINT } });
expect(mock.history.post).toContainEqual(
expect.objectContaining({ url: ENDPOINT, method: 'post' }),
......@@ -53,7 +71,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
it('should post to the retry environment path', async () => {
mock.onPost(ENDPOINT).reply(200);
await mockResolvers.Mutations.rollbackEnvironment(null, {
await mockResolvers.Mutation.rollbackEnvironment(null, {
environment: { retryUrl: ENDPOINT },
});
......@@ -66,7 +84,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
it('should DELETE to the delete environment path', async () => {
mock.onDelete(ENDPOINT).reply(200);
await mockResolvers.Mutations.deleteEnvironment(null, {
await mockResolvers.Mutation.deleteEnvironment(null, {
environment: { deletePath: ENDPOINT },
});
......@@ -79,7 +97,7 @@ describe('~/frontend/environments/graphql/resolvers', () => {
it('should post to the auto stop path', async () => {
mock.onPost(ENDPOINT).reply(200);
await mockResolvers.Mutations.cancelAutoStop(null, {
await mockResolvers.Mutation.cancelAutoStop(null, {
environment: { autoStopPath: ENDPOINT },
});
......
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