Skip to content
Snippets Groups Projects
Commit fb0507ab authored by Anna Vovchenko's avatar Anna Vovchenko :flag_ua: Committed by Jose Ivan Vargas
Browse files

Use GraphQl to update environment info

Update `edit_environment` to use GraphQl for updating environment info

Changelog: changed
parent 3d6884cb
No related branches found
No related tags found
1 merge request!121503Allow to select agent in environment setting page
......@@ -60,6 +60,7 @@ export default {
input: {
id: this.formEnvironment.id,
externalUrl: this.formEnvironment.externalUrl,
clusterAgentId: this.formEnvironment.clusterAgentId,
},
},
});
......
<script>
import { GlButton, GlForm, GlFormGroup, GlFormInput, GlLink, GlSprintf } from '@gitlab/ui';
import {
GlButton,
GlForm,
GlFormGroup,
GlFormInput,
GlCollapsibleListbox,
GlLink,
GlSprintf,
} from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { isAbsolute } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import { __, s__ } from '~/locale';
import {
ENVIRONMENT_NEW_HELP_TEXT,
ENVIRONMENT_EDIT_HELP_TEXT,
} from 'ee_else_ce/environments/constants';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import getUserAuthorizedAgents from '../graphql/queries/user_authorized_agents.query.graphql';
export default {
components: {
......@@ -14,10 +24,15 @@ export default {
GlForm,
GlFormGroup,
GlFormInput,
GlCollapsibleListbox,
GlLink,
GlSprintf,
},
inject: { protectedEnvironmentSettingsPath: { default: '' } },
mixins: [glFeatureFlagsMixin()],
inject: {
protectedEnvironmentSettingsPath: { default: '' },
projectPath: { default: '' },
},
props: {
environment: {
required: true,
......@@ -47,8 +62,11 @@ export default {
nameDisabledLinkText: __('How do I rename an environment?'),
urlLabel: __('External URL'),
urlFeedback: __('The URL should start with http:// or https://'),
agentLabel: s__('Environments|GitLab agent'),
agentHelpText: s__('Environments|Select agent'),
save: __('Save'),
cancel: __('Cancel'),
reset: __('Reset'),
},
helpPagePath: helpPagePath('ci/environments/index.md'),
renamingDisabledHelpPagePath: helpPagePath('ci/environments/index.md', {
......@@ -60,6 +78,10 @@ export default {
name: null,
url: null,
},
userAccessAuthorizedAgents: [],
loadingAgentsList: false,
selectedAgentId: this.environment.clusterAgentId,
searchTerm: '',
};
},
computed: {
......@@ -75,6 +97,37 @@ export default {
url: this.visited.url && isAbsolute(this.environment.externalUrl),
};
},
agentsList() {
return this.userAccessAuthorizedAgents.map((node) => {
return {
value: node?.agent?.id,
text: node?.agent?.name,
};
});
},
dropdownToggleText() {
if (!this.selectedAgentId) {
return this.$options.i18n.agentHelpText;
}
const selectedAgentById = this.agentsList.find(
(agent) => agent.value === this.selectedAgentId,
);
return selectedAgentById?.text;
},
filteredAgentsList() {
const lowerCasedSearchTerm = this.searchTerm.toLowerCase();
return this.agentsList.filter((item) =>
item.text.toLowerCase().includes(lowerCasedSearchTerm),
);
},
showAgentsSelect() {
return this.glFeatures?.environmentSettingsToGraphql;
},
},
watch: {
environment(change) {
this.selectedAgentId = change.clusterAgentId;
},
},
methods: {
onChange(env) {
......@@ -83,6 +136,23 @@ export default {
visit(field) {
this.visited[field] = true;
},
getAgentsList() {
this.$apollo.addSmartQuery('userAccessAuthorizedAgents', {
variables() {
return { projectFullPath: this.projectPath };
},
query: getUserAuthorizedAgents,
update: (data) => {
return data?.project?.userAccessAuthorizedAgents?.nodes || [];
},
watchLoading: (isLoading) => {
this.loadingAgentsList = isLoading;
},
});
},
onAgentSearch(search) {
this.searchTerm = search;
},
},
};
</script>
......@@ -153,6 +223,29 @@ export default {
/>
</gl-form-group>
<gl-form-group
v-if="showAgentsSelect"
:label="$options.i18n.agentLabel"
label-for="environment_agent"
>
<gl-collapsible-listbox
id="environment_agent"
v-model="selectedAgentId"
class="gl-w-full"
block
:items="filteredAgentsList"
:loading="loadingAgentsList"
:toggle-text="dropdownToggleText"
:header-text="$options.i18n.agentHelpText"
:reset-button-label="$options.i18n.reset"
:searchable="true"
@shown="getAgentsList"
@search="onAgentSearch"
@select="onChange({ ...environment, clusterAgentId: $event })"
@reset="onChange({ ...environment, clusterAgentId: null })"
/>
</gl-form-group>
<div class="gl-mr-6">
<gl-button
:loading="loading"
......
......@@ -42,6 +42,7 @@ export default {
name: this.environment.name,
externalUrl: this.environment.externalUrl,
projectPath: this.projectPath,
clusterAgentId: this.environment.clusterAgentId,
},
},
});
......
query getUserAuthorizedAgents($projectFullPath: ID!) {
project(fullPath: $projectFullPath) {
id
userAccessAuthorizedAgents {
nodes {
agent {
id
name
}
}
}
}
}
......@@ -17205,6 +17205,9 @@ msgstr ""
msgid "Environments|Get started with environments"
msgstr ""
 
msgid "Environments|GitLab agent"
msgstr ""
msgid "Environments|Job"
msgstr ""
 
......@@ -17259,6 +17262,9 @@ msgstr ""
msgid "Environments|Search by environment name"
msgstr ""
 
msgid "Environments|Select agent"
msgstr ""
msgid "Environments|Select which environments to clean up. Protected environments are excluded. Learn more about cleaning up environments."
msgstr ""
 
......@@ -20,7 +20,10 @@ jest.mock('~/alert');
const newExternalUrl = 'https://google.ca';
const environment = { id: '1', name: 'foo', externalUrl: 'https://foo.example.com' };
const resolvedEnvironment = { project: { id: '1', environment } };
const environmentUpdate = { environment: { id: '1', path: 'path/to/environment' }, errors: [] };
const environmentUpdate = {
environment: { id: '1', path: 'path/to/environment', clusterAgentId: null },
errors: [],
};
const environmentUpdateError = {
environment: null,
errors: [{ message: 'uh oh!' }],
......
import { GlLoadingIcon } from '@gitlab/ui';
import { GlLoadingIcon, GlCollapsibleListbox } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import waitForPromises from 'helpers/wait_for_promises';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import EnvironmentForm from '~/environments/components/environment_form.vue';
import getUserAuthorizedAgents from '~/environments/graphql/queries/user_authorized_agents.query.graphql';
import createMockApollo from '../__helpers__/mock_apollo_helper';
jest.mock('~/lib/utils/csrf');
......@@ -11,6 +16,10 @@ const DEFAULT_PROPS = {
};
const PROVIDE = { protectedEnvironmentSettingsPath: '/projects/not_real/settings/ci_cd' };
const userAccessAuthorizedAgents = [
{ agent: { id: '1', name: 'agent-1' } },
{ agent: { id: '2', name: 'agent-2' } },
];
describe('~/environments/components/form.vue', () => {
let wrapper;
......@@ -25,6 +34,38 @@ describe('~/environments/components/form.vue', () => {
},
});
const createWrapperWithApollo = ({ propsData = {} } = {}) => {
Vue.use(VueApollo);
return mountExtended(EnvironmentForm, {
provide: {
...PROVIDE,
glFeatures: {
environmentSettingsToGraphql: true,
},
},
propsData: {
...DEFAULT_PROPS,
...propsData,
},
apolloProvider: createMockApollo([
[
getUserAuthorizedAgents,
jest.fn().mockResolvedValue({
data: {
project: {
id: '1',
userAccessAuthorizedAgents: { nodes: userAccessAuthorizedAgents },
},
},
}),
],
]),
});
};
const findAgentSelector = () => wrapper.findComponent(GlCollapsibleListbox);
describe('default', () => {
beforeEach(() => {
wrapper = createWrapper();
......@@ -167,4 +208,66 @@ describe('~/environments/components/form.vue', () => {
expect(urlInput.element.value).toBe('https://example.com');
});
});
describe('when `environmentSettingsToGraphql feature flag is enabled', () => {
beforeEach(() => {
wrapper = createWrapperWithApollo();
});
it('renders an agent selector listbox', () => {
expect(findAgentSelector().props()).toMatchObject({
searchable: true,
toggleText: EnvironmentForm.i18n.agentHelpText,
headerText: EnvironmentForm.i18n.agentHelpText,
resetButtonLabel: EnvironmentForm.i18n.reset,
loading: false,
items: [],
});
});
it('sets the items prop of the agent selector after fetching the list', async () => {
findAgentSelector().vm.$emit('shown');
await waitForPromises();
expect(findAgentSelector().props('items')).toEqual([
{ value: '1', text: 'agent-1' },
{ value: '2', text: 'agent-2' },
]);
});
it('sets the loading prop of the agent selector while fetching the list', async () => {
await findAgentSelector().vm.$emit('shown');
expect(findAgentSelector().props('loading')).toBe(true);
await waitForPromises();
expect(findAgentSelector().props('loading')).toBe(false);
});
it('filters the agent list on user search', async () => {
findAgentSelector().vm.$emit('shown');
await waitForPromises();
await findAgentSelector().vm.$emit('search', 'agent-2');
expect(findAgentSelector().props('items')).toEqual([{ value: '2', text: 'agent-2' }]);
});
it('updates agent selector field with the name of selected agent', async () => {
findAgentSelector().vm.$emit('shown');
await waitForPromises();
await findAgentSelector().vm.$emit('select', '2');
expect(findAgentSelector().props('toggleText')).toBe('agent-2');
});
it('emits changes to the clusterAgentId', async () => {
findAgentSelector().vm.$emit('shown');
await waitForPromises();
await findAgentSelector().vm.$emit('select', '2');
expect(wrapper.emitted('change')).toEqual([
[{ name: '', externalUrl: '', clusterAgentId: '2' }],
]);
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment