From cf323691310a7305b7b2275ffbc61d9bae87043d Mon Sep 17 00:00:00 2001 From: Phil Hughes <me@iamphill.com> Date: Thu, 28 Mar 2019 12:25:24 +0000 Subject: [PATCH] Changes GraphQL client to allow for multipart requests This is to mainly allow for images to be uploaded in design management. --- app/assets/javascripts/lib/graphql.js | 19 +++-- doc/development/fe_guide/graphql.md | 28 ++++-- .../javascripts/design_management/graphql.js | 43 ++++++---- .../design_management/pages/design/index.vue | 8 ++ .../design_management/pages/index.vue | 15 ++-- .../queries/uploadDesign.graphql | 4 +- .../javascripts/design_management/router.js | 9 ++ .../design_management/pages/index_spec.js | 3 +- locale/gitlab.pot | 3 + package.json | 3 +- yarn.lock | 85 +++++++++++-------- 11 files changed, 137 insertions(+), 83 deletions(-) diff --git a/app/assets/javascripts/lib/graphql.js b/app/assets/javascripts/lib/graphql.js index 64e4e899f4472..ae02559415c3b 100644 --- a/app/assets/javascripts/lib/graphql.js +++ b/app/assets/javascripts/lib/graphql.js @@ -1,11 +1,16 @@ -import ApolloClient from 'apollo-boost'; +import { ApolloClient } from 'apollo-client'; +import { InMemoryCache } from 'apollo-cache-inmemory'; +import { createUploadLink } from 'apollo-upload-client'; import csrf from '~/lib/utils/csrf'; -export default (clientState = {}) => +export default (resolvers = {}) => new ApolloClient({ - uri: `${gon.relative_url_root}/api/graphql`, - headers: { - [csrf.headerKey]: csrf.token, - }, - clientState, + link: createUploadLink({ + uri: `${gon.relative_url_root}/api/graphql`, + headers: { + [csrf.headerKey]: csrf.token, + }, + }), + cache: new InMemoryCache(), + resolvers, }); diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md index 3290f29530a6b..5f6123b5f9b6c 100644 --- a/doc/development/fe_guide/graphql.md +++ b/doc/development/fe_guide/graphql.md @@ -46,7 +46,9 @@ Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation][vue-a ### Local state with `apollo-link-state` It is possible to use our Apollo setup with [apollo-link-state][apollo-link-state] by passing -in the client state object when creating the default client. +in a resolvers object when creating the default client. The default state can be set by writing +to the cache after setting up the default client. + ```javascript import Vue from 'vue'; @@ -54,15 +56,23 @@ import VueApollo from 'vue-apollo'; import createDefaultClient from '~/lib/graphql'; Vue.use(VueApollo); +const defaultClient = createDefaultClient({ + Query: { + ... + }, + Mutations: { + ... + }, +}); + +defaultClient.cache.writeData({ + data: { + isLoading: true, + }, +}); + const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient({ - defaults: { - testing: true, - }, - resolvers: { - ... - }, - }), + defaultClient, }); ``` diff --git a/ee/app/assets/javascripts/design_management/graphql.js b/ee/app/assets/javascripts/design_management/graphql.js index 6287df2e20aca..36e30ccd5341b 100644 --- a/ee/app/assets/javascripts/design_management/graphql.js +++ b/ee/app/assets/javascripts/design_management/graphql.js @@ -28,31 +28,38 @@ const designsStore = [ ]; const defaultClient = createDefaultClient({ - defaults: { - designs: designsStore, - }, - resolvers: { - Query: { - design(ctx, { id }) { - return designsStore.find(design => design.id === id); - }, + Query: { + design(ctx, { id }) { + return designsStore.find(design => design.id === id); }, - Mutation: { - uploadDesign(ctx, { name }, { cache }) { - const designs = name.map(n => ({ - ...createMockDesign(_.uniqueId()), - name: n, - commentsCount: 0, - })); + }, + Mutation: { + uploadDesign(ctx, { files }, { cache }) { + const previousDesigns = cache.readQuery({ query: allDesigns }); + const designs = Array.from(files).map(n => ({ + ...createMockDesign(_.uniqueId()), + name: n.name, + commentsCount: 0, + })); + const data = { + designs: designs.concat(previousDesigns.designs), + }; + + designsStore.unshift(...designs); - cache.writeData({ data: designs }); + cache.writeQuery({ query: allDesigns, data }); - return designs; - }, + return designs; }, }, }); +defaultClient.cache.writeData({ + data: { + designs: designsStore, + }, +}); + defaultClient .watchQuery({ query: allDesigns, diff --git a/ee/app/assets/javascripts/design_management/pages/design/index.vue b/ee/app/assets/javascripts/design_management/pages/design/index.vue index e23bebf628072..c203a13aab86b 100644 --- a/ee/app/assets/javascripts/design_management/pages/design/index.vue +++ b/ee/app/assets/javascripts/design_management/pages/design/index.vue @@ -1,4 +1,6 @@ <script> +import createFlash from '~/flash'; +import { s__ } from '~/locale'; import Toolbar from '../../components/toolbar/index.vue'; import DesignImage from '../../components/image.vue'; import getDesignQuery from '../../queries/getDesign.graphql'; @@ -27,6 +29,12 @@ export default { id: this.id, }; }, + result({ data }) { + if (!data) { + createFlash(s__('DesignManagement|Could not find design, please try again.')); + this.$router.push('/designs'); + } + }, }, }, computed: { diff --git a/ee/app/assets/javascripts/design_management/pages/index.vue b/ee/app/assets/javascripts/design_management/pages/index.vue index 487678c924db2..c3ae667d1c6f1 100644 --- a/ee/app/assets/javascripts/design_management/pages/index.vue +++ b/ee/app/assets/javascripts/design_management/pages/index.vue @@ -63,7 +63,7 @@ export default { onUploadDesign(files) { if (!this.canCreateDesign) return null; - const optimisticResponse = [...files].map(file => ({ + const optimisticResponse = Array.from(files).map(file => ({ __typename: 'Design', id: -1, image: '', @@ -78,14 +78,15 @@ export default { .mutate({ mutation: uploadDesignQuery, variables: { - name: [...files].map(({ name }) => name), + files, }, - update: (store, { data: { uploadDesign } }) => { - const data = store.readQuery({ query: allDesignsQuery }); + // update: (store, { data: { uploadDesign } }) => { + // const data = store.readQuery({ query: allDesignsQuery }); + // console.log(data, uploadDesign); - data.designs.unshift(...uploadDesign); - store.writeQuery({ query: allDesignsQuery, data }); - }, + // data.designs.unshift(...uploadDesign); + // store.writeQuery({ query: allDesignsQuery, data }); + // }, optimisticResponse: { __typename: 'Mutation', uploadDesign: optimisticResponse, diff --git a/ee/app/assets/javascripts/design_management/queries/uploadDesign.graphql b/ee/app/assets/javascripts/design_management/queries/uploadDesign.graphql index 16f23c0d4d352..d3c145fb20548 100644 --- a/ee/app/assets/javascripts/design_management/queries/uploadDesign.graphql +++ b/ee/app/assets/javascripts/design_management/queries/uploadDesign.graphql @@ -1,5 +1,5 @@ -mutation addDesigns($name: [String]) { - uploadDesign(name: $name) @client { +mutation addDesigns($files: [Upload!]!) { + uploadDesign(files: $files) @client { id image name diff --git a/ee/app/assets/javascripts/design_management/router.js b/ee/app/assets/javascripts/design_management/router.js index 385dbbe7b9236..fbf660ab954a2 100644 --- a/ee/app/assets/javascripts/design_management/router.js +++ b/ee/app/assets/javascripts/design_management/router.js @@ -32,6 +32,15 @@ const router = new VueRouter({ meta: { el: 'designs', }, + beforeEnter( + { + params: { id }, + }, + from, + next, + ) { + if (id !== -1) next(); + }, props: ({ params: { id } }) => ({ id: parseInt(id, 10) }), }, ], diff --git a/ee/spec/frontend/design_management/pages/index_spec.js b/ee/spec/frontend/design_management/pages/index_spec.js index a78fcaebf5e33..1bc0743fe3488 100644 --- a/ee/spec/frontend/design_management/pages/index_spec.js +++ b/ee/spec/frontend/design_management/pages/index_spec.js @@ -75,9 +75,8 @@ describe('Design management index page', () => { .then(() => { expect(mutate).toHaveBeenCalledWith({ mutation: uploadDesignQuery, - update: expect.any(Function), variables: { - name: ['test'], + files: [{ name: 'test' }], }, optimisticResponse: { __typename: 'Mutation', diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 27dd698af86d5..d3d20b1689947 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3520,6 +3520,9 @@ msgstr "" msgid "Description:" msgstr "" +msgid "DesignManagement|Could not find design, please try again." +msgstr "" + msgid "DesignManagement|Error uploading a new design. Please try again" msgstr "" diff --git a/package.json b/package.json index 547111d7b44f4..d672370e9a3ec 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,9 @@ "@gitlab/csslab": "^1.9.0", "@gitlab/svgs": "^1.57.0", "@gitlab/ui": "^3.0.0", - "apollo-boost": "^0.3.1", + "apollo-cache-inmemory": "^1.5.1", "apollo-client": "^2.5.1", + "apollo-upload-client": "^10.0.0", "at.js": "^1.5.4", "autosize": "^4.0.0", "axios": "^0.17.1", diff --git a/yarn.lock b/yarn.lock index 970be4c774aaf..61f2736cab87c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1114,21 +1114,6 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -apollo-boost@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/apollo-boost/-/apollo-boost-0.3.1.tgz#b6a896e020a0eab7e415032fe565734a955c65f8" - integrity sha512-VdXcTMxLBeNvANW/FtiarEkrRr/cepYKG6wTAURdy8CS33WYpEHtIg9S8tAjxwVzIECpE4lWyDKyPLoESJ072Q== - dependencies: - apollo-cache "^1.2.1" - apollo-cache-inmemory "^1.5.1" - apollo-client "^2.5.1" - apollo-link "^1.0.6" - apollo-link-error "^1.0.3" - apollo-link-http "^1.3.1" - graphql-tag "^2.4.2" - ts-invariant "^0.2.1" - tslib "^1.9.3" - apollo-cache-inmemory@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.5.1.tgz#265d1ee67b0bf0aca9c37629d410bfae44e62953" @@ -1170,29 +1155,16 @@ apollo-link-dedup@^1.0.0: dependencies: apollo-link "^1.2.3" -apollo-link-error@^1.0.3: - version "1.1.1" - resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.1.tgz#69d7124d4dc11ce60f505c940f05d4f1aa0945fb" - integrity sha512-/yPcaQWcBdB94vpJ4FsiCJt1dAGGRm+6Tsj3wKwP+72taBH+UsGRQQZk7U/1cpZwl1yqhHZn+ZNhVOebpPcIlA== +apollo-link-http-common@^0.2.8: + version "0.2.13" + resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.13.tgz#c688f6baaffdc7b269b2db7ae89dae7c58b5b350" + integrity sha512-Uyg1ECQpTTA691Fwx5e6Rc/6CPSu4TB4pQRTGIpwZ4l5JDOQ+812Wvi/e3IInmzOZpwx5YrrOfXrtN8BrsDXoA== dependencies: - apollo-link "^1.2.3" - -apollo-link-http-common@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.5.tgz#d094beb7971523203359bf830bfbfa7b4e7c30ed" - integrity sha512-6FV1wr5AqAyJ64Em1dq5hhGgiyxZE383VJQmhIoDVc3MyNcFL92TkhxREOs4rnH2a9X2iJMko7nodHSGLC6d8w== - dependencies: - apollo-link "^1.2.3" - -apollo-link-http@^1.3.1: - version "1.5.5" - resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.5.tgz#7dbe851821771ad67fa29e3900c57f38cbd80da8" - integrity sha512-C5N6N/mRwmepvtzO27dgMEU3MMtRKSqcljBkYNZmWwH11BxkUQ5imBLPM3V4QJXNE7NFuAQAB5PeUd4ligivTQ== - dependencies: - apollo-link "^1.2.3" - apollo-link-http-common "^0.2.5" + apollo-link "^1.2.11" + ts-invariant "^0.3.2" + tslib "^1.9.3" -apollo-link@^1.0.0, apollo-link@^1.0.6, apollo-link@^1.2.3: +apollo-link@^1.0.0, apollo-link@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.3.tgz#9bd8d5fe1d88d31dc91dae9ecc22474d451fb70d" integrity sha512-iL9yS2OfxYhigme5bpTbmRyC+Htt6tyo2fRMHT3K1XRL/C5IQDDz37OjpPy4ndx7WInSvfSZaaOTKFja9VWqSw== @@ -1200,6 +1172,25 @@ apollo-link@^1.0.0, apollo-link@^1.0.6, apollo-link@^1.2.3: apollo-utilities "^1.0.0" zen-observable-ts "^0.8.10" +apollo-link@^1.2.11, apollo-link@^1.2.6: + version "1.2.11" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.11.tgz#493293b747ad3237114ccd22e9f559e5e24a194d" + integrity sha512-PQvRCg13VduLy3X/0L79M6uOpTh5iHdxnxYuo8yL7sJlWybKRJwsv4IcRBJpMFbChOOaHY7Og9wgPo6DLKDKDA== + dependencies: + apollo-utilities "^1.2.1" + ts-invariant "^0.3.2" + tslib "^1.9.3" + zen-observable-ts "^0.8.18" + +apollo-upload-client@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-10.0.0.tgz#6cc3d0ea2aef40bc237b655f5042809cacee1859" + integrity sha512-N0SENiEkZXoY4nl9xxrXFcj/cL0AVkSNQ4aYXSaruCBWE0aKpK6aCe4DBmiEHrK3FAsMxZPEJxBRIWNbsXT8dw== + dependencies: + apollo-link "^1.2.6" + apollo-link-http-common "^0.2.8" + extract-files "^5.0.0" + apollo-utilities@1.2.1, apollo-utilities@^1.0.0, apollo-utilities@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.2.1.tgz#1c3a1ebf5607d7c8efe7636daaf58e7463b41b3c" @@ -4134,6 +4125,11 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" +extract-files@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-5.0.1.tgz#c9492a8410be643e260a376f0151361993d5f659" + integrity sha512-qRW6y9eKF0VbCyOoOEtFhzJ3uykAw8GKwQVXyAIqwocyEWW4m+v+evec34RwtUkkxxHh7NKBLJ6AnXM8W4dH5w== + extract-from-css@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/extract-from-css/-/extract-from-css-0.4.4.tgz#1ea7df2e7c7c6eb9922fa08e8adaea486f6f8f92" @@ -4778,7 +4774,7 @@ graphlibrary@^2.2.0: dependencies: lodash "^4.17.5" -graphql-tag@^2.10.0, graphql-tag@^2.4.2: +graphql-tag@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.0.tgz#87da024be863e357551b2b8700e496ee2d4353ae" integrity sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w== @@ -10460,6 +10456,13 @@ ts-invariant@^0.2.1: dependencies: tslib "^1.9.3" +ts-invariant@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.3.2.tgz#89a2ffeb70879b777258df1df1c59383c35209b0" + integrity sha512-QsY8BCaRnHiB5T6iE4DPlJMAKEG3gzMiUco9FEt1jUXQf0XP6zi0idT0i0rMTu8A326JqNSDsmlkA9dRSh1TRg== + dependencies: + tslib "^1.9.3" + ts-jest@24.0.0, ts-jest@^23.10.5: version "24.0.0" resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.0.0.tgz#3f26bf2ec1fa584863a5a9c29bd8717d549efbf6" @@ -11540,6 +11543,14 @@ zen-observable-ts@^0.8.10: dependencies: zen-observable "^0.8.0" +zen-observable-ts@^0.8.18: + version "0.8.18" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.18.tgz#ade44b1060cc4a800627856ec10b9c67f5f639c8" + integrity sha512-q7d05s75Rn1j39U5Oapg3HI2wzriVwERVo4N7uFGpIYuHB9ff02P/E92P9B8T7QVC93jCMHpbXH7X0eVR5LA7A== + dependencies: + tslib "^1.9.3" + zen-observable "^0.8.0" + zen-observable@^0.8.0: version "0.8.11" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199" -- GitLab