summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/jira_connect
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/jira_connect')
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/api.js10
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue52
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/app.vue48
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue21
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue10
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/constants.js17
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/index.js7
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue65
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue68
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue72
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue88
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_page.vue35
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue43
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue54
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/actions.js73
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/index.js11
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js12
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/mutations.js40
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/store/state.js20
19 files changed, 579 insertions, 167 deletions
diff --git a/app/assets/javascripts/jira_connect/subscriptions/api.js b/app/assets/javascripts/jira_connect/subscriptions/api.js
index 14947b6c835..de67703356f 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/api.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/api.js
@@ -29,3 +29,13 @@ export const fetchGroups = async (groupsPath, { page, perPage, search }) => {
},
});
};
+
+export const fetchSubscriptions = async (subscriptionsPath) => {
+ const jwt = await getJwt();
+
+ return axios.get(subscriptionsPath, {
+ params: {
+ jwt,
+ },
+ });
+};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue
index 1fc40e5c0d6..fa1c2e1912c 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list_item.vue
@@ -1,17 +1,27 @@
<script>
+import { mapActions } from 'vuex';
import { GlButton } from '@gitlab/ui';
-import { helpPagePath } from '~/helpers/help_page_helper';
import { addSubscription } from '~/jira_connect/subscriptions/api';
import { persistAlert, reloadPage } from '~/jira_connect/subscriptions/utils';
-import { s__ } from '~/locale';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import GroupItemName from '../group_item_name.vue';
+import {
+ INTEGRATIONS_DOC_LINK,
+ I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
+ I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
+ I18N_ADD_SUBSCRIPTIONS_ERROR_MESSAGE,
+} from '../../constants';
export default {
components: {
GlButton,
GroupItemName,
},
+ mixins: [glFeatureFlagMixin()],
inject: {
+ addSubscriptionsPath: {
+ default: '',
+ },
subscriptionsPath: {
default: '',
},
@@ -32,31 +42,41 @@ export default {
isLoading: false,
};
},
+ computed: {
+ oauthEnabled() {
+ return this.glFeatures.jiraConnectOauth;
+ },
+ },
methods: {
- onClick() {
+ ...mapActions(['addSubscription']),
+ async onClick() {
+ if (this.oauthEnabled) {
+ this.isLoading = true;
+ await this.addSubscription({
+ namespacePath: this.group.full_path,
+ subscriptionsPath: this.subscriptionsPath,
+ });
+ this.isLoading = false;
+ } else {
+ this.deprecatedAddSubscription();
+ }
+ },
+ deprecatedAddSubscription() {
this.isLoading = true;
- addSubscription(this.subscriptionsPath, this.group.full_path)
+ addSubscription(this.addSubscriptionsPath, 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: 'use-the-integration',
- }),
+ title: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
+ message: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
+ linkUrl: INTEGRATIONS_DOC_LINK,
variant: 'success',
});
reloadPage();
})
.catch((error) => {
- this.$emit(
- 'error',
- error?.response?.data?.error ||
- s__('Integrations|Failed to link namespace. Please try again.'),
- );
+ this.$emit('error', error?.response?.data?.error || I18N_ADD_SUBSCRIPTIONS_ERROR_MESSAGE);
this.isLoading = false;
});
},
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
index 51db3e784aa..22422872183 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
@@ -1,14 +1,14 @@
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { isEmpty } from 'lodash';
-import { mapState, mapMutations } from 'vuex';
+import { mapState, mapMutations, mapActions } from 'vuex';
import { retrieveAlert } from '~/jira_connect/subscriptions/utils';
import AccessorUtilities from '~/lib/utils/accessor';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE } from '../constants';
import { SET_ALERT } from '../store/mutation_types';
-import SignInPage from '../pages/sign_in.vue';
-import SubscriptionsPage from '../pages/subscriptions.vue';
+import SignInPage from '../pages/sign_in/sign_in_page.vue';
+import SubscriptionsPage from '../pages/subscriptions_page.vue';
import UserLink from './user_link.vue';
import CompatibilityAlert from './compatibility_alert.vue';
import BrowserSupportAlert from './browser_support_alert.vue';
@@ -30,17 +30,13 @@ export default {
usersPath: {
default: '',
},
- subscriptions: {
- default: [],
+ subscriptionsPath: {
+ default: '',
},
},
- data() {
- return {
- user: null,
- };
- },
computed: {
- ...mapState(['alert']),
+ ...mapState(['currentUser']),
+ ...mapState(['alert', 'subscriptions']),
shouldShowAlert() {
return Boolean(this.alert?.message);
},
@@ -48,7 +44,11 @@ export default {
return !isEmpty(this.subscriptions);
},
userSignedIn() {
- return Boolean(!this.usersPath || this.user);
+ if (this.isOauthEnabled) {
+ return Boolean(this.currentUser);
+ }
+
+ return Boolean(!this.usersPath);
},
isOauthEnabled() {
return this.glFeatures.jiraConnectOauth;
@@ -64,16 +64,29 @@ export default {
created() {
this.setInitialAlert();
},
+ mounted() {
+ this.fetchSubscriptionsOauth();
+ },
methods: {
...mapMutations({
setAlert: SET_ALERT,
}),
+ ...mapActions(['fetchSubscriptions']),
+ /**
+ * Fetch subscriptions from the REST API,
+ * if the jiraConnectOauth flag is enabled.
+ */
+ fetchSubscriptionsOauth() {
+ if (!this.isOauthEnabled) return;
+
+ this.fetchSubscriptions(this.subscriptionsPath);
+ },
setInitialAlert() {
const { linkUrl, title, message, variant } = retrieveAlert() || {};
this.setAlert({ linkUrl, title, message, variant });
},
- onSignInOauth(user) {
- this.user = user;
+ onSignInOauth() {
+ this.fetchSubscriptionsOauth();
},
onSignInError() {
this.setAlert({
@@ -109,9 +122,12 @@ export default {
</template>
</gl-alert>
- <user-link :user-signed-in="userSignedIn" :has-subscriptions="hasSubscriptions" :user="user" />
+ <user-link
+ :user-signed-in="userSignedIn"
+ :has-subscriptions="hasSubscriptions"
+ :user="currentUser"
+ />
- <h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
<div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7">
<sign-in-page
v-if="!userSignedIn"
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
index dfed57df7d6..b9e8bab019f 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
@@ -1,4 +1,5 @@
<script>
+import { mapActions, mapMutations } from 'vuex';
import { GlButton } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import {
@@ -8,8 +9,8 @@ import {
} from '~/jira_connect/subscriptions/constants';
import { setUrlParams } from '~/lib/utils/url_utility';
import AccessorUtilities from '~/lib/utils/accessor';
-
import { createCodeVerifier, createCodeChallenge } from '../pkce';
+import { SET_ACCESS_TOKEN } from '../store/mutation_types';
export default {
components: {
@@ -31,6 +32,10 @@ export default {
window.removeEventListener('message', this.handleWindowMessage);
},
methods: {
+ ...mapActions(['loadCurrentUser']),
+ ...mapMutations({
+ setAccessToken: SET_ACCESS_TOKEN,
+ }),
async startOAuthFlow() {
this.loading = true;
@@ -40,6 +45,7 @@ export default {
// Build the initial OAuth authorization URL
const { oauth_authorize_url: oauthAuthorizeURL } = this.oauthMetadata;
+
const oauthAuthorizeURLWithChallenge = setUrlParams(
{
code_challenge: codeChallenge,
@@ -57,7 +63,6 @@ export default {
async handleWindowMessage(event) {
if (window.origin !== event.origin) {
this.loading = false;
- this.handleError();
return;
}
@@ -73,7 +78,10 @@ export default {
const code = event.data?.code;
try {
const accessToken = await this.getOAuthToken(code);
- await this.loadUser(accessToken);
+ await this.loadCurrentUser(accessToken);
+
+ this.setAccessToken(accessToken);
+ this.$emit('sign-in');
} catch (e) {
this.handleError();
} finally {
@@ -96,13 +104,6 @@ export default {
return data.access_token;
},
- async loadUser(accessToken) {
- const { data } = await axios.get('/api/v4/user', {
- headers: { Authorization: `Bearer ${accessToken}` },
- });
-
- this.$emit('sign-in', data);
- },
},
i18n: {
defaultButtonText: I18N_DEFAULT_SIGN_IN_BUTTON_TEXT,
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
index 0251728c896..4c039be9ba5 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
@@ -1,7 +1,7 @@
<script>
import { GlButton, GlTableLite } from '@gitlab/ui';
import { isEmpty } from 'lodash';
-import { mapMutations } from 'vuex';
+import { mapMutations, mapState } from 'vuex';
import { removeSubscription } from '~/jira_connect/subscriptions/api';
import { reloadPage } from '~/jira_connect/subscriptions/utils';
import { __, s__ } from '~/locale';
@@ -16,11 +16,6 @@ export default {
GroupItemName,
TimeagoTooltip,
},
- inject: {
- subscriptions: {
- default: [],
- },
- },
data() {
return {
loadingItem: null,
@@ -45,6 +40,9 @@ export default {
i18n: {
unlinkError: s__('Integrations|Failed to unlink namespace. Please try again.'),
},
+ computed: {
+ ...mapState(['subscriptions']),
+ },
methods: {
...mapMutations({
setAlert: SET_ALERT,
diff --git a/app/assets/javascripts/jira_connect/subscriptions/constants.js b/app/assets/javascripts/jira_connect/subscriptions/constants.js
index d30ebdbb487..8faafb1b0d0 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/constants.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/constants.js
@@ -1,4 +1,5 @@
import { s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
export const DEFAULT_GROUPS_PER_PAGE = 10;
export const ALERT_LOCALSTORAGE_KEY = 'gitlab_alert';
@@ -8,6 +9,22 @@ export const ADD_NAMESPACE_MODAL_ID = 'add-namespace-modal';
export const I18N_DEFAULT_SIGN_IN_BUTTON_TEXT = s__('Integrations|Sign in to GitLab');
export const I18N_DEFAULT_SIGN_IN_ERROR_MESSAGE = s__('Integrations|Failed to sign in to GitLab.');
+export const I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE = s__(
+ 'Integrations|Failed to load subscriptions.',
+);
+export const I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE = s__(
+ 'Integrations|Namespace successfully linked',
+);
+export const I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE = s__(
+ 'Integrations|You should now see GitLab.com activity inside your Jira Cloud issues. %{linkStart}Learn more%{linkEnd}',
+);
+export const INTEGRATIONS_DOC_LINK = helpPagePath('integration/jira_development_panel', {
+ anchor: 'use-the-integration',
+});
+
+export const I18N_ADD_SUBSCRIPTIONS_ERROR_MESSAGE = s__(
+ 'Integrations|Failed to link namespace. Please try again.',
+);
const OAUTH_WINDOW_SIZE = 800;
export const OAUTH_WINDOW_OPTIONS = [
diff --git a/app/assets/javascripts/jira_connect/subscriptions/index.js b/app/assets/javascripts/jira_connect/subscriptions/index.js
index 3b584b5fe98..8e9f73538b9 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/index.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/index.js
@@ -9,8 +9,6 @@ import JiraConnectApp from './components/app.vue';
import createStore from './store';
import { sizeToParent } from './utils';
-const store = createStore();
-
export function initJiraConnect() {
const el = document.querySelector('.js-jira-connect-app');
if (!el) {
@@ -24,6 +22,7 @@ export function initJiraConnect() {
const {
groupsPath,
subscriptions,
+ addSubscriptionsPath,
subscriptionsPath,
usersPath,
gitlabUserPath,
@@ -31,12 +30,14 @@ export function initJiraConnect() {
} = el.dataset;
sizeToParent();
+ const store = createStore({ subscriptions: JSON.parse(subscriptions) });
+
return new Vue({
el,
store,
provide: {
groupsPath,
- subscriptions: JSON.parse(subscriptions),
+ addSubscriptionsPath,
subscriptionsPath,
usersPath,
gitlabUserPath,
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue
deleted file mode 100644
index a24ee33b723..00000000000
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue
+++ /dev/null
@@ -1,65 +0,0 @@
-<script>
-import { s__ } from '~/locale';
-
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
-import SubscriptionsList from '../components/subscriptions_list.vue';
-
-export default {
- name: 'SignInPage',
- components: {
- SubscriptionsList,
- SignInLegacyButton: () => import('../components/sign_in_legacy_button.vue'),
- SignInOauthButton: () => import('../components/sign_in_oauth_button.vue'),
- },
- mixins: [glFeatureFlagMixin()],
- inject: ['usersPath'],
- props: {
- hasSubscriptions: {
- type: Boolean,
- required: true,
- },
- },
- computed: {
- useSignInOauthButton() {
- return this.glFeatures.jiraConnectOauth;
- },
- },
- i18n: {
- signInButtonTextWithSubscriptions: s__('Integrations|Sign in to add namespaces'),
- signInText: s__('JiraService|Sign in to GitLab.com to get started.'),
- },
- methods: {
- onSignInError() {
- this.$emit('error');
- },
- },
-};
-</script>
-
-<template>
- <div v-if="hasSubscriptions">
- <div class="gl-display-flex gl-justify-content-end">
- <sign-in-oauth-button
- v-if="useSignInOauthButton"
- @sign-in="$emit('sign-in-oauth', $event)"
- @error="onSignInError"
- >
- {{ $options.i18n.signInButtonTextWithSubscriptions }}
- </sign-in-oauth-button>
- <sign-in-legacy-button v-else :users-path="usersPath">
- {{ $options.i18n.signInButtonTextWithSubscriptions }}
- </sign-in-legacy-button>
- </div>
-
- <subscriptions-list />
- </div>
- <div v-else class="gl-text-center">
- <p class="gl-mb-7">{{ $options.i18n.signInText }}</p>
- <sign-in-oauth-button
- v-if="useSignInOauthButton"
- @sign-in="$emit('sign-in-oauth', $event)"
- @error="onSignInError"
- />
- <sign-in-legacy-button v-else class="gl-mb-7" :users-path="usersPath" />
- </div>
-</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue
new file mode 100644
index 00000000000..91b66c87694
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_com.vue
@@ -0,0 +1,68 @@
+<script>
+import { s__ } from '~/locale';
+
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import SubscriptionsList from '../../components/subscriptions_list.vue';
+
+export default {
+ name: 'SignInGitlabCom',
+ components: {
+ SubscriptionsList,
+ SignInLegacyButton: () => import('../../components/sign_in_legacy_button.vue'),
+ SignInOauthButton: () => import('../../components/sign_in_oauth_button.vue'),
+ },
+ mixins: [glFeatureFlagMixin()],
+ inject: ['usersPath'],
+ props: {
+ hasSubscriptions: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ useSignInOauthButton() {
+ return this.glFeatures.jiraConnectOauth;
+ },
+ },
+ i18n: {
+ signInButtonTextWithSubscriptions: s__('Integrations|Sign in to add namespaces'),
+ signInText: s__('JiraService|Sign in to GitLab.com to get started.'),
+ },
+ methods: {
+ onSignInError() {
+ this.$emit('error');
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
+ <div v-if="hasSubscriptions">
+ <div class="gl-display-flex gl-justify-content-end gl-mb-3">
+ <sign-in-oauth-button
+ v-if="useSignInOauthButton"
+ @sign-in="$emit('sign-in-oauth', $event)"
+ @error="onSignInError"
+ >
+ {{ $options.i18n.signInButtonTextWithSubscriptions }}
+ </sign-in-oauth-button>
+ <sign-in-legacy-button v-else :users-path="usersPath">
+ {{ $options.i18n.signInButtonTextWithSubscriptions }}
+ </sign-in-legacy-button>
+ </div>
+
+ <subscriptions-list />
+ </div>
+ <div v-else class="gl-text-center">
+ <p class="gl-mb-7">{{ $options.i18n.signInText }}</p>
+ <sign-in-oauth-button
+ v-if="useSignInOauthButton"
+ @sign-in="$emit('sign-in-oauth', $event)"
+ @error="onSignInError"
+ />
+ <sign-in-legacy-button v-else class="gl-mb-7" :users-path="usersPath" />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue
new file mode 100644
index 00000000000..4f5aa4c255c
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue
@@ -0,0 +1,72 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import SignInOauthButton from '../../../components/sign_in_oauth_button.vue';
+import VersionSelectForm from './version_select_form.vue';
+
+export default {
+ name: 'SignInGitlabMultiversion',
+ components: {
+ GlButton,
+ SignInOauthButton,
+ VersionSelectForm,
+ },
+ data() {
+ return {
+ gitlabBasePath: null,
+ };
+ },
+ computed: {
+ hasSelectedVersion() {
+ return this.gitlabBasePath !== null;
+ },
+ subtitle() {
+ return this.hasSelectedVersion
+ ? this.$options.i18n.signInSubtitle
+ : this.$options.i18n.versionSelectSubtitle;
+ },
+ },
+ methods: {
+ resetGitlabBasePath() {
+ this.gitlabBasePath = null;
+ },
+ onVersionSelect(gitlabBasePath) {
+ this.gitlabBasePath = gitlabBasePath;
+ },
+ onSignInError() {
+ this.$emit('error');
+ },
+ },
+ i18n: {
+ title: s__('JiraService|Welcome to GitLab for Jira'),
+ signInSubtitle: s__('JiraService|Sign in to GitLab to link namespaces.'),
+ versionSelectSubtitle: s__('JiraService|What version of GitLab are you using?'),
+ changeVersionButtonText: s__('JiraService|Change GitLab version'),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div class="gl-text-center">
+ <h2>{{ $options.i18n.title }}</h2>
+ <p data-testid="subtitle">{{ subtitle }}</p>
+ </div>
+
+ <version-select-form v-if="!hasSelectedVersion" class="gl-mt-7" @submit="onVersionSelect" />
+
+ <div v-else class="gl-text-center">
+ <sign-in-oauth-button
+ class="gl-mb-5"
+ @sign-in="$emit('sign-in-oauth', $event)"
+ @error="onSignInError"
+ />
+
+ <div>
+ <gl-button category="tertiary" variant="confirm" @click="resetGitlabBasePath">
+ {{ $options.i18n.changeVersionButtonText }}
+ </gl-button>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue
new file mode 100644
index 00000000000..0fa745ed7e3
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue
@@ -0,0 +1,88 @@
+<script>
+import {
+ GlForm,
+ GlFormGroup,
+ GlFormRadioGroup,
+ GlFormInput,
+ GlFormRadio,
+ GlButton,
+} from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+
+const RADIO_OPTIONS = {
+ saas: 'saas',
+ selfManaged: 'selfManaged',
+};
+
+const DEFAULT_RADIO_OPTION = RADIO_OPTIONS.saas;
+const GITLAB_COM_BASE_PATH = 'https://gitlab.com';
+
+export default {
+ name: 'VersionSelectForm',
+ components: {
+ GlForm,
+ GlFormGroup,
+ GlFormRadioGroup,
+ GlFormInput,
+ GlFormRadio,
+ GlButton,
+ },
+ data() {
+ return {
+ selected: DEFAULT_RADIO_OPTION,
+ selfManagedBasePathInput: '',
+ };
+ },
+ computed: {
+ isSelfManagedSelected() {
+ return this.selected === RADIO_OPTIONS.selfManaged;
+ },
+ },
+ methods: {
+ onSubmit() {
+ const gitlabBasePath =
+ this.selected === RADIO_OPTIONS.saas ? GITLAB_COM_BASE_PATH : this.selfManagedBasePathInput;
+ this.$emit('submit', gitlabBasePath);
+ },
+ },
+ radioOptions: RADIO_OPTIONS,
+ i18n: {
+ title: s__('JiraService|Welcome to GitLab for Jira'),
+ saasRadioLabel: __('GitLab.com (SaaS)'),
+ saasRadioHelp: __('Most common'),
+ selfManagedRadioLabel: __('GitLab (self-managed)'),
+ instanceURLInputLabel: s__('JiraService|GitLab instance URL'),
+ instanceURLInputDescription: s__('JiraService|For example: https://gitlab.example.com'),
+ },
+};
+</script>
+
+<template>
+ <gl-form class="gl-max-w-62 gl-mx-auto" @submit.prevent="onSubmit">
+ <gl-form-radio-group v-model="selected" class="gl-mb-3" name="gitlab_version">
+ <gl-form-radio :value="$options.radioOptions.saas">
+ {{ $options.i18n.saasRadioLabel }}
+ <template #help>
+ {{ $options.i18n.saasRadioHelp }}
+ </template>
+ </gl-form-radio>
+ <gl-form-radio :value="$options.radioOptions.selfManaged">
+ {{ $options.i18n.selfManagedRadioLabel }}
+ </gl-form-radio>
+ </gl-form-radio-group>
+
+ <gl-form-group
+ v-if="isSelfManagedSelected"
+ class="gl-ml-6"
+ :label="$options.i18n.instanceURLInputLabel"
+ :description="$options.i18n.instanceURLInputDescription"
+ label-for="self-managed-instance-input"
+ >
+ <gl-form-input id="self-managed-instance-input" v-model="selfManagedBasePathInput" required />
+ </gl-form-group>
+
+ <div class="gl-display-flex gl-justify-content-end">
+ <gl-button variant="confirm" type="submit">{{ __('Save') }}</gl-button>
+ </div>
+ </gl-form>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_page.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_page.vue
new file mode 100644
index 00000000000..f4c59b2184e
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_page.vue
@@ -0,0 +1,35 @@
+<script>
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import SignInGitlabCom from './sign_in_gitlab_com.vue';
+import SignInGitlabMultiversion from './sign_in_gitlab_multiversion/index.vue';
+
+export default {
+ name: 'SignInPage',
+ components: { SignInGitlabCom, SignInGitlabMultiversion },
+ mixins: [glFeatureFlagMixin()],
+ props: {
+ hasSubscriptions: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ isOauthSelfManagedEnabled() {
+ return this.glFeatures.jiraConnectOauth && this.glFeatures.jiraConnectOauthSelfManaged;
+ },
+ },
+};
+</script>
+<template>
+ <sign-in-gitlab-multiversion
+ v-if="isOauthSelfManagedEnabled"
+ @sign-in-oauth="$emit('sign-in-oauth', $event)"
+ @error="$emit('error', $event)"
+ />
+ <sign-in-gitlab-com
+ v-else
+ :has-subscriptions="hasSubscriptions"
+ @sign-in-oauth="$emit('sign-in-oauth')"
+ @error="$emit('error', $event)"
+ />
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue
deleted file mode 100644
index 426f2999370..00000000000
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue
+++ /dev/null
@@ -1,43 +0,0 @@
-<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>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue
new file mode 100644
index 00000000000..b1c1ae73e14
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions_page.vue
@@ -0,0 +1,54 @@
+<script>
+import { mapState } from 'vuex';
+import { GlEmptyState, GlLoadingIcon } from '@gitlab/ui';
+
+import SubscriptionsList from '../components/subscriptions_list.vue';
+import AddNamespaceButton from '../components/add_namespace_button.vue';
+
+export default {
+ name: 'SubscriptionsPage',
+ components: {
+ GlEmptyState,
+ GlLoadingIcon,
+ SubscriptionsList,
+ AddNamespaceButton,
+ },
+ props: {
+ hasSubscriptions: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['subscriptionsLoading', 'subscriptionsError']),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
+
+ <gl-loading-icon v-if="subscriptionsLoading" size="md" />
+ <div v-else-if="hasSubscriptions && !subscriptionsError">
+ <div class="gl-display-flex gl-justify-content-end gl-mb-3">
+ <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>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/actions.js b/app/assets/javascripts/jira_connect/subscriptions/store/actions.js
new file mode 100644
index 00000000000..4a83ee8671d
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/actions.js
@@ -0,0 +1,73 @@
+import { fetchSubscriptions as fetchSubscriptionsREST } from '~/jira_connect/subscriptions/api';
+import { getCurrentUser } from '~/rest_api';
+import { addJiraConnectSubscription } from '~/api/integrations_api';
+import {
+ I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
+ I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
+ INTEGRATIONS_DOC_LINK,
+ I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE,
+} from '../constants';
+import { getJwt } from '../utils';
+import {
+ SET_SUBSCRIPTIONS,
+ SET_SUBSCRIPTIONS_LOADING,
+ SET_SUBSCRIPTIONS_ERROR,
+ ADD_SUBSCRIPTION_LOADING,
+ ADD_SUBSCRIPTION_ERROR,
+ SET_ALERT,
+ SET_CURRENT_USER,
+ SET_CURRENT_USER_ERROR,
+} from './mutation_types';
+
+export const fetchSubscriptions = async ({ commit }, subscriptionsPath) => {
+ commit(SET_SUBSCRIPTIONS_LOADING, true);
+
+ try {
+ const data = await fetchSubscriptionsREST(subscriptionsPath);
+ commit(SET_SUBSCRIPTIONS, data.data.subscriptions);
+ } catch {
+ commit(SET_SUBSCRIPTIONS_ERROR, true);
+ commit(SET_ALERT, { message: I18N_DEFAULT_SUBSCRIPTIONS_ERROR_MESSAGE, variant: 'danger' });
+ } finally {
+ commit(SET_SUBSCRIPTIONS_LOADING, false);
+ }
+};
+
+export const loadCurrentUser = async ({ commit }, accessToken) => {
+ try {
+ const { data: user } = await getCurrentUser({
+ headers: { Authorization: `Bearer ${accessToken}` },
+ });
+
+ commit(SET_CURRENT_USER, user);
+ } catch (e) {
+ commit(SET_CURRENT_USER_ERROR, e);
+ }
+};
+
+export const addSubscription = async (
+ { commit, state, dispatch },
+ { namespacePath, subscriptionsPath },
+) => {
+ try {
+ commit(ADD_SUBSCRIPTION_LOADING, true);
+
+ await addJiraConnectSubscription(namespacePath, {
+ jwt: await getJwt(),
+ accessToken: state.accessToken,
+ });
+
+ commit(SET_ALERT, {
+ title: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_TITLE,
+ message: I18N_ADD_SUBSCRIPTION_SUCCESS_ALERT_MESSAGE,
+ linkUrl: INTEGRATIONS_DOC_LINK,
+ variant: 'success',
+ });
+
+ dispatch('fetchSubscriptions', subscriptionsPath);
+ } catch (e) {
+ commit(ADD_SUBSCRIPTION_ERROR, e);
+ } finally {
+ commit(ADD_SUBSCRIPTION_LOADING, false);
+ }
+};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/index.js b/app/assets/javascripts/jira_connect/subscriptions/store/index.js
index de830e3891a..abad1920bcc 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/index.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/index.js
@@ -1,12 +1,15 @@
import Vue from 'vue';
import Vuex from 'vuex';
+import * as actions from './actions';
import mutations from './mutations';
-import state from './state';
+import createState from './state';
Vue.use(Vuex);
-export default () =>
- new Vuex.Store({
+export default function createStore(initialState) {
+ return new Vuex.Store({
mutations,
- state,
+ actions,
+ state: createState(initialState),
});
+}
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js b/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js
index 15f36b824d9..d4893fbcaf6 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/mutation_types.js
@@ -1 +1,13 @@
export const SET_ALERT = 'SET_ALERT';
+
+export const SET_SUBSCRIPTIONS = 'SET_SUBSCRIPTIONS';
+export const SET_SUBSCRIPTIONS_LOADING = 'SET_SUBSCRIPTIONS_LOADING';
+export const SET_SUBSCRIPTIONS_ERROR = 'SET_SUBSCRIPTIONS_ERROR';
+
+export const ADD_SUBSCRIPTION_LOADING = 'ADD_SUBSCRIPTION_LOADING';
+export const ADD_SUBSCRIPTION_ERROR = 'ADD_SUBSCRIPTION_ERROR';
+
+export const SET_CURRENT_USER = 'SET_CURRENT_USER';
+export const SET_CURRENT_USER_ERROR = 'SET_CURRENT_USER_ERROR';
+
+export const SET_ACCESS_TOKEN = 'SET_ACCESS_TOKEN';
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js b/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js
index 2a25e0fe25f..60076c918fd 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/mutations.js
@@ -1,7 +1,45 @@
-import { SET_ALERT } from './mutation_types';
+import {
+ SET_ALERT,
+ SET_SUBSCRIPTIONS,
+ SET_SUBSCRIPTIONS_LOADING,
+ SET_SUBSCRIPTIONS_ERROR,
+ ADD_SUBSCRIPTION_LOADING,
+ ADD_SUBSCRIPTION_ERROR,
+ SET_CURRENT_USER,
+ SET_CURRENT_USER_ERROR,
+ SET_ACCESS_TOKEN,
+} from './mutation_types';
export default {
[SET_ALERT](state, { title, message, variant, linkUrl } = {}) {
state.alert = { title, message, variant, linkUrl };
},
+
+ [SET_SUBSCRIPTIONS](state, subscriptions = []) {
+ state.subscriptions = subscriptions;
+ },
+ [SET_SUBSCRIPTIONS_LOADING](state, subscriptionsLoading) {
+ state.subscriptionsLoading = subscriptionsLoading;
+ },
+ [SET_SUBSCRIPTIONS_ERROR](state, subscriptionsError) {
+ state.subscriptionsError = subscriptionsError;
+ },
+
+ [ADD_SUBSCRIPTION_LOADING](state, loading) {
+ state.addSubscriptionLoading = loading;
+ },
+ [ADD_SUBSCRIPTION_ERROR](state, error) {
+ state.addSubscriptionError = error;
+ },
+
+ [SET_CURRENT_USER](state, currentUser) {
+ state.currentUser = currentUser;
+ },
+ [SET_CURRENT_USER_ERROR](state, currentUserError) {
+ state.currentUserError = currentUserError;
+ },
+
+ [SET_ACCESS_TOKEN](state, accessToken) {
+ state.accessToken = accessToken;
+ },
};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/store/state.js b/app/assets/javascripts/jira_connect/subscriptions/store/state.js
index c807df03f00..03a83f18b4c 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/store/state.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/store/state.js
@@ -1,3 +1,17 @@
-export default () => ({
- alert: undefined,
-});
+export default function createState({ subscriptions = [], subscriptionsLoading = false } = {}) {
+ return {
+ alert: undefined,
+
+ subscriptions,
+ subscriptionsLoading,
+ subscriptionsError: false,
+
+ addSubscriptionLoading: false,
+ addSubscriptionError: false,
+
+ currentUser: null,
+ currentUserError: null,
+
+ accessToken: null,
+ };
+}