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/api.js33
-rw-r--r--app/assets/javascripts/jira_connect/components/app.vue57
-rw-r--r--app/assets/javascripts/jira_connect/components/groups_list.vue88
-rw-r--r--app/assets/javascripts/jira_connect/components/groups_list_item.vue42
-rw-r--r--app/assets/javascripts/jira_connect/constants.js1
-rw-r--r--app/assets/javascripts/jira_connect/index.js97
-rw-r--r--app/assets/javascripts/jira_connect/store/index.js9
-rw-r--r--app/assets/javascripts/jira_connect/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/jira_connect/store/mutations.js7
-rw-r--r--app/assets/javascripts/jira_connect/store/state.js3
10 files changed, 285 insertions, 53 deletions
diff --git a/app/assets/javascripts/jira_connect/api.js b/app/assets/javascripts/jira_connect/api.js
new file mode 100644
index 00000000000..d689a2d1962
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/api.js
@@ -0,0 +1,33 @@
+import axios from 'axios';
+
+const getJwt = async () => {
+ return AP.context.getToken();
+};
+
+export const addSubscription = async (addPath, namespace) => {
+ const jwt = await getJwt();
+
+ return axios.post(addPath, {
+ jwt,
+ namespace_path: namespace,
+ });
+};
+
+export const removeSubscription = async (removePath) => {
+ const jwt = await getJwt();
+
+ return axios.delete(removePath, {
+ params: {
+ jwt,
+ },
+ });
+};
+
+export const fetchGroups = async (groupsPath, { page, perPage }) => {
+ return axios.get(groupsPath, {
+ params: {
+ page,
+ per_page: perPage,
+ },
+ });
+};
diff --git a/app/assets/javascripts/jira_connect/components/app.vue b/app/assets/javascripts/jira_connect/components/app.vue
index 490bf2fdd66..f5bf30f4488 100644
--- a/app/assets/javascripts/jira_connect/components/app.vue
+++ b/app/assets/javascripts/jira_connect/components/app.vue
@@ -1,16 +1,63 @@
<script>
+import { mapState } from 'vuex';
+import { GlAlert, GlButton, GlModal, GlModalDirective } from '@gitlab/ui';
+import { __ } from '~/locale';
+import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+import GroupsList from './groups_list.vue';
+
export default {
name: 'JiraConnectApp',
+ components: {
+ GlAlert,
+ GlButton,
+ GlModal,
+ GroupsList,
+ },
+ directives: {
+ GlModalDirective,
+ },
+ mixins: [glFeatureFlagsMixin()],
computed: {
- state() {
- return this.$root.$data.state || {};
+ ...mapState(['errorMessage']),
+ showNewUI() {
+ return this.glFeatures.newJiraConnectUi;
},
- error() {
- return this.state.error;
+ },
+ modal: {
+ cancelProps: {
+ text: __('Cancel'),
},
},
};
</script>
+
<template>
- <div></div>
+ <div>
+ <gl-alert v-if="errorMessage" class="gl-mb-6" variant="danger" :dismissible="false">
+ {{ errorMessage }}
+ </gl-alert>
+
+ <h1>GitLab for Jira Configuration</h1>
+
+ <div
+ v-if="showNewUI"
+ class="gl-display-flex gl-justify-content-space-between gl-my-5 gl-pb-4 gl-border-b-solid gl-border-b-1 gl-border-b-gray-200"
+ >
+ <h3 data-testid="new-jira-connect-ui-heading">{{ s__('Integrations|Linked namespaces') }}</h3>
+ <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>
+ </div>
+ </div>
</template>
diff --git a/app/assets/javascripts/jira_connect/components/groups_list.vue b/app/assets/javascripts/jira_connect/components/groups_list.vue
new file mode 100644
index 00000000000..eeddd32addc
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/components/groups_list.vue
@@ -0,0 +1,88 @@
+<script>
+import { GlTabs, GlTab, GlLoadingIcon, GlPagination } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import { fetchGroups } from '~/jira_connect/api';
+import { defaultPerPage } from '~/jira_connect/constants';
+import GroupsListItem from './groups_list_item.vue';
+
+export default {
+ components: {
+ GlTabs,
+ GlTab,
+ GlLoadingIcon,
+ GlPagination,
+ GroupsListItem,
+ },
+ inject: {
+ groupsPath: {
+ default: '',
+ },
+ },
+ data() {
+ return {
+ groups: [],
+ isLoading: false,
+ page: 1,
+ perPage: defaultPerPage,
+ totalItems: 0,
+ };
+ },
+ mounted() {
+ this.loadGroups();
+ },
+ methods: {
+ loadGroups() {
+ this.isLoading = true;
+
+ fetchGroups(this.groupsPath, {
+ page: this.page,
+ perPage: this.perPage,
+ })
+ .then((response) => {
+ const { page, total } = parseIntPagination(normalizeHeaders(response.headers));
+ this.page = page;
+ this.totalItems = total;
+ this.groups = response.data;
+ })
+ .catch(() => {
+ // eslint-disable-next-line no-alert
+ alert(s__('Integrations|Failed to load namespaces. Please try again.'));
+ })
+ .finally(() => {
+ this.isLoading = false;
+ });
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-tabs>
+ <gl-tab :title="__('Groups and subgroups')" class="gl-pt-3">
+ <gl-loading-icon v-if="isLoading" 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">
+ <groups-list-item v-for="group in groups" :key="group.id" :group="group" />
+ </ul>
+
+ <div class="gl-display-flex gl-justify-content-center gl-mt-5">
+ <gl-pagination
+ v-if="totalItems > perPage && groups.length > 0"
+ v-model="page"
+ class="gl-mb-0"
+ :per-page="perPage"
+ :total-items="totalItems"
+ @input="loadGroups"
+ />
+ </div>
+ </gl-tab>
+ </gl-tabs>
+</template>
diff --git a/app/assets/javascripts/jira_connect/components/groups_list_item.vue b/app/assets/javascripts/jira_connect/components/groups_list_item.vue
new file mode 100644
index 00000000000..15e37ab3cb0
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/components/groups_list_item.vue
@@ -0,0 +1,42 @@
+<script>
+import { GlIcon, GlAvatar } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlIcon,
+ GlAvatar,
+ },
+ props: {
+ group: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <li class="gl-border-b-1 gl-border-b-solid gl-border-b-gray-200">
+ <div class="gl-display-flex gl-align-items-center gl-py-3">
+ <gl-icon name="folder-o" class="gl-mr-3" />
+ <div class="gl-display-none gl-flex-shrink-0 gl-display-sm-flex gl-mr-3">
+ <gl-avatar :size="32" shape="rect" :entity-name="group.name" :src="group.avatar_url" />
+ </div>
+ <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">
+ <div class="gl-display-flex gl-align-items-center gl-flex-wrap">
+ <span
+ class="gl-mr-3 gl-text-gray-900! gl-font-weight-bold"
+ data-testid="group-list-item-name"
+ >
+ {{ group.full_name }}
+ </span>
+ </div>
+ <div v-if="group.description" data-testid="group-list-item-description">
+ <p class="gl-mt-2! gl-mb-0 gl-text-gray-600" v-text="group.description"></p>
+ </div>
+ </div>
+ </div>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/jira_connect/constants.js b/app/assets/javascripts/jira_connect/constants.js
new file mode 100644
index 00000000000..2b3be5cd5cd
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/constants.js
@@ -0,0 +1 @@
+export const defaultPerPage = 10;
diff --git a/app/assets/javascripts/jira_connect/index.js b/app/assets/javascripts/jira_connect/index.js
index e7aa4c437bb..dc2a77f4e0c 100644
--- a/app/assets/javascripts/jira_connect/index.js
+++ b/app/assets/javascripts/jira_connect/index.js
@@ -1,18 +1,21 @@
import Vue from 'vue';
+import Vuex from 'vuex';
import $ from 'jquery';
-import App from './components/app.vue';
-
-const store = {
- state: {
- error: '',
- },
- setErrorMessage(errorMessage) {
- this.state.error = errorMessage;
- },
-};
+import setConfigs from '@gitlab/ui/dist/config';
+import Translate from '~/vue_shared/translate';
+import GlFeatureFlagsPlugin from '~/vue_shared/gl_feature_flags_plugin';
+
+import JiraConnectApp from './components/app.vue';
+import { addSubscription, removeSubscription } from '~/jira_connect/api';
+import createStore from './store';
+import { SET_ERROR_MESSAGE } from './store/mutation_types';
+
+Vue.use(Vuex);
+
+const store = createStore();
/**
- * Initialize necessary form handlers for the Jira Connect app
+ * Initialize form handlers for the Jira Connect app
*/
const initJiraFormHandlers = () => {
const reqComplete = () => {
@@ -20,53 +23,40 @@ const initJiraFormHandlers = () => {
};
const reqFailed = (res, fallbackErrorMessage) => {
- const { responseJSON: { error = fallbackErrorMessage } = {} } = res || {};
+ const { error = fallbackErrorMessage } = res || {};
- store.setErrorMessage(error);
- // eslint-disable-next-line no-alert
- alert(error);
+ store.commit(SET_ERROR_MESSAGE, error);
};
- AP.getLocation(location => {
- $('.js-jira-connect-sign-in').each(function updateSignInLink() {
- const updatedLink = `${$(this).attr('href')}?return_to=${location}`;
- $(this).attr('href', updatedLink);
+ if (typeof AP.getLocation === 'function') {
+ AP.getLocation((location) => {
+ $('.js-jira-connect-sign-in').each(function updateSignInLink() {
+ const updatedLink = `${$(this).attr('href')}?return_to=${location}`;
+ $(this).attr('href', updatedLink);
+ });
});
- });
+ }
$('#add-subscription-form').on('submit', function onAddSubscriptionForm(e) {
- const actionUrl = $(this).attr('action');
+ const addPath = $(this).attr('action');
+ const namespace = $('#namespace-input').val();
+
e.preventDefault();
- AP.context.getToken(token => {
- // eslint-disable-next-line no-jquery/no-ajax
- $.post(actionUrl, {
- jwt: token,
- namespace_path: $('#namespace-input').val(),
- format: 'json',
- })
- .done(reqComplete)
- .fail(err => reqFailed(err, 'Failed to add namespace. Please try again.'));
- });
+ addSubscription(addPath, namespace)
+ .then(reqComplete)
+ .catch((err) => reqFailed(err.response.data, 'Failed to add namespace. Please try again.'));
});
$('.remove-subscription').on('click', function onRemoveSubscriptionClick(e) {
- const href = $(this).attr('href');
+ const removePath = $(this).attr('href');
e.preventDefault();
- AP.context.getToken(token => {
- // eslint-disable-next-line no-jquery/no-ajax
- $.ajax({
- url: href,
- method: 'DELETE',
- data: {
- jwt: token,
- format: 'json',
- },
- })
- .done(reqComplete)
- .fail(err => reqFailed(err, 'Failed to remove namespace. Please try again.'));
- });
+ removeSubscription(removePath)
+ .then(reqComplete)
+ .catch((err) =>
+ reqFailed(err.response.data, 'Failed to remove namespace. Please try again.'),
+ );
});
};
@@ -75,13 +65,24 @@ function initJiraConnect() {
initJiraFormHandlers();
+ if (!el) {
+ return null;
+ }
+
+ setConfigs();
+ Vue.use(Translate);
+ Vue.use(GlFeatureFlagsPlugin);
+
+ const { groupsPath } = el.dataset;
+
return new Vue({
el,
- data: {
- state: store.state,
+ store,
+ provide: {
+ groupsPath,
},
render(createElement) {
- return createElement(App, {});
+ return createElement(JiraConnectApp);
},
});
}
diff --git a/app/assets/javascripts/jira_connect/store/index.js b/app/assets/javascripts/jira_connect/store/index.js
new file mode 100644
index 00000000000..aa7e14269a4
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/store/index.js
@@ -0,0 +1,9 @@
+import Vuex from 'vuex';
+import mutations from './mutations';
+import state from './state';
+
+export default () =>
+ new Vuex.Store({
+ state,
+ mutations,
+ });
diff --git a/app/assets/javascripts/jira_connect/store/mutation_types.js b/app/assets/javascripts/jira_connect/store/mutation_types.js
new file mode 100644
index 00000000000..7f6ff1256bb
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/store/mutation_types.js
@@ -0,0 +1 @@
+export const SET_ERROR_MESSAGE = 'SET_ERROR_MESSAGE';
diff --git a/app/assets/javascripts/jira_connect/store/mutations.js b/app/assets/javascripts/jira_connect/store/mutations.js
new file mode 100644
index 00000000000..c3acd07f89f
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/store/mutations.js
@@ -0,0 +1,7 @@
+import { SET_ERROR_MESSAGE } from './mutation_types';
+
+export default {
+ [SET_ERROR_MESSAGE](state, errorMessage) {
+ state.errorMessage = errorMessage;
+ },
+};
diff --git a/app/assets/javascripts/jira_connect/store/state.js b/app/assets/javascripts/jira_connect/store/state.js
new file mode 100644
index 00000000000..079b8350770
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/store/state.js
@@ -0,0 +1,3 @@
+export default () => ({
+ errorMessage: undefined,
+});