Commit b72c2c4d authored by Michael Lunøe's avatar Michael Lunøe Committed by Mark Chao

Refactor(CI Minutes): use customer graphql client

parent 12476cb9
...@@ -3,8 +3,8 @@ import { GlEmptyState } from '@gitlab/ui'; ...@@ -3,8 +3,8 @@ import { GlEmptyState } from '@gitlab/ui';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import { __ } from '~/locale'; import { __ } from '~/locale';
const ERROR_FETCHING_DATA_HEADER = __('Could not get the data properly'); export const ERROR_FETCHING_DATA_HEADER = __('Could not get the data properly');
const ERROR_FETCHING_DATA_DESCRIPTION = __( export const ERROR_FETCHING_DATA_DESCRIPTION = __(
'Please try and refresh the page. If the problem persists please contact support.', 'Please try and refresh the page. If the problem persists please contact support.',
); );
......
...@@ -18,11 +18,21 @@ export const fetchPolicies = { ...@@ -18,11 +18,21 @@ export const fetchPolicies = {
}; };
export default (resolvers = {}, config = {}) => { export default (resolvers = {}, config = {}) => {
let uri = `${gon.relative_url_root || ''}/api/graphql`; const {
assumeImmutableResults,
baseUrl,
batchMax = 10,
cacheConfig,
fetchPolicy = fetchPolicies.CACHE_FIRST,
typeDefs,
path = '/api/graphql',
useGet = false,
} = config;
let uri = `${gon.relative_url_root || ''}${path}`;
if (config.baseUrl) { if (baseUrl) {
// Prepend baseUrl and ensure that `///` are replaced with `/` // Prepend baseUrl and ensure that `///` are replaced with `/`
uri = `${config.baseUrl}${uri}`.replace(/\/{3,}/g, '/'); uri = `${baseUrl}${uri}`.replace(/\/{3,}/g, '/');
} }
const httpOptions = { const httpOptions = {
...@@ -34,7 +44,7 @@ export default (resolvers = {}, config = {}) => { ...@@ -34,7 +44,7 @@ export default (resolvers = {}, config = {}) => {
// We set to `same-origin` which is default value in modern browsers. // We set to `same-origin` which is default value in modern browsers.
// See https://github.com/whatwg/fetch/pull/585 for more information. // See https://github.com/whatwg/fetch/pull/585 for more information.
credentials: 'same-origin', credentials: 'same-origin',
batchMax: config.batchMax || 10, batchMax,
}; };
const requestCounterLink = new ApolloLink((operation, forward) => { const requestCounterLink = new ApolloLink((operation, forward) => {
...@@ -50,7 +60,7 @@ export default (resolvers = {}, config = {}) => { ...@@ -50,7 +60,7 @@ export default (resolvers = {}, config = {}) => {
const uploadsLink = ApolloLink.split( const uploadsLink = ApolloLink.split(
(operation) => operation.getContext().hasUpload || operation.getContext().isSingleRequest, (operation) => operation.getContext().hasUpload || operation.getContext().isSingleRequest,
createUploadLink(httpOptions), createUploadLink(httpOptions),
config.useGet ? createHttpLink(httpOptions) : new BatchHttpLink(httpOptions), useGet ? createHttpLink(httpOptions) : new BatchHttpLink(httpOptions),
); );
const performanceBarLink = new ApolloLink((operation, forward) => { const performanceBarLink = new ApolloLink((operation, forward) => {
...@@ -74,7 +84,7 @@ export default (resolvers = {}, config = {}) => { ...@@ -74,7 +84,7 @@ export default (resolvers = {}, config = {}) => {
}); });
return new ApolloClient({ return new ApolloClient({
typeDefs: config.typeDefs, typeDefs,
link: ApolloLink.from([ link: ApolloLink.from([
requestCounterLink, requestCounterLink,
performanceBarLink, performanceBarLink,
...@@ -83,14 +93,14 @@ export default (resolvers = {}, config = {}) => { ...@@ -83,14 +93,14 @@ export default (resolvers = {}, config = {}) => {
uploadsLink, uploadsLink,
]), ]),
cache: new InMemoryCache({ cache: new InMemoryCache({
...config.cacheConfig, ...cacheConfig,
freezeResults: config.assumeImmutableResults, freezeResults: assumeImmutableResults,
}), }),
resolvers, resolvers,
assumeImmutableResults: config.assumeImmutableResults, assumeImmutableResults,
defaultOptions: { defaultOptions: {
query: { query: {
fetchPolicy: config.fetchPolicy || fetchPolicies.CACHE_FIRST, fetchPolicy,
}, },
}, },
}); });
......
...@@ -118,6 +118,8 @@ To distinguish queries from mutations and fragments, the following naming conven ...@@ -118,6 +118,8 @@ To distinguish queries from mutations and fragments, the following naming conven
- `add_user.mutation.graphql` for mutations; - `add_user.mutation.graphql` for mutations;
- `basic_user.fragment.graphql` for fragments. - `basic_user.fragment.graphql` for fragments.
If you are using queries for the [CustomersDot GraphQL endpoint](https://gitlab.com/gitlab-org/gitlab/-/blob/be78ccd832fd40315c5e63bb48ee1596ae146f56/app/controllers/customers_dot/proxy_controller.rb), end the filename with `.customer.query.graphql`, `.customer.mutation.graphql`, or `.customer.fragment.graphql`.
### Fragments ### Fragments
[Fragments](https://graphql.org/learn/queries/#fragments) are a way to make your complex GraphQL queries more readable and re-usable. Here is an example of GraphQL fragment: [Fragments](https://graphql.org/learn/queries/#fragments) are a way to make your complex GraphQL queries more readable and re-usable. Here is an example of GraphQL fragment:
......
<script> <script>
import emptySvg from '@gitlab/svgs/dist/illustrations/security-dashboard-empty-state.svg';
import { GlEmptyState } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue'; import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
import { ERROR_FETCHING_DATA_HEADER, ERROR_FETCHING_DATA_DESCRIPTION } from '~/ensure_data';
import plansQuery from '../../graphql/queries/plans.customer.query.graphql';
import { planTags, CUSTOMER_CLIENT } from '../constants';
import Checkout from './checkout.vue'; import Checkout from './checkout.vue';
export default { export default {
components: { components: {
Checkout, Checkout,
GlEmptyState,
StepOrderApp, StepOrderApp,
}, },
i18n: {
ERROR_FETCHING_DATA_HEADER,
ERROR_FETCHING_DATA_DESCRIPTION,
},
emptySvg,
data() {
return {
plans: [],
hasError: false,
};
},
apollo: {
plans: {
client: CUSTOMER_CLIENT,
query: plansQuery,
variables: {
tags: [planTags.CI_1000_MINUTES_PLAN],
},
update(data) {
return data.plans;
},
error(error) {
this.hasError = true;
Sentry.captureException(error);
},
},
},
}; };
</script> </script>
<template> <template>
<step-order-app> <gl-empty-state
v-if="hasError"
:title="$options.i18n.ERROR_FETCHING_DATA_HEADER"
:description="$options.i18n.ERROR_FETCHING_DATA_DESCRIPTION"
:svg-path="`data:image/svg+xml;utf8,${encodeURIComponent($options.emptySvg)}`"
/>
<step-order-app v-else>
<template #checkout> <template #checkout>
<checkout /> <checkout :plans="plans" />
</template> </template>
<template #order-summary></template> <template #order-summary></template>
</step-order-app> </step-order-app>
......
...@@ -7,6 +7,12 @@ import SubscriptionDetails from './checkout/subscription_details.vue'; ...@@ -7,6 +7,12 @@ import SubscriptionDetails from './checkout/subscription_details.vue';
export default { export default {
components: { ProgressBar, SubscriptionDetails }, components: { ProgressBar, SubscriptionDetails },
props: {
plans: {
type: Array,
required: true,
},
},
apollo: { apollo: {
state: { state: {
query: STATE_QUERY, query: STATE_QUERY,
...@@ -30,7 +36,7 @@ export default { ...@@ -30,7 +36,7 @@ export default {
<progress-bar v-if="isNewUser" :steps="$options.steps" :current-step="$options.currentStep" /> <progress-bar v-if="isNewUser" :steps="$options.steps" :current-step="$options.currentStep" />
<div class="flash-container"></div> <div class="flash-container"></div>
<h2 class="gl-mt-4 gl-mb-3 gl-mb-lg-5">{{ $options.i18n.checkout }}</h2> <h2 class="gl-mt-4 gl-mb-3 gl-mb-lg-5">{{ $options.i18n.checkout }}</h2>
<subscription-details /> <subscription-details :plans="plans" />
</div> </div>
</div> </div>
</template> </template>
...@@ -20,6 +20,12 @@ export default { ...@@ -20,6 +20,12 @@ export default {
directives: { directives: {
autofocusonshow, autofocusonshow,
}, },
props: {
plans: {
type: Array,
required: true,
},
},
apollo: { apollo: {
state: { state: {
query: STATE_QUERY, query: STATE_QUERY,
...@@ -29,9 +35,6 @@ export default { ...@@ -29,9 +35,6 @@ export default {
subscription() { subscription() {
return this.state.subscription; return this.state.subscription;
}, },
plans() {
return this.state.plans;
},
namespaces() { namespaces() {
return this.state.namespaces; return this.state.namespaces;
}, },
...@@ -71,7 +74,12 @@ export default { ...@@ -71,7 +74,12 @@ export default {
}, },
}, },
selectedPlan() { selectedPlan() {
return this.state.plans.find((plan) => plan.code === this.subscription.planId); const selectedPlan = this.plans.find((plan) => plan.code === this.subscription.planId);
if (!selectedPlan) {
return this.plans[0];
}
return selectedPlan;
}, },
selectedPlanTextLine() { selectedPlanTextLine() {
return sprintf(this.$options.i18n.selectedPlan, { selectedPlanText: this.selectedPlan.code }); return sprintf(this.$options.i18n.selectedPlan, { selectedPlanText: this.selectedPlan.code });
......
/* eslint-disable @gitlab/require-i18n-strings */
export const planTags = {
CI_1000_MINUTES_PLAN: 'CI_1000_MINUTES_PLAN',
};
/* eslint-enable @gitlab/require-i18n-strings */
export const CUSTOMER_CLIENT = 'customerClient';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createClient from '~/lib/graphql';
import { CUSTOMER_CLIENT } from './constants';
import { resolvers } from './graphql/resolvers'; import { resolvers } from './graphql/resolvers';
Vue.use(VueApollo); Vue.use(VueApollo);
const defaultClient = createDefaultClient(resolvers, { assumeImmutableResults: true }); const defaultClient = createClient(resolvers, { assumeImmutableResults: true });
const customerClient = createClient(
{},
{ path: '/-/customers_dot/proxy/graphql', useGet: true, assumeImmutableResults: true },
);
export default new VueApollo({ export default new VueApollo({
defaultClient, defaultClient,
clients: {
[CUSTOMER_CLIENT]: customerClient,
},
}); });
import Vue from 'vue'; import Vue from 'vue';
import App from 'ee/subscriptions/buy_minutes/components/app.vue'; import App from 'ee/subscriptions/buy_minutes/components/app.vue';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import { STEPS } from 'ee/subscriptions/new/constants'; import { STEPS } from 'ee/subscriptions/new/constants';
import ensureData from '~/ensure_data';
import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase, parseBoolean } from '~/lib/utils/common_utils';
import stateQuery from '../graphql/queries/state.query.graphql';
import apolloProvider from './graphql'; import apolloProvider from './graphql';
import { parseData } from './utils';
const arrayToGraphqlArray = (arr, typename) => const arrayToGraphqlArray = (arr, typename) =>
Array.from(arr, (item) => Array.from(arr, (item) =>
...@@ -13,12 +11,11 @@ const arrayToGraphqlArray = (arr, typename) => ...@@ -13,12 +11,11 @@ const arrayToGraphqlArray = (arr, typename) =>
); );
const writeInitialDataToApolloProvider = (dataset) => { const writeInitialDataToApolloProvider = (dataset) => {
const { groupData, newUser, setupForCompany, fullName, planId } = dataset;
// eslint-disable-next-line @gitlab/require-i18n-strings // eslint-disable-next-line @gitlab/require-i18n-strings
const plans = arrayToGraphqlArray(JSON.parse(dataset.ciMinutesPlans), 'Plan'); const namespaces = arrayToGraphqlArray(JSON.parse(groupData), 'Namespace');
// eslint-disable-next-line @gitlab/require-i18n-strings const isNewUser = parseBoolean(newUser);
const namespaces = arrayToGraphqlArray(JSON.parse(dataset.groupData), 'Namespace'); const isSetupForCompany = parseBoolean(setupForCompany) || !isNewUser;
const isNewUser = parseBoolean(dataset.newUser);
const isSetupForCompany = parseBoolean(dataset.setupForCompany) || !isNewUser;
apolloProvider.clients.defaultClient.cache.writeQuery({ apolloProvider.clients.defaultClient.cache.writeQuery({
query: stateQuery, query: stateQuery,
...@@ -26,11 +23,10 @@ const writeInitialDataToApolloProvider = (dataset) => { ...@@ -26,11 +23,10 @@ const writeInitialDataToApolloProvider = (dataset) => {
state: { state: {
isNewUser, isNewUser,
isSetupForCompany, isSetupForCompany,
plans,
namespaces, namespaces,
fullName: dataset.fullName, fullName,
subscription: { subscription: {
planId: plans[0].code, planId,
paymentMethodId: null, paymentMethodId: null,
quantity: 1, quantity: 1,
namespaceId: null, namespaceId: null,
...@@ -62,18 +58,13 @@ export default (el) => { ...@@ -62,18 +58,13 @@ export default (el) => {
return null; return null;
} }
const ExtendedApp = ensureData(App, {
parseData,
data: el.dataset,
});
writeInitialDataToApolloProvider(el.dataset); writeInitialDataToApolloProvider(el.dataset);
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
render(createElement) { render(createElement) {
return createElement(ExtendedApp); return createElement(App);
}, },
}); });
}; };
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
export function parseData(dataset) {
const { ciMinutesPlans } = dataset;
return {
ciMinutesPlans: convertObjectPropsToCamelCase(JSON.parse(ciMinutesPlans), {
deep: true,
}),
};
}
query getPlans($tags: [PlanTag!]) {
plans(planTags: $tags) {
id
name
code
active
deprecated
free
pricePerMonth
pricePerYear
features
aboutPageHref
hideDeprecatedCard
}
}
query state { query state {
state @client { state @client {
plans {
name
code
pricePerYear
}
namespaces { namespaces {
id id
name name
......
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import App from 'ee/subscriptions/buy_minutes/components/app.vue';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
import waitForPromises from 'helpers/wait_for_promises';
import { createMockApolloProvider } from '../spec_helper';
const localVue = createLocalVue();
localVue.use(VueApollo);
describe('App', () => {
let wrapper;
function createComponent(options = {}) {
const { apolloProvider, propsData } = options;
return shallowMount(App, {
localVue,
propsData,
apolloProvider,
});
}
afterEach(() => {
wrapper.destroy();
});
describe('when data is received', () => {
it('should display the StepOrderApp', async () => {
const mockApollo = createMockApolloProvider();
wrapper = createComponent({ apolloProvider: mockApollo });
await waitForPromises();
expect(wrapper.findComponent(StepOrderApp).exists()).toBe(true);
expect(wrapper.findComponent(GlEmptyState).exists()).toBe(false);
});
});
describe('when data is not received', () => {
it('should display the GlEmptyState', async () => {
const mockApollo = createMockApolloProvider({
plansQueryMock: jest.fn().mockRejectedValue(new Error('An error happened!')),
});
wrapper = createComponent({ apolloProvider: mockApollo });
await waitForPromises();
expect(wrapper.findComponent(StepOrderApp).exists()).toBe(false);
expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true);
});
});
});
...@@ -11,6 +11,7 @@ import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers ...@@ -11,6 +11,7 @@ import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers
import { import {
stateData as initialStateData, stateData as initialStateData,
namespaces as defaultNamespaces, namespaces as defaultNamespaces,
mockCiMinutesPlans,
} from 'ee_jest/subscriptions/buy_minutes/mock_data'; } from 'ee_jest/subscriptions/buy_minutes/mock_data';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
...@@ -40,6 +41,9 @@ describe('Subscription Details', () => { ...@@ -40,6 +41,9 @@ describe('Subscription Details', () => {
return mount(SubscriptionDetails, { return mount(SubscriptionDetails, {
localVue, localVue,
apolloProvider, apolloProvider,
propsData: {
plans: mockCiMinutesPlans,
},
stubs: { stubs: {
Step, Step,
}, },
......
...@@ -6,7 +6,10 @@ import Checkout from 'ee/subscriptions/buy_minutes/components/checkout.vue'; ...@@ -6,7 +6,10 @@ import Checkout from 'ee/subscriptions/buy_minutes/components/checkout.vue';
import subscriptionsResolvers from 'ee/subscriptions/buy_minutes/graphql/resolvers'; import subscriptionsResolvers from 'ee/subscriptions/buy_minutes/graphql/resolvers';
import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql'; import stateQuery from 'ee/subscriptions/graphql/queries/state.query.graphql';
import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers'; import purchaseFlowResolvers from 'ee/vue_shared/purchase_flow/graphql/resolvers';
import { stateData as initialStateData } from 'ee_jest/subscriptions/buy_minutes/mock_data'; import {
stateData as initialStateData,
mockCiMinutesPlans,
} from 'ee_jest/subscriptions/buy_minutes/mock_data';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
const localVue = createLocalVue(); const localVue = createLocalVue();
...@@ -35,6 +38,9 @@ describe('Checkout', () => { ...@@ -35,6 +38,9 @@ describe('Checkout', () => {
wrapper = shallowMount(Checkout, { wrapper = shallowMount(Checkout, {
apolloProvider, apolloProvider,
localVue, localVue,
propsData: {
plans: mockCiMinutesPlans,
},
}); });
}; };
......
import { GlEmptyState } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { createWrapper } from '@vue/test-utils'; import { createWrapper } from '@vue/test-utils';
import initBuyMinutesApp from 'ee/subscriptions/buy_minutes'; import initBuyMinutesApp from 'ee/subscriptions/buy_minutes';
import * as utils from 'ee/subscriptions/buy_minutes/utils';
import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue'; import StepOrderApp from 'ee/vue_shared/purchase_flow/components/step_order_app.vue';
import { mockCiMinutesPlans, mockParsedCiMinutesPlans } from './mock_data'; import { mockCiMinutesPlans } from './mock_data';
import { createMockApolloProvider } from './spec_helper';
jest.mock('ee/subscriptions/buy_minutes/utils'); jest.doMock('ee/subscriptions/buy_minutes/graphql', createMockApolloProvider());
describe('initBuyMinutesApp', () => { describe('initBuyMinutesApp', () => {
let vm; let vm;
...@@ -15,7 +13,7 @@ describe('initBuyMinutesApp', () => { ...@@ -15,7 +13,7 @@ describe('initBuyMinutesApp', () => {
function createComponent() { function createComponent() {
const el = document.createElement('div'); const el = document.createElement('div');
Object.assign(el.dataset, { Object.assign(el.dataset, {
ciMinutesPlans: mockCiMinutesPlans, planId: mockCiMinutesPlans[0].code,
groupData: '[]', groupData: '[]',
fullName: 'GitLab', fullName: 'GitLab',
}); });
...@@ -23,39 +21,16 @@ describe('initBuyMinutesApp', () => { ...@@ -23,39 +21,16 @@ describe('initBuyMinutesApp', () => {
wrapper = createWrapper(vm); wrapper = createWrapper(vm);
} }
beforeEach(() => {
Sentry.captureException = jest.fn();
});
afterEach(() => { afterEach(() => {
if (vm) { if (vm) {
vm.$destroy(); vm.$destroy();
} }
wrapper.destroy(); wrapper.destroy();
vm = null; vm = null;
Sentry.captureException.mockClear();
utils.parseData.mockClear();
});
describe('when parsing fails', () => {
it('displays the EmptyState', () => {
utils.parseData.mockImplementation(() => {
throw new Error();
});
createComponent();
expect(wrapper.find(StepOrderApp).exists()).toBe(false);
expect(wrapper.find(GlEmptyState).exists()).toBe(true);
expect(Sentry.captureException).not.toHaveBeenCalled();
});
}); });
describe('when parsing succeeds', () => {
it('displays the StepOrderApp', () => { it('displays the StepOrderApp', () => {
utils.parseData.mockImplementation(() => mockParsedCiMinutesPlans);
createComponent(); createComponent();
expect(wrapper.find(GlEmptyState).exists()).toBe(false);
expect(wrapper.find(StepOrderApp).exists()).toBe(true); expect(wrapper.find(StepOrderApp).exists()).toBe(true);
expect(Sentry.captureException).not.toHaveBeenCalled();
});
}); });
}); });
import { STEPS } from 'ee/subscriptions/new/constants'; import { STEPS } from 'ee/subscriptions/new/constants';
export const mockCiMinutesPlans = export const mockCiMinutesPlans = [
'[{"deprecated":false,"name":"1000 CI minutes pack","code":"ci_minutes","active":true,"free":null,"price_per_month":0.8333333333333334,"price_per_year":10.0,"features":null,"about_page_href":null,"hide_deprecated_card":false}]';
export const mockParsedCiMinutesPlans = [
{ {
id: 'ci_minutes',
deprecated: false, deprecated: false,
name: '1000 CI minutes pack', name: '1000 CI minutes pack',
code: 'ci_minutes', code: 'ci_minutes',
......
import VueApollo from 'vue-apollo';
import plansQuery from 'ee/subscriptions/graphql/queries/plans.customer.query.graphql';
import { createMockClient } from 'helpers/mock_apollo_helper';
import { mockCiMinutesPlans } from './mock_data';
export function createMockApolloProvider(mockResponses = {}) {
const {
plansQueryMock = jest.fn().mockResolvedValue({ data: { plans: mockCiMinutesPlans } }),
} = mockResponses;
const mockDefaultClient = createMockClient();
const mockCustomerClient = createMockClient([[plansQuery, plansQueryMock]]);
return new VueApollo({
defaultClient: mockDefaultClient,
clients: { customerClient: mockCustomerClient },
});
}
import { parseData } from 'ee/subscriptions/buy_minutes/utils';
import { mockCiMinutesPlans, mockParsedCiMinutesPlans } from './mock_data';
describe('utils', () => {
describe('#parseData', () => {
describe.each`
ciMinutesPlans | parsedCiMinutesPlans | throws
${'[]'} | ${[]} | ${false}
${'null'} | ${{}} | ${false}
${mockCiMinutesPlans} | ${mockParsedCiMinutesPlans} | ${false}
${''} | ${{}} | ${true}
`('parameter decoding', ({ ciMinutesPlans, parsedCiMinutesPlans, throws }) => {
it(`decodes ${ciMinutesPlans} to ${parsedCiMinutesPlans}`, () => {
if (throws) {
expect(() => {
parseData({ ciMinutesPlans });
}).toThrow();
} else {
const result = parseData({ ciMinutesPlans });
expect(result.ciMinutesPlans).toEqual(parsedCiMinutesPlans);
}
});
});
});
});
mutation updatePlans($tags: [PlanTag!]) {
plans(planTags: $tags) {
name
}
}
query getPlans($tags: [PlanTag!]) {
plans(planTags: $tags) {
name
}
}
...@@ -264,7 +264,7 @@ module Gitlab ...@@ -264,7 +264,7 @@ module Gitlab
definitions = [] definitions = []
::Find.find(root.to_s) do |path| ::Find.find(root.to_s) do |path|
definitions << Definition.new(path, fragments) if query?(path) definitions << Definition.new(path, fragments) if query_for_gitlab_schema?(path)
end end
definitions definitions
...@@ -288,10 +288,11 @@ module Gitlab ...@@ -288,10 +288,11 @@ module Gitlab
@known_failures.fetch('filenames', []).any? { |known_failure| path.to_s.ends_with?(known_failure) } @known_failures.fetch('filenames', []).any? { |known_failure| path.to_s.ends_with?(known_failure) }
end end
def self.query?(path) def self.query_for_gitlab_schema?(path)
path.ends_with?('.graphql') && path.ends_with?('.graphql') &&
!path.ends_with?('.fragment.graphql') && !path.ends_with?('.fragment.graphql') &&
!path.ends_with?('typedefs.graphql') !path.ends_with?('typedefs.graphql') &&
!/.*\.customer\.(query|mutation)\.graphql$/.match?(path)
end end
end end
end end
......
import { InMemoryCache } from 'apollo-cache-inmemory'; import { InMemoryCache } from 'apollo-cache-inmemory';
import { createMockClient } from 'mock-apollo-client'; import { createMockClient as createMockApolloClient } from 'mock-apollo-client';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
const defaultCacheOptions = { const defaultCacheOptions = {
...@@ -7,13 +7,13 @@ const defaultCacheOptions = { ...@@ -7,13 +7,13 @@ const defaultCacheOptions = {
addTypename: false, addTypename: false,
}; };
export default (handlers = [], resolvers = {}, cacheOptions = {}) => { export function createMockClient(handlers = [], resolvers = {}, cacheOptions = {}) {
const cache = new InMemoryCache({ const cache = new InMemoryCache({
...defaultCacheOptions, ...defaultCacheOptions,
...cacheOptions, ...cacheOptions,
}); });
const mockClient = createMockClient({ cache, resolvers }); const mockClient = createMockApolloClient({ cache, resolvers });
if (Array.isArray(handlers)) { if (Array.isArray(handlers)) {
handlers.forEach(([query, value]) => mockClient.setRequestHandler(query, value)); handlers.forEach(([query, value]) => mockClient.setRequestHandler(query, value));
...@@ -21,7 +21,12 @@ export default (handlers = [], resolvers = {}, cacheOptions = {}) => { ...@@ -21,7 +21,12 @@ export default (handlers = [], resolvers = {}, cacheOptions = {}) => {
throw new Error('You should pass an array of handlers to mock Apollo client'); throw new Error('You should pass an array of handlers to mock Apollo client');
} }
return mockClient;
}
export default function createMockApollo(handlers, resolvers, cacheOptions) {
const mockClient = createMockClient(handlers, resolvers, cacheOptions);
const apolloProvider = new VueApollo({ defaultClient: mockClient }); const apolloProvider = new VueApollo({ defaultClient: mockClient });
return apolloProvider; return apolloProvider;
}; }
# frozen_string_literal: true # frozen_string_literal: true
require 'spec_helper'
require 'fast_spec_helper' require 'fast_spec_helper'
require "test_prof/recipes/rspec/let_it_be" require "test_prof/recipes/rspec/let_it_be"
...@@ -124,6 +125,18 @@ RSpec.describe Gitlab::Graphql::Queries do ...@@ -124,6 +125,18 @@ RSpec.describe Gitlab::Graphql::Queries do
expect(described_class.find(path)).to be_empty expect(described_class.find(path)).to be_empty
end end
it 'ignores customer.query.graphql' do
path = root / 'plans.customer.query.graphql'
expect(described_class.find(path)).to be_empty
end
it 'ignores customer.mutation.graphql' do
path = root / 'plans.customer.mutation.graphql'
expect(described_class.find(path)).to be_empty
end
it 'finds all query definitions under a root directory' do it 'finds all query definitions under a root directory' do
found = described_class.find(root) found = described_class.find(root)
...@@ -137,7 +150,9 @@ RSpec.describe Gitlab::Graphql::Queries do ...@@ -137,7 +150,9 @@ RSpec.describe Gitlab::Graphql::Queries do
expect(found).not_to include( expect(found).not_to include(
definition_of(root / 'typedefs.graphql'), definition_of(root / 'typedefs.graphql'),
definition_of(root / 'author.fragment.graphql') definition_of(root / 'author.fragment.graphql'),
definition_of(root / 'plans.customer.query.graphql'),
definition_of(root / 'plans.customer.mutation.graphql')
) )
end end
end end
......
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