Commit 3a14b9ac authored by peterhegman's avatar peterhegman

Render initially selected projects on load

On the access token form render initially selected projects. Will be
used when validation fails
parent 03a90e30
...@@ -15,7 +15,9 @@ export default { ...@@ -15,7 +15,9 @@ export default {
}, },
data() { data() {
return { return {
selectedRadio: this.$options.ALL_PROJECTS, selectedRadio: !this.inputAttrs.value
? this.$options.ALL_PROJECTS
: this.$options.SELECTED_PROJECTS,
selectedProjects: [], selectedProjects: [],
}; };
}, },
...@@ -28,6 +30,13 @@ export default { ...@@ -28,6 +30,13 @@ export default {
? null ? null
: this.selectedProjects.map((project) => project.id).join(','); : this.selectedProjects.map((project) => project.id).join(',');
}, },
initialProjectIds() {
if (!this.inputAttrs.value) {
return [];
}
return this.inputAttrs.value.split(',');
},
}, },
methods: { methods: {
handleTokenSelectorFocus() { handleTokenSelectorFocus() {
...@@ -50,7 +59,11 @@ export default { ...@@ -50,7 +59,11 @@ export default {
__('Selected projects') __('Selected projects')
}}</gl-form-radio> }}</gl-form-radio>
<input :id="inputAttrs.id" type="hidden" :name="inputAttrs.name" :value="hiddenInputValue" /> <input :id="inputAttrs.id" type="hidden" :name="inputAttrs.name" :value="hiddenInputValue" />
<projects-token-selector v-model="selectedProjects" @focus="handleTokenSelectorFocus" /> <projects-token-selector
v-model="selectedProjects"
:initial-project-ids="initialProjectIds"
@focus="handleTokenSelectorFocus"
/>
</gl-form-group> </gl-form-group>
</div> </div>
</template> </template>
...@@ -8,12 +8,13 @@ import { ...@@ -8,12 +8,13 @@ import {
} from '@gitlab/ui'; } from '@gitlab/ui';
import produce from 'immer'; import produce from 'immer';
import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import getProjectsQuery from '../graphql/queries/get_projects.query.graphql'; import getProjectsQuery from '../graphql/queries/get_projects.query.graphql';
const DEBOUNCE_DELAY = 250; const DEBOUNCE_DELAY = 250;
const PROJECTS_PER_PAGE = 20; const PROJECTS_PER_PAGE = 20;
const GRAPHQL_ENTITY_TYPE = 'Project';
export default { export default {
name: 'ProjectsTokenSelector', name: 'ProjectsTokenSelector',
...@@ -32,6 +33,10 @@ export default { ...@@ -32,6 +33,10 @@ export default {
type: Array, type: Array,
required: true, required: true,
}, },
initialProjectIds: {
type: Array,
required: true,
},
}, },
apollo: { apollo: {
projects: { projects: {
...@@ -46,10 +51,7 @@ export default { ...@@ -46,10 +51,7 @@ export default {
}, },
update({ projects }) { update({ projects }) {
return { return {
list: projects.nodes.map((project) => ({ list: this.formatProjectNodes(projects),
...project,
id: getIdFromGraphQLId(project.id),
})),
pageInfo: projects.pageInfo, pageInfo: projects.pageInfo,
}; };
}, },
...@@ -58,6 +60,21 @@ export default { ...@@ -58,6 +60,21 @@ export default {
this.isSearching = false; this.isSearching = false;
}, },
}, },
initialProjects: {
query: getProjectsQuery,
variables() {
return {
ids: this.initialProjectIds.map((id) => convertToGraphQLId(GRAPHQL_ENTITY_TYPE, id)),
};
},
manual: true,
skip() {
return !this.initialProjectIds.length;
},
result({ data: { projects } }) {
this.$emit('input', this.formatProjectNodes(projects));
},
},
}, },
data() { data() {
return { return {
...@@ -71,6 +88,12 @@ export default { ...@@ -71,6 +88,12 @@ export default {
}; };
}, },
methods: { methods: {
formatProjectNodes(projects) {
return projects.nodes.map((project) => ({
...project,
id: getIdFromGraphQLId(project.id),
}));
},
handleSearch(query) { handleSearch(query) {
this.isSearching = true; this.isSearching = true;
this.searchQuery = query; this.searchQuery = query;
......
#import "~/graphql_shared/fragments/pageInfo.fragment.graphql" #import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
query getProjects($search: String!, $after: String = "", $first: Int!) { query getProjects(
$search: String = ""
$after: String = ""
$first: Int = null
$ids: [ID!] = null
) {
projects( projects(
search: $search search: $search
after: $after after: $after
first: $first first: $first
ids: $ids
membership: true membership: true
searchNamespaces: true searchNamespaces: true
sort: "UPDATED_ASC" sort: "UPDATED_ASC"
......
...@@ -10,6 +10,7 @@ const getInputAttrs = (el) => { ...@@ -10,6 +10,7 @@ const getInputAttrs = (el) => {
return { return {
id: input.id, id: input.id,
name: input.name, name: input.name,
value: input.value,
placeholder: input.placeholder, placeholder: input.placeholder,
}; };
}; };
......
...@@ -6,12 +6,13 @@ import ProjectsTokenSelector from '~/access_tokens/components/projects_token_sel ...@@ -6,12 +6,13 @@ import ProjectsTokenSelector from '~/access_tokens/components/projects_token_sel
describe('ProjectsField', () => { describe('ProjectsField', () => {
let wrapper; let wrapper;
const createComponent = () => { const createComponent = ({ inputAttrsValue = '' } = {}) => {
wrapper = mount(ProjectsField, { wrapper = mount(ProjectsField, {
propsData: { propsData: {
inputAttrs: { inputAttrs: {
id: 'projects', id: 'projects',
name: 'projects', name: 'projects',
value: inputAttrsValue,
}, },
}, },
}); });
...@@ -24,39 +25,63 @@ describe('ProjectsField', () => { ...@@ -24,39 +25,63 @@ describe('ProjectsField', () => {
const findProjectsTokenSelector = () => wrapper.findComponent(ProjectsTokenSelector); const findProjectsTokenSelector = () => wrapper.findComponent(ProjectsTokenSelector);
const findHiddenInput = () => wrapper.find('input[type="hidden"]'); const findHiddenInput = () => wrapper.find('input[type="hidden"]');
beforeEach(() => {
createComponent();
});
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null; wrapper = null;
}); });
it('renders label and sub-label', () => { it('renders label and sub-label', () => {
createComponent();
expect(queryByText('Projects')).not.toBe(null); expect(queryByText('Projects')).not.toBe(null);
expect(queryByText('Set access permissions for this token.')).not.toBe(null); expect(queryByText('Set access permissions for this token.')).not.toBe(null);
}); });
it('renders "All projects" radio selected by default', () => { describe('when `inputAttrs.value` is empty', () => {
const allProjectsRadio = findAllProjectsRadio(); beforeEach(() => {
createComponent();
});
it('renders "All projects" radio as checked', () => {
expect(findAllProjectsRadio().checked).toBe(true);
});
it('renders "Selected projects" radio as unchecked', () => {
expect(findSelectedProjectsRadio().checked).toBe(false);
});
it('sets `projects-token-selector` `initialProjectIds` prop to an empty array', () => {
expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual([]);
});
});
expect(allProjectsRadio).not.toBe(null); describe('when `inputAttrs.value` is a comma separated list of project IDs', () => {
expect(allProjectsRadio.checked).toBe(true); beforeEach(() => {
createComponent({ inputAttrsValue: '1,2' });
}); });
it('renders "Selected projects" radio unchecked by default', () => { it('renders "All projects" radio as unchecked', () => {
const selectedProjectsRadio = findSelectedProjectsRadio(); expect(findAllProjectsRadio().checked).toBe(false);
});
it('renders "Selected projects" radio as checked', () => {
expect(findSelectedProjectsRadio().checked).toBe(true);
});
expect(selectedProjectsRadio).not.toBe(null); it('sets `projects-token-selector` `initialProjectIds` prop to an array of project IDs', () => {
expect(selectedProjectsRadio.checked).toBe(false); expect(findProjectsTokenSelector().props('initialProjectIds')).toEqual(['1', '2']);
});
}); });
it('renders `projects-token-selector` component', () => { it('renders `projects-token-selector` component', () => {
createComponent();
expect(findProjectsTokenSelector().exists()).toBe(true); expect(findProjectsTokenSelector().exists()).toBe(true);
}); });
it('renders hidden input with correct `name` and `id` attributes', () => { it('renders hidden input with correct `name` and `id` attributes', () => {
createComponent();
expect(findHiddenInput().attributes()).toEqual( expect(findHiddenInput().attributes()).toEqual(
expect.objectContaining({ expect.objectContaining({
id: 'projects', id: 'projects',
...@@ -67,6 +92,8 @@ describe('ProjectsField', () => { ...@@ -67,6 +92,8 @@ describe('ProjectsField', () => {
describe('when `projects-token-selector` is focused', () => { describe('when `projects-token-selector` is focused', () => {
beforeEach(() => { beforeEach(() => {
createComponent();
findProjectsTokenSelector().vm.$emit('focus'); findProjectsTokenSelector().vm.$emit('focus');
}); });
......
...@@ -44,10 +44,15 @@ describe('ProjectsTokenSelector', () => { ...@@ -44,10 +44,15 @@ describe('ProjectsTokenSelector', () => {
let wrapper; let wrapper;
let resolveGetProjectsQuery; let resolveGetProjectsQuery;
let resolveGetInitialProjectsQuery;
const getProjectsQueryRequestHandler = jest.fn( const getProjectsQueryRequestHandler = jest.fn(
() => ({ ids }) =>
new Promise((resolve) => { new Promise((resolve) => {
if (ids) {
resolveGetInitialProjectsQuery = resolve;
} else {
resolveGetProjectsQuery = resolve; resolveGetProjectsQuery = resolve;
}
}), }),
); );
...@@ -63,6 +68,7 @@ describe('ProjectsTokenSelector', () => { ...@@ -63,6 +68,7 @@ describe('ProjectsTokenSelector', () => {
apolloProvider, apolloProvider,
propsData: { propsData: {
selectedProjects: [], selectedProjects: [],
initialProjectIds: [],
...propsData, ...propsData,
}, },
stubs: ['gl-intersection-observer'], stubs: ['gl-intersection-observer'],
...@@ -156,6 +162,7 @@ describe('ProjectsTokenSelector', () => { ...@@ -156,6 +162,7 @@ describe('ProjectsTokenSelector', () => {
search: searchTerm, search: searchTerm,
after: null, after: null,
first: 20, first: 20,
ids: null,
}); });
}); });
...@@ -181,6 +188,7 @@ describe('ProjectsTokenSelector', () => { ...@@ -181,6 +188,7 @@ describe('ProjectsTokenSelector', () => {
after: pageInfo.endCursor, after: pageInfo.endCursor,
first: 20, first: 20,
search: '', search: '',
ids: null,
}); });
}); });
...@@ -221,4 +229,41 @@ describe('ProjectsTokenSelector', () => { ...@@ -221,4 +229,41 @@ describe('ProjectsTokenSelector', () => {
expect(wrapper.emitted('focus')[0]).toEqual([event]); expect(wrapper.emitted('focus')[0]).toEqual([event]);
}); });
}); });
describe('when `initialProjectIds` is an empty array', () => {
it('does not request initial projects', async () => {
await createComponent();
expect(getProjectsQueryRequestHandler).toHaveBeenCalledTimes(1);
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith(
expect.objectContaining({
ids: null,
}),
);
});
});
describe('when `initialProjectIds` is an array of project IDs', () => {
it('requests those projects and emits `input` event with result', async () => {
await createComponent({
propsData: {
initialProjectIds: [getIdFromGraphQLId(project1.id), getIdFromGraphQLId(project2.id)],
},
});
resolveGetInitialProjectsQuery(getProjectsQueryResponse);
await waitForPromises();
expect(getProjectsQueryRequestHandler).toHaveBeenCalledWith({
after: '',
first: null,
search: '',
ids: [project1.id, project2.id],
});
expect(wrapper.emitted('input')[0][0]).toEqual([
{ ...project1, id: getIdFromGraphQLId(project1.id) },
{ ...project2, id: getIdFromGraphQLId(project2.id) },
]);
});
});
}); });
...@@ -38,6 +38,7 @@ describe('access tokens', () => { ...@@ -38,6 +38,7 @@ describe('access tokens', () => {
input.setAttribute('name', 'foo-bar'); input.setAttribute('name', 'foo-bar');
input.setAttribute('id', 'foo-bar'); input.setAttribute('id', 'foo-bar');
input.setAttribute('placeholder', 'Foo bar'); input.setAttribute('placeholder', 'Foo bar');
input.setAttribute('value', '1,2');
mountEl.appendChild(input); mountEl.appendChild(input);
...@@ -58,6 +59,7 @@ describe('access tokens', () => { ...@@ -58,6 +59,7 @@ describe('access tokens', () => {
expect(component.props('inputAttrs')).toEqual({ expect(component.props('inputAttrs')).toEqual({
name: 'foo-bar', name: 'foo-bar',
id: 'foo-bar', id: 'foo-bar',
value: '1,2',
placeholder: 'Foo bar', placeholder: 'Foo bar',
}); });
}); });
......
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