diff options
Diffstat (limited to 'app/assets/javascripts/jira_connect')
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, + }; +} |