Commit 1b33e388 authored by David O'Regan's avatar David O'Regan

Merge branch '346089-jira-connect-architecture-refactor' into 'master'

Break Jira connect app into multiple page components

See merge request gitlab-org/gitlab!79301
parents df807ed2 34fa04d0
<script> <script>
import { GlAlert, GlLink, GlSprintf, GlEmptyState } from '@gitlab/ui'; import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { mapState, mapMutations } from 'vuex'; import { mapState, mapMutations } from 'vuex';
import { retrieveAlert } from '~/jira_connect/subscriptions/utils'; import { retrieveAlert } from '~/jira_connect/subscriptions/utils';
import { SET_ALERT } from '../store/mutation_types'; import { SET_ALERT } from '../store/mutation_types';
import SubscriptionsList from './subscriptions_list.vue'; import SignInPage from '../pages/sign_in.vue';
import AddNamespaceButton from './add_namespace_button.vue'; import SubscriptionsPage from '../pages/subscriptions.vue';
import SignInButton from './sign_in_button.vue';
import UserLink from './user_link.vue'; import UserLink from './user_link.vue';
import CompatibilityAlert from './compatibility_alert.vue'; import CompatibilityAlert from './compatibility_alert.vue';
...@@ -16,12 +15,10 @@ export default { ...@@ -16,12 +15,10 @@ export default {
GlAlert, GlAlert,
GlLink, GlLink,
GlSprintf, GlSprintf,
GlEmptyState,
SubscriptionsList,
AddNamespaceButton,
SignInButton,
UserLink, UserLink,
CompatibilityAlert, CompatibilityAlert,
SignInPage,
SubscriptionsPage,
}, },
inject: { inject: {
usersPath: { usersPath: {
...@@ -84,43 +81,9 @@ export default { ...@@ -84,43 +81,9 @@ export default {
<user-link :user-signed-in="userSignedIn" :has-subscriptions="hasSubscriptions" /> <user-link :user-signed-in="userSignedIn" :has-subscriptions="hasSubscriptions" />
<h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2> <h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
<div class="jira-connect-app-body gl-mx-auto gl-px-5 gl-mb-7"> <div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7">
<template v-if="hasSubscriptions"> <sign-in-page v-if="!userSignedIn" :has-subscriptions="hasSubscriptions" />
<div class="gl-display-flex gl-justify-content-end"> <subscriptions-page v-else :has-subscriptions="hasSubscriptions" />
<sign-in-button v-if="!userSignedIn" :users-path="usersPath" />
<add-namespace-button v-else />
</div>
<subscriptions-list />
</template>
<template v-else>
<div v-if="!userSignedIn" class="gl-text-center">
<p class="gl-mb-7">{{ s__('JiraService|Sign in to GitLab.com to get started.') }}</p>
<sign-in-button class="gl-mb-7" :users-path="usersPath">
{{ __('Sign in to GitLab') }}
</sign-in-button>
<p>
{{
s__(
'Integrations|Note: this integration only works with accounts on GitLab.com (SaaS).',
)
}}
</p>
</div>
<gl-empty-state
v-else
:title="s__('Integrations|No linked namespaces')"
:description="
s__(
'Integrations|Namespaces are the GitLab groups and subgroups you link to this Jira instance.',
)
"
>
<template #actions>
<add-namespace-button />
</template>
</gl-empty-state>
</template>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { GlButton } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { getGitlabSignInURL } from '~/jira_connect/subscriptions/utils'; import { getGitlabSignInURL } from '~/jira_connect/subscriptions/utils';
import { s__ } from '~/locale';
export default { export default {
components: { components: {
...@@ -25,12 +26,15 @@ export default { ...@@ -25,12 +26,15 @@ export default {
this.signInURL = await getGitlabSignInURL(this.usersPath); this.signInURL = await getGitlabSignInURL(this.usersPath);
}, },
}, },
i18n: {
defaultButtonText: s__('Integrations|Sign in to GitLab'),
},
}; };
</script> </script>
<template> <template>
<gl-button category="primary" variant="info" :href="signInURL" target="_blank"> <gl-button category="primary" variant="info" :href="signInURL" target="_blank">
<slot> <slot>
{{ s__('Integrations|Sign in to add namespaces') }} {{ $options.i18n.defaultButtonText }}
</slot> </slot>
</gl-button> </gl-button>
</template> </template>
<script>
import { s__ } from '~/locale';
import SubscriptionsList from '../components/subscriptions_list.vue';
import SignInButton from '../components/sign_in_button.vue';
export default {
name: 'SignInPage',
components: {
SubscriptionsList,
SignInButton,
},
inject: ['usersPath'],
props: {
hasSubscriptions: {
type: Boolean,
required: true,
},
},
i18n: {
signinButtonTextWithSubscriptions: s__('Integrations|Sign in to add namespaces'),
signInText: s__('JiraService|Sign in to GitLab.com to get started.'),
},
};
</script>
<template>
<div v-if="hasSubscriptions">
<div class="gl-display-flex gl-justify-content-end">
<sign-in-button :users-path="usersPath">
{{ $options.i18n.signinButtonTextWithSubscriptions }}
</sign-in-button>
</div>
<subscriptions-list />
</div>
<div v-else class="gl-text-center">
<p class="gl-mb-7">{{ $options.i18n.signInText }}</p>
<sign-in-button class="gl-mb-7" :users-path="usersPath" />
</div>
</template>
<script>
import { GlEmptyState } from '@gitlab/ui';
import SubscriptionsList from '../components/subscriptions_list.vue';
import AddNamespaceButton from '../components/add_namespace_button.vue';
export default {
name: 'SubscriptionsPage',
components: {
GlEmptyState,
SubscriptionsList,
AddNamespaceButton,
},
props: {
hasSubscriptions: {
type: Boolean,
required: true,
},
},
};
</script>
<template>
<div v-if="hasSubscriptions">
<div class="gl-display-flex gl-justify-content-end">
<add-namespace-button />
</div>
<subscriptions-list />
</div>
<gl-empty-state
v-else
:title="s__('Integrations|No linked namespaces')"
:description="
s__(
'Integrations|Namespaces are the GitLab groups and subgroups you link to this Jira instance.',
)
"
>
<template #actions>
<add-namespace-button />
</template>
</gl-empty-state>
</template>
...@@ -40,10 +40,6 @@ $header-height: 40px; ...@@ -40,10 +40,6 @@ $header-height: 40px;
max-width: 1000px; max-width: 1000px;
} }
.jira-connect-app-body {
max-width: 768px;
}
// needed for external_link // needed for external_link
svg.s16 { svg.s16 {
width: 16px; width: 16px;
......
...@@ -19340,9 +19340,6 @@ msgstr "" ...@@ -19340,9 +19340,6 @@ msgstr ""
msgid "Integrations|No linked namespaces" msgid "Integrations|No linked namespaces"
msgstr "" msgstr ""
msgid "Integrations|Note: this integration only works with accounts on GitLab.com (SaaS)."
msgstr ""
msgid "Integrations|Projects using custom settings" msgid "Integrations|Projects using custom settings"
msgstr "" msgstr ""
...@@ -19379,6 +19376,9 @@ msgstr "" ...@@ -19379,6 +19376,9 @@ msgstr ""
msgid "Integrations|Send notifications about project events to a Unify Circuit conversation. %{docs_link}" msgid "Integrations|Send notifications about project events to a Unify Circuit conversation. %{docs_link}"
msgstr "" msgstr ""
msgid "Integrations|Sign in to GitLab"
msgstr ""
msgid "Integrations|Sign in to add namespaces" msgid "Integrations|Sign in to add namespaces"
msgstr "" msgstr ""
......
import { GlLink, GlEmptyState } from '@gitlab/ui'; import { GlLink } from '@gitlab/ui';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import JiraConnectApp from '~/jira_connect/subscriptions/components/app.vue'; import JiraConnectApp from '~/jira_connect/subscriptions/components/app.vue';
import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue'; import SignInPage from '~/jira_connect/subscriptions/pages/sign_in.vue';
import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.vue'; import SubscriptionsPage from '~/jira_connect/subscriptions/pages/subscriptions.vue';
import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
import UserLink from '~/jira_connect/subscriptions/components/user_link.vue'; import UserLink from '~/jira_connect/subscriptions/components/user_link.vue';
import createStore from '~/jira_connect/subscriptions/store'; import createStore from '~/jira_connect/subscriptions/store';
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types'; import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
...@@ -23,10 +22,8 @@ describe('JiraConnectApp', () => { ...@@ -23,10 +22,8 @@ describe('JiraConnectApp', () => {
const findAlert = () => wrapper.findByTestId('jira-connect-persisted-alert'); const findAlert = () => wrapper.findByTestId('jira-connect-persisted-alert');
const findAlertLink = () => findAlert().findComponent(GlLink); const findAlertLink = () => findAlert().findComponent(GlLink);
const findSignInButton = () => wrapper.findComponent(SignInButton); const findSignInPage = () => wrapper.findComponent(SignInPage);
const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton); const findSubscriptionsPage = () => wrapper.findComponent(SubscriptionsPage);
const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const createComponent = ({ provide, mountFn = shallowMountExtended } = {}) => { const createComponent = ({ provide, mountFn = shallowMountExtended } = {}) => {
store = createStore(); store = createStore();
...@@ -43,49 +40,35 @@ describe('JiraConnectApp', () => { ...@@ -43,49 +40,35 @@ describe('JiraConnectApp', () => {
describe('template', () => { describe('template', () => {
describe.each` describe.each`
scenario | usersPath | subscriptions | expectSignInButton | expectEmptyState | expectNamespaceButton | expectSubscriptionsList scenario | usersPath | shouldRenderSignInPage | shouldRenderSubscriptionsPage
${'user is not signed in with subscriptions'} | ${'/users'} | ${[mockSubscription]} | ${true} | ${false} | ${false} | ${true} ${'user is not signed in'} | ${'/users'} | ${true} | ${false}
${'user is not signed in without subscriptions'} | ${'/users'} | ${undefined} | ${true} | ${false} | ${false} | ${false} ${'user is signed in'} | ${undefined} | ${false} | ${true}
${'user is signed in with subscriptions'} | ${undefined} | ${[mockSubscription]} | ${false} | ${false} | ${true} | ${true} `('when $scenario', ({ usersPath, shouldRenderSignInPage, shouldRenderSubscriptionsPage }) => {
${'user is signed in without subscriptions'} | ${undefined} | ${undefined} | ${false} | ${true} | ${false} | ${false} beforeEach(() => {
`( createComponent({
'when $scenario', provide: {
({ usersPath,
usersPath, subscriptions: [mockSubscription],
expectSignInButton, },
subscriptions,
expectEmptyState,
expectNamespaceButton,
expectSubscriptionsList,
}) => {
beforeEach(() => {
createComponent({
provide: {
usersPath,
subscriptions,
},
});
});
it(`${expectSignInButton ? 'renders' : 'does not render'} sign in button`, () => {
expect(findSignInButton().exists()).toBe(expectSignInButton);
});
it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
expect(findEmptyState().exists()).toBe(expectEmptyState);
}); });
});
it(`${ it(`${shouldRenderSignInPage ? 'renders' : 'does not render'} sign in page`, () => {
expectNamespaceButton ? 'renders' : 'does not render' expect(findSignInPage().exists()).toBe(shouldRenderSignInPage);
} button to add namespace`, () => { if (shouldRenderSignInPage) {
expect(findAddNamespaceButton().exists()).toBe(expectNamespaceButton); expect(findSignInPage().props('hasSubscriptions')).toBe(true);
}); }
});
it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => { it(`${
expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList); shouldRenderSubscriptionsPage ? 'renders' : 'does not render'
}); } subscriptions page`, () => {
}, expect(findSubscriptionsPage().exists()).toBe(shouldRenderSubscriptionsPage);
); if (shouldRenderSubscriptionsPage) {
expect(findSubscriptionsPage().props('hasSubscriptions')).toBe(true);
}
});
});
it('renders UserLink component', () => { it('renders UserLink component', () => {
createComponent({ createComponent({
......
...@@ -11,11 +11,12 @@ jest.mock('~/jira_connect/subscriptions/utils'); ...@@ -11,11 +11,12 @@ jest.mock('~/jira_connect/subscriptions/utils');
describe('SignInButton', () => { describe('SignInButton', () => {
let wrapper; let wrapper;
const createComponent = () => { const createComponent = ({ slots } = {}) => {
wrapper = shallowMount(SignInButton, { wrapper = shallowMount(SignInButton, {
propsData: { propsData: {
usersPath: MOCK_USERS_PATH, usersPath: MOCK_USERS_PATH,
}, },
slots,
}); });
}; };
...@@ -29,6 +30,7 @@ describe('SignInButton', () => { ...@@ -29,6 +30,7 @@ describe('SignInButton', () => {
createComponent(); createComponent();
expect(findButton().exists()).toBe(true); expect(findButton().exists()).toBe(true);
expect(findButton().text()).toBe(SignInButton.i18n.defaultButtonText);
}); });
describe.each` describe.each`
...@@ -45,4 +47,12 @@ describe('SignInButton', () => { ...@@ -45,4 +47,12 @@ describe('SignInButton', () => {
expect(findButton().attributes('href')).toBe(expectedHref); expect(findButton().attributes('href')).toBe(expectedHref);
}); });
}); });
describe('with slot', () => {
const mockSlotContent = 'custom button content!';
it('renders slot content in button', () => {
createComponent({ slots: { default: mockSlotContent } });
expect(wrapper.text()).toMatchInterpolatedText(mockSlotContent);
});
});
}); });
import { mount } from '@vue/test-utils';
import SignInPage from '~/jira_connect/subscriptions/pages/sign_in.vue';
import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.vue';
import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
import createStore from '~/jira_connect/subscriptions/store';
jest.mock('~/jira_connect/subscriptions/utils');
describe('SignInPage', () => {
let wrapper;
let store;
const findSignInButton = () => wrapper.findComponent(SignInButton);
const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
const createComponent = ({ provide, props } = {}) => {
store = createStore();
wrapper = mount(SignInPage, {
store,
provide,
propsData: props,
});
};
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
const mockUsersPath = '/test';
describe.each`
scenario | expectSubscriptionsList | signInButtonText
${'with subscriptions'} | ${true} | ${SignInPage.i18n.signinButtonTextWithSubscriptions}
${'without subscriptions'} | ${false} | ${SignInButton.i18n.defaultButtonText}
`('$scenario', ({ expectSubscriptionsList, signInButtonText }) => {
beforeEach(() => {
createComponent({
provide: {
usersPath: mockUsersPath,
},
props: {
hasSubscriptions: expectSubscriptionsList,
},
});
});
it(`renders sign in button with text ${signInButtonText}`, () => {
expect(findSignInButton().text()).toMatchInterpolatedText(signInButtonText);
});
it('renders sign in button with `usersPath` prop', () => {
expect(findSignInButton().props('usersPath')).toBe(mockUsersPath);
});
it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
});
});
});
});
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import SubscriptionsPage from '~/jira_connect/subscriptions/pages/subscriptions.vue';
import AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue';
import createStore from '~/jira_connect/subscriptions/store';
describe('SubscriptionsPage', () => {
let wrapper;
let store;
const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const createComponent = ({ props } = {}) => {
store = createStore();
wrapper = shallowMount(SubscriptionsPage, {
store,
propsData: props,
});
};
afterEach(() => {
wrapper.destroy();
});
describe('template', () => {
describe.each`
scenario | expectSubscriptionsList | expectEmptyState
${'with subscriptions'} | ${true} | ${false}
${'without subscriptions'} | ${false} | ${true}
`('$scenario', ({ expectEmptyState, expectSubscriptionsList }) => {
beforeEach(() => {
createComponent({
props: {
hasSubscriptions: expectSubscriptionsList,
},
});
});
it('renders button to add namespace', () => {
expect(findAddNamespaceButton().exists()).toBe(true);
});
it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
expect(findEmptyState().exists()).toBe(expectEmptyState);
});
it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
});
});
});
});
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