Commit 80646fd8 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'design-management-vue-app' into 'master'

Setup initial Vue app for design management

See merge request gitlab-org/gitlab-ee!9652
parents 80360029 aafc8c24
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import defaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import App from './components/app.vue'; import App from './components/app.vue';
Vue.use(VueApollo); Vue.use(VueApollo);
...@@ -10,7 +10,7 @@ export default function() { ...@@ -10,7 +10,7 @@ export default function() {
const issueTitle = document.getElementById('issue_title'); const issueTitle = document.getElementById('issue_title');
const { projectPath } = el.dataset; const { projectPath } = el.dataset;
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient, defaultClient: createDefaultClient(),
}); });
return new Vue({ return new Vue({
......
import ApolloClient from 'apollo-boost'; import ApolloClient from 'apollo-boost';
import csrf from '~/lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
export default new ApolloClient({ export default (clientState = {}) =>
new ApolloClient({
uri: `${gon.relative_url_root}/api/graphql`, uri: `${gon.relative_url_root}/api/graphql`,
headers: { headers: {
[csrf.headerKey]: csrf.token, [csrf.headerKey]: csrf.token,
}, },
}); clientState,
});
...@@ -27,11 +27,11 @@ the Vue application is mounted. ...@@ -27,11 +27,11 @@ the Vue application is mounted.
```javascript ```javascript
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import defaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo); Vue.use(VueApollo);
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
defaultClient, defaultClient: createDefaultClient(),
}); });
new Vue({ new Vue({
...@@ -43,6 +43,29 @@ new Vue({ ...@@ -43,6 +43,29 @@ new Vue({
Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation][vue-apollo-docs]. Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation][vue-apollo-docs].
### 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.
```javascript
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient({
defaults: {
testing: true,
},
resolvers: {
...
},
}),
});
```
### Testing ### Testing
With [Vue test utils][vue-test-utils] it is easy to quickly test components that With [Vue test utils][vue-test-utils] it is easy to quickly test components that
...@@ -81,3 +104,4 @@ Read more about the [Apollo] client in the [Apollo documentation][apollo-client- ...@@ -81,3 +104,4 @@ Read more about the [Apollo] client in the [Apollo documentation][apollo-client-
[default-client]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/lib/graphql.js [default-client]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/lib/graphql.js
[apollo-client-docs]: https://www.apollographql.com/docs/tutorial/client.html [apollo-client-docs]: https://www.apollographql.com/docs/tutorial/client.html
[vue-test-utils]: https://vue-test-utils.vuejs.org/ [vue-test-utils]: https://vue-test-utils.vuejs.org/
[apollo-link-state]: https://www.apollographql.com/docs/link/links/state.html
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
Vue.use(VueApollo);
export default new VueApollo({
defaultClient: createDefaultClient(),
});
import $ from 'jquery';
import Vue from 'vue';
import router from './router';
import App from './components/app.vue';
import apolloProvider from './graphql';
export default () => {
$('.js-issue-tabs').on('shown.bs.tab', ({ target: { id } }) => {
if (id === 'designs' && router.currentRoute.name === 'root') {
router.push('/designs');
} else if (id === 'discussion') {
router.push('/');
}
});
return new Vue({
el: document.getElementById('js-design-management'),
router,
apolloProvider,
render(createElement) {
return createElement(App);
},
});
};
<script>
export default {
props: {
id: {
type: Number,
required: true,
},
},
};
</script>
<template>
<div>
Design detail for {{ id }}
<router-link to="/designs">All designs</router-link>
</div>
</template>
<template>
<div>
Home
<router-link to="/designs/1">Design</router-link>
</div>
</template>
import $ from 'jquery';
import Vue from 'vue';
import VueRouter from 'vue-router';
import Home from './pages/index.vue';
import DesignDetail from './pages/design/index.vue';
Vue.use(VueRouter);
const router = new VueRouter({
base: window.location.pathname,
routes: [
{
name: 'root',
path: '/',
component: null,
meta: {
el: 'discussion',
},
},
{
name: 'designs',
path: '/designs',
component: Home,
meta: {
el: 'designs',
},
},
{
name: 'design',
path: '/designs/:id',
component: DesignDetail,
meta: {
el: 'designs',
},
props: ({ params: { id } }) => ({ id: parseInt(id, 10) }),
},
],
});
router.beforeEach(({ meta: { el } }, from, next) => {
$(`#${el}`).tab('show');
next();
});
export default router;
...@@ -8,6 +8,12 @@ document.addEventListener('DOMContentLoaded', () => { ...@@ -8,6 +8,12 @@ document.addEventListener('DOMContentLoaded', () => {
initSidebarBundle(); initSidebarBundle();
initRelatedIssues(); initRelatedIssues();
if (gon.features.versionedDesigns) {
import(/* webpackChunkName: 'design_management' */ 'ee/design_management')
.then(module => module.default())
.catch(() => {});
}
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new UserCallout({ className: 'js-epics-sidebar-callout' }); new UserCallout({ className: 'js-epics-sidebar-callout' });
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
......
...@@ -16,6 +16,9 @@ module EE ...@@ -16,6 +16,9 @@ module EE
before_action :check_export_issues_available!, only: [:export_csv] before_action :check_export_issues_available!, only: [:export_csv]
before_action :check_service_desk_available!, only: [:service_desk] before_action :check_service_desk_available!, only: [:service_desk]
before_action :whitelist_query_limiting_ee, only: [:update] before_action :whitelist_query_limiting_ee, only: [:update]
before_action only: :show do
push_frontend_feature_flag(:versioned_designs)
end
end end
override :issue_except_actions override :issue_except_actions
......
- if Feature.enabled?(:versioned_designs) - if Feature.enabled?(:versioned_designs)
%ul.nav-tabs.nav.nav-links{ role: 'tablist' } %ul.nav-tabs.nav.nav-links{ role: 'tablist' }
%li %li
= link_to '#discussion-tab', class: 'active', id: 'discussion', role: 'tab', 'aria-controls': 'js-discussion', 'aria-selected': 'true', data: { toggle: 'tab', target: '#discussion-tab' } do = link_to '#discussion-tab', class: 'active js-issue-tabs', id: 'discussion', role: 'tab', 'aria-controls': 'js-discussion', 'aria-selected': 'true', data: { toggle: 'tab', target: '#discussion-tab' } do
= _('Discussion') = _('Discussion')
%span.badge.badge-pill.js-discussions-count %span.badge.badge-pill.js-discussions-count
%li %li
= link_to '#designs-tab', id: 'designs', role: 'tab', 'aria-controls': 'js-designs', 'aria-selected': 'false', data: { toggle: 'tab', target: '#designs-tab' } do = link_to '#designs-tab', class: 'js-issue-tabs', id: 'designs', role: 'tab', 'aria-controls': 'js-designs', 'aria-selected': 'false', data: { toggle: 'tab', target: '#designs-tab' } do
= _('Designs') = _('Designs')
%span.badge.badge-pill.js-designs-count %span.badge.badge-pill.js-designs-count
.tab-content .tab-content
#discussion-tab.tab-pane.show.active{ role: 'tabpanel', 'aria-labelledby': 'discussion' } #discussion-tab.tab-pane.show.active{ role: 'tabpanel', 'aria-labelledby': 'discussion' }
= render_ce 'projects/issues/discussion' = render_ce 'projects/issues/discussion'
#designs-tab.tab-pane{ role: 'tabpanel', 'aria-labelledby': 'designs' } #designs-tab.tab-pane{ role: 'tabpanel', 'aria-labelledby': 'designs' }
= spinner nil, true #js-design-management
- else - else
= render_ce 'projects/issues/discussion' = render_ce 'projects/issues/discussion'
import { mount, createLocalVue } from '@vue/test-utils';
import VueRouter from 'vue-router';
import App from 'ee/design_management/components/app.vue';
import Designs from 'ee/design_management/pages/index.vue';
import DesignDetail from 'ee/design_management/pages/design/index.vue';
import router from 'ee/design_management/router';
describe('Design management router', () => {
let vm;
function factory() {
const localVue = createLocalVue();
localVue.use(VueRouter);
vm = mount(App, { localVue, router });
}
beforeEach(() => {
factory();
});
afterEach(() => {
vm.destroy();
router.app.$destroy();
window.location.hash = '';
});
describe('root', () => {
it('pushes empty component', () => {
router.push('/');
expect(vm.isEmpty()).toBe(true);
});
});
describe('designs', () => {
it('pushes designs root component', () => {
router.push('/designs');
expect(vm.find(Designs).exists()).toBe(true);
});
});
describe('designs detail', () => {
it('pushes designs detail component', () => {
router.push('/designs/1');
const detail = vm.find(DesignDetail);
expect(detail.exists()).toBe(true);
expect(detail.props('id')).toEqual(1);
});
});
});
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