Skip to content
Snippets Groups Projects
Unverified Commit 5fc4826b authored by Ho Tuan Duong's avatar Ho Tuan Duong Committed by GitLab
Browse files

Merge branch 'update-admin-organization-selector-graphql-query' into 'master'

parents e3bbf853 e833ec1d
No related branches found
No related tags found
No related merge requests found
Showing
with 177 additions and 84 deletions
Loading
Loading
@@ -4,7 +4,7 @@ import { __, s__ } from '~/locale';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import { DEFAULT_PER_PAGE } from '~/api';
import { createAlert } from '~/alert';
import organizationsQuery from '../graphql/queries/organizations.query.graphql';
import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
 
export default {
name: 'AdminOrganizationsIndexApp',
Loading
Loading
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "~/organizations/shared/graphql/fragments/organization.fragment.graphql"
query getOrganizations($first: Int, $last: Int, $before: String, $after: String) {
organizations(first: $first, last: $last, before: $before, after: $after) {
nodes {
...Organization
}
pageInfo {
...PageInfo
}
}
}
Loading
Loading
@@ -3,10 +3,12 @@ import { GlAvatarLabeled } from '@gitlab/ui';
import OrganizationSelect from '~/vue_shared/components/entity_select/organization_select.vue';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import { s__ } from '~/locale';
import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import OrganizationRoleField from './organization_role_field.vue';
 
export default {
AVATAR_SHAPE_OPTION_RECT,
organizationsQuery,
inputName: 'user[organization_id]',
inputId: 'user_organization_id',
i18n: {
Loading
Loading
@@ -38,11 +40,14 @@ export default {
<div>
<organization-select
v-if="hasMultipleOrganizations"
:query="$options.organizationsQuery"
query-path="organizations"
block
:initial-selection="initialSelection"
:input-name="$options.inputName"
:input-id="$options.inputId"
toggle-class="gl-form-input-xl"
:searchable="false"
>
<template #label>
<span class="gl-sr-only">{{ $options.i18n.organizationSelectLabel }}</span>
Loading
Loading
Loading
Loading
@@ -4,7 +4,7 @@ import { __, s__ } from '~/locale';
import { createAlert } from '~/alert';
import { DEFAULT_PER_PAGE } from '~/api';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import organizationsQuery from '../../shared/graphql/queries/organizations.query.graphql';
import currentUserOrganizationsQuery from '../../shared/graphql/queries/current_user_organizations.query.graphql';
 
export default {
name: 'OrganizationsIndexApp',
Loading
Loading
@@ -33,7 +33,7 @@ export default {
},
apollo: {
organizations: {
query: organizationsQuery,
query: currentUserOrganizationsQuery,
variables() {
return this.pagination;
},
Loading
Loading
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "../fragments/organization.fragment.graphql"
query getCurrentUserOrganizations(
$search: String
$first: Int
$last: Int
$before: String
$after: String
) {
currentUser {
id
organizations(search: $search, first: $first, last: $last, before: $before, after: $after) {
nodes {
...Organization
}
pageInfo {
...PageInfo
}
}
}
}
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "../fragments/organization.fragment.graphql"
#import "~/organizations/shared/graphql/fragments/organization.fragment.graphql"
 
query getCurrentUserOrganizations(
$search: String
$first: Int
$last: Int
$before: String
$after: String
) {
currentUser {
id
organizations(search: $search, first: $first, last: $last, before: $before, after: $after) {
nodes {
...Organization
}
pageInfo {
...PageInfo
}
query getOrganizations($first: Int, $last: Int, $before: String, $after: String) {
organizations(first: $first, last: $last, before: $before, after: $after) {
nodes {
...Organization
}
pageInfo {
...PageInfo
}
}
}
<script>
import { GlDisclosureDropdown, GlAvatar, GlIcon, GlLoadingIcon, GlLink } from '@gitlab/ui';
import getCurrentUserOrganizations from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import getCurrentUserOrganizations from '~/organizations/shared/graphql/queries/current_user_organizations.query.graphql';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { s__, __ } from '~/locale';
Loading
Loading
<script>
import { debounce, isObject } from 'lodash';
import { GlFormGroup, GlCollapsibleListbox } from '@gitlab/ui';
import { GlFormGroup, GlCollapsibleListbox, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { RESET_LABEL, QUERY_TOO_SHORT_MESSAGE } from './constants';
Loading
Loading
@@ -12,6 +12,7 @@ export default {
components: {
GlFormGroup,
GlCollapsibleListbox,
GlLoadingIcon,
},
props: {
block: {
Loading
Loading
@@ -69,11 +70,16 @@ export default {
required: false,
default: '',
},
searchable: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
pristine: true,
searching: false,
loading: false,
hasMoreItems: true,
infiniteScrollLoading: false,
searchString: '',
Loading
Loading
@@ -136,7 +142,7 @@ export default {
}, DEFAULT_DEBOUNCE_AND_THROTTLE_MS),
async fetchEntities(page = 1) {
if (page === 1) {
this.searching = true;
this.loading = true;
this.items = [];
this.hasMoreItems = true;
} else {
Loading
Loading
@@ -152,7 +158,7 @@ export default {
}
 
this.page = page;
this.searching = false;
this.loading = false;
this.infiniteScrollLoading = false;
},
async getInitialSelection() {
Loading
Loading
@@ -167,10 +173,10 @@ export default {
);
}
 
this.searching = true;
this.loading = true;
this.initialSelectedItem = await this.fetchInitialSelection(this.initialSelection);
this.pristine = false;
this.searching = false;
this.loading = false;
},
onShown() {
if (!this.searchString && !this.items.length) {
Loading
Loading
@@ -205,14 +211,14 @@ export default {
:header-text="headerText"
:reset-button-label="resetButtonLabel"
:toggle-text="toggleText"
:loading="searching && pristine"
:searching="searching"
:loading="loading && pristine"
:searching="loading"
:items="items"
:no-results-text="noResultsText"
:infinite-scroll="hasMoreItems"
:infinite-scroll-loading="infiniteScrollLoading"
:toggle-class="toggleClass"
searchable
:searchable="searchable"
@shown="onShown"
@search="search"
@reset="onReset"
Loading
Loading
@@ -221,6 +227,9 @@ export default {
<template #list-item="{ item }">
<slot name="list-item" :item="item"></slot>
</template>
<template v-if="!searchable" #footer>
<gl-loading-icon v-if="loading" size="md" class="gl-my-3" />
</template>
</gl-collapsible-listbox>
<input :id="inputId" data-testid="input" type="hidden" :name="inputName" :value="selected" />
</gl-form-group>
Loading
Loading
<script>
import { get } from 'lodash';
import { GlAlert } from '@gitlab/ui';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import getCurrentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import getCurrentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/current_user_organizations.query.graphql';
import getOrganizationQuery from '~/organizations/shared/graphql/queries/organization.query.graphql';
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPE_ORGANIZATION } from '~/graphql_shared/constants';
Loading
Loading
@@ -61,6 +62,23 @@ export default {
required: false,
default: '',
},
query: {
type: Object,
required: false,
default() {
return getCurrentUserOrganizationsQuery;
},
},
queryPath: {
type: String,
required: false,
default: 'currentUser.organizations',
},
searchable: {
type: Boolean,
required: false,
default: true,
},
},
data() {
return {
Loading
Loading
@@ -76,10 +94,10 @@ export default {
 
try {
const response = await this.$apollo.query({
query: getCurrentUserOrganizationsQuery,
query: this.query,
variables: { search, after: this.endCursor, first: DEFAULT_PER_PAGE },
});
const { nodes, pageInfo } = response.data.currentUser.organizations;
const { nodes, pageInfo } = get(response.data, this.queryPath);
this.endCursor = pageInfo.endCursor;
 
return {
Loading
Loading
@@ -147,6 +165,7 @@ export default {
:fetch-items="fetchOrganizations"
:fetch-initial-selection="fetchInitialOrganization"
:toggle-class="toggleClass"
:searchable="searchable"
v-on="$listeners"
>
<template #label>
Loading
Loading
Loading
Loading
@@ -7,7 +7,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import { DEFAULT_PER_PAGE } from '~/api';
import organizationsQuery from '~/admin/organizations/index/graphql/queries/organizations.query.graphql';
import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import OrganizationsIndexApp from '~/admin/organizations/index/components/app.vue';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import { MOCK_NEW_ORG_URL } from 'jest/organizations/shared/mock_data';
Loading
Loading
@@ -22,9 +22,7 @@ describe('AdminOrganizationsIndexApp', () => {
let mockApollo;
 
const {
data: {
currentUser: { organizations },
},
data: { organizations },
} = organizationsGraphQlResponse;
 
const organizationEmpty = {
Loading
Loading
@@ -32,11 +30,7 @@ describe('AdminOrganizationsIndexApp', () => {
pageInfo: pageInfoEmpty,
};
 
const successHandler = jest.fn().mockResolvedValue({
data: {
organizations,
},
});
const successHandler = jest.fn().mockResolvedValue(organizationsGraphQlResponse);
 
const createComponent = (handler = successHandler) => {
mockApollo = createMockApollo([[organizationsQuery, handler]]);
Loading
Loading
Loading
Loading
@@ -4,6 +4,7 @@ import NewUserOrganizationField from '~/admin/users/components/new_user_organiza
import OrganizationRoleField from '~/admin/users/components/organization_role_field.vue';
import { AVATAR_SHAPE_OPTION_RECT } from '~/vue_shared/constants';
import OrganizationSelect from '~/vue_shared/components/entity_select/organization_select.vue';
import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
 
describe('NewUserOrganizationField', () => {
let wrapper;
Loading
Loading
@@ -50,9 +51,14 @@ describe('NewUserOrganizationField', () => {
});
 
it('renders organization select with default organization selected', () => {
expect(findOrganizationSelect().props('initialSelection')).toEqual({
text: defaultPropsData.initialOrganization.name,
value: defaultPropsData.initialOrganization.id,
expect(findOrganizationSelect().props()).toMatchObject({
searchable: false,
query: organizationsQuery,
queryPath: 'organizations',
initialSelection: {
text: defaultPropsData.initialOrganization.name,
value: defaultPropsData.initialOrganization.id,
},
});
});
});
Loading
Loading
Loading
Loading
@@ -61,7 +61,21 @@
sign_in(current_user)
end
 
describe 'organizations' do
describe 'current user organizations' do
base_input_path = 'organizations/shared/graphql/queries/'
base_output_path = 'graphql/organizations/'
query_name = 'current_user_organizations.query.graphql'
it "#{base_output_path}#{query_name}.json" do
query = get_graphql_query_as_string("#{base_input_path}#{query_name}")
post_graphql(query, current_user: current_user, variables: { search: '', first: 3 })
expect_graphql_errors_to_be_empty
end
end
describe 'instance organizations' do
base_input_path = 'organizations/shared/graphql/queries/'
base_output_path = 'graphql/organizations/'
query_name = 'organizations.query.graphql'
Loading
Loading
import { GlButton } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
import currentUserOrganizationsGraphQlResponse from 'test_fixtures/graphql/organizations/current_user_organizations.query.graphql.json';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';
import { DEFAULT_PER_PAGE } from '~/api';
import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import currentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/current_user_organizations.query.graphql';
import OrganizationsIndexApp from '~/organizations/index/components/app.vue';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import { pageInfoEmpty } from 'jest/organizations/mock_data';
Loading
Loading
@@ -25,17 +25,17 @@ describe('OrganizationsIndexApp', () => {
data: {
currentUser: { organizations },
},
} = organizationsGraphQlResponse;
} = currentUserOrganizationsGraphQlResponse;
 
const organizationEmpty = {
nodes: [],
pageInfo: pageInfoEmpty,
};
 
const successHandler = jest.fn().mockResolvedValue(organizationsGraphQlResponse);
const successHandler = jest.fn().mockResolvedValue(currentUserOrganizationsGraphQlResponse);
 
const createComponent = (handler = successHandler) => {
mockApollo = createMockApollo([[organizationsQuery, handler]]);
mockApollo = createMockApollo([[currentUserOrganizationsQuery, handler]]);
 
wrapper = shallowMountExtended(OrganizationsIndexApp, {
apolloProvider: mockApollo,
Loading
Loading
import { GlAvatarLabeled } from '@gitlab/ui';
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
import currentUserOrganizationsGraphQlResponse from 'test_fixtures/graphql/organizations/current_user_organizations.query.graphql.json';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import OrganizationsListItem from '~/organizations/shared/components/list/organizations_list_item.vue';
Loading
Loading
@@ -15,7 +15,7 @@ describe('OrganizationsListItem', () => {
},
},
},
} = organizationsGraphQlResponse;
} = currentUserOrganizationsGraphQlResponse;
 
const defaultProps = {
organization,
Loading
Loading
import { GlKeysetPagination } from '@gitlab/ui';
import { omit } from 'lodash';
import { shallowMount } from '@vue/test-utils';
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
import currentUserOrganizationsGraphQlResponse from 'test_fixtures/graphql/organizations/current_user_organizations.query.graphql.json';
import OrganizationsList from '~/organizations/shared/components/list/organizations_list.vue';
import OrganizationsListItem from '~/organizations/shared/components/list/organizations_list_item.vue';
import { pageInfoMultiplePages, pageInfoOnePage } from 'jest/organizations/mock_data';
Loading
Loading
@@ -13,7 +13,7 @@ describe('OrganizationsList', () => {
data: {
currentUser: { organizations },
},
} = organizationsGraphQlResponse;
} = currentUserOrganizationsGraphQlResponse;
 
const createComponent = ({ propsData = {} } = {}) => {
wrapper = shallowMount(OrganizationsList, {
Loading
Loading
import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
import currentUserOrganizationsGraphQlResponse from 'test_fixtures/graphql/organizations/current_user_organizations.query.graphql.json';
import OrganizationsView from '~/organizations/shared/components/organizations_view.vue';
import OrganizationsList from '~/organizations/shared/components/list/organizations_list.vue';
import { MOCK_NEW_ORG_URL } from '../mock_data';
Loading
Loading
@@ -19,7 +19,7 @@ describe('OrganizationsView', () => {
organizations: { nodes: organizations },
},
},
} = organizationsGraphQlResponse;
} = currentUserOrganizationsGraphQlResponse;
 
const createComponent = (props = {}) => {
wrapper = shallowMount(OrganizationsView, {
Loading
Loading
Loading
Loading
@@ -2,14 +2,14 @@ import { GlAvatar, GlDisclosureDropdown, GlLoadingIcon, GlLink } from '@gitlab/u
import VueApollo from 'vue-apollo';
import Vue from 'vue';
 
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
import currentUserOrganizationsGraphQlResponse from 'test_fixtures/graphql/organizations/current_user_organizations.query.graphql.json';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import OrganizationSwitcher from '~/super_sidebar/components/organization_switcher.vue';
import {
pageInfoEmpty,
defaultOrganization as currentOrganization,
} from 'jest/organizations/mock_data';
import organizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import organizationsQuery from '~/organizations/shared/graphql/queries/current_user_organizations.query.graphql';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { helpPagePath } from '~/helpers/help_page_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
Loading
Loading
@@ -27,7 +27,7 @@ describe('OrganizationSwitcher', () => {
organizations: { nodes, pageInfo },
},
},
} = organizationsGraphQlResponse;
} = currentUserOrganizationsGraphQlResponse;
 
const [firstOrganization, secondOrganization] = nodes;
 
Loading
Loading
import { nextTick } from 'vue';
import { GlCollapsibleListbox, GlFormGroup } from '@gitlab/ui';
import { GlCollapsibleListbox, GlFormGroup, GlLoadingIcon } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import EntitySelect from '~/vue_shared/components/entity_select/entity_select.vue';
import { QUERY_TOO_SHORT_MESSAGE } from '~/vue_shared/components/entity_select/constants';
Loading
Loading
@@ -34,6 +34,7 @@ describe('EntitySelect', () => {
// Finders
const findListbox = () => wrapper.findComponent(GlCollapsibleListbox);
const findInput = () => wrapper.findByTestId('input');
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
 
// Helpers
const createComponent = ({ props = {}, slots = {}, stubs = {} } = {}) => {
Loading
Loading
@@ -261,6 +262,23 @@ describe('EntitySelect', () => {
expect(fetchItemsMock).toHaveBeenCalledTimes(1);
expect(findListbox().props('noResultsText')).toBe(QUERY_TOO_SHORT_MESSAGE);
});
describe('when searchable prop is false', () => {
beforeEach(() => {
createComponent({ props: { searchable: false } });
});
it('sets searchable prop on GlCollapsibleListbox to false', () => {
expect(findListbox().props('searchable')).toBe(false);
});
it('shows loading icon when first opening the dropdown', async () => {
openListbox();
await nextTick();
expect(findLoadingIcon().exists()).toBe(true);
});
});
});
 
describe('pagination', () => {
Loading
Loading
Loading
Loading
@@ -2,6 +2,7 @@ import VueApollo from 'vue-apollo';
import Vue from 'vue';
import { GlCollapsibleListbox, GlAlert } from '@gitlab/ui';
import { chunk } from 'lodash';
import currentUserOrganizationsGraphQlResponse from 'test_fixtures/graphql/organizations/current_user_organizations.query.graphql.json';
import organizationsGraphQlResponse from 'test_fixtures/graphql/organizations/organizations.query.graphql.json';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import OrganizationSelect from '~/vue_shared/components/entity_select/organization_select.vue';
Loading
Loading
@@ -13,8 +14,9 @@ import {
FETCH_ORGANIZATIONS_ERROR,
FETCH_ORGANIZATION_ERROR,
} from '~/vue_shared/components/entity_select/constants';
import getCurrentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import getCurrentUserOrganizationsQuery from '~/organizations/shared/graphql/queries/current_user_organizations.query.graphql';
import getOrganizationQuery from '~/organizations/shared/graphql/queries/organization.query.graphql';
import getOrganizationsQuery from '~/organizations/shared/graphql/queries/organizations.query.graphql';
import { pageInfoMultiplePages, pageInfoEmpty } from 'jest/organizations/mock_data';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
Loading
Loading
@@ -30,15 +32,11 @@ describe('OrganizationSelect', () => {
const {
data: {
currentUser: {
organizations: { nodes, pageInfo },
organizations: { nodes },
},
},
} = organizationsGraphQlResponse;
} = currentUserOrganizationsGraphQlResponse;
const [organization] = nodes;
const organizations = {
nodes,
pageInfo,
};
 
// Props
const label = 'label';
Loading
Loading
@@ -54,9 +52,9 @@ describe('OrganizationSelect', () => {
 
// Mock handlers
const handleInput = jest.fn();
const getCurrentUserOrganizationsQueryHandler = jest.fn().mockResolvedValue({
data: { currentUser: { id: 'gid://gitlab/User/1', __typename: 'CurrentUser', organizations } },
});
const getCurrentUserOrganizationsQueryHandler = jest
.fn()
.mockResolvedValue(currentUserOrganizationsGraphQlResponse);
const getOrganizationQueryHandler = jest.fn().mockResolvedValue({
data: { organization },
});
Loading
Loading
@@ -102,6 +100,7 @@ describe('OrganizationSelect', () => {
${'defaultToggleText'} | ${ORGANIZATION_TOGGLE_TEXT}
${'headerText'} | ${ORGANIZATION_HEADER_TEXT}
${'toggleClass'} | ${toggleClass}
${'searchable'} | ${true}
`('passes the $prop prop to entity-select', ({ prop, expectedValue }) => {
expect(findEntitySelect().props(prop)).toBe(expectedValue);
});
Loading
Loading
@@ -232,4 +231,33 @@ describe('OrganizationSelect', () => {
 
expect(handleInput).toHaveBeenCalledTimes(1);
});
describe('when query and queryPath props are passed', () => {
const getOrganizationsQueryHandler = jest.fn().mockResolvedValue(organizationsGraphQlResponse);
beforeEach(async () => {
createComponent({
props: {
query: getOrganizationsQuery,
queryPath: 'organizations',
},
handlers: [
[getOrganizationsQuery, getOrganizationsQueryHandler],
[getOrganizationQuery, getOrganizationQueryHandler],
],
});
openListbox();
await waitForPromises();
});
it('uses passed GraphQL query', () => {
const expectedItems = organizationsGraphQlResponse.data.organizations.nodes.map((node) => ({
...node,
text: node.name,
value: getIdFromGraphQLId(node.id),
}));
expect(findListbox().props('items')).toEqual(expectedItems);
});
});
});
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