Commit 42860513 authored by Phil Hughes's avatar Phil Hughes

Merge branch '235909-make-it-clearer-what-to-do-after-adding-a-namespace' into 'master'

Display success message after successfully adding a namespace

See merge request gitlab-org/gitlab!53332
parents f62d6148 ef82e4c0
<script>
import { GlAlert, GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
import { mapState } from 'vuex';
import { GlAlert, GlButton, GlModal, GlModalDirective, GlLink, GlSprintf } from '@gitlab/ui';
import { mapState, mapMutations } from 'vuex';
import { getLocation } from '~/jira_connect/api';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { SET_ALERT } from '../store/mutation_types';
import { retrieveAlert } from '../utils';
import GroupsList from './groups_list.vue';
export default {
......@@ -14,6 +15,8 @@ export default {
GlButton,
GlModal,
GroupsList,
GlLink,
GlSprintf,
},
directives: {
GlModalDirective,
......@@ -30,7 +33,7 @@ export default {
};
},
computed: {
...mapState(['errorMessage']),
...mapState(['alert']),
usersPathWithReturnTo() {
if (this.location) {
return `${this.usersPath}?return_to=${this.location}`;
......@@ -38,6 +41,9 @@ export default {
return this.usersPath;
},
shouldShowAlert() {
return Boolean(this.alert?.message);
},
},
modal: {
cancelProps: {
......@@ -45,20 +51,42 @@ export default {
},
},
created() {
this.setInitialAlert();
this.setLocation();
},
methods: {
...mapMutations({
setAlert: SET_ALERT,
}),
async setLocation() {
this.location = await getLocation();
},
setInitialAlert() {
const { linkUrl, title, message, variant } = retrieveAlert() || {};
this.setAlert({ linkUrl, title, message, variant });
},
},
};
</script>
<template>
<div>
<gl-alert v-if="errorMessage" class="gl-mb-7" variant="danger" :dismissible="false">
{{ errorMessage }}
<gl-alert
v-if="shouldShowAlert"
class="gl-mb-7"
:variant="alert.variant"
:title="alert.title"
@dismiss="setAlert"
>
<gl-sprintf v-if="alert.linkUrl" :message="alert.message">
<template #link="{ content }">
<gl-link :href="alert.linkUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
<template v-else>
{{ alert.message }}
</template>
</gl-alert>
<h2 class="gl-text-center">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
......
<script>
import { GlAvatar, GlButton, GlIcon } from '@gitlab/ui';
import { helpPagePath } from '~/helpers/help_page_helper';
import { addSubscription } from '~/jira_connect/api';
import { s__ } from '~/locale';
import { persistAlert } from '../utils';
export default {
components: {
......@@ -31,6 +33,15 @@ export default {
addSubscription(this.subscriptionsPath, this.group.full_path)
.then(() => {
persistAlert({
title: s__('Integrations|Namespace successfully linked'),
message: s__(
'Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}',
),
linkUrl: helpPagePath('integration/jira_development_panel.html', { anchor: 'usage' }),
variant: 'success',
});
AP.navigator.reload();
})
.catch((error) => {
......
export const defaultPerPage = 10;
export const ALERT_LOCALSTORAGE_KEY = 'gitlab_alert';
......@@ -6,7 +6,7 @@ import Translate from '~/vue_shared/translate';
import JiraConnectApp from './components/app.vue';
import createStore from './store';
import { SET_ERROR_MESSAGE } from './store/mutation_types';
import { SET_ALERT } from './store/mutation_types';
const store = createStore();
......@@ -17,7 +17,7 @@ const reqComplete = () => {
const reqFailed = (res, fallbackErrorMessage) => {
const { error = fallbackErrorMessage } = res || {};
store.commit(SET_ERROR_MESSAGE, error);
store.commit(SET_ALERT, { message: error, variant: 'danger' });
};
const updateSignInLinks = async () => {
......
export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE';
export const SET_ALERT = 'SET_ALERT';
import { SET_ERROR_MESSAGE } from './mutation_types';
import { SET_ALERT } from './mutation_types';
export default {
[SET_ERROR_MESSAGE](state, errorMessage) {
state.errorMessage = errorMessage;
[SET_ALERT](state, { title, message, variant, linkUrl } = {}) {
state.alert = { title, message, variant, linkUrl };
},
};
export default () => ({
errorMessage: undefined,
alert: undefined,
});
import AccessorUtilities from '~/lib/utils/accessor';
import { ALERT_LOCALSTORAGE_KEY } from './constants';
/**
* Persist alert data to localStorage.
*/
export const persistAlert = ({ title, message, linkUrl, variant } = {}) => {
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
return;
}
const payload = JSON.stringify({ title, message, linkUrl, variant });
localStorage.setItem(ALERT_LOCALSTORAGE_KEY, payload);
};
/**
* Return alert data from localStorage.
*/
export const retrieveAlert = () => {
if (!AccessorUtilities.isLocalStorageAccessSafe()) {
return null;
}
const initialAlertJSON = localStorage.getItem(ALERT_LOCALSTORAGE_KEY);
// immediately clean up
localStorage.removeItem(ALERT_LOCALSTORAGE_KEY);
if (!initialAlertJSON) {
return null;
}
return JSON.parse(initialAlertJSON);
};
---
title: Display success message after successfully adding a namespace in Jira Connect
merge_request: 53332
author:
type: added
......@@ -16380,6 +16380,9 @@ msgstr ""
msgid "Integrations|Linked namespaces"
msgstr ""
msgid "Integrations|Namespace successfully linked"
msgstr ""
msgid "Integrations|Namespaces are your GitLab groups and subgroups that will be linked to this Jira instance."
msgstr ""
......@@ -16452,6 +16455,9 @@ msgstr ""
msgid "Integrations|You must have owner or maintainer permissions to link namespaces."
msgstr ""
msgid "Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}"
msgstr ""
msgid "Interactive mode"
msgstr ""
......
import { GlAlert, GlButton, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { GlAlert, GlButton, GlModal, GlLink } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import JiraConnectApp from '~/jira_connect/components/app.vue';
import createStore from '~/jira_connect/store';
import { SET_ERROR_MESSAGE } from '~/jira_connect/store/mutation_types';
import { SET_ALERT } from '~/jira_connect/store/mutation_types';
import { persistAlert } from '~/jira_connect/utils';
import { __ } from '~/locale';
jest.mock('~/jira_connect/api');
......@@ -13,18 +15,19 @@ describe('JiraConnectApp', () => {
let store;
const findAlert = () => wrapper.findComponent(GlAlert);
const findAlertLink = () => findAlert().find(GlLink);
const findGlButton = () => wrapper.findComponent(GlButton);
const findGlModal = () => wrapper.findComponent(GlModal);
const findHeader = () => wrapper.findByTestId('new-jira-connect-ui-heading');
const findHeaderText = () => findHeader().text();
const createComponent = (options = {}) => {
const createComponent = ({ provide, mountFn = shallowMount } = {}) => {
store = createStore();
wrapper = extendedWrapper(
shallowMount(JiraConnectApp, {
mountFn(JiraConnectApp, {
store,
...options,
provide,
}),
);
};
......@@ -68,25 +71,72 @@ describe('JiraConnectApp', () => {
});
});
it.each`
errorMessage | errorShouldRender
${'Test error'} | ${true}
${''} | ${false}
${undefined} | ${false}
`(
'renders correct alert when errorMessage is `$errorMessage`',
async ({ errorMessage, errorShouldRender }) => {
describe('alert', () => {
it.each`
message | variant | alertShouldRender
${'Test error'} | ${'danger'} | ${true}
${'Test notice'} | ${'info'} | ${true}
${''} | ${undefined} | ${false}
${undefined} | ${undefined} | ${false}
`(
'renders correct alert when message is `$message` and variant is `$variant`',
async ({ message, alertShouldRender, variant }) => {
createComponent();
store.commit(SET_ALERT, { message, variant });
await wrapper.vm.$nextTick();
const alert = findAlert();
expect(alert.exists()).toBe(alertShouldRender);
if (alertShouldRender) {
expect(alert.isVisible()).toBe(alertShouldRender);
expect(alert.html()).toContain(message);
expect(alert.props('variant')).toBe(variant);
expect(findAlertLink().exists()).toBe(false);
}
},
);
it('hides alert on @dismiss event', async () => {
createComponent();
store.commit(SET_ERROR_MESSAGE, errorMessage);
store.commit(SET_ALERT, { message: 'test message' });
await wrapper.vm.$nextTick();
expect(findAlert().exists()).toBe(errorShouldRender);
if (errorShouldRender) {
expect(findAlert().isVisible()).toBe(errorShouldRender);
expect(findAlert().html()).toContain(errorMessage);
}
},
);
findAlert().vm.$emit('dismiss');
await wrapper.vm.$nextTick();
expect(findAlert().exists()).toBe(false);
});
it('renders link when `linkUrl` is set', async () => {
createComponent({ mountFn: mount });
store.commit(SET_ALERT, {
message: __('test message %{linkStart}test link%{linkEnd}'),
linkUrl: 'https://gitlab.com',
});
await wrapper.vm.$nextTick();
const alertLink = findAlertLink();
expect(alertLink.exists()).toBe(true);
expect(alertLink.text()).toContain('test link');
expect(alertLink.attributes('href')).toBe('https://gitlab.com');
});
describe('when alert is set in localStoage', () => {
it('renders alert on mount', () => {
persistAlert({ message: 'error message' });
createComponent();
const alert = findAlert();
expect(alert.exists()).toBe(true);
expect(alert.html()).toContain('error message');
});
});
});
});
});
......@@ -5,8 +5,11 @@ import waitForPromises from 'helpers/wait_for_promises';
import * as JiraConnectApi from '~/jira_connect/api';
import GroupsListItem from '~/jira_connect/components/groups_list_item.vue';
import { persistAlert } from '~/jira_connect/utils';
import { mockGroup1 } from '../mock_data';
jest.mock('~/jira_connect/utils');
describe('GroupsListItem', () => {
let wrapper;
const mockSubscriptionPath = 'subscriptionPath';
......@@ -85,7 +88,16 @@ describe('GroupsListItem', () => {
expect(findLinkButton().props('loading')).toBe(true);
await waitForPromises();
expect(addSubscriptionSpy).toHaveBeenCalledWith(mockSubscriptionPath, mockGroup1.full_path);
expect(persistAlert).toHaveBeenCalledWith({
linkUrl: '/help/integration/jira_development_panel.html#usage',
message:
'You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}',
title: 'Namespace successfully linked',
variant: 'success',
});
});
describe('when request is successful', () => {
......
......@@ -8,11 +8,21 @@ describe('JiraConnect store mutations', () => {
localState = state();
});
describe('SET_ERROR_MESSAGE', () => {
it('sets error message', () => {
mutations.SET_ERROR_MESSAGE(localState, 'test error');
describe('SET_ALERT', () => {
it('sets alert state', () => {
mutations.SET_ALERT(localState, {
message: 'test error',
variant: 'danger',
title: 'test title',
linkUrl: 'linkUrl',
});
expect(localState.errorMessage).toBe('test error');
expect(localState.alert).toMatchObject({
message: 'test error',
variant: 'danger',
title: 'test title',
linkUrl: 'linkUrl',
});
});
});
});
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { ALERT_LOCALSTORAGE_KEY } from '~/jira_connect/constants';
import { persistAlert, retrieveAlert } from '~/jira_connect/utils';
useLocalStorageSpy();
describe('JiraConnect utils', () => {
describe('alert utils', () => {
it.each`
arg | expectedRetrievedValue
${{ title: 'error' }} | ${{ title: 'error' }}
${{ title: 'error', randomKey: 'test' }} | ${{ title: 'error' }}
${{ title: 'error', message: 'error message', linkUrl: 'link', variant: 'danger' }} | ${{ title: 'error', message: 'error message', linkUrl: 'link', variant: 'danger' }}
${undefined} | ${{}}
`(
'persists and retrieves alert data from localStorage when arg is $arg',
({ arg, expectedRetrievedValue }) => {
persistAlert(arg);
expect(localStorage.setItem).toHaveBeenCalledWith(
ALERT_LOCALSTORAGE_KEY,
JSON.stringify(expectedRetrievedValue),
);
const retrievedValue = retrieveAlert();
expect(localStorage.getItem).toHaveBeenCalledWith(ALERT_LOCALSTORAGE_KEY);
expect(retrievedValue).toEqual(expectedRetrievedValue);
},
);
});
});
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