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')
-rw-r--r--app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue99
-rw-r--r--app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue33
-rw-r--r--app/assets/javascripts/jira_connect/branches/constants.js3
-rw-r--r--app/assets/javascripts/jira_connect/branches/graphql/queries/get_projects.query.graphql3
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list.vue34
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/app.vue58
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue63
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/sign_in_button.vue6
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue40
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue43
10 files changed, 294 insertions, 88 deletions
diff --git a/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue b/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue
index 66fcb8e10eb..46c27c33f56 100644
--- a/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue
+++ b/app/assets/javascripts/jira_connect/branches/components/new_branch_form.vue
@@ -1,5 +1,6 @@
<script>
-import { GlFormGroup, GlButton, GlFormInput, GlForm, GlAlert } from '@gitlab/ui';
+import { GlFormGroup, GlButton, GlFormInput, GlForm, GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
import {
CREATE_BRANCH_ERROR_GENERIC,
CREATE_BRANCH_ERROR_WITH_CONTEXT,
@@ -7,6 +8,7 @@ import {
I18N_NEW_BRANCH_LABEL_BRANCH,
I18N_NEW_BRANCH_LABEL_SOURCE,
I18N_NEW_BRANCH_SUBMIT_BUTTON_TEXT,
+ I18N_NEW_BRANCH_PERMISSION_ALERT,
} from '../constants';
import createBranchMutation from '../graphql/mutations/create_branch.mutation.graphql';
import ProjectDropdown from './project_dropdown.vue';
@@ -17,6 +19,8 @@ const DEFAULT_ALERT_PARAMS = {
title: '',
message: '',
variant: DEFAULT_ALERT_VARIANT,
+ link: undefined,
+ dismissible: true,
};
export default {
@@ -27,10 +31,16 @@ export default {
GlFormInput,
GlForm,
GlAlert,
+ GlSprintf,
+ GlLink,
ProjectDropdown,
SourceBranchDropdown,
},
- inject: ['initialBranchName'],
+ inject: {
+ initialBranchName: {
+ default: '',
+ },
+ },
data() {
return {
selectedProject: null,
@@ -40,6 +50,7 @@ export default {
alertParams: {
...DEFAULT_ALERT_PARAMS,
},
+ hasPermission: false,
};
},
computed: {
@@ -49,19 +60,38 @@ export default {
showAlert() {
return Boolean(this.alertParams?.message);
},
+ isBranchNameValid() {
+ return (this.branchName ?? '').trim().length > 0;
+ },
disableSubmitButton() {
- return !(this.selectedProject && this.selectedSourceBranchName && this.branchName);
+ return !(this.selectedProject && this.selectedSourceBranchName && this.isBranchNameValid);
},
},
methods: {
- displayAlert({ title, message, variant = DEFAULT_ALERT_VARIANT } = {}) {
+ displayAlert({
+ title,
+ message,
+ variant = DEFAULT_ALERT_VARIANT,
+ link,
+ dismissible = true,
+ } = {}) {
this.alertParams = {
title,
message,
variant,
+ link,
+ dismissible,
};
},
- onAlertDismiss() {
+ setPermissionAlert() {
+ this.displayAlert({
+ message: I18N_NEW_BRANCH_PERMISSION_ALERT,
+ variant: 'warning',
+ link: helpPagePath('user/permissions', { anchor: 'project-members-permissions' }),
+ dismissible: false,
+ });
+ },
+ dismissAlert() {
this.alertParams = {
...DEFAULT_ALERT_PARAMS,
};
@@ -69,6 +99,14 @@ export default {
onProjectSelect(project) {
this.selectedProject = project;
this.selectedSourceBranchName = null; // reset branch selection
+ this.hasPermission = this.selectedProject.userPermissions.pushCode;
+
+ if (!this.hasPermission) {
+ this.setPermissionAlert();
+ } else {
+ // clear alert if the user has permissions for the newly-selected project.
+ this.dismissAlert();
+ }
},
onSourceBranchSelect(branchName) {
this.selectedSourceBranchName = branchName;
@@ -127,10 +165,18 @@ export default {
class="gl-mb-5"
:variant="alertParams.variant"
:title="alertParams.title"
- @dismiss="onAlertDismiss"
+ :dismissible="alertParams.dismissible"
+ @dismiss="dismissAlert"
>
- {{ alertParams.message }}
+ <gl-sprintf :message="alertParams.message">
+ <template #link="{ content }">
+ <gl-link :href="alertParams.link" target="_blank">
+ {{ content }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
</gl-alert>
+
<gl-form-group :label="$options.i18n.I18N_NEW_BRANCH_LABEL_DROPDOWN" label-for="project-select">
<project-dropdown
id="project-select"
@@ -140,25 +186,28 @@ export default {
/>
</gl-form-group>
- <gl-form-group
- :label="$options.i18n.I18N_NEW_BRANCH_LABEL_BRANCH"
- label-for="branch-name-input"
- >
- <gl-form-input id="branch-name-input" v-model="branchName" type="text" required />
- </gl-form-group>
+ <template v-if="selectedProject && hasPermission">
+ <gl-form-group
+ :label="$options.i18n.I18N_NEW_BRANCH_LABEL_SOURCE"
+ label-for="source-branch-select"
+ >
+ <source-branch-dropdown
+ id="source-branch-select"
+ :selected-project="selectedProject"
+ :selected-branch-name="selectedSourceBranchName"
+ @change="onSourceBranchSelect"
+ @error="onError"
+ />
+ </gl-form-group>
- <gl-form-group
- :label="$options.i18n.I18N_NEW_BRANCH_LABEL_SOURCE"
- label-for="source-branch-select"
- >
- <source-branch-dropdown
- id="source-branch-select"
- :selected-project="selectedProject"
- :selected-branch-name="selectedSourceBranchName"
- @change="onSourceBranchSelect"
- @error="onError"
- />
- </gl-form-group>
+ <gl-form-group
+ :label="$options.i18n.I18N_NEW_BRANCH_LABEL_BRANCH"
+ label-for="branch-name-input"
+ class="gl-max-w-62"
+ >
+ <gl-form-input id="branch-name-input" v-model="branchName" type="text" required />
+ </gl-form-group>
+ </template>
<div class="form-actions">
<gl-button
diff --git a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue
index 751f3e9639d..88005cccd89 100644
--- a/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue
+++ b/app/assets/javascripts/jira_connect/branches/components/project_dropdown.vue
@@ -1,5 +1,11 @@
<script>
-import { GlDropdown, GlDropdownItem, GlSearchBoxByType, GlLoadingIcon } from '@gitlab/ui';
+import {
+ GlDropdown,
+ GlSearchBoxByType,
+ GlLoadingIcon,
+ GlDropdownItem,
+ GlAvatarLabeled,
+} from '@gitlab/ui';
import { __ } from '~/locale';
import { PROJECTS_PER_PAGE } from '../constants';
import getProjectsQuery from '../graphql/queries/get_projects.query.graphql';
@@ -14,6 +20,7 @@ export default {
GlDropdownItem,
GlSearchBoxByType,
GlLoadingIcon,
+ GlAvatarLabeled,
},
props: {
selectedProject: {
@@ -56,7 +63,7 @@ export default {
return Boolean(this.$apollo.queries.projects.loading);
},
projectDropdownText() {
- return this.selectedProject?.nameWithNamespace || __('Select a project');
+ return this.selectedProject?.nameWithNamespace || this.$options.i18n.selectProjectText;
},
},
methods: {
@@ -70,11 +77,19 @@ export default {
return project.id === this.selectedProject?.id;
},
},
+ i18n: {
+ selectProjectText: __('Select a project'),
+ },
};
</script>
<template>
- <gl-dropdown :text="projectDropdownText" :loading="initialProjectsLoading">
+ <gl-dropdown
+ :text="projectDropdownText"
+ :loading="initialProjectsLoading"
+ menu-class="gl-w-auto!"
+ :header-text="$options.i18n.selectProjectText"
+ >
<template #header>
<gl-search-box-by-type v-model.trim="projectSearchQuery" :debounce="250" />
</template>
@@ -85,10 +100,20 @@ export default {
v-for="project in projects"
:key="project.id"
is-check-item
+ is-check-centered
:is-checked="isProjectSelected(project)"
+ :data-testid="`test-project-${project.id}`"
@click="onProjectSelect(project)"
>
- {{ project.nameWithNamespace }}
+ <gl-avatar-labeled
+ class="gl-text-truncate"
+ shape="rect"
+ :size="32"
+ :src="project.avatarUrl"
+ :label="project.name"
+ :entity-name="project.name"
+ :sub-label="project.nameWithNamespace"
+ />
</gl-dropdown-item>
</template>
</gl-dropdown>
diff --git a/app/assets/javascripts/jira_connect/branches/constants.js b/app/assets/javascripts/jira_connect/branches/constants.js
index ab9d3b2c110..43be774ce7c 100644
--- a/app/assets/javascripts/jira_connect/branches/constants.js
+++ b/app/assets/javascripts/jira_connect/branches/constants.js
@@ -23,3 +23,6 @@ export const I18N_NEW_BRANCH_SUCCESS_TITLE = s__(
export const I18N_NEW_BRANCH_SUCCESS_MESSAGE = s__(
'JiraConnect|You can now close this window and return to Jira.',
);
+export const I18N_NEW_BRANCH_PERMISSION_ALERT = s__(
+ "JiraConnect|You don't have permission to create branches for this project. Select a different project or contact the project owner for access. %{linkStart}Learn more.%{linkEnd}",
+);
diff --git a/app/assets/javascripts/jira_connect/branches/graphql/queries/get_projects.query.graphql b/app/assets/javascripts/jira_connect/branches/graphql/queries/get_projects.query.graphql
index 32fbc1113bc..03e8e3e986b 100644
--- a/app/assets/javascripts/jira_connect/branches/graphql/queries/get_projects.query.graphql
+++ b/app/assets/javascripts/jira_connect/branches/graphql/queries/get_projects.query.graphql
@@ -26,6 +26,9 @@ query jiraGetProjects(
repository {
empty
}
+ userPermissions {
+ pushCode
+ }
}
pageInfo {
...PageInfo
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list.vue b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list.vue
index 5a49d7c1a90..7f035dddafe 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/add_namespace_modal/groups_list.vue
@@ -30,7 +30,8 @@ export default {
page: 1,
totalItems: 0,
errorMessage: null,
- searchTerm: '',
+ userSearchTerm: '',
+ searchValue: '',
};
},
computed: {
@@ -45,16 +46,11 @@ export default {
},
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,
+ search: this.searchValue,
})
.then((response) => {
const { page, total } = parseIntPagination(normalizeHeaders(response.headers));
@@ -69,12 +65,24 @@ export default {
this.isLoadingMore = false;
});
},
- onGroupSearch(searchTerm) {
- // keep a copy of the search term for pagination
- this.searchTerm = searchTerm;
- // reset the current page
+ onGroupSearch(userSearchTerm = '') {
+ this.userSearchTerm = userSearchTerm;
+
+ // 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 newSearchValue =
+ this.userSearchTerm.length < MINIMUM_SEARCH_TERM_LENGTH ? '' : this.userSearchTerm;
+
+ // don't fetch new results if the search value didn't change.
+ if (newSearchValue === this.searchValue) {
+ return;
+ }
+
+ // reset the page.
this.page = 1;
- return this.loadGroups();
+ this.searchValue = newSearchValue;
+ this.loadGroups();
},
},
DEFAULT_GROUPS_PER_PAGE,
@@ -92,7 +100,7 @@ export default {
debounce="500"
:placeholder="__('Search by name')"
:is-loading="isLoadingMore"
- :value="searchTerm"
+ :value="userSearchTerm"
@input="onGroupSearch"
/>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
index 7fd4cc38f11..905e242e977 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
@@ -1,13 +1,13 @@
<script>
-import { GlAlert, GlLink, GlSprintf, GlEmptyState } from '@gitlab/ui';
+import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { mapState, mapMutations } from 'vuex';
import { retrieveAlert } from '~/jira_connect/subscriptions/utils';
import { SET_ALERT } from '../store/mutation_types';
-import SubscriptionsList from './subscriptions_list.vue';
-import AddNamespaceButton from './add_namespace_button.vue';
-import SignInButton from './sign_in_button.vue';
+import SignInPage from '../pages/sign_in.vue';
+import SubscriptionsPage from '../pages/subscriptions.vue';
import UserLink from './user_link.vue';
+import CompatibilityAlert from './compatibility_alert.vue';
export default {
name: 'JiraConnectApp',
@@ -15,11 +15,10 @@ export default {
GlAlert,
GlLink,
GlSprintf,
- GlEmptyState,
- SubscriptionsList,
- AddNamespaceButton,
- SignInButton,
UserLink,
+ CompatibilityAlert,
+ SignInPage,
+ SubscriptionsPage,
},
inject: {
usersPath: {
@@ -58,11 +57,14 @@ export default {
<template>
<div>
+ <compatibility-alert />
+
<gl-alert
v-if="shouldShowAlert"
class="gl-mb-7"
:variant="alert.variant"
:title="alert.title"
+ data-testid="jira-connect-persisted-alert"
@dismiss="setAlert"
>
<gl-sprintf v-if="alert.linkUrl" :message="alert.message">
@@ -79,43 +81,9 @@ export default {
<user-link :user-signed-in="userSignedIn" :has-subscriptions="hasSubscriptions" />
<h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2>
- <div class="jira-connect-app-body gl-mx-auto gl-px-5 gl-mb-7">
- <template v-if="hasSubscriptions">
- <div class="gl-display-flex gl-justify-content-end">
- <sign-in-button v-if="!userSignedIn" :users-path="usersPath" />
- <add-namespace-button v-else />
- </div>
-
- <subscriptions-list />
- </template>
- <template v-else>
- <div v-if="!userSignedIn" class="gl-text-center">
- <p class="gl-mb-7">{{ s__('JiraService|Sign in to GitLab.com to get started.') }}</p>
- <sign-in-button class="gl-mb-7" :users-path="usersPath">
- {{ __('Sign in to GitLab') }}
- </sign-in-button>
- <p>
- {{
- s__(
- 'Integrations|Note: this integration only works with accounts on GitLab.com (SaaS).',
- )
- }}
- </p>
- </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>
+ <div class="gl-layout-w-limited gl-mx-auto gl-px-5 gl-mb-7">
+ <sign-in-page v-if="!userSignedIn" :has-subscriptions="hasSubscriptions" />
+ <subscriptions-page v-else :has-subscriptions="hasSubscriptions" />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue b/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue
new file mode 100644
index 00000000000..3cfbd87ac53
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/compatibility_alert.vue
@@ -0,0 +1,63 @@
+<script>
+import { GlAlert, GlSprintf, GlLink } from '@gitlab/ui';
+import { s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
+
+const COMPATIBILITY_ALERT_STATE_KEY = 'compatibility_alert_dismissed';
+
+export default {
+ name: 'CompatibilityAlert',
+ components: {
+ GlAlert,
+ GlSprintf,
+ GlLink,
+ LocalStorageSync,
+ },
+ data() {
+ return {
+ alertDismissed: false,
+ };
+ },
+ computed: {
+ shouldShowAlert() {
+ return !this.alertDismissed;
+ },
+ },
+ methods: {
+ dismissAlert() {
+ this.alertDismissed = true;
+ },
+ },
+ i18n: {
+ title: s__('Integrations|Known limitations'),
+ body: s__(
+ 'Integrations|This integration only works with GitLab.com. Adding a namespace only works in browsers that allow cross-site cookies. %{linkStart}Learn more%{linkEnd}.',
+ ),
+ },
+ DOCS_LINK_URL: helpPagePath('integration/jira/connect-app'),
+ COMPATIBILITY_ALERT_STATE_KEY,
+};
+</script>
+<template>
+ <local-storage-sync
+ v-model="alertDismissed"
+ :storage-key="$options.COMPATIBILITY_ALERT_STATE_KEY"
+ >
+ <gl-alert
+ v-if="shouldShowAlert"
+ class="gl-mb-7"
+ variant="info"
+ :title="$options.i18n.title"
+ @dismiss="dismissAlert"
+ >
+ <gl-sprintf :message="$options.i18n.body">
+ <template #link="{ content }">
+ <gl-link :href="$options.DOCS_LINK_URL" target="_blank" rel="noopener noreferrer">{{
+ content
+ }}</gl-link>
+ </template>
+ </gl-sprintf>
+ </gl-alert>
+ </local-storage-sync>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_button.vue b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_button.vue
index dc0a77e99c2..627abcdd4a0 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_button.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_button.vue
@@ -1,6 +1,7 @@
<script>
import { GlButton } from '@gitlab/ui';
import { getGitlabSignInURL } from '~/jira_connect/subscriptions/utils';
+import { s__ } from '~/locale';
export default {
components: {
@@ -25,12 +26,15 @@ export default {
this.signInURL = await getGitlabSignInURL(this.usersPath);
},
},
+ i18n: {
+ defaultButtonText: s__('Integrations|Sign in to GitLab'),
+ },
};
</script>
<template>
<gl-button category="primary" variant="info" :href="signInURL" target="_blank">
<slot>
- {{ s__('Integrations|Sign in to add namespaces') }}
+ {{ $options.i18n.defaultButtonText }}
</slot>
</gl-button>
</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue
new file mode 100644
index 00000000000..2bce5afc72b
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in.vue
@@ -0,0 +1,40 @@
+<script>
+import { s__ } from '~/locale';
+import SubscriptionsList from '../components/subscriptions_list.vue';
+import SignInButton from '../components/sign_in_button.vue';
+
+export default {
+ name: 'SignInPage',
+ components: {
+ SubscriptionsList,
+ SignInButton,
+ },
+ inject: ['usersPath'],
+ props: {
+ hasSubscriptions: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ i18n: {
+ signinButtonTextWithSubscriptions: s__('Integrations|Sign in to add namespaces'),
+ signInText: s__('JiraService|Sign in to GitLab.com to get started.'),
+ },
+};
+</script>
+
+<template>
+ <div v-if="hasSubscriptions">
+ <div class="gl-display-flex gl-justify-content-end">
+ <sign-in-button :users-path="usersPath">
+ {{ $options.i18n.signinButtonTextWithSubscriptions }}
+ </sign-in-button>
+ </div>
+
+ <subscriptions-list />
+ </div>
+ <div v-else class="gl-text-center">
+ <p class="gl-mb-7">{{ $options.i18n.signInText }}</p>
+ <sign-in-button class="gl-mb-7" :users-path="usersPath" />
+ </div>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue
new file mode 100644
index 00000000000..426f2999370
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/subscriptions.vue
@@ -0,0 +1,43 @@
+<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>