Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts/jira_connect/subscriptions/components')
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/app.vue125
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/group_item_name.vue34
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/groups_list.vue132
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/groups_list_item.vue85
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue109
5 files changed, 485 insertions, 0 deletions
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
new file mode 100644
index 00000000000..413424be28d
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
@@ -0,0 +1,125 @@
+<script>
+import { GlAlert, GlButton, GlLink, GlModal, GlModalDirective, GlSprintf } from '@gitlab/ui';
+import { mapState, mapMutations } from 'vuex';
+import { retrieveAlert, getLocation } from '~/jira_connect/subscriptions/utils';
+import { __ } from '~/locale';
+import { SET_ALERT } from '../store/mutation_types';
+import GroupsList from './groups_list.vue';
+import SubscriptionsList from './subscriptions_list.vue';
+
+export default {
+ name: 'JiraConnectApp',
+ components: {
+ GlAlert,
+ GlButton,
+ GlLink,
+ GlModal,
+ GlSprintf,
+ GroupsList,
+ SubscriptionsList,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ inject: {
+ usersPath: {
+ default: '',
+ },
+ },
+ data() {
+ return {
+ location: '',
+ };
+ },
+ computed: {
+ ...mapState(['alert']),
+ usersPathWithReturnTo() {
+ if (this.location) {
+ return `${this.usersPath}?return_to=${this.location}`;
+ }
+
+ return this.usersPath;
+ },
+ shouldShowAlert() {
+ return Boolean(this.alert?.message);
+ },
+ },
+ modal: {
+ cancelProps: {
+ text: __('Cancel'),
+ },
+ },
+ 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="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>
+
+ <div class="jira-connect-app-body gl-my-7 gl-px-5 gl-pb-4">
+ <div class="gl-display-flex gl-justify-content-end">
+ <gl-button
+ v-if="usersPath"
+ category="primary"
+ variant="info"
+ class="gl-align-self-center"
+ :href="usersPathWithReturnTo"
+ target="_blank"
+ >{{ s__('Integrations|Sign in to add namespaces') }}</gl-button
+ >
+ <template v-else>
+ <gl-button
+ v-gl-modal-directive="'add-namespace-modal'"
+ category="primary"
+ variant="info"
+ class="gl-align-self-center"
+ >{{ s__('Integrations|Add namespace') }}</gl-button
+ >
+ <gl-modal
+ modal-id="add-namespace-modal"
+ :title="s__('Integrations|Link namespaces')"
+ :action-cancel="$options.modal.cancelProps"
+ >
+ <groups-list />
+ </gl-modal>
+ </template>
+ </div>
+
+ <subscriptions-list />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/group_item_name.vue b/app/assets/javascripts/jira_connect/subscriptions/components/group_item_name.vue
new file mode 100644
index 00000000000..e6c172dae9e
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/group_item_name.vue
@@ -0,0 +1,34 @@
+<script>
+import { GlAvatar, GlIcon } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlAvatar,
+ GlIcon,
+ },
+ props: {
+ group: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="gl-display-flex gl-align-items-center">
+ <gl-icon name="folder-o" class="gl-mr-3" />
+ <div class="gl-display-none gl-flex-shrink-0 gl-sm-display-flex gl-mr-3">
+ <gl-avatar :size="32" shape="rect" :entity-name="group.name" :src="group.avatar_url" />
+ </div>
+
+ <div>
+ <span class="gl-mr-3 gl-text-gray-900! gl-font-weight-bold">
+ {{ group.full_name }}
+ </span>
+ <div v-if="group.description">
+ <p class="gl-mt-2! gl-mb-0 gl-text-gray-600" v-text="group.description"></p>
+ </div>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/groups_list.vue b/app/assets/javascripts/jira_connect/subscriptions/components/groups_list.vue
new file mode 100644
index 00000000000..5a49d7c1a90
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/groups_list.vue
@@ -0,0 +1,132 @@
+<script>
+import { GlLoadingIcon, GlPagination, GlAlert, GlSearchBoxByType } from '@gitlab/ui';
+import { fetchGroups } from '~/jira_connect/subscriptions/api';
+import {
+ DEFAULT_GROUPS_PER_PAGE,
+ MINIMUM_SEARCH_TERM_LENGTH,
+} from '~/jira_connect/subscriptions/constants';
+import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import { s__ } from '~/locale';
+import GroupsListItem from './groups_list_item.vue';
+
+export default {
+ components: {
+ GlLoadingIcon,
+ GlPagination,
+ GlAlert,
+ GlSearchBoxByType,
+ GroupsListItem,
+ },
+ inject: {
+ groupsPath: {
+ default: '',
+ },
+ },
+ data() {
+ return {
+ groups: [],
+ isLoadingInitial: true,
+ isLoadingMore: false,
+ page: 1,
+ totalItems: 0,
+ errorMessage: null,
+ searchTerm: '',
+ };
+ },
+ computed: {
+ showPagination() {
+ return this.totalItems > this.$options.DEFAULT_GROUPS_PER_PAGE && this.groups.length > 0;
+ },
+ },
+ mounted() {
+ return this.loadGroups().finally(() => {
+ this.isLoadingInitial = false;
+ });
+ },
+ methods: {
+ loadGroups() {
+ // fetchGroups returns no results for search terms 0 < {length} < 3.
+ // The desired UX is to return the unfiltered results for searches {length} < 3.
+ // Here, we set the search to an empty string if {length} < 3
+ const search = this.searchTerm?.length < MINIMUM_SEARCH_TERM_LENGTH ? '' : this.searchTerm;
+
+ this.isLoadingMore = true;
+ return fetchGroups(this.groupsPath, {
+ page: this.page,
+ perPage: this.$options.DEFAULT_GROUPS_PER_PAGE,
+ search,
+ })
+ .then((response) => {
+ const { page, total } = parseIntPagination(normalizeHeaders(response.headers));
+ this.page = page;
+ this.totalItems = total;
+ this.groups = response.data;
+ })
+ .catch(() => {
+ this.errorMessage = s__('Integrations|Failed to load namespaces. Please try again.');
+ })
+ .finally(() => {
+ this.isLoadingMore = false;
+ });
+ },
+ onGroupSearch(searchTerm) {
+ // keep a copy of the search term for pagination
+ this.searchTerm = searchTerm;
+ // reset the current page
+ this.page = 1;
+ return this.loadGroups();
+ },
+ },
+ DEFAULT_GROUPS_PER_PAGE,
+};
+</script>
+
+<template>
+ <div>
+ <gl-alert v-if="errorMessage" class="gl-mb-5" variant="danger" @dismiss="errorMessage = null">
+ {{ errorMessage }}
+ </gl-alert>
+
+ <gl-search-box-by-type
+ class="gl-mb-5"
+ debounce="500"
+ :placeholder="__('Search by name')"
+ :is-loading="isLoadingMore"
+ :value="searchTerm"
+ @input="onGroupSearch"
+ />
+
+ <gl-loading-icon v-if="isLoadingInitial" size="md" />
+ <div v-else-if="groups.length === 0" class="gl-text-center">
+ <h5>{{ s__('Integrations|No available namespaces.') }}</h5>
+ <p class="gl-mt-5">
+ {{ s__('Integrations|You must have owner or maintainer permissions to link namespaces.') }}
+ </p>
+ </div>
+ <ul
+ v-else
+ class="gl-list-style-none gl-pl-0 gl-border-t-1 gl-border-t-solid gl-border-t-gray-100"
+ :class="{ 'gl-opacity-5': isLoadingMore }"
+ data-testid="groups-list"
+ >
+ <groups-list-item
+ v-for="group in groups"
+ :key="group.id"
+ :group="group"
+ :disabled="isLoadingMore"
+ @error="errorMessage = $event"
+ />
+ </ul>
+
+ <div class="gl-display-flex gl-justify-content-center gl-mt-5">
+ <gl-pagination
+ v-if="showPagination"
+ v-model="page"
+ class="gl-mb-0"
+ :per-page="$options.DEFAULT_GROUPS_PER_PAGE"
+ :total-items="totalItems"
+ @input="loadGroups"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/groups_list_item.vue b/app/assets/javascripts/jira_connect/subscriptions/components/groups_list_item.vue
new file mode 100644
index 00000000000..ed7585e8a88
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/groups_list_item.vue
@@ -0,0 +1,85 @@
+<script>
+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 GroupItemName from './group_item_name.vue';
+
+export default {
+ components: {
+ GlButton,
+ GroupItemName,
+ },
+ inject: {
+ subscriptionsPath: {
+ default: '',
+ },
+ },
+ props: {
+ group: {
+ type: Object,
+ required: true,
+ },
+ disabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ isLoading: false,
+ };
+ },
+ methods: {
+ onClick() {
+ this.isLoading = true;
+
+ 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',
+ });
+
+ reloadPage();
+ })
+ .catch((error) => {
+ this.$emit(
+ 'error',
+ error?.response?.data?.error ||
+ s__('Integrations|Failed to link namespace. Please try again.'),
+ );
+ this.isLoading = false;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <li class="gl-border-b-1 gl-border-b-solid gl-border-b-gray-100">
+ <div class="gl-display-flex gl-align-items-center gl-py-3">
+ <div class="gl-min-w-0 gl-display-flex gl-flex-grow-1 gl-flex-shrink-1 gl-align-items-center">
+ <div class="gl-min-w-0 gl-flex-grow-1 flex-shrink-1">
+ <group-item-name :group="group" />
+ </div>
+
+ <gl-button
+ category="secondary"
+ variant="confirm"
+ :loading="isLoading"
+ :disabled="disabled"
+ @click.prevent="onClick"
+ >
+ {{ __('Link') }}
+ </gl-button>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
new file mode 100644
index 00000000000..7062fb370ed
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue
@@ -0,0 +1,109 @@
+<script>
+import { GlButton, GlEmptyState, GlTable } from '@gitlab/ui';
+import { isEmpty } from 'lodash';
+import { mapMutations } from 'vuex';
+import { removeSubscription } from '~/jira_connect/subscriptions/api';
+import { reloadPage } from '~/jira_connect/subscriptions/utils';
+import { __, s__ } from '~/locale';
+import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
+import { SET_ALERT } from '../store/mutation_types';
+import GroupItemName from './group_item_name.vue';
+
+export default {
+ components: {
+ GlButton,
+ GlEmptyState,
+ GlTable,
+ GroupItemName,
+ TimeagoTooltip,
+ },
+ inject: {
+ subscriptions: {
+ default: [],
+ },
+ },
+ data() {
+ return {
+ loadingItem: null,
+ };
+ },
+ fields: [
+ {
+ key: 'name',
+ label: s__('Integrations|Linked namespaces'),
+ },
+ {
+ key: 'created_at',
+ label: __('Added'),
+ tdClass: 'gl-vertical-align-middle! gl-w-20p',
+ },
+ {
+ key: 'actions',
+ label: '',
+ tdClass: 'gl-text-right gl-vertical-align-middle! gl-pl-0!',
+ },
+ ],
+ 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.'),
+ },
+ methods: {
+ ...mapMutations({
+ setAlert: SET_ALERT,
+ }),
+ isEmpty,
+ isLoadingItem(item) {
+ return this.loadingItem === item;
+ },
+ unlinkBtnClass(item) {
+ return this.isLoadingItem(item) ? '' : 'gl-ml-6';
+ },
+ onClick(item) {
+ this.loadingItem = item;
+
+ removeSubscription(item.unlink_path)
+ .then(() => {
+ reloadPage();
+ })
+ .catch((error) => {
+ this.setAlert({
+ message: error?.response?.data?.error || this.$options.i18n.unlinkError,
+ variant: 'danger',
+ });
+ this.loadingItem = null;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <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 }">
+ <group-item-name :group="item.group" />
+ </template>
+ <template #cell(created_at)="{ item }">
+ <timeago-tooltip :time="item.created_at" />
+ </template>
+ <template #cell(actions)="{ item }">
+ <gl-button
+ :class="unlinkBtnClass(item)"
+ category="secondary"
+ :loading="isLoadingItem(item)"
+ :disabled="!isEmpty(loadingItem)"
+ @click.prevent="onClick(item)"
+ >{{ __('Unlink') }}</gl-button
+ >
+ </template>
+ </gl-table>
+ </div>
+</template>