Commit 29f41dfa authored by Mikołaj Wawrzyniak's avatar Mikołaj Wawrzyniak

Merge branch 'tomquirk/339428-move-add-namespace-button-part-3' into 'master'

Move "Add Namespace" button to GlEmptyState

See merge request gitlab-org/gitlab!73714
parents 4db620a3 a7a5d85f
<script> <script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; import { GlAlert, GlLink, GlSprintf, GlEmptyState } from '@gitlab/ui';
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';
...@@ -13,6 +14,7 @@ export default { ...@@ -13,6 +14,7 @@ export default {
GlAlert, GlAlert,
GlLink, GlLink,
GlSprintf, GlSprintf,
GlEmptyState,
SubscriptionsList, SubscriptionsList,
AddNamespaceButton, AddNamespaceButton,
SignInButton, SignInButton,
...@@ -21,12 +23,18 @@ export default { ...@@ -21,12 +23,18 @@ export default {
usersPath: { usersPath: {
default: '', default: '',
}, },
subscriptions: {
default: [],
},
}, },
computed: { computed: {
...mapState(['alert']), ...mapState(['alert']),
shouldShowAlert() { shouldShowAlert() {
return Boolean(this.alert?.message); return Boolean(this.alert?.message);
}, },
hasSubscriptions() {
return !isEmpty(this.subscriptions);
},
userSignedIn() { userSignedIn() {
return Boolean(!this.usersPath); return Boolean(!this.usersPath);
}, },
...@@ -66,15 +74,44 @@ export default { ...@@ -66,15 +74,44 @@ export default {
</template> </template>
</gl-alert> </gl-alert>
<h2 class="gl-text-center">{{ 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="jira-connect-app-body gl-my-7 gl-px-5 gl-pb-4"> <template v-if="hasSubscriptions">
<div class="gl-display-flex gl-justify-content-end"> <div class="gl-display-flex gl-justify-content-end">
<sign-in-button v-if="!userSignedIn" :users-path="usersPath" /> <sign-in-button v-if="!userSignedIn" :users-path="usersPath" />
<add-namespace-button v-else /> <add-namespace-button v-else />
</div> </div>
<subscriptions-list /> <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, GlEmptyState, GlTable } from '@gitlab/ui'; import { GlButton, GlTable } from '@gitlab/ui';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { mapMutations } from 'vuex'; import { mapMutations } from 'vuex';
import { removeSubscription } from '~/jira_connect/subscriptions/api'; import { removeSubscription } from '~/jira_connect/subscriptions/api';
...@@ -12,7 +12,6 @@ import GroupItemName from './group_item_name.vue'; ...@@ -12,7 +12,6 @@ import GroupItemName from './group_item_name.vue';
export default { export default {
components: { components: {
GlButton, GlButton,
GlEmptyState,
GlTable, GlTable,
GroupItemName, GroupItemName,
TimeagoTooltip, TimeagoTooltip,
...@@ -44,17 +43,15 @@ export default { ...@@ -44,17 +43,15 @@ export default {
}, },
], ],
i18n: { i18n: {
emptyTitle: s__('Integrations|No linked namespaces'),
emptyDescription: s__(
'Integrations|Namespaces are the GitLab groups and subgroups you link to this Jira instance.',
),
unlinkError: s__('Integrations|Failed to unlink namespace. Please try again.'), unlinkError: s__('Integrations|Failed to unlink namespace. Please try again.'),
}, },
methods: { methods: {
...mapMutations({ ...mapMutations({
setAlert: SET_ALERT, setAlert: SET_ALERT,
}), }),
isEmpty, isUnlinkButtonDisabled(item) {
return !isEmpty(item);
},
isLoadingItem(item) { isLoadingItem(item) {
return this.loadingItem === item; return this.loadingItem === item;
}, },
...@@ -81,13 +78,7 @@ export default { ...@@ -81,13 +78,7 @@ export default {
</script> </script>
<template> <template>
<div> <gl-table :items="subscriptions" :fields="$options.fields">
<gl-empty-state
v-if="isEmpty(subscriptions)"
:title="$options.i18n.emptyTitle"
:description="$options.i18n.emptyDescription"
/>
<gl-table v-else :items="subscriptions" :fields="$options.fields">
<template #cell(name)="{ item }"> <template #cell(name)="{ item }">
<group-item-name :group="item.group" /> <group-item-name :group="item.group" />
</template> </template>
...@@ -99,11 +90,10 @@ export default { ...@@ -99,11 +90,10 @@ export default {
:class="unlinkBtnClass(item)" :class="unlinkBtnClass(item)"
category="secondary" category="secondary"
:loading="isLoadingItem(item)" :loading="isLoadingItem(item)"
:disabled="!isEmpty(loadingItem)" :disabled="isUnlinkButtonDisabled(loadingItem)"
@click.prevent="onClick(item)" @click.prevent="onClick(item)"
>{{ __('Unlink') }}</gl-button >{{ __('Unlink') }}</gl-button
> >
</template> </template>
</gl-table> </gl-table>
</div>
</template> </template>
...@@ -42,8 +42,6 @@ $header-height: 40px; ...@@ -42,8 +42,6 @@ $header-height: 40px;
.jira-connect-app-body { .jira-connect-app-body {
max-width: 768px; max-width: 768px;
margin-left: auto;
margin-right: auto;
} }
// needed for external_link // needed for external_link
......
...@@ -9,20 +9,9 @@ ...@@ -9,20 +9,9 @@
= link_to _('Sign in to GitLab'), jira_connect_users_path, target: '_blank', rel: 'noopener noreferrer', class: 'js-jira-connect-sign-in' = link_to _('Sign in to GitLab'), jira_connect_users_path, target: '_blank', rel: 'noopener noreferrer', class: 'js-jira-connect-sign-in'
%main.jira-connect-app.gl-px-5.gl-pt-7.gl-mx-auto %main.jira-connect-app.gl-px-5.gl-pt-7.gl-mx-auto
- if current_user.blank? && @subscriptions.empty?
.jira-connect-app-body.gl-px-5.gl-text-center
%h2= s_('JiraService|GitLab for Jira Configuration')
%p= s_('JiraService|Sign in to GitLab.com to get started.')
.gl-mt-7
= external_link _('Sign in to GitLab'), jira_connect_users_path, class: "btn gl-button btn-confirm js-jira-connect-sign-in"
.gl-mt-7
%p= s_('Integrations|Note: this integration only works with accounts on GitLab.com (SaaS).')
- else
.js-jira-connect-app{ data: jira_connect_app_data(@subscriptions) } .js-jira-connect-app{ data: jira_connect_app_data(@subscriptions) }
%p.jira-connect-app-body.gl-px-5.gl-mt-7.gl-font-base.gl-text-center %p.jira-connect-app-body.gl-px-5.gl-font-base.gl-text-center.gl-mx-auto
%strong= s_('Integrations|Browser limitations') %strong= s_('Integrations|Browser limitations')
- browser_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">' - browser_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'
- firefox_link_start = browser_link_start.html_safe % { url: 'https://www.mozilla.org/en-US/firefox/' } - firefox_link_start = browser_link_start.html_safe % { url: 'https://www.mozilla.org/en-US/firefox/' }
......
import { GlAlert, GlLink } from '@gitlab/ui'; import { GlAlert, GlLink, GlEmptyState } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils'; import { mount, shallowMount } from '@vue/test-utils';
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 AddNamespaceButton from '~/jira_connect/subscriptions/components/add_namespace_button.vue';
import SignInButton from '~/jira_connect/subscriptions/components/sign_in_button.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'; 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';
import { __ } from '~/locale'; import { __ } from '~/locale';
import { mockSubscription } from '../mock_data';
jest.mock('~/jira_connect/subscriptions/utils', () => ({ jest.mock('~/jira_connect/subscriptions/utils', () => ({
retrieveAlert: jest.fn().mockReturnValue({ message: 'error message' }), retrieveAlert: jest.fn().mockReturnValue({ message: 'error message' }),
...@@ -20,6 +22,8 @@ describe('JiraConnectApp', () => { ...@@ -20,6 +22,8 @@ describe('JiraConnectApp', () => {
const findAlertLink = () => findAlert().findComponent(GlLink); const findAlertLink = () => findAlert().findComponent(GlLink);
const findSignInButton = () => wrapper.findComponent(SignInButton); const findSignInButton = () => wrapper.findComponent(SignInButton);
const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton); const findAddNamespaceButton = () => wrapper.findComponent(AddNamespaceButton);
const findSubscriptionsList = () => wrapper.findComponent(SubscriptionsList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
const createComponent = ({ provide, mountFn = shallowMount } = {}) => { const createComponent = ({ provide, mountFn = shallowMount } = {}) => {
store = createStore(); store = createStore();
...@@ -36,25 +40,49 @@ describe('JiraConnectApp', () => { ...@@ -36,25 +40,49 @@ describe('JiraConnectApp', () => {
describe('template', () => { describe('template', () => {
describe.each` describe.each`
scenario | usersPath | expectSignInButton | expectNamespaceButton scenario | usersPath | subscriptions | expectSignInButton | expectEmptyState | expectNamespaceButton | expectSubscriptionsList
${'user is not signed in'} | ${'/users'} | ${true} | ${false} ${'user is not signed in with subscriptions'} | ${'/users'} | ${[mockSubscription]} | ${true} | ${false} | ${false} | ${true}
${'user is signed in'} | ${undefined} | ${false} | ${true} ${'user is not signed in without subscriptions'} | ${'/users'} | ${undefined} | ${true} | ${false} | ${false} | ${false}
`('when $scenario', ({ usersPath, expectSignInButton, expectNamespaceButton }) => { ${'user is signed in with subscriptions'} | ${undefined} | ${[mockSubscription]} | ${false} | ${false} | ${true} | ${true}
${'user is signed in without subscriptions'} | ${undefined} | ${undefined} | ${false} | ${true} | ${false} | ${false}
`(
'when $scenario',
({
usersPath,
expectSignInButton,
subscriptions,
expectEmptyState,
expectNamespaceButton,
expectSubscriptionsList,
}) => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({
provide: { provide: {
usersPath, usersPath,
subscriptions,
}, },
}); });
}); });
it('renders sign in button as expected', () => { it(`${expectSignInButton ? 'renders' : 'does not render'} sign in button`, () => {
expect(findSignInButton().exists()).toBe(expectSignInButton); expect(findSignInButton().exists()).toBe(expectSignInButton);
}); });
it('renders "Add Namespace" button as expected', () => { it(`${expectEmptyState ? 'renders' : 'does not render'} empty state`, () => {
expect(findEmptyState().exists()).toBe(expectEmptyState);
});
it(`${
expectNamespaceButton ? 'renders' : 'does not render'
} button to add namespace`, () => {
expect(findAddNamespaceButton().exists()).toBe(expectNamespaceButton); expect(findAddNamespaceButton().exists()).toBe(expectNamespaceButton);
}); });
it(`${expectSubscriptionsList ? 'renders' : 'does not render'} subscriptions list`, () => {
expect(findSubscriptionsList().exists()).toBe(expectSubscriptionsList);
});
},
);
}); });
describe('alert', () => { describe('alert', () => {
...@@ -123,5 +151,4 @@ describe('JiraConnectApp', () => { ...@@ -123,5 +151,4 @@ describe('JiraConnectApp', () => {
}); });
}); });
}); });
});
}); });
import { GlButton, GlEmptyState, GlTable } from '@gitlab/ui'; import { GlButton } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import * as JiraConnectApi from '~/jira_connect/subscriptions/api'; import * as JiraConnectApi from '~/jira_connect/subscriptions/api';
import GroupItemName from '~/jira_connect/subscriptions/components/group_item_name.vue';
import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.vue'; import SubscriptionsList from '~/jira_connect/subscriptions/components/subscriptions_list.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';
import { reloadPage } from '~/jira_connect/subscriptions/utils'; import { reloadPage } from '~/jira_connect/subscriptions/utils';
import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
import { mockSubscription } from '../mock_data'; import { mockSubscription } from '../mock_data';
jest.mock('~/jira_connect/subscriptions/utils'); jest.mock('~/jira_connect/subscriptions/utils');
...@@ -15,11 +18,13 @@ describe('SubscriptionsList', () => { ...@@ -15,11 +18,13 @@ describe('SubscriptionsList', () => {
let wrapper; let wrapper;
let store; let store;
const createComponent = ({ mountFn = shallowMount, provide = {} } = {}) => { const createComponent = () => {
store = createStore(); store = createStore();
wrapper = mountFn(SubscriptionsList, { wrapper = mount(SubscriptionsList, {
provide, provide: {
subscriptions: [mockSubscription],
},
store, store,
}); });
}; };
...@@ -28,28 +33,28 @@ describe('SubscriptionsList', () => { ...@@ -28,28 +33,28 @@ describe('SubscriptionsList', () => {
wrapper.destroy(); wrapper.destroy();
}); });
const findGlEmptyState = () => wrapper.findComponent(GlEmptyState); const findUnlinkButton = () => wrapper.findComponent(GlButton);
const findGlTable = () => wrapper.findComponent(GlTable);
const findUnlinkButton = () => findGlTable().findComponent(GlButton);
const clickUnlinkButton = () => findUnlinkButton().trigger('click'); const clickUnlinkButton = () => findUnlinkButton().trigger('click');
describe('template', () => { describe('template', () => {
it('renders GlEmptyState when subscriptions is empty', () => { beforeEach(() => {
createComponent(); createComponent();
expect(findGlEmptyState().exists()).toBe(true);
expect(findGlTable().exists()).toBe(false);
}); });
it('renders GlTable when subscriptions are present', () => { it('renders "name" cell correctly', () => {
createComponent({ const groupItemNames = wrapper.findAllComponents(GroupItemName);
provide: { expect(groupItemNames.wrappers).toHaveLength(1);
subscriptions: [mockSubscription],
}, const item = groupItemNames.at(0);
expect(item.props('group')).toBe(mockSubscription.group);
}); });
expect(findGlEmptyState().exists()).toBe(false); it('renders "created at" cell correctly', () => {
expect(findGlTable().exists()).toBe(true); const timeAgoTooltips = wrapper.findAllComponents(TimeagoTooltip);
expect(timeAgoTooltips.wrappers).toHaveLength(1);
const item = timeAgoTooltips.at(0);
expect(item.props('time')).toBe(mockSubscription.created_at);
}); });
}); });
...@@ -57,12 +62,7 @@ describe('SubscriptionsList', () => { ...@@ -57,12 +62,7 @@ describe('SubscriptionsList', () => {
let removeSubscriptionSpy; let removeSubscriptionSpy;
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent();
mountFn: mount,
provide: {
subscriptions: [mockSubscription],
},
});
removeSubscriptionSpy = jest.spyOn(JiraConnectApi, 'removeSubscription').mockResolvedValue(); removeSubscriptionSpy = jest.spyOn(JiraConnectApi, 'removeSubscription').mockResolvedValue();
}); });
......
...@@ -7,7 +7,7 @@ RSpec.describe 'jira_connect/subscriptions/index.html.haml' do ...@@ -7,7 +7,7 @@ RSpec.describe 'jira_connect/subscriptions/index.html.haml' do
before do before do
allow(view).to receive(:current_user).and_return(user) allow(view).to receive(:current_user).and_return(user)
assign(:subscriptions, []) assign(:subscriptions, create_list(:jira_connect_subscription, 1))
end end
context 'when the user is signed in' do context 'when the user is signed in' do
......
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