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
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-10-17 21:09:13 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-17 21:09:13 +0300
commit5150ecc452f4cf1c899f79d35d52af978ff2d43f (patch)
treeed36b7982b574d6b4ec5b4e3f68a61a0f7e762d1 /app
parent3884d9d7160e80a70ad327813ada6cab03cded65 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/editor/schema/ci.json7
-rw-r--r--app/assets/javascripts/header_search/components/app.vue19
-rw-r--r--app/assets/javascripts/listbox/index.js70
-rw-r--r--app/assets/javascripts/nav/components/top_nav_app.vue10
-rw-r--r--app/assets/javascripts/pdf/index.vue17
-rw-r--r--app/assets/javascripts/runner/admin_runners/admin_runners_app.vue18
-rw-r--r--app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue11
-rw-r--r--app/assets/javascripts/runner/components/runner_details.vue7
-rw-r--r--app/assets/javascripts/runner/components/runner_list.vue24
-rw-r--r--app/assets/javascripts/runner/graphql/list/local_state.js18
-rw-r--r--app/assets/javascripts/runner/group_runners/group_runners_app.vue9
-rw-r--r--app/assets/javascripts/runner/group_runners/index.js6
-rw-r--r--app/assets/javascripts/work_items/components/work_item_detail.vue63
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/index.js10
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue8
-rw-r--r--app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue30
-rw-r--r--app/assets/javascripts/work_items/constants.js6
-rw-r--r--app/assets/javascripts/work_items/graphql/get_issue_details.query.graphql9
-rw-r--r--app/assets/javascripts/work_items/index.js10
-rw-r--r--app/assets/stylesheets/page_bundles/work_items.scss20
-rw-r--r--app/controllers/admin/runners_controller.rb4
-rw-r--r--app/controllers/groups/runners_controller.rb4
-rw-r--r--app/models/ci/pipeline.rb9
-rw-r--r--app/models/ci/runner.rb6
-rw-r--r--app/models/hooks/web_hook.rb2
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml6
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml3
-rw-r--r--app/views/admin/application_settings/_performance_bar.html.haml2
-rw-r--r--app/views/ci/variables/_url_query_variable_row.html.haml4
-rw-r--r--app/views/ci/variables/_variable_row.html.haml6
-rw-r--r--app/views/clusters/clusters/_health.html.haml2
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml32
-rw-r--r--app/views/layouts/terms.html.haml2
-rw-r--r--app/views/projects/blob/_template_selectors.html.haml12
-rw-r--r--app/views/projects/buttons/_clone.html.haml8
-rw-r--r--app/views/projects/issues/_work_item_links.html.haml2
-rw-r--r--app/views/shared/_clone_panel.html.haml4
-rw-r--r--app/views/shared/web_hooks/_hook_errors.html.haml12
40 files changed, 298 insertions, 198 deletions
diff --git a/app/assets/javascripts/editor/schema/ci.json b/app/assets/javascripts/editor/schema/ci.json
index cd99e6ef64f..e56932a9a31 100644
--- a/app/assets/javascripts/editor/schema/ci.json
+++ b/app/assets/javascripts/editor/schema/ci.json
@@ -103,6 +103,7 @@
"workflow": {
"type": "object",
"properties": {
+ "name": { "$ref": "#/definitions/workflowName" },
"rules": {
"type": "array",
"items": {
@@ -714,6 +715,12 @@
]
}
},
+ "workflowName": {
+ "type": "string",
+ "markdownDescription": "Defines the pipeline name. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#workflowname).",
+ "minLength": 1,
+ "maxLength": 255
+ },
"globalVariables": {
"markdownDescription": "Defines default variables for all jobs. Job level property overrides global variables. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#variables).",
"type": "object",
diff --git a/app/assets/javascripts/header_search/components/app.vue b/app/assets/javascripts/header_search/components/app.vue
index f4b939fb20f..8fc0ce48e61 100644
--- a/app/assets/javascripts/header_search/components/app.vue
+++ b/app/assets/javascripts/header_search/components/app.vue
@@ -14,6 +14,7 @@ import { visitUrl } from '~/lib/utils/url_utility';
import { truncate } from '~/lib/utils/text_utility';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { s__, sprintf } from '~/locale';
+import Tracking from '~/tracking';
import DropdownKeyboardNavigation from '~/vue_shared/components/dropdown_keyboard_navigation.vue';
import {
FIRST_DROPDOWN_INDEX,
@@ -163,8 +164,17 @@ export default {
...mapActions(['setSearch', 'fetchAutocompleteOptions', 'clearAutocomplete']),
openDropdown() {
this.showDropdown = true;
- this.isFocused = true;
- this.$emit('expandSearchBar', true);
+
+ // check isFocused state to avoid firing duplicate events
+ if (!this.isFocused) {
+ this.isFocused = true;
+ this.$emit('expandSearchBar', true);
+
+ Tracking.event(undefined, 'focus_input', {
+ label: 'global_search',
+ property: 'top_navigation',
+ });
+ }
},
closeDropdown() {
this.showDropdown = false;
@@ -178,6 +188,11 @@ export default {
this.showDropdown = false;
this.isFocused = false;
this.$emit('collapseSearchBar');
+
+ Tracking.event(undefined, 'blur_input', {
+ label: 'global_search',
+ property: 'top_navigation',
+ });
}, 200);
},
submitSearch() {
diff --git a/app/assets/javascripts/listbox/index.js b/app/assets/javascripts/listbox/index.js
index 2eeb0a77032..7eacbf7fcdd 100644
--- a/app/assets/javascripts/listbox/index.js
+++ b/app/assets/javascripts/listbox/index.js
@@ -1,4 +1,4 @@
-import { GlDropdown, GlDropdownItem, GlListbox } from '@gitlab/ui';
+import { GlListbox } from '@gitlab/ui';
import Vue from 'vue';
import { parseBoolean } from '~/lib/utils/common_utils';
@@ -31,59 +31,25 @@ export function initListbox(el, { onChange } = {}) {
},
},
render(h) {
- if (gon.features?.glListboxForSortDropdowns) {
- return h(GlListbox, {
- props: {
- items,
- right,
- selected: this.selected,
- toggleText: this.text,
- },
- class: className,
- on: {
- select: (selectedValue) => {
- this.selected = selectedValue;
- const selectedItem = items.find(({ value }) => value === selectedValue);
-
- if (typeof onChange === 'function') {
- onChange(selectedItem);
- }
- },
- },
- });
- }
-
- return h(
- GlDropdown,
- {
- props: {
- text: this.text,
- right,
+ return h(GlListbox, {
+ props: {
+ items,
+ right,
+ selected: this.selected,
+ toggleText: this.text,
+ },
+ class: className,
+ on: {
+ select: (selectedValue) => {
+ this.selected = selectedValue;
+ const selectedItem = items.find(({ value }) => value === selectedValue);
+
+ if (typeof onChange === 'function') {
+ onChange(selectedItem);
+ }
},
- class: className,
},
- items.map((item) =>
- h(
- GlDropdownItem,
- {
- props: {
- isCheckItem: true,
- isChecked: this.selected === item.value,
- },
- on: {
- click: () => {
- this.selected = item.value;
-
- if (typeof onChange === 'function') {
- onChange(item);
- }
- },
- },
- },
- item.text,
- ),
- ),
- );
+ });
},
});
}
diff --git a/app/assets/javascripts/nav/components/top_nav_app.vue b/app/assets/javascripts/nav/components/top_nav_app.vue
index ca6e6567f74..e55bf25a60c 100644
--- a/app/assets/javascripts/nav/components/top_nav_app.vue
+++ b/app/assets/javascripts/nav/components/top_nav_app.vue
@@ -1,5 +1,6 @@
<script>
import { GlNav, GlIcon, GlNavItemDropdown, GlDropdownForm, GlTooltipDirective } from '@gitlab/ui';
+import Tracking from '~/tracking';
import TopNavDropdownMenu from './top_nav_dropdown_menu.vue';
export default {
@@ -19,6 +20,14 @@ export default {
required: true,
},
},
+ methods: {
+ trackToggleEvent() {
+ Tracking.event(undefined, 'click_nav', {
+ label: 'hamburger_menu',
+ property: 'top_navigation',
+ });
+ },
+ },
};
</script>
@@ -32,6 +41,7 @@ export default {
toggle-class="top-nav-toggle js-top-nav-dropdown-toggle gl-px-3!"
no-flip
no-caret
+ @toggle="trackToggleEvent"
>
<template #button-content>
<gl-icon name="hamburger" />
diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue
index ddc880db227..f35f9341fa1 100644
--- a/app/assets/javascripts/pdf/index.vue
+++ b/app/assets/javascripts/pdf/index.vue
@@ -1,9 +1,10 @@
<script>
-import pdfjsLib from 'pdfjs-dist/build/pdf';
-import workerSrc from 'pdfjs-dist/build/pdf.worker.min';
+import { getDocument, GlobalWorkerOptions } from 'pdfjs-dist/legacy/build/pdf';
import Page from './page/index.vue';
+GlobalWorkerOptions.workerSrc = '/assets/webpack/pdfjs/pdf.worker.min.js';
+
export default {
components: { Page },
props: {
@@ -30,18 +31,16 @@ export default {
},
watch: { pdf: 'load' },
mounted() {
- pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
if (this.hasPDF) this.load();
},
methods: {
load() {
this.pages = [];
- return pdfjsLib
- .getDocument({
- url: this.document,
- cMapUrl: '/assets/webpack/cmaps/',
- cMapPacked: true,
- })
+ return getDocument({
+ url: this.document,
+ cMapUrl: '/assets/webpack/pdfjs/cmaps/',
+ cMapPacked: true,
+ })
.promise.then(this.renderPages)
.then((pages) => {
this.pages = pages;
diff --git a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
index 34c44321a9b..dbaabb35cde 100644
--- a/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
+++ b/app/assets/javascripts/runner/admin_runners/admin_runners_app.vue
@@ -17,8 +17,6 @@ import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_cou
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerStackedLayoutBanner from '../components/runner_stacked_layout_banner.vue';
import RunnerFilteredSearchBar from '../components/runner_filtered_search_bar.vue';
-import RunnerBulkDelete from '../components/runner_bulk_delete.vue';
-import RunnerBulkDeleteCheckbox from '../components/runner_bulk_delete_checkbox.vue';
import RunnerList from '../components/runner_list.vue';
import RunnerListEmptyState from '../components/runner_list_empty_state.vue';
import RunnerName from '../components/runner_name.vue';
@@ -45,8 +43,6 @@ export default {
RegistrationDropdown,
RunnerStackedLayoutBanner,
RunnerFilteredSearchBar,
- RunnerBulkDelete,
- RunnerBulkDeleteCheckbox,
RunnerList,
RunnerListEmptyState,
RunnerName,
@@ -56,7 +52,7 @@ export default {
RunnerActionsCell,
},
mixins: [glFeatureFlagMixin()],
- inject: ['emptyStateSvgPath', 'emptyStateFilteredSvgPath', 'localMutations'],
+ inject: ['emptyStateSvgPath', 'emptyStateFilteredSvgPath'],
props: {
registrationToken: {
type: String,
@@ -155,12 +151,6 @@ export default {
reportToSentry(error) {
captureException({ error, component: this.$options.name });
},
- onChecked({ runner, isChecked }) {
- this.localMutations.setRunnerChecked({
- runner,
- isChecked,
- });
- },
onPaginationInput(value) {
this.search.pagination = value;
},
@@ -211,16 +201,12 @@ export default {
:filtered-svg-path="emptyStateFilteredSvgPath"
/>
<template v-else>
- <runner-bulk-delete :runners="runners.items" @deleted="onDeleted" />
<runner-list
:runners="runners.items"
:loading="runnersLoading"
:checkable="true"
- @checked="onChecked"
+ @deleted="onDeleted"
>
- <template #head-checkbox>
- <runner-bulk-delete-checkbox :runners="runners.items" />
- </template>
<template #runner-name="{ runner }">
<gl-link :href="runner.adminUrl">
<runner-name :runner="runner" />
diff --git a/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue b/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue
index c4e7cad9da9..75afb7a00bc 100644
--- a/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue
+++ b/app/assets/javascripts/runner/components/runner_bulk_delete_checkbox.vue
@@ -26,14 +26,17 @@ export default {
},
},
computed: {
+ deletableRunners() {
+ return this.runners.filter((runner) => runner.userPermissions?.deleteRunner);
+ },
disabled() {
- return !this.runners.length;
+ return !this.deletableRunners.length;
},
checked() {
- return Boolean(this.runners.length) && this.runners.every(this.isChecked);
+ return Boolean(this.deletableRunners.length) && this.deletableRunners.every(this.isChecked);
},
indeterminate() {
- return !this.checked && this.runners.some(this.isChecked);
+ return !this.checked && this.deletableRunners.some(this.isChecked);
},
label() {
return this.checked ? s__('Runners|Unselect all') : s__('Runners|Select all');
@@ -45,7 +48,7 @@ export default {
},
onChange($event) {
this.localMutations.setRunnersChecked({
- runners: this.runners,
+ runners: this.deletableRunners,
isChecked: $event,
});
},
diff --git a/app/assets/javascripts/runner/components/runner_details.vue b/app/assets/javascripts/runner/components/runner_details.vue
index 79f934764c6..3d72abcd393 100644
--- a/app/assets/javascripts/runner/components/runner_details.vue
+++ b/app/assets/javascripts/runner/components/runner_details.vue
@@ -4,7 +4,6 @@ import { helpPagePath } from '~/helpers/help_page_helper';
import { s__ } from '~/locale';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
-import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
import { ACCESS_LEVEL_REF_PROTECTED, GROUP_TYPE, PROJECT_TYPE } from '../constants';
import RunnerDetail from './runner_detail.vue';
@@ -29,7 +28,6 @@ export default {
RunnerTags,
TimeAgo,
},
- mixins: [glFeatureFlagMixin()],
props: {
runner: {
type: Object,
@@ -117,10 +115,7 @@ export default {
</template>
</runner-detail>
<runner-detail :label="s__('Runners|Maximum job timeout')" :value="maximumTimeout" />
- <runner-detail
- v-if="glFeatures.enforceRunnerTokenExpiresAt"
- :empty-value="s__('Runners|Never expires')"
- >
+ <runner-detail :empty-value="s__('Runners|Never expires')">
<template #label>
{{ s__('Runners|Token expiry') }}
<help-popover :options="tokenExpirationHelpPopoverOptions">
diff --git a/app/assets/javascripts/runner/components/runner_list.vue b/app/assets/javascripts/runner/components/runner_list.vue
index d4a3311ff9f..e895537dcdc 100644
--- a/app/assets/javascripts/runner/components/runner_list.vue
+++ b/app/assets/javascripts/runner/components/runner_list.vue
@@ -5,6 +5,8 @@ import { s__ } from '~/locale';
import HelpPopover from '~/vue_shared/components/help_popover.vue';
import checkedRunnerIdsQuery from '../graphql/list/checked_runner_ids.query.graphql';
import { formatJobCount, tableField } from '../utils';
+import RunnerBulkDelete from './runner_bulk_delete.vue';
+import RunnerBulkDeleteCheckbox from './runner_bulk_delete_checkbox.vue';
import RunnerStackedSummaryCell from './cells/runner_stacked_summary_cell.vue';
import RunnerStatusPopover from './runner_status_popover.vue';
import RunnerStatusCell from './cells/runner_status_cell.vue';
@@ -23,6 +25,8 @@ export default {
GlTableLite,
GlSkeletonLoader,
HelpPopover,
+ RunnerBulkDelete,
+ RunnerBulkDeleteCheckbox,
RunnerStatusPopover,
RunnerStackedSummaryCell,
RunnerStatusCell,
@@ -39,6 +43,7 @@ export default {
},
},
},
+ inject: ['localMutations'],
props: {
checkable: {
type: Boolean,
@@ -55,7 +60,7 @@ export default {
required: true,
},
},
- emits: ['checked'],
+ emits: ['deleted'],
data() {
return { checkedRunnerIds: [] };
},
@@ -84,6 +89,12 @@ export default {
},
},
methods: {
+ canDelete(runner) {
+ return runner.userPermissions?.deleteRunner;
+ },
+ onDeleted(event) {
+ this.$emit('deleted', event);
+ },
formatJobCount(jobCount) {
return formatJobCount(jobCount);
},
@@ -96,7 +107,7 @@ export default {
return {};
},
onCheckboxChange(runner, isChecked) {
- this.$emit('checked', {
+ this.localMutations.setRunnerChecked({
runner,
isChecked,
});
@@ -109,6 +120,7 @@ export default {
</script>
<template>
<div>
+ <runner-bulk-delete v-if="checkable" :runners="runners" @deleted="onDeleted" />
<gl-table-lite
:aria-busy="loading"
:class="tableClass"
@@ -121,11 +133,15 @@ export default {
fixed
>
<template #head(checkbox)>
- <slot name="head-checkbox"></slot>
+ <runner-bulk-delete-checkbox :runners="runners" />
</template>
<template #cell(checkbox)="{ item }">
- <gl-form-checkbox :checked="isChecked(item)" @change="onCheckboxChange(item, $event)" />
+ <gl-form-checkbox
+ v-if="canDelete(item)"
+ :checked="isChecked(item)"
+ @change="onCheckboxChange(item, $event)"
+ />
</template>
<template #head(status)="{ label }">
diff --git a/app/assets/javascripts/runner/graphql/list/local_state.js b/app/assets/javascripts/runner/graphql/list/local_state.js
index 4e1625cb1ac..e0477c660b4 100644
--- a/app/assets/javascripts/runner/graphql/list/local_state.js
+++ b/app/assets/javascripts/runner/graphql/list/local_state.js
@@ -48,16 +48,18 @@ export const createLocalState = () => {
const localMutations = {
setRunnerChecked({ runner, isChecked }) {
- checkedRunnerIdsVar({
- ...checkedRunnerIdsVar(),
- [runner.id]: isChecked,
- });
+ const { id, userPermissions } = runner;
+ if (userPermissions?.deleteRunner) {
+ checkedRunnerIdsVar({
+ ...checkedRunnerIdsVar(),
+ [id]: isChecked,
+ });
+ }
},
setRunnersChecked({ runners, isChecked }) {
- const newVal = runners.reduce(
- (acc, { id }) => ({ ...acc, [id]: isChecked }),
- checkedRunnerIdsVar(),
- );
+ const newVal = runners
+ .filter(({ userPermissions }) => userPermissions?.deleteRunner)
+ .reduce((acc, { id }) => ({ ...acc, [id]: isChecked }), checkedRunnerIdsVar());
checkedRunnerIdsVar(newVal);
},
clearChecked() {
diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
index bc943c03a62..7f56d895682 100644
--- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
@@ -12,6 +12,7 @@ import {
} from 'ee_else_ce/runner/runner_search_utils';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import groupRunnersQuery from 'ee_else_ce/runner/graphql/list/group_runners.query.graphql';
+import groupRunnersCountQuery from 'ee_else_ce/runner/graphql/list/group_runners_count.query.graphql';
import RegistrationDropdown from '../components/registration/registration_dropdown.vue';
import RunnerStackedLayoutBanner from '../components/runner_stacked_layout_banner.vue';
@@ -173,13 +174,17 @@ export default {
editUrl(runner) {
return this.runners.urlsById[runner.id]?.edit;
},
+ refetchCounts() {
+ this.$apollo.getClient().refetchQueries({ include: [groupRunnersCountQuery] });
+ },
onToggledPaused() {
// When a runner becomes Paused, the tab count can
// become stale, refetch outdated counts.
- this.$refs['runner-type-tabs'].refetch();
+ this.refetchCounts();
},
onDeleted({ message }) {
this.$root.$toast?.show(message);
+ this.refetchCounts();
},
reportToSentry(error) {
captureException({ error, component: this.$options.name });
@@ -245,7 +250,7 @@ export default {
:filtered-svg-path="emptyStateFilteredSvgPath"
/>
<template v-else>
- <runner-list :runners="runners.items" :loading="runnersLoading">
+ <runner-list :runners="runners.items" :loading="runnersLoading" @deleted="onDeleted">
<template #runner-name="{ runner }">
<gl-link :href="webUrl(runner)">
<runner-name :runner="runner" />
diff --git a/app/assets/javascripts/runner/group_runners/index.js b/app/assets/javascripts/runner/group_runners/index.js
index feed6b0ceb7..0e7efd2b8a1 100644
--- a/app/assets/javascripts/runner/group_runners/index.js
+++ b/app/assets/javascripts/runner/group_runners/index.js
@@ -2,6 +2,7 @@ import { GlToast } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql';
+import { createLocalState } from '../graphql/list/local_state';
import GroupRunnersApp from './group_runners_app.vue';
Vue.use(GlToast);
@@ -26,8 +27,10 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
emptyStateFilteredSvgPath,
} = el.dataset;
+ const { cacheConfig, typeDefs, localMutations } = createLocalState();
+
const apolloProvider = new VueApollo({
- defaultClient: createDefaultClient(),
+ defaultClient: createDefaultClient({}, { cacheConfig, typeDefs }),
});
return new Vue({
@@ -35,6 +38,7 @@ export const initGroupRunners = (selector = '#js-group-runners') => {
apolloProvider,
provide: {
runnerInstallHelpPage,
+ localMutations,
groupId,
onlineContactTimeoutSecs: parseInt(onlineContactTimeoutSecs, 10),
staleTimeoutSecs: parseInt(staleTimeoutSecs, 10),
diff --git a/app/assets/javascripts/work_items/components/work_item_detail.vue b/app/assets/javascripts/work_items/components/work_item_detail.vue
index 3255c7e5e4d..ccb58706fe0 100644
--- a/app/assets/javascripts/work_items/components/work_item_detail.vue
+++ b/app/assets/javascripts/work_items/components/work_item_detail.vue
@@ -23,6 +23,7 @@ import {
WIDGET_TYPE_WEIGHT,
WIDGET_TYPE_HIERARCHY,
WORK_ITEM_VIEWED_STORAGE_KEY,
+ WIDGET_TYPE_ITERATION,
} from '../constants';
import workItemQuery from '../graphql/work_item.query.graphql';
@@ -65,6 +66,7 @@ export default {
WorkItemInformation,
LocalStorageSync,
WorkItemTypeIcon,
+ WorkItemIteration: () => import('ee_component/work_items/components/work_item_iteration.vue'),
},
mixins: [glFeatureFlagMixin()],
props: {
@@ -134,7 +136,7 @@ export default {
};
},
skip() {
- return !this.workItemDueDate;
+ return !this.isWidgetPresent(WIDGET_TYPE_START_AND_DUE_DATE);
},
},
{
@@ -145,7 +147,7 @@ export default {
};
},
skip() {
- return !this.workItemAssignees;
+ return !this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES);
},
},
],
@@ -170,28 +172,8 @@ export default {
workItemsMvc2Enabled() {
return this.glFeatures.workItemsMvc2;
},
- hasDescriptionWidget() {
- return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_DESCRIPTION);
- },
- workItemAssignees() {
- return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_ASSIGNEES);
- },
- workItemLabels() {
- return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_LABELS);
- },
- workItemDueDate() {
- return this.workItem?.widgets?.find(
- (widget) => widget.type === WIDGET_TYPE_START_AND_DUE_DATE,
- );
- },
- workItemWeight() {
- return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_WEIGHT);
- },
- workItemHierarchy() {
- return this.workItem?.widgets?.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY);
- },
parentWorkItem() {
- return this.workItemHierarchy?.parent;
+ return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY)?.parent;
},
parentWorkItemConfidentiality() {
return this.parentWorkItem?.confidential;
@@ -205,6 +187,27 @@ export default {
noAccessSvgPath() {
return `data:image/svg+xml;utf8,${encodeURIComponent(noAccessSvg)}`;
},
+ hasDescriptionWidget() {
+ return this.isWidgetPresent(WIDGET_TYPE_DESCRIPTION);
+ },
+ workItemAssignees() {
+ return this.isWidgetPresent(WIDGET_TYPE_ASSIGNEES);
+ },
+ workItemLabels() {
+ return this.isWidgetPresent(WIDGET_TYPE_LABELS);
+ },
+ workItemDueDate() {
+ return this.isWidgetPresent(WIDGET_TYPE_START_AND_DUE_DATE);
+ },
+ workItemWeight() {
+ return this.isWidgetPresent(WIDGET_TYPE_WEIGHT);
+ },
+ workItemHierarchy() {
+ return this.isWidgetPresent(WIDGET_TYPE_HIERARCHY);
+ },
+ workItemIteration() {
+ return this.isWidgetPresent(WIDGET_TYPE_ITERATION);
+ },
},
beforeDestroy() {
/** make sure that if the user has not even dismissed the alert ,
@@ -212,6 +215,9 @@ export default {
this.dismissBanner();
},
methods: {
+ isWidgetPresent(type) {
+ return this.workItem?.widgets?.find((widget) => widget.type === type);
+ },
dismissBanner() {
this.showInfoBanner = false;
},
@@ -416,6 +422,17 @@ export default {
:work-item-type="workItemType"
@error="updateError = $event"
/>
+ <template v-if="workItemsMvc2Enabled">
+ <work-item-iteration
+ v-if="workItemIteration"
+ class="gl-mb-5"
+ :iteration="workItemIteration.iteration"
+ :can-update="canUpdate"
+ :work-item-id="workItem.id"
+ :work-item-type="workItemType"
+ @error="updateError = $event"
+ />
+ </template>
<work-item-description
v-if="hasDescriptionWidget"
:work-item-id="workItem.id"
diff --git a/app/assets/javascripts/work_items/components/work_item_links/index.js b/app/assets/javascripts/work_items/components/work_item_links/index.js
index 8f31b07b6a3..37aa48be6e5 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/index.js
+++ b/app/assets/javascripts/work_items/components/work_item_links/index.js
@@ -16,7 +16,13 @@ export default function initWorkItemLinks() {
return;
}
- const { projectPath, wiHasIssueWeightsFeature, iid } = workItemLinksRoot.dataset;
+ const {
+ projectPath,
+ wiHasIssueWeightsFeature,
+ iid,
+ wiHasIterationsFeature,
+ projectNamespace,
+ } = workItemLinksRoot.dataset;
// eslint-disable-next-line no-new
new Vue({
@@ -31,6 +37,8 @@ export default function initWorkItemLinks() {
iid,
fullPath: projectPath,
hasIssueWeightsFeature: wiHasIssueWeightsFeature,
+ hasIterationsFeature: wiHasIterationsFeature,
+ projectNamespace,
},
render: (createElement) =>
createElement('work-item-links', {
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
index 827ec64f98a..0d3e951de7e 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links.vue
@@ -5,7 +5,7 @@ import { s__ } from '~/locale';
import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { TYPE_WORK_ITEM } from '~/graphql_shared/constants';
-import issueConfidentialQuery from '~/sidebar/queries/issue_confidential.query.graphql';
+import getIssueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_details.query.graphql';
import { isMetaKey } from '~/lib/utils/common_utils';
import { setUrlParams, updateHistory } from '~/lib/utils/url_utility';
@@ -59,7 +59,7 @@ export default {
},
},
parentIssue: {
- query: issueConfidentialQuery,
+ query: getIssueDetailsQuery,
variables() {
return {
fullPath: this.projectPath,
@@ -86,6 +86,9 @@ export default {
confidential() {
return this.parentIssue?.confidential || this.workItem?.confidential || false;
},
+ issuableIteration() {
+ return this.parentIssue?.iteration;
+ },
children() {
return (
this.workItem?.widgets.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY)?.children
@@ -305,6 +308,7 @@ export default {
:issuable-gid="issuableGid"
:children-ids="childrenIds"
:parent-confidential="confidential"
+ :parent-iteration="issuableIteration"
@cancel="hideAddForm"
@addWorkItemChild="addChild"
/>
diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
index eafd9ee88dd..a01f4616cab 100644
--- a/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
+++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_links_form.vue
@@ -16,7 +16,7 @@ export default {
GlFormGroup,
GlFormInput,
},
- inject: ['projectPath'],
+ inject: ['projectPath', 'hasIterationsFeature'],
props: {
issuableGid: {
type: String,
@@ -33,6 +33,11 @@ export default {
required: false,
default: false,
},
+ parentIteration: {
+ type: Object,
+ required: false,
+ default: () => {},
+ },
},
apollo: {
workItemTypes: {
@@ -77,6 +82,9 @@ export default {
taskWorkItemType() {
return this.workItemTypes.find((type) => type.name === TASK_TYPE_NAME)?.id;
},
+ parentIterationId() {
+ return this.parentIteration?.id;
+ },
},
methods: {
getIdFromGraphQLId,
@@ -133,6 +141,13 @@ export default {
} else {
this.unsetError();
this.$emit('addWorkItemChild', data.workItemCreate.workItem);
+ /**
+ * call update mutation only when there is an iteration associated with the issue
+ */
+ // TODO: setting the iteration should be moved to the creation mutation once the backend is done
+ if (this.parentIterationId && this.hasIterationsFeature) {
+ this.addIterationToWorkItem(data.workItemCreate.workItem.id);
+ }
}
})
.catch(() => {
@@ -143,6 +158,19 @@ export default {
this.childToCreateTitle = null;
});
},
+ async addIterationToWorkItem(workItemId) {
+ await this.$apollo.mutate({
+ mutation: updateWorkItemMutation,
+ variables: {
+ input: {
+ id: workItemId,
+ iterationWidget: {
+ iterationId: this.parentIterationId,
+ },
+ },
+ },
+ });
+ },
},
i18n: {
inputLabel: __('Title'),
diff --git a/app/assets/javascripts/work_items/constants.js b/app/assets/javascripts/work_items/constants.js
index 97c445de711..0d426299408 100644
--- a/app/assets/javascripts/work_items/constants.js
+++ b/app/assets/javascripts/work_items/constants.js
@@ -17,6 +17,8 @@ export const WIDGET_TYPE_LABELS = 'LABELS';
export const WIDGET_TYPE_START_AND_DUE_DATE = 'START_AND_DUE_DATE';
export const WIDGET_TYPE_WEIGHT = 'WEIGHT';
export const WIDGET_TYPE_HIERARCHY = 'HIERARCHY';
+export const WIDGET_TYPE_ITERATION = 'ITERATION';
+
export const WORK_ITEM_VIEWED_STORAGE_KEY = 'gl-show-work-item-banner';
export const WORK_ITEM_TYPE_ENUM_INCIDENT = 'INCIDENT';
@@ -54,6 +56,10 @@ export const I18N_WORK_ITEM_ARE_YOU_SURE_DELETE = s__(
);
export const I18N_WORK_ITEM_DELETED = s__('WorkItem|%{workItemType} deleted');
+export const I18N_WORK_ITEM_FETCH_ITERATIONS_ERROR = s__(
+ 'WorkItem|Something went wrong when fetching iterations. Please try again.',
+);
+
export const sprintfWorkItem = (msg, workItemTypeArg) => {
const workItemType = workItemTypeArg || s__('WorkItem|Work item');
return capitalizeFirstCharacter(
diff --git a/app/assets/javascripts/work_items/graphql/get_issue_details.query.graphql b/app/assets/javascripts/work_items/graphql/get_issue_details.query.graphql
new file mode 100644
index 00000000000..6edb6c89f16
--- /dev/null
+++ b/app/assets/javascripts/work_items/graphql/get_issue_details.query.graphql
@@ -0,0 +1,9 @@
+query issuableDetails($fullPath: ID!, $iid: String) {
+ workspace: project(fullPath: $fullPath) {
+ id
+ issuable: issue(iid: $iid) {
+ id
+ confidential
+ }
+ }
+}
diff --git a/app/assets/javascripts/work_items/index.js b/app/assets/javascripts/work_items/index.js
index bb4c7052238..f872d8c6b12 100644
--- a/app/assets/javascripts/work_items/index.js
+++ b/app/assets/javascripts/work_items/index.js
@@ -6,7 +6,13 @@ import { createRouter } from './router';
export const initWorkItemsRoot = () => {
const el = document.querySelector('#js-work-items');
- const { fullPath, hasIssueWeightsFeature, issuesListPath } = el.dataset;
+ const {
+ fullPath,
+ hasIssueWeightsFeature,
+ issuesListPath,
+ projectNamespace,
+ hasIterationsFeature,
+ } = el.dataset;
return new Vue({
el,
@@ -17,6 +23,8 @@ export const initWorkItemsRoot = () => {
fullPath,
hasIssueWeightsFeature: parseBoolean(hasIssueWeightsFeature),
issuesListPath,
+ projectNamespace,
+ hasIterationsFeature: parseBoolean(hasIterationsFeature),
},
render(createElement) {
return createElement(App);
diff --git a/app/assets/stylesheets/page_bundles/work_items.scss b/app/assets/stylesheets/page_bundles/work_items.scss
index d0fc011dde7..7a5cc72ceb8 100644
--- a/app/assets/stylesheets/page_bundles/work_items.scss
+++ b/app/assets/stylesheets/page_bundles/work_items.scss
@@ -63,3 +63,23 @@
display: none;
}
}
+
+.work-item-iteration {
+ .gl-dropdown-toggle {
+ background: none !important;
+
+ &:hover,
+ &:focus {
+ box-shadow: inset 0 0 0 $gl-border-size-1 var(--gray-darkest, $gray-darkest) !important;
+ }
+
+ &.is-not-focused:not(:hover, :focus) {
+ box-shadow: none;
+
+ .gl-button-icon {
+ display: none;
+ }
+ }
+ }
+}
+
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 2e0807cdab4..96fe0c9331d 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -5,10 +5,6 @@ class Admin::RunnersController < Admin::ApplicationController
before_action :runner, except: [:index, :tag_list, :runner_setup_scripts]
- before_action only: [:show] do
- push_frontend_feature_flag(:enforce_runner_token_expires_at)
- end
-
feature_category :runner
urgency :low
diff --git a/app/controllers/groups/runners_controller.rb b/app/controllers/groups/runners_controller.rb
index 01c1529e831..18b055b3f05 100644
--- a/app/controllers/groups/runners_controller.rb
+++ b/app/controllers/groups/runners_controller.rb
@@ -5,10 +5,6 @@ class Groups::RunnersController < Groups::ApplicationController
before_action :authorize_update_runner!, only: [:edit, :update, :destroy, :pause, :resume]
before_action :runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
- before_action only: [:show] do
- push_frontend_feature_flag(:enforce_runner_token_expires_at)
- end
-
feature_category :runner
urgency :low
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 853437b2d27..4287c0b7884 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -617,6 +617,15 @@ module Ci
# auto_canceled_by_pipeline_id - store the pipeline_id of the pipeline that triggered cancellation
# execute_async - if true cancel the children asyncronously
def cancel_running(retries: 1, cascade_to_children: true, auto_canceled_by_pipeline_id: nil, execute_async: true)
+ Gitlab::AppJsonLogger.info(
+ event: 'pipeline_cancel_running',
+ pipeline_id: id,
+ auto_canceled_by_pipeline_id: auto_canceled_by_pipeline_id,
+ cascade_to_children: cascade_to_children,
+ execute_async: execute_async,
+ **Gitlab::ApplicationContext.current
+ )
+
update(auto_canceled_by_id: auto_canceled_by_pipeline_id) if auto_canceled_by_pipeline_id
cancel_jobs(cancelable_statuses, retries: retries, auto_canceled_by_pipeline_id: auto_canceled_by_pipeline_id)
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 2bd26e15953..3be627989b1 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -14,7 +14,7 @@ module Ci
include Presentable
include EachBatch
- add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration, expiration_enforced?: :token_expiration_enforced?
+ add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration
enum access_level: {
not_protected: 0,
@@ -480,10 +480,6 @@ module Ci
end
end
- def self.token_expiration_enforced?
- Feature.enabled?(:enforce_runner_token_expires_at)
- end
-
private
scope :with_upgrade_status, ->(upgrade_status) do
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index f7f29b3e82e..ed04b0c3d1f 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -7,7 +7,7 @@ class WebHook < ApplicationRecord
MAX_FAILURES = 100
FAILURE_THRESHOLD = 3 # three strikes
- INITIAL_BACKOFF = 10.minutes
+ INITIAL_BACKOFF = 1.minute
MAX_BACKOFF = 1.day
BACKOFF_GROWTH_FACTOR = 2.0
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index e676dae37b3..b08a549148d 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -17,14 +17,14 @@
.form-group
= f.label :receive_max_input_size, _('Maximum push size (MB)'), class: 'label-light'
- = f.number_field :receive_max_input_size, class: 'form-control gl-form-input qa-receive-max-input-size-field', title: _('Maximum size limit for a single commit.'), data: { toggle: 'tooltip', container: 'body' }
+ = f.number_field :receive_max_input_size, class: 'form-control gl-form-input', title: _('Maximum size limit for a single commit.'), data: { toggle: 'tooltip', container: 'body', qa_selector: 'receive_max_input_size_field' }
.form-group
= f.label :max_export_size, _('Maximum export size (MB)'), class: 'label-light'
= f.number_field :max_export_size, class: 'form-control gl-form-input', title: _('Maximum size of export files.'), data: { toggle: 'tooltip', container: 'body' }
%span.form-text.text-muted= _('Set to 0 for no size limit.')
.form-group
= f.label :max_import_size, _('Maximum import size (MB)'), class: 'label-light'
- = f.number_field :max_import_size, class: 'form-control gl-form-input qa-receive-max-import-size-field', title: _('Maximum size of import files.'), data: { toggle: 'tooltip', container: 'body' }
+ = f.number_field :max_import_size, class: 'form-control gl-form-input', title: _('Maximum size of import files.'), data: { toggle: 'tooltip', container: 'body' }
%span.form-text.text-muted= _('Only effective when remote storage is enabled. Set to 0 for no size limit.')
.form-group
= f.label :session_expire_delay, _('Session duration (minutes)'), class: 'label-light'
@@ -69,4 +69,4 @@
= render 'admin/application_settings/invitation_flow_enforcement', form: f
= render 'admin/application_settings/user_restrictions', form: f
= render_if_exists 'admin/application_settings/availability_on_namespace_setting', form: f
- = f.submit _('Save changes'), class: 'qa-save-changes-button', pajamas_button: true
+ = f.submit _('Save changes'), pajamas_button: true, data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index 05aea2b343d..f6635ad17ef 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -53,8 +53,7 @@
= link_to sprite_icon('question-o'), help_page_path('ci/pipelines/settings', anchor: 'specify-a-custom-cicd-configuration-file'), target: '_blank', rel: 'noopener noreferrer'
.form-group
= f.gitlab_ui_checkbox_component :suggest_pipeline_enabled, s_('AdminSettings|Enable pipeline suggestion banner'), help_text: s_('AdminSettings|Display a banner on merge requests in projects with no pipelines to initiate steps to add a .gitlab-ci.yml file.')
- - if Feature.enabled?(:enforce_runner_token_expires_at)
- #js-runner-token-expiration-intervals{ data: runner_token_expiration_interval_attributes }
+ #js-runner-token-expiration-intervals{ data: runner_token_expiration_interval_attributes }
= f.submit _('Save changes'), pajamas_button: true
diff --git a/app/views/admin/application_settings/_performance_bar.html.haml b/app/views/admin/application_settings/_performance_bar.html.haml
index 823bc0380af..d4f6d84ea74 100644
--- a/app/views/admin/application_settings/_performance_bar.html.haml
+++ b/app/views/admin/application_settings/_performance_bar.html.haml
@@ -10,4 +10,4 @@
= f.label :performance_bar_allowed_group_path, _('Allow access to members of the following group'), class: 'label-bold'
= f.text_field :performance_bar_allowed_group_path, class: 'form-control gl-form-input', placeholder: 'my-org/my-group', value: @application_setting.performance_bar_allowed_group&.full_path
- = f.submit _('Save changes'), class: 'qa-save-changes-button', pajamas_button: true
+ = f.submit _('Save changes'), pajamas_button: true, data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/ci/variables/_url_query_variable_row.html.haml b/app/views/ci/variables/_url_query_variable_row.html.haml
index 9c34daf88bd..77bcacdb94b 100644
--- a/app/views/ci/variables/_url_query_variable_row.html.haml
+++ b/app/views/ci/variables/_url_query_variable_row.html.haml
@@ -15,12 +15,12 @@
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
%select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control.custom-select.table-section.section-15{ name: variable_type_input_name }
= options_for_select(ci_variable_type_options, variable_type)
- %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control.table-section.section-15{ type: "text",
+ %input.js-ci-variable-input-key.ci-variable-body-item.form-control.table-section.section-15{ type: "text",
name: key_input_name,
value: key,
placeholder: s_('CiVariables|Input variable key') }
.ci-variable-body-item.gl-show-field-errors.table-section.section-15.border-top-0.p-0
- %textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control{ rows: 1,
+ %textarea.js-ci-variable-input-value.js-secret-value.form-control{ rows: 1,
name: value_input_name,
placeholder: s_('CiVariables|Input variable value') }
= value
diff --git a/app/views/ci/variables/_variable_row.html.haml b/app/views/ci/variables/_variable_row.html.haml
index 483c767d029..c5e518d8526 100644
--- a/app/views/ci/variables/_variable_row.html.haml
+++ b/app/views/ci/variables/_variable_row.html.haml
@@ -18,14 +18,14 @@
%input.js-ci-variable-input-destroy{ type: "hidden", name: destroy_input_name }
%select.js-ci-variable-input-variable-type.ci-variable-body-item.form-control.select-control.custom-select.table-section.section-15{ name: variable_type_input_name }
= options_for_select(ci_variable_type_options, variable_type)
- %input.js-ci-variable-input-key.ci-variable-body-item.qa-ci-variable-input-key.form-control.gl-form-input.table-section.section-15{ type: "text",
+ %input.js-ci-variable-input-key.ci-variable-body-item.form-control.gl-form-input.table-section.section-15{ type: "text",
name: key_input_name,
value: key,
placeholder: s_('CiVariables|Input variable key') }
.ci-variable-body-item.gl-show-field-errors.table-section.section-15.border-top-0.p-0
- .form-control.js-secret-value-placeholder.qa-ci-variable-input-value.overflow-hidden{ class: ('hide' unless id) }
+ .form-control.js-secret-value-placeholder.overflow-hidden{ class: ('hide' unless id) }
= '*' * 17
- %textarea.js-ci-variable-input-value.js-secret-value.qa-ci-variable-input-value.form-control.gl-form-input{ class: ('hide' if id),
+ %textarea.js-ci-variable-input-value.js-secret-value.form-control.gl-form-input{ class: ('hide' if id),
rows: 1,
name: value_input_name,
placeholder: s_('CiVariables|Input variable value') }
diff --git a/app/views/clusters/clusters/_health.html.haml b/app/views/clusters/clusters/_health.html.haml
index 50facdf91a2..9e7820d3136 100644
--- a/app/views/clusters/clusters/_health.html.haml
+++ b/app/views/clusters/clusters/_health.html.haml
@@ -1,6 +1,6 @@
- add_page_specific_style 'page_bundles/prometheus'
-%section.settings.no-animate.expanded.cluster-health-graphs.qa-cluster-health-section#cluster-health
+%section.settings.no-animate.expanded.cluster-health-graphs#cluster-health
- if @cluster&.integration_prometheus_available?
#prometheus-graphs{ data: @cluster.health_data(clusterable) }
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index bf7b24181c1..557c95f8478 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -36,7 +36,7 @@
= platform_kubernetes_field.form_group :authorization_type,
{ help: '%{help_text} %{help_link}'.html_safe % { help_text: rbac_help_text, help_link: rbac_help_link } } do
= platform_kubernetes_field.check_box :authorization_type,
- { class: 'qa-rbac-checkbox', label: s_('ClusterIntegration|RBAC-enabled cluster'),
+ { data: { qa_selector: 'rbac_checkbox'}, label: s_('ClusterIntegration|RBAC-enabled cluster'),
label_class: 'label-bold', inline: true }, 'rbac', 'abac'
.form-group
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 800dcef5b72..b74dfd4d3a1 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -132,7 +132,7 @@
- if header_link?(:user_dropdown)
%li.nav-item.header-user.js-nav-user-dropdown.dropdown{ data: { track_label: "profile_dropdown", track_action: "click_dropdown", track_value: "", qa_selector: 'user_menu', testid: 'user-menu' }, class: ('mr-0' if has_impersonation_link) }
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
- = render Pajamas::AvatarComponent.new(current_user, size: 24, class: 'header-user-avatar qa-user-avatar')
+ = render Pajamas::AvatarComponent.new(current_user, size: 24, class: 'header-user-avatar', avatar_options: { data: { qa_selector: 'user_avatar_content' } })
= render_if_exists 'layouts/header/user_notification_dot', project: project, namespace: group
= sprite_icon('chevron-down', css_class: 'caret-down')
.dropdown-menu.dropdown-menu-right
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 56f333664df..8815dec5a6b 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -1,4 +1,4 @@
-%aside.nav-sidebar.qa-admin-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), 'aria-label': _('Admin navigation') }
+%aside.nav-sidebar{ class: ("sidebar-collapsed-desktop" if collapsed_sidebar?), 'aria-label': _('Admin navigation'), data: { qa_selector: 'admin_sidebar_content' } }
.nav-sidebar-inner-scroll
.context-header
= link_to admin_root_path, title: _('Admin Overview'), class: 'has-tooltip', data: { container: 'body', placement: 'right' } do
@@ -6,7 +6,7 @@
= sprite_icon('admin', size: 18)
%span.sidebar-context-title
= _('Admin Area')
- %ul.sidebar-top-level-items{ data: { qa_selector: 'admin_sidebar_overview_submenu_content' } }
+ %ul.sidebar-top-level-items{ data: { qa_selector: 'admin_overview_submenu_content' } }
= nav_link(controller: %w[dashboard admin admin/projects users groups admin/topics jobs runners gitaly_servers cohorts], html_options: {class: 'home'}) do
= link_to admin_root_path, class: 'has-sub-items' do
.nav-icon-container
@@ -28,15 +28,15 @@
%span
= _('Projects')
= nav_link(controller: %w[users cohorts]) do
- = link_to admin_users_path, title: _('Users'), data: { qa_selector: 'users_overview_link' } do
+ = link_to admin_users_path, title: _('Users'), data: { qa_selector: 'admin_overview_users_link' } do
%span
= _('Users')
= nav_link(controller: :groups) do
- = link_to admin_groups_path, title: _('Groups'), data: { qa_selector: 'groups_overview_link' } do
+ = link_to admin_groups_path, title: _('Groups'), data: { qa_selector: 'admin_overview_groups_link' } do
%span
= _('Groups')
= nav_link(controller: [:admin, 'admin/topics']) do
- = link_to admin_topics_path, title: _('Topics'), data: { qa_selector: 'topics_overview_link' } do
+ = link_to admin_topics_path, title: _('Topics') do
%span
= _('Topics')
= nav_link path: 'jobs#index' do
@@ -75,13 +75,13 @@
= _('Usage Trends')
= nav_link(controller: admin_monitoring_nav_links) do
- = link_to admin_system_info_path, data: { qa_selector: 'admin_monitoring_link' }, class: 'has-sub-items' do
+ = link_to admin_system_info_path, data: { qa_selector: 'admin_monitoring_menu_link' }, class: 'has-sub-items' do
.nav-icon-container
= sprite_icon('monitor')
%span.nav-item-name
= _('Monitoring')
- %ul.sidebar-sub-level-items{ data: { qa_selector: 'admin_sidebar_monitoring_submenu_content' } }
+ %ul.sidebar-sub-level-items{ data: { qa_selector: 'admin_monitoring_submenu_content' } }
= nav_link(controller: admin_monitoring_nav_links, html_options: { class: "fly-out-top-item" } ) do
= link_to admin_system_info_path do
%strong.fly-out-top-item-name
@@ -222,10 +222,10 @@
= link_to general_admin_application_settings_path, class: 'has-sub-items' do
.nav-icon-container
= sprite_icon('settings')
- %span.nav-item-name.qa-admin-settings-item
+ %span.nav-item-name{ data: { qa_selector: 'admin_settings_menu_link' } }
= _('Settings')
- %ul.sidebar-sub-level-items{ data: { qa_selector: 'admin_sidebar_settings_submenu_content' } }
+ %ul.sidebar-sub-level-items{ data: { qa_selector: 'admin_settings_submenu_content' } }
-# This active_nav_link check is also used in `app/views/layouts/admin.html.haml`
= nav_link(controller: [:application_settings, :integrations, :appearances], html_options: { class: "fly-out-top-item" } ) do
= link_to general_admin_application_settings_path do
@@ -233,24 +233,24 @@
= _('Settings')
%li.divider.fly-out-top-item
= nav_link(path: 'application_settings#general') do
- = link_to general_admin_application_settings_path, title: _('General'), class: 'qa-admin-settings-general-item' do
+ = link_to general_admin_application_settings_path, title: _('General'), data: { qa_selector: 'admin_settings_general_link' } do
%span
= _('General')
- = render_if_exists 'layouts/nav/sidebar/advanced_search', class: 'qa-admin-settings-advanced-search'
+ = render_if_exists 'layouts/nav/sidebar/advanced_search', data: { qa_selector: 'admin_settings_advanced_search_link' }
- if instance_level_integrations?
= nav_link(path: ['application_settings#integrations', 'integrations#edit']) do
- = link_to integrations_admin_application_settings_path, title: _('Integrations'), data: { qa_selector: 'integration_settings_link' } do
+ = link_to integrations_admin_application_settings_path, title: _('Integrations'), data: { qa_selector: 'admin_settings_integrations_link' } do
%span
= _('Integrations')
= nav_link(path: 'application_settings#repository') do
- = link_to repository_admin_application_settings_path, title: _('Repository'), class: 'qa-admin-settings-repository-item' do
+ = link_to repository_admin_application_settings_path, title: _('Repository'), data: { qa_selector: 'admin_settings_repository_link' } do
%span
= _('Repository')
- if Gitlab.ee? && License.feature_available?(:custom_file_templates)
= nav_link(path: 'application_settings#templates') do
- = link_to templates_admin_application_settings_path, title: _('Templates'), class: 'qa-admin-settings-template-item' do
+ = link_to templates_admin_application_settings_path, title: _('Templates'), data: { qa_selector: 'admin_settings_templates_link' } do
%span
= _('Templates')
= nav_link(path: 'application_settings#ci_cd') do
@@ -262,7 +262,7 @@
%span
= _('Reporting')
= nav_link(path: 'application_settings#metrics_and_profiling') do
- = link_to metrics_and_profiling_admin_application_settings_path, title: _('Metrics and profiling'), class: 'qa-admin-settings-metrics-and-profiling-item' do
+ = link_to metrics_and_profiling_admin_application_settings_path, title: _('Metrics and profiling'), data: { qa_selector: 'admin_settings_metrics_and_profiling_link' } do
%span
= _('Metrics and profiling')
= nav_link(path: ['application_settings#service_usage_data']) do
@@ -270,7 +270,7 @@
%span
= _('Service usage data')
= nav_link(path: 'application_settings#network') do
- = link_to network_admin_application_settings_path, title: _('Network'), data: { qa_selector: 'admin_settings_network_item' } do
+ = link_to network_admin_application_settings_path, title: _('Network'), data: { qa_selector: 'admin_settings_network_link' } do
%span
= _('Network')
= nav_link(controller: :appearances ) do
diff --git a/app/views/layouts/terms.html.haml b/app/views/layouts/terms.html.haml
index 91848b4f54b..032be73f70c 100644
--- a/app/views/layouts/terms.html.haml
+++ b/app/views/layouts/terms.html.haml
@@ -25,7 +25,7 @@
%ul.nav.navbar-nav
%li.header-user.dropdown
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
- = render Pajamas::AvatarComponent.new(current_user, size: 24, class: 'gl-mr-3', avatar_options: { data: { qa_selector: 'user_avatar' } })
+ = render Pajamas::AvatarComponent.new(current_user, size: 24, class: 'gl-mr-3', avatar_options: { data: { qa_selector: 'user_avatar_content' } })
= sprite_icon('chevron-down')
.dropdown-menu.dropdown-menu-right
= render 'layouts/header/current_user_dropdown'
diff --git a/app/views/projects/blob/_template_selectors.html.haml b/app/views/projects/blob/_template_selectors.html.haml
index a76e61bc3dd..249c474587c 100644
--- a/app/views/projects/blob/_template_selectors.html.haml
+++ b/app/views/projects/blob/_template_selectors.html.haml
@@ -2,14 +2,14 @@
.template-selector-dropdowns-wrap
.template-type-selector.js-template-type-selector-wrap.hidden
- toggle_text = should_suggest_gitlab_ci_yml? ? '.gitlab-ci.yml' : 'Select a template type'
- = dropdown_tag(_(toggle_text), options: { toggle_class: 'js-template-type-selector qa-template-type-dropdown', dropdown_class: 'dropdown-menu-selectable' })
+ = dropdown_tag(_(toggle_text), options: { toggle_class: 'js-template-type-selector', dropdown_class: 'dropdown-menu-selectable', data: { qa_selector: 'template_type_dropdown' } })
.license-selector.js-license-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-license-selector qa-license-dropdown', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: licenses_for_select(@project), project: @project.name, fullname: @project.namespace.human_name } } )
+ = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-license-selector', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: licenses_for_select(@project), project: @project.name, fullname: @project.namespace.human_name, qa_selector: 'license_dropdown' } } )
.gitignore-selector.js-gitignore-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-gitignore-selector qa-gitignore-dropdown', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: gitignore_names(@project) } } )
+ = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-gitignore-selector', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: gitignore_names(@project), qa_selector: 'gitignore_dropdown' } } )
.metrics-dashboard-selector.js-metrics-dashboard-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-metrics-dashboard-selector qa-metrics-dashboard-dropdown', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: metrics_dashboard_ymls(@project) } } )
+ = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-metrics-dashboard-selector', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: metrics_dashboard_ymls(@project), qa_selector: 'metrics_dashboard_dropdown' } } )
#gitlab-ci-yml-selector.gitlab-ci-yml-selector.js-gitlab-ci-yml-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-gitlab-ci-yml-selector qa-gitlab-ci-yml-dropdown', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls(@project), selected: params[:template] } } )
+ = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-gitlab-ci-yml-selector', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: gitlab_ci_ymls(@project), selected: params[:template], qa_selector: 'gitlab_ci_yml_dropdown' } } )
.dockerfile-selector.js-dockerfile-selector-wrap.js-template-selector-wrap.hidden
- = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-dockerfile-selector qa-dockerfile-dropdown', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: dockerfile_names(@project) } } )
+ = dropdown_tag(_("Apply a template"), options: { toggle_class: 'js-dockerfile-selector', dropdown_class: 'dropdown-menu-selectable', filter: true, placeholder: "Filter", data: { data: dockerfile_names(@project), qa_selector: 'dockerfile_dropdown' } } )
diff --git a/app/views/projects/buttons/_clone.html.haml b/app/views/projects/buttons/_clone.html.haml
index 10a6bc6b524..34aecd31c57 100644
--- a/app/views/projects/buttons/_clone.html.haml
+++ b/app/views/projects/buttons/_clone.html.haml
@@ -2,17 +2,17 @@
- dropdown_class = local_assigns.fetch(:dropdown_class, '')
.git-clone-holder.js-git-clone-holder
- %a#clone-dropdown.gl-button.btn.btn-confirm.clone-dropdown-btn.qa-clone-dropdown{ href: '#', data: { toggle: 'dropdown' } }
+ %a#clone-dropdown.gl-button.btn.btn-confirm.clone-dropdown-btn{ href: '#', data: { toggle: 'dropdown', qa_selector: 'clone_dropdown' } }
%span.gl-mr-2.js-clone-dropdown-label
= _('Clone')
= sprite_icon("chevron-down", css_class: "icon")
- %ul.dropdown-menu.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown.qa-clone-options{ class: dropdown_class }
+ %ul.dropdown-menu.dropdown-menu-large.dropdown-menu-selectable.clone-options-dropdown{ class: dropdown_class, data: { qa_selector: 'clone_dropdown_content' } }
- if ssh_enabled?
%li{ class: 'gl-px-4!' }
%label.label-bold
= _('Clone with SSH')
.input-group.btn-group
- = text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control qa-ssh-clone-url", readonly: true, aria: { label: _('Repository clone URL') }
+ = text_field_tag :ssh_project_clone, project.ssh_url_to_repo, class: "js-select-on-focus form-control", readonly: true, aria: { label: _('Repository clone URL') }, data: { qa_selector: 'ssh_clone_url_content' }
.input-group-append
= clipboard_button(target: '#ssh_project_clone', title: _("Copy URL"), class: "input-group-text gl-button btn btn-icon btn-default")
= render_if_exists 'projects/buttons/geo'
@@ -21,7 +21,7 @@
%label.label-bold
= _('Clone with %{http_label}') % { http_label: gitlab_config.protocol.upcase }
.input-group.btn-group
- = text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control qa-http-clone-url", readonly: true, aria: { label: _('Repository clone URL') }
+ = text_field_tag :http_project_clone, project.http_url_to_repo, class: "js-select-on-focus form-control", readonly: true, aria: { label: _('Repository clone URL') }, data: { qa_selector: 'http_clone_url_content' }
.input-group-append
= clipboard_button(target: '#http_project_clone', title: _("Copy URL"), class: "input-group-text gl-button btn btn-icon btn-default")
= render_if_exists 'projects/buttons/geo'
diff --git a/app/views/projects/issues/_work_item_links.html.haml b/app/views/projects/issues/_work_item_links.html.haml
index bc2136b89fb..c0de711136a 100644
--- a/app/views/projects/issues/_work_item_links.html.haml
+++ b/app/views/projects/issues/_work_item_links.html.haml
@@ -1,2 +1,2 @@
- if Feature.enabled?(:work_items_hierarchy, @project)
- .js-work-item-links-root{ data: { issuable_id: @issue.id, iid: @issue.iid, project_path: @project.full_path, wi: work_items_index_data(@project) } }
+ .js-work-item-links-root{ data: { issuable_id: @issue.id, iid: @issue.iid, project_namespace: @project.namespace.path, project_path: @project.full_path, wi: work_items_index_data(@project) } }
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 6b502ee928e..abaf250fa69 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -9,14 +9,14 @@
%span.js-clone-dropdown-label
= default_clone_protocol.upcase
= sprite_icon('chevron-down', css_class: 'gl-icon')
- %ul.dropdown-menu.dropdown-menu-selectable.clone-options-dropdown
+ %ul.dropdown-menu.dropdown-menu-selectable{ data: { qa_selector: 'clone_dropdown_content' } }
%li
= ssh_clone_button(container)
%li
= http_clone_button(container)
= render_if_exists 'shared/kerberos_clone_button', container: container
- = text_field_tag :clone_url, default_url_to_repo(container), class: "js-select-on-focus btn gl-button", readonly: true, aria: { label: _('Repository clone URL') }
+ = text_field_tag :clone_url, default_url_to_repo(container), class: "js-select-on-focus btn gl-button", readonly: true, aria: { label: _('Repository clone URL') }, data: { qa_selector: 'clone_url_content' }
.input-group-append
= clipboard_button(target: '#clone_url', title: _("Copy URL"), class: "input-group-text gl-button btn-default btn-clipboard")
diff --git a/app/views/shared/web_hooks/_hook_errors.html.haml b/app/views/shared/web_hooks/_hook_errors.html.haml
index d95efe83e15..098cc19c435 100644
--- a/app/views/shared/web_hooks/_hook_errors.html.haml
+++ b/app/views/shared/web_hooks/_hook_errors.html.haml
@@ -4,16 +4,12 @@
- link_end = '</a>'.html_safe
- if hook.rate_limited?
- - support_path = 'https://support.gitlab.com/hc/en-us/requests/new'
- - placeholders = { strong_start: strong_start,
- strong_end: strong_end,
- limit: hook.rate_limit,
- support_link_start: link_start % { url: support_path },
- support_link_end: link_end }
- = render Pajamas::AlertComponent.new(title: s_('Webhooks|Webhook was automatically disabled'),
+ - placeholders = { limit: number_with_delimiter(hook.rate_limit),
+ root_namespace: hook.parent.root_namespace.path }
+ = render Pajamas::AlertComponent.new(title: s_('Webhooks|Webhook rate limit has been reached'),
variant: :danger) do |c|
= c.body do
- = s_('Webhooks|The webhook was triggered more than %{limit} times per minute and is now disabled. To re-enable this webhook, fix the problems shown in %{strong_start}Recent events%{strong_end}, then re-test your settings. %{support_link_start}Contact Support%{support_link_end} if you need help re-enabling your webhook.').html_safe % placeholders
+ = s_("Webhooks|Webhooks for %{root_namespace} are now disabled because they've been triggered more than %{limit} times per minute. They'll be automatically re-enabled in the next minute.").html_safe % placeholders
- elsif hook.permanently_disabled?
= render Pajamas::AlertComponent.new(title: s_('Webhooks|Webhook failed to connect'),
variant: :danger) do |c|