summaryrefslogtreecommitdiff
path: root/app/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/assets/javascripts')
-rw-r--r--app/assets/javascripts/import/details/api.js11
-rw-r--r--app/assets/javascripts/import/details/components/import_details_app.vue7
-rw-r--r--app/assets/javascripts/import/details/components/import_details_table.vue78
-rw-r--r--app/assets/javascripts/import/details/index.js5
-rw-r--r--app/assets/javascripts/import_entities/components/import_status.vue23
-rw-r--r--app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue8
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/app.vue7
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue18
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/index.vue19
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue22
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue9
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/version_select_form.vue101
-rw-r--r--app/assets/javascripts/jobs/components/table/cells/actions_cell.vue18
-rw-r--r--app/assets/javascripts/labels/index.js5
-rw-r--r--app/assets/javascripts/lib/utils/dom_utils.js2
-rw-r--r--app/assets/javascripts/projects/commit_box/info/index.js3
-rw-r--r--app/assets/javascripts/projects/commit_box/info/init_details_button.js18
-rw-r--r--app/assets/javascripts/projects/commit_box/info/load_branches.js3
-rw-r--r--app/assets/javascripts/super_sidebar/components/nav_item.vue21
-rw-r--r--app/assets/javascripts/super_sidebar/components/nav_item_link.vue35
-rw-r--r--app/assets/javascripts/super_sidebar/components/nav_item_router_link.vue37
-rw-r--r--app/assets/javascripts/super_sidebar/constants.js2
-rw-r--r--app/assets/javascripts/super_sidebar/utils.js2
23 files changed, 342 insertions, 112 deletions
diff --git a/app/assets/javascripts/import/details/api.js b/app/assets/javascripts/import/details/api.js
new file mode 100644
index 00000000000..1fb3ee526d7
--- /dev/null
+++ b/app/assets/javascripts/import/details/api.js
@@ -0,0 +1,11 @@
+import axios from '~/lib/utils/axios_utils';
+
+export const fetchImportFailures = (failuresPath, { projectId, page, perPage }) => {
+ return axios.get(failuresPath, {
+ params: {
+ project_id: projectId,
+ page,
+ per_page: perPage,
+ },
+ });
+};
diff --git a/app/assets/javascripts/import/details/components/import_details_app.vue b/app/assets/javascripts/import/details/components/import_details_app.vue
index 86820663025..13483fa8ba2 100644
--- a/app/assets/javascripts/import/details/components/import_details_app.vue
+++ b/app/assets/javascripts/import/details/components/import_details_app.vue
@@ -4,13 +4,6 @@ import ImportDetailsTable from './import_details_table.vue';
export default {
components: { ImportDetailsTable },
- props: {
- project: {
- type: Object,
- required: false,
- default: () => ({}),
- },
- },
i18n: {
pageTitle: s__('Import|GitHub import details'),
},
diff --git a/app/assets/javascripts/import/details/components/import_details_table.vue b/app/assets/javascripts/import/details/components/import_details_table.vue
index 9ce58e8a9bc..b32b5778265 100644
--- a/app/assets/javascripts/import/details/components/import_details_table.vue
+++ b/app/assets/javascripts/import/details/components/import_details_table.vue
@@ -1,9 +1,13 @@
<script>
-import { GlEmptyState, GlIcon, GlLink, GlTable } from '@gitlab/ui';
-import { __ } from '~/locale';
+import { GlEmptyState, GlIcon, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui';
+import { __, s__ } from '~/locale';
+import { createAlert } from '~/alert';
+import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import { getParameterValues } from '~/lib/utils/url_utility';
import PaginationBar from '~/vue_shared/components/pagination_bar/pagination_bar.vue';
import { STATISTIC_ITEMS } from '../../constants';
+import { fetchImportFailures } from '../api';
const DEFAULT_PAGE_SIZE = 20;
@@ -12,6 +16,7 @@ export default {
GlEmptyState,
GlIcon,
GlLink,
+ GlLoadingIcon,
GlTable,
PaginationBar,
},
@@ -38,40 +43,85 @@ export default {
label: __('Details'),
},
],
+
+ i18n: {
+ fetchErrorMessage: s__('Import|An error occurred while fetching import details.'),
+ emptyText: s__('Import|No import details'),
+ },
+
+ inject: {
+ failuresPath: {
+ default: undefined,
+ },
+ },
+
data() {
return {
+ items: [],
+ loading: false,
page: 1,
perPage: DEFAULT_PAGE_SIZE,
+ totalPages: 0,
+ total: 0,
};
},
- computed: {
- items() {
- return [];
- },
+ computed: {
hasItems() {
return this.items.length > 0;
},
pageInfo() {
- const mockPageInfo = {
+ return {
page: this.page,
perPage: this.perPage,
- totalPages: this.page,
- total: this.items.length,
+ totalPages: this.totalPages,
+ total: this.total,
};
- return mockPageInfo;
},
},
+ mounted() {
+ this.loadImportFailures();
+ },
+
methods: {
setPage(page) {
this.page = page;
+ this.loadImportFailures();
},
setPageSize(size) {
this.perPage = size;
this.page = 1;
+ this.loadImportFailures();
+ },
+
+ async loadImportFailures() {
+ if (!this.failuresPath) {
+ return;
+ }
+
+ this.loading = true;
+ try {
+ const response = await fetchImportFailures(this.failuresPath, {
+ projectId: getParameterValues('project_id')[0],
+ page: this.page,
+ perPage: this.perPage,
+ });
+
+ const { page, perPage, totalPages, total } = parseIntPagination(
+ normalizeHeaders(response.headers),
+ );
+ this.page = page;
+ this.perPage = perPage;
+ this.totalPages = totalPages;
+ this.total = total;
+ this.items = response.data;
+ } catch (error) {
+ createAlert({ message: this.$options.i18n.fetchErrorMessage });
+ }
+ this.loading = false;
},
},
};
@@ -79,9 +129,13 @@ export default {
<template>
<div>
- <gl-table :fields="$options.fields" :items="items" class="gl-mt-5" show-empty>
+ <gl-table :fields="$options.fields" :items="items" class="gl-mt-5" :busy="loading" show-empty>
+ <template #table-busy>
+ <gl-loading-icon size="lg" class="gl-my-5" />
+ </template>
+
<template #empty>
- <gl-empty-state :title="s__('Import|No import details')" />
+ <gl-empty-state :title="$options.i18n.emptyText" />
</template>
<template #cell(type)="{ item: { type } }">
diff --git a/app/assets/javascripts/import/details/index.js b/app/assets/javascripts/import/details/index.js
index 70850d947e2..7421846f103 100644
--- a/app/assets/javascripts/import/details/index.js
+++ b/app/assets/javascripts/import/details/index.js
@@ -8,9 +8,14 @@ export default () => {
return null;
}
+ const { failuresPath } = el.dataset;
+
return new Vue({
el,
name: 'ImportDetailsRoot',
+ provide: {
+ failuresPath,
+ },
render(createElement) {
return createElement(ImportDetailsApp);
},
diff --git a/app/assets/javascripts/import_entities/components/import_status.vue b/app/assets/javascripts/import_entities/components/import_status.vue
index 96d07803545..6c84684dedc 100644
--- a/app/assets/javascripts/import_entities/components/import_status.vue
+++ b/app/assets/javascripts/import_entities/components/import_status.vue
@@ -65,6 +65,11 @@ export default {
},
},
props: {
+ projectId: {
+ type: Number,
+ required: false,
+ default: null,
+ },
status: {
type: String,
required: true,
@@ -111,7 +116,19 @@ export default {
},
showDetails() {
- return Boolean(this.detailsPath) && this.glFeatures.importDetailsPage && this.isIncomplete;
+ return (
+ Boolean(this.detailsPathForProject) &&
+ this.glFeatures.importDetailsPage &&
+ this.isIncomplete
+ );
+ },
+
+ detailsPathForProject() {
+ if (!this.projectId || !this.detailsPath) {
+ return null;
+ }
+
+ return `${this.detailsPath}?project_id=${this.projectId}`;
},
},
@@ -163,7 +180,9 @@ export default {
</div>
</li>
</ul>
- <gl-link v-if="showDetails" :href="detailsPath">{{ $options.i18n.detailsLink }}</gl-link>
+ <gl-link v-if="showDetails" :href="detailsPathForProject">{{
+ $options.i18n.detailsLink
+ }}</gl-link>
</gl-accordion-item>
</gl-accordion>
</div>
diff --git a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
index b20309baac7..735939f991f 100644
--- a/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
+++ b/app/assets/javascripts/import_entities/import_projects/components/provider_repo_table_row.vue
@@ -70,7 +70,7 @@ export default {
...mapGetters(['getImportTarget']),
displayFullPath() {
- return this.repo.importedProject.fullPath.replace(/^\//, '');
+ return this.repo.importedProject?.fullPath.replace(/^\//, '');
},
isFinished() {
@@ -105,6 +105,10 @@ export default {
return this.getImportTarget(this.repo.importSource.id);
},
+ importedProjectId() {
+ return this.repo.importedProject?.id;
+ },
+
importButtonText() {
if (this.ciCdOnly) {
return __('Connect');
@@ -220,7 +224,7 @@ export default {
</div>
</td>
<td data-qa-selector="import_status_indicator">
- <import-status :status="importStatus" :stats="stats" />
+ <import-status :project-id="importedProjectId" :status="importStatus" :stats="stats" />
</td>
<td data-testid="actions" class="gl-white-space-nowrap">
<gl-tooltip :target="() => $refs.cancelButton.$el">
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
index 15e61ff1cd9..7e79572f76d 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue
@@ -100,12 +100,7 @@ export default {
<gl-link :href="gitlabUrl" target="_blank">
<img :src="gitlabLogo" class="gl-h-6" :alt="__('GitLab')" />
</gl-link>
- <user-link
- :user-signed-in="userSignedIn"
- :has-subscriptions="hasSubscriptions"
- :user="currentUser"
- class="gl-fixed gl-right-4"
- />
+ <user-link v-if="userSignedIn" :user="currentUser" class="gl-fixed gl-right-4" />
</header>
<main class="jira-connect-app gl-px-5 gl-pt-7 gl-mx-auto">
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue b/app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue
index d05e0d8610e..cc0af0b9ab7 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/user_link.vue
@@ -6,7 +6,6 @@ export default {
components: {
GlLink,
GlSprintf,
- SignInOauthButton: () => import('./sign_in_oauth_button.vue'),
},
inject: {
gitlabUserPath: {
@@ -14,14 +13,6 @@ export default {
},
},
props: {
- userSignedIn: {
- type: Boolean,
- required: true,
- },
- hasSubscriptions: {
- type: Boolean,
- required: true,
- },
user: {
type: Object,
required: false,
@@ -45,7 +36,6 @@ export default {
},
},
i18n: {
- signInText: __('Sign in to GitLab'),
signedInAsUserText: __('Signed in to GitLab as %{user_link}'),
signedInText: __('Signed in to GitLab'),
},
@@ -53,18 +43,12 @@ export default {
</script>
<template>
<div class="gl-font-base">
- <gl-sprintf v-if="userSignedIn" :message="signedInText">
+ <gl-sprintf :message="signedInText">
<template #user_link>
<gl-link data-testid="gitlab-user-link" :href="gitlabUserLink" target="_blank">
{{ gitlabUserHandle }}
</gl-link>
</template>
</gl-sprintf>
-
- <template v-else-if="hasSubscriptions">
- <sign-in-oauth-button category="tertiary">
- {{ $options.i18n.signInText }}
- </sign-in-oauth-button>
- </template>
</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
index 373c5970e64..8cc107930d1 100644
--- 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
@@ -12,7 +12,6 @@ import {
import { SET_ALERT } from '~/jira_connect/subscriptions/store/mutation_types';
import SignInOauthButton from '../../../components/sign_in_oauth_button.vue';
-import SetupInstructions from './setup_instructions.vue';
import VersionSelectForm from './version_select_form.vue';
export default {
@@ -20,31 +19,23 @@ export default {
components: {
GlButton,
SignInOauthButton,
- SetupInstructions,
VersionSelectForm,
},
data() {
return {
gitlabBasePath: null,
loadingVersionSelect: false,
- showSetupInstructions: false,
};
},
computed: {
hasSelectedVersion() {
return this.gitlabBasePath !== null;
},
- subtitle() {
- return this.hasSelectedVersion
- ? this.$options.i18n.signInSubtitle
- : this.$options.i18n.versionSelectSubtitle;
- },
},
mounted() {
this.gitlabBasePath = retrieveBaseUrl();
if (this.gitlabBasePath !== GITLAB_COM_BASE_PATH) {
setApiBaseURL(this.gitlabBasePath);
- this.showSetupInstructions = true;
}
},
methods: {
@@ -70,9 +61,6 @@ export default {
this.loadingVersionSelect = false;
});
},
- onSetupNext() {
- this.showSetupInstructions = false;
- },
onSignInError() {
this.$emit('error');
},
@@ -80,7 +68,6 @@ export default {
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'),
},
};
@@ -90,7 +77,6 @@ export default {
<div>
<div class="gl-text-center">
<h2>{{ $options.i18n.title }}</h2>
- <p data-testid="subtitle">{{ subtitle }}</p>
</div>
<version-select-form
@@ -101,9 +87,8 @@ export default {
/>
<template v-else>
- <setup-instructions v-if="showSetupInstructions" @next="onSetupNext" />
-
- <div v-else class="gl-text-center">
+ <div class="gl-text-center">
+ <p data-testid="subtitle">{{ $options.i18n.signInSubtitle }}</p>
<sign-in-oauth-button
class="gl-mb-5"
:gitlab-base-path="gitlabBasePath"
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue
new file mode 100644
index 00000000000..8ddbbffa708
--- /dev/null
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/self_managed_alert.vue
@@ -0,0 +1,22 @@
+<script>
+import { GlAlert } from '@gitlab/ui';
+import { s__ } from '~/locale';
+
+export default {
+ components: {
+ GlAlert,
+ },
+ i18n: {
+ title: s__('JiraService|Are you a GitLab administrator?'),
+ body: s__(
+ "JiraService|Setting up this integration is only possible if you're a GitLab administrator.",
+ ),
+ },
+};
+</script>
+
+<template>
+ <gl-alert variant="warning" :title="$options.i18n.title" :dismissible="false">
+ {{ $options.i18n.body }}
+ </gl-alert>
+</template>
diff --git a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue
index 00fa739b518..621bcccd19a 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/pages/sign_in/sign_in_gitlab_multiversion/setup_instructions.vue
@@ -12,7 +12,7 @@ export default {
</script>
<template>
- <div class="gl-max-w-62 gl-mx-auto gl-mt-7">
+ <div class="gl-mt-5">
<h3>{{ s__('JiraService|Continue setup in GitLab') }}</h3>
<p>
{{
@@ -28,8 +28,9 @@ export default {
>
</p>
- <gl-button variant="confirm" @click="$emit('next')">
- {{ __('Next') }}
- </gl-button>
+ <div class="gl-display-flex gl-justify-content-space-between">
+ <gl-button @click="$emit('back')">{{ __('Back') }}</gl-button>
+ <gl-button variant="confirm" @click="$emit('next')">{{ __('Next') }}</gl-button>
+ </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
index 37a65946b3f..3a080afd3c5 100644
--- 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
@@ -10,6 +10,8 @@ import {
import { __, s__ } from '~/locale';
import { GITLAB_COM_BASE_PATH } from '~/jira_connect/subscriptions/constants';
+import SelfManagedAlert from './self_managed_alert.vue';
+import SetupInstructions from './setup_instructions.vue';
const RADIO_OPTIONS = {
saas: 'saas',
@@ -27,6 +29,8 @@ export default {
GlFormInput,
GlFormRadio,
GlButton,
+ SelfManagedAlert,
+ SetupInstructions,
},
props: {
loading: {
@@ -39,25 +43,54 @@ export default {
return {
selected: DEFAULT_RADIO_OPTION,
selfManagedBasePathInput: '',
+ showSetupInstructions: false,
+ showSelfManagedInstanceInput: false,
};
},
computed: {
isSelfManagedSelected() {
return this.selected === RADIO_OPTIONS.selfManaged;
},
+ submitText() {
+ return this.isSelfManagedSelected
+ ? this.$options.i18n.buttonNext
+ : this.$options.i18n.buttonSave;
+ },
+ showVersonSelect() {
+ return !this.showSetupInstructions && !this.showSelfManagedInstanceInput;
+ },
},
methods: {
onSubmit() {
- const gitlabBasePath =
- this.selected === RADIO_OPTIONS.saas ? GITLAB_COM_BASE_PATH : this.selfManagedBasePathInput;
+ if (this.isSelfManagedSelected && !this.showSelfManagedInstanceInput) {
+ this.showSetupInstructions = true;
+ return;
+ }
+
+ const gitlabBasePath = this.isSelfManagedSelected
+ ? this.selfManagedBasePathInput
+ : GITLAB_COM_BASE_PATH;
this.$emit('submit', gitlabBasePath);
},
+
+ onSetupNext() {
+ this.showSetupInstructions = false;
+ this.showSelfManagedInstanceInput = true;
+ },
+
+ onSetupBack() {
+ this.showSetupInstructions = false;
+ this.showSelfManagedInstanceInput = false;
+ },
},
radioOptions: RADIO_OPTIONS,
i18n: {
+ title: s__('JiraService|What version of GitLab are you using?'),
saasRadioLabel: __('GitLab.com (SaaS)'),
saasRadioHelp: __('Most common'),
selfManagedRadioLabel: __('GitLab (self-managed)'),
+ buttonNext: __('Next'),
+ buttonSave: __('Save'),
instanceURLInputLabel: s__('JiraService|GitLab instance URL'),
instanceURLInputDescription: s__('JiraService|For example: https://gitlab.example.com'),
},
@@ -66,30 +99,50 @@ export default {
<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>
+ <div v-if="showVersonSelect">
+ <h5 class="gl-mb-5">{{ $options.i18n.title }}</h5>
+ <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>
+ <self-managed-alert v-if="isSelfManagedSelected" />
+
+ <div class="gl-display-flex gl-justify-content-end gl-mt-5">
+ <gl-button variant="confirm" type="submit" :loading="loading" data-testid="submit-button">{{
+ submitText
+ }}</gl-button>
+ </div>
+ </div>
- <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>
+ <setup-instructions v-else-if="showSetupInstructions" @next="onSetupNext" @back="onSetupBack" />
- <div class="gl-display-flex gl-justify-content-end">
- <gl-button variant="confirm" type="submit" :loading="loading">{{ __('Save') }}</gl-button>
+ <div v-else-if="showSelfManagedInstanceInput">
+ <gl-form-group
+ :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-space-between">
+ <gl-button data-testid="back-button" @click.prevent="onSetupBack">{{
+ __('Back')
+ }}</gl-button>
+ <gl-button variant="confirm" type="submit" :loading="loading">{{
+ $options.i18n.buttonSave
+ }}</gl-button>
+ </div>
</div>
</gl-form>
</template>
diff --git a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
index 17766b4d162..f4061f3d375 100644
--- a/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
+++ b/app/assets/javascripts/jobs/components/table/cells/actions_cell.vue
@@ -1,5 +1,12 @@
<script>
-import { GlButton, GlButtonGroup, GlModal, GlModalDirective, GlSprintf } from '@gitlab/ui';
+import {
+ GlButton,
+ GlButtonGroup,
+ GlModal,
+ GlModalDirective,
+ GlSprintf,
+ GlTooltipDirective,
+} from '@gitlab/ui';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
import { redirectTo } from '~/lib/utils/url_utility';
import {
@@ -49,6 +56,7 @@ export default {
},
directives: {
GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
inject: {
admin: {
@@ -178,11 +186,12 @@ export default {
<template v-if="canReadJob && canUpdateJob">
<gl-button
v-if="isActive"
- data-testid="cancel-button"
+ v-gl-tooltip
icon="cancel"
:title="$options.CANCEL"
:aria-label="$options.CANCEL"
:disabled="cancelBtnDisabled"
+ data-testid="cancel-button"
@click="cancelJob()"
/>
<template v-else-if="isScheduled">
@@ -191,6 +200,7 @@ export default {
</gl-button>
<gl-button
v-gl-modal-directive="$options.playJobModalId"
+ v-gl-tooltip
icon="play"
:title="$options.ACTIONS_START_NOW"
:aria-label="$options.ACTIONS_START_NOW"
@@ -206,6 +216,7 @@ export default {
</gl-sprintf>
</gl-modal>
<gl-button
+ v-gl-tooltip
icon="time-out"
:title="$options.ACTIONS_UNSCHEDULE"
:aria-label="$options.ACTIONS_UNSCHEDULE"
@@ -218,6 +229,7 @@ export default {
<!--Note: This is the manual job play button -->
<gl-button
v-if="manualJobPlayable"
+ v-gl-tooltip
icon="play"
:title="$options.ACTIONS_PLAY"
:aria-label="$options.ACTIONS_PLAY"
@@ -227,6 +239,7 @@ export default {
/>
<gl-button
v-else-if="isRetryable"
+ v-gl-tooltip
icon="retry"
:title="retryButtonTitle"
:aria-label="retryButtonTitle"
@@ -239,6 +252,7 @@ export default {
</template>
<gl-button
v-if="shouldDisplayArtifacts"
+ v-gl-tooltip
icon="download"
:title="$options.ACTIONS_DOWNLOAD_ARTIFACTS"
:aria-label="$options.ACTIONS_DOWNLOAD_ARTIFACTS"
diff --git a/app/assets/javascripts/labels/index.js b/app/assets/javascripts/labels/index.js
index 0d4113bba4c..c7c17607af6 100644
--- a/app/assets/javascripts/labels/index.js
+++ b/app/assets/javascripts/labels/index.js
@@ -120,16 +120,15 @@ export function initAdminLabels() {
const emptyState = document.querySelector('.js-admin-labels-empty-state');
function removeLabelSuccessCallback() {
- this.closest('li').classList.add('gl-display-none!');
+ this.closest('li.label-list-item').classList.add('gl-display-none!');
const labelsCount = document.querySelectorAll(
- 'ul.manage-labels-list li:not(.gl-display-none\\!)',
+ 'ul.manage-labels-list li.label-list-item:not(.gl-display-none\\!)',
).length;
// display the empty state if there are no more labels
if (labelsCount < 1 && !pagination && emptyState) {
emptyState.classList.remove('gl-display-none');
- labelsContainer.classList.add('gl-display-none');
}
}
diff --git a/app/assets/javascripts/lib/utils/dom_utils.js b/app/assets/javascripts/lib/utils/dom_utils.js
index 317c401e404..198f2da385c 100644
--- a/app/assets/javascripts/lib/utils/dom_utils.js
+++ b/app/assets/javascripts/lib/utils/dom_utils.js
@@ -39,7 +39,7 @@ export const toggleContainerClasses = (containerEl, classList) => {
* Return a object mapping element dataset names to booleans.
*
* This is useful for data- attributes whose presense represent
- * a truthiness, no matter the value of the attribute. The absense of the
+ * a truthiness, no matter the value of the attribute. The absence of the
* attribute represents falsiness.
*
* This can be useful when Rails-provided boolean-like values are passed
diff --git a/app/assets/javascripts/projects/commit_box/info/index.js b/app/assets/javascripts/projects/commit_box/info/index.js
index 7500c152b6a..7c4b76fd62f 100644
--- a/app/assets/javascripts/projects/commit_box/info/index.js
+++ b/app/assets/javascripts/projects/commit_box/info/index.js
@@ -1,6 +1,5 @@
import { fetchCommitMergeRequests } from '~/commit_merge_requests';
import { initCommitPipelineMiniGraph } from './init_commit_pipeline_mini_graph';
-import { initDetailsButton } from './init_details_button';
import { loadBranches } from './load_branches';
import initCommitPipelineStatus from './init_commit_pipeline_status';
@@ -14,7 +13,5 @@ export const initCommitBoxInfo = () => {
// Display pipeline mini graph for this commit
initCommitPipelineMiniGraph();
- initDetailsButton();
-
initCommitPipelineStatus();
};
diff --git a/app/assets/javascripts/projects/commit_box/info/init_details_button.js b/app/assets/javascripts/projects/commit_box/info/init_details_button.js
index bc2c16b9e83..667d6bd0250 100644
--- a/app/assets/javascripts/projects/commit_box/info/init_details_button.js
+++ b/app/assets/javascripts/projects/commit_box/info/init_details_button.js
@@ -1,7 +1,17 @@
export const initDetailsButton = () => {
- document.querySelector('.commit-info').addEventListener('click', function expand(e) {
- e.preventDefault();
- this.querySelector('.js-details-content').classList.remove('hide');
- this.querySelector('.js-details-expand').classList.add('gl-display-none');
+ const expandButton = document.querySelector('.js-details-expand');
+
+ if (!expandButton) {
+ return;
+ }
+
+ expandButton.addEventListener('click', (event) => {
+ const btn = event.target;
+ const contentEl = btn.parentElement.querySelector('.js-details-content');
+
+ if (contentEl) {
+ contentEl.classList.remove('hide');
+ btn.classList.add('gl-display-none');
+ }
});
};
diff --git a/app/assets/javascripts/projects/commit_box/info/load_branches.js b/app/assets/javascripts/projects/commit_box/info/load_branches.js
index d1136817cb3..8333e70b951 100644
--- a/app/assets/javascripts/projects/commit_box/info/load_branches.js
+++ b/app/assets/javascripts/projects/commit_box/info/load_branches.js
@@ -1,6 +1,7 @@
import axios from 'axios';
import { sanitize } from '~/lib/dompurify';
import { __ } from '~/locale';
+import { initDetailsButton } from './init_details_button';
export const loadBranches = (containerSelector = '.js-commit-box-info') => {
const containerEl = document.querySelector(containerSelector);
@@ -14,6 +15,8 @@ export const loadBranches = (containerSelector = '.js-commit-box-info') => {
.get(commitPath)
.then(({ data }) => {
branchesEl.innerHTML = sanitize(data);
+
+ initDetailsButton();
})
.catch(() => {
branchesEl.textContent = __('Failed to load branches. Please try again.');
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item.vue b/app/assets/javascripts/super_sidebar/components/nav_item.vue
index 9dc26ad10f5..8382ae8987b 100644
--- a/app/assets/javascripts/super_sidebar/components/nav_item.vue
+++ b/app/assets/javascripts/super_sidebar/components/nav_item.vue
@@ -7,6 +7,8 @@ import {
TRACKING_UNKNOWN_ID,
TRACKING_UNKNOWN_PANEL,
} from '~/super_sidebar/constants';
+import NavItemLink from './nav_item_link.vue';
+import NavItemRouterLink from './nav_item_router_link.vue';
export default {
i18n: {
@@ -18,6 +20,8 @@ export default {
GlButton,
GlIcon,
GlBadge,
+ NavItemLink,
+ NavItemRouterLink,
},
inject: {
pinnedItemIds: { default: { ids: [] } },
@@ -55,9 +59,6 @@ export default {
(typeof this.pillData === 'string' && this.pillData !== '')
);
},
- isActive() {
- return this.item.is_active;
- },
isPinnable() {
return this.panelSupportsPins && !this.isStatic;
},
@@ -86,27 +87,30 @@ export default {
return {
...this.$attrs,
...this.trackingProps,
- href: this.item.link,
- 'aria-current': this.isActive ? 'page' : null,
+ item: this.item,
'data-qa-submenu-item': this.item.title,
};
},
computedLinkClasses() {
return {
- 'gl-bg-t-gray-a-08': this.isActive,
'gl-py-2': this.isPinnable,
'gl-py-3': !this.isPinnable,
[this.item.link_classes]: this.item.link_classes,
...this.linkClasses,
};
},
+ navItemLinkComponent() {
+ return this.item.to ? NavItemRouterLink : NavItemLink;
+ },
},
};
</script>
<template>
<li>
- <a
+ <component
+ :is="navItemLinkComponent"
+ #default="{ isActive }"
v-bind="linkProps"
class="nav-item-link gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-mb-1 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-focus--focus"
:class="computedLinkClasses"
@@ -118,6 +122,7 @@ export default {
class="gl-absolute gl-left-2 gl-top-2 gl-bottom-2 gl-transition-slow"
aria-hidden="true"
style="width: 3px; border-radius: 3px; margin-right: 1px"
+ data-testid="active-indicator"
></div>
<div class="gl-flex-shrink-0 gl-w-6 gl-mx-3">
<slot name="icon">
@@ -162,6 +167,6 @@ export default {
@click.prevent="$emit('pin-remove', item.id)"
/>
</span>
- </a>
+ </component>
</li>
</template>
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item_link.vue b/app/assets/javascripts/super_sidebar/components/nav_item_link.vue
new file mode 100644
index 00000000000..8358e96db94
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/nav_item_link.vue
@@ -0,0 +1,35 @@
+<script>
+import { NAV_ITEM_LINK_ACTIVE_CLASS } from '../constants';
+import { ariaCurrent } from '../utils';
+
+export default {
+ props: {
+ item: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ isActive() {
+ return this.item.is_active;
+ },
+ linkProps() {
+ return {
+ href: this.item.link,
+ 'aria-current': ariaCurrent(this.isActive),
+ };
+ },
+ computedLinkClasses() {
+ return {
+ [NAV_ITEM_LINK_ACTIVE_CLASS]: this.isActive,
+ };
+ },
+ },
+};
+</script>
+
+<template>
+ <a v-bind="linkProps" :class="computedLinkClasses">
+ <slot :is-active="isActive"></slot>
+ </a>
+</template>
diff --git a/app/assets/javascripts/super_sidebar/components/nav_item_router_link.vue b/app/assets/javascripts/super_sidebar/components/nav_item_router_link.vue
new file mode 100644
index 00000000000..78aca24d9a6
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/nav_item_router_link.vue
@@ -0,0 +1,37 @@
+<script>
+import { NAV_ITEM_LINK_ACTIVE_CLASS } from '../constants';
+import { ariaCurrent } from '../utils';
+
+export default {
+ NAV_ITEM_LINK_ACTIVE_CLASS,
+ props: {
+ item: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ linkProps() {
+ return {
+ to: this.item.to,
+ };
+ },
+ },
+ methods: {
+ ariaCurrent,
+ },
+};
+</script>
+
+<template>
+ <router-link
+ #default="{ href, navigate, isActive }"
+ v-bind="linkProps"
+ :active-class="$options.NAV_ITEM_LINK_ACTIVE_CLASS"
+ custom
+ >
+ <a :href="href" :aria-current="ariaCurrent(isActive)" @click="navigate">
+ <slot :is-active="isActive"></slot>
+ </a>
+ </router-link>
+</template>
diff --git a/app/assets/javascripts/super_sidebar/constants.js b/app/assets/javascripts/super_sidebar/constants.js
index 4a5d0bf637f..00ceaebe2cc 100644
--- a/app/assets/javascripts/super_sidebar/constants.js
+++ b/app/assets/javascripts/super_sidebar/constants.js
@@ -50,3 +50,5 @@ export const SIDEBAR_PINS_EXPANDED_COOKIE = 'sidebar_pinned_section_expanded';
export const SIDEBAR_COOKIE_EXPIRATION = 365 * 10;
export const DROPDOWN_Y_OFFSET = 4;
+
+export const NAV_ITEM_LINK_ACTIVE_CLASS = 'gl-bg-t-gray-a-08';
diff --git a/app/assets/javascripts/super_sidebar/utils.js b/app/assets/javascripts/super_sidebar/utils.js
index 4bbef67742c..3b17a35c5bc 100644
--- a/app/assets/javascripts/super_sidebar/utils.js
+++ b/app/assets/javascripts/super_sidebar/utils.js
@@ -83,3 +83,5 @@ export const formatContextSwitcherItems = (items) =>
avatar,
link,
}));
+
+export const ariaCurrent = (isActive) => (isActive ? 'page' : null);