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>2023-01-31 21:10:00 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-01-31 21:10:00 +0300
commit22dde36e800253350e5fa1d902f191a7f64bc6e9 (patch)
tree3b53aee41daed5efa0674f9ee2da83e17ef4e676 /app
parent6f18a8d0b00eae84d262dff137fddd9639f3c52a (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/analytics/shared/constants.js114
-rw-r--r--app/assets/javascripts/behaviors/shortcuts.js5
-rw-r--r--app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue3
-rw-r--r--app/assets/javascripts/issuable/issuable_bulk_update_sidebar.js3
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue3
-rw-r--r--app/assets/javascripts/layout_nav.js6
-rw-r--r--app/assets/javascripts/sidebar/mount_sidebar.js69
-rw-r--r--app/assets/javascripts/super_sidebar/components/bottom_bar.vue24
-rw-r--r--app/assets/javascripts/super_sidebar/components/help_center.vue88
-rw-r--r--app/assets/javascripts/super_sidebar/components/super_sidebar.vue10
-rw-r--r--app/assets/javascripts/vue_shared/components/user_select/user_select.vue24
-rw-r--r--app/assets/javascripts/whats_new/utils/notification.js2
-rw-r--r--app/assets/stylesheets/framework/super_sidebar.scss1
-rw-r--r--app/helpers/sidebars_helper.rb4
-rw-r--r--app/models/concerns/issuable.rb19
-rw-r--r--app/models/concerns/sanitizable.rb9
-rw-r--r--app/models/namespace_setting.rb13
-rw-r--r--app/services/packages/helm/extract_file_metadata_service.rb5
-rw-r--r--app/views/admin/application_settings/appearances/_form.html.haml2
-rw-r--r--app/views/layouts/_page.html.haml4
-rw-r--r--app/views/shared/issuable/_bulk_update_sidebar.html.haml5
21 files changed, 321 insertions, 92 deletions
diff --git a/app/assets/javascripts/analytics/shared/constants.js b/app/assets/javascripts/analytics/shared/constants.js
index c62736d55a8..a82633035b5 100644
--- a/app/assets/javascripts/analytics/shared/constants.js
+++ b/app/assets/javascripts/analytics/shared/constants.js
@@ -1,5 +1,6 @@
import { masks } from '~/lib/dateformat';
import { s__ } from '~/locale';
+import { helpPagePath } from '~/helpers/help_page_helper';
export const DATE_RANGE_LIMIT = 180;
export const PROJECTS_PER_PAGE = 50;
@@ -12,8 +13,101 @@ export const dateFormats = {
month: 'mmmm',
};
-// Some content is duplicated due to backward compatibility.
-// It will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/350614 in 14.9
+export const KEY_METRICS = {
+ LEAD_TIME: 'lead_time',
+ CYCLE_TIME: 'cycle_time',
+ ISSUES: 'issues',
+ COMMITS: 'commits',
+ DEPLOYS: 'deploys',
+};
+
+export const DORA_METRICS = {
+ DEPLOYMENT_FREQUENCY: 'deployment_frequency',
+ LEAD_TIME_FOR_CHANGES: 'lead_time_for_changes',
+ TIME_TO_RESTORE_SERVICE: 'time_to_restore_service',
+ CHANGE_FAILURE_RATE: 'change_failure_rate',
+};
+
+export const VSA_METRICS_GROUPS = [
+ {
+ key: 'key_metrics',
+ title: s__('ValueStreamAnalytics|Key metrics'),
+ keys: Object.values(KEY_METRICS),
+ },
+ {
+ key: 'dora_metrics',
+ title: s__('ValueStreamAnalytics|DORA metrics'),
+ keys: Object.values(DORA_METRICS),
+ },
+];
+
+export const METRIC_TOOLTIPS = {
+ [DORA_METRICS.DEPLOYMENT_FREQUENCY]: {
+ description: s__(
+ 'ValueStreamAnalytics|Average number of deployments to production per day. This metric measures how often value is delivered to end users.',
+ ),
+ groupLink: '-/analytics/ci_cd?tab=deployment-frequency',
+ projectLink: '-/pipelines/charts?chart=deployment-frequency',
+ docsLink: helpPagePath('user/analytics/dora_metrics', { anchor: 'deployment-frequency' }),
+ },
+ [DORA_METRICS.LEAD_TIME_FOR_CHANGES]: {
+ description: s__(
+ 'ValueStreamAnalytics|The time to successfully deliver a commit into production. This metric reflects the efficiency of CI/CD pipelines.',
+ ),
+ groupLink: '-/analytics/ci_cd?tab=lead-time',
+ projectLink: '-/pipelines/charts?chart=lead-time',
+ docsLink: helpPagePath('user/analytics/dora_metrics', { anchor: 'lead-time-for-changes' }),
+ },
+ [DORA_METRICS.TIME_TO_RESTORE_SERVICE]: {
+ description: s__(
+ 'ValueStreamAnalytics|The time it takes an organization to recover from a failure in production.',
+ ),
+ groupLink: '-/analytics/ci_cd?tab=time-to-restore-service',
+ projectLink: '-/pipelines/charts?chart=time-to-restore-service',
+ docsLink: helpPagePath('user/analytics/dora_metrics', { anchor: 'time-to-restore-service' }),
+ },
+ [DORA_METRICS.CHANGE_FAILURE_RATE]: {
+ description: s__(
+ 'ValueStreamAnalytics|Percentage of deployments that cause an incident in production.',
+ ),
+ groupLink: '-/analytics/ci_cd?tab=change-failure-rate',
+ projectLink: '-/pipelines/charts?chart=change-failure-rate',
+ docsLink: helpPagePath('user/analytics/dora_metrics', { anchor: 'change-failure-rate' }),
+ },
+ [KEY_METRICS.LEAD_TIME]: {
+ description: s__('ValueStreamAnalytics|Median time from issue created to issue closed.'),
+ groupLink: '-/analytics/value_stream_analytics',
+ projectLink: '-/value_stream_analytics',
+ docsLink: helpPagePath('user/analytics/value_stream_analytics', {
+ anchor: 'view-the-lead-time-and-cycle-time-for-issues',
+ }),
+ },
+ [KEY_METRICS.CYCLE_TIME]: {
+ description: s__(
+ "ValueStreamAnalytics|Median time from the earliest commit of a linked issue's merge request to when that issue is closed.",
+ ),
+ groupLink: '-/analytics/value_stream_analytics',
+ projectLink: '-/value_stream_analytics',
+ docsLink: helpPagePath('user/analytics/value_stream_analytics', {
+ anchor: 'view-the-lead-time-and-cycle-time-for-issues',
+ }),
+ },
+ [KEY_METRICS.ISSUES]: {
+ description: s__('ValueStreamAnalytics|Number of new issues created.'),
+ groupLink: '-/issues_analytics',
+ projectLink: '-/analytics/issues_analytics',
+ docsLink: helpPagePath('user/analytics/issue_analytics'),
+ },
+ [KEY_METRICS.DEPLOYS]: {
+ description: s__('ValueStreamAnalytics|Total number of deploys to production.'),
+ groupLink: '-/analytics/productivity_analytics',
+ projectLink: '-/analytics/merge_request_analytics',
+ docsLink: helpPagePath('user/analytics/merge_request_analytics'),
+ },
+};
+
+// TODO: Remove this once the migration to METRIC_TOOLTIPS is complete
+// https://gitlab.com/gitlab-org/gitlab/-/issues/388067
export const METRICS_POPOVER_CONTENT = {
lead_time: {
description: s__('ValueStreamAnalytics|Median time from issue created to issue closed.'),
@@ -47,19 +141,3 @@ export const METRICS_POPOVER_CONTENT = {
),
},
};
-
-const KEY_METRICS_TITLE = s__('ValueStreamAnalytics|Key metrics');
-const KEY_METRICS_KEYS = ['lead_time', 'cycle_time', 'issues', 'commits', 'deploys'];
-
-const DORA_METRICS_TITLE = s__('ValueStreamAnalytics|DORA metrics');
-const DORA_METRICS_KEYS = [
- 'deployment_frequency',
- 'lead_time_for_changes',
- 'time_to_restore_service',
- 'change_failure_rate',
-];
-
-export const VSA_METRICS_GROUPS = [
- { key: 'key_metrics', title: KEY_METRICS_TITLE, keys: KEY_METRICS_KEYS },
- { key: 'dora_metrics', title: DORA_METRICS_TITLE, keys: DORA_METRICS_KEYS },
-];
diff --git a/app/assets/javascripts/behaviors/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts.js
index 7352be0dbd5..12fdb2e2981 100644
--- a/app/assets/javascripts/behaviors/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts.js
@@ -28,7 +28,10 @@ export default function initPageShortcuts() {
// TODO: replace this whitelist with something more automated/maintainable
if (page && !pagesWithCustomShortcuts.includes(page)) {
import(/* webpackChunkName: 'shortcutsBundle' */ './shortcuts/shortcuts')
- .then(({ default: Shortcuts }) => new Shortcuts())
+ .then(({ default: Shortcuts }) => {
+ const shortcuts = new Shortcuts();
+ window.toggleShortcutsHelp = shortcuts.onToggleHelp;
+ })
.catch(() => {});
}
return false;
diff --git a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
index b8719d0cd4d..430498e7194 100644
--- a/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
+++ b/app/assets/javascripts/frequent_items/components/frequent_items_list_item.vue
@@ -1,6 +1,5 @@
<script>
import { GlButton, GlTooltipDirective, GlIcon } from '@gitlab/ui';
-import { snakeCase } from 'lodash';
import SafeHtml from '~/vue_shared/directives/safe_html';
import highlight from '~/lib/utils/highlight';
import { truncateNamespace } from '~/lib/utils/text_utility';
@@ -61,7 +60,7 @@ export default {
return highlight(this.itemName, this.matcher);
},
itemTrackingLabel() {
- return `${this.dropdownType}_dropdown_frequent_items_list_item_${snakeCase(this.itemName)}`;
+ return `${this.dropdownType}_dropdown_frequent_items_list_item`;
},
},
methods: {
diff --git a/app/assets/javascripts/issuable/issuable_bulk_update_sidebar.js b/app/assets/javascripts/issuable/issuable_bulk_update_sidebar.js
index 095da60a583..9c891bcfc9e 100644
--- a/app/assets/javascripts/issuable/issuable_bulk_update_sidebar.js
+++ b/app/assets/javascripts/issuable/issuable_bulk_update_sidebar.js
@@ -1,9 +1,9 @@
/* eslint-disable class-methods-use-this, no-new */
-
import $ from 'jquery';
import issuableEventHub from '~/issues/list/eventhub';
import LabelsSelect from '~/labels/labels_select';
import {
+ mountAssigneesDropdown,
mountMilestoneDropdown,
mountMoveIssuesButton,
mountStatusDropdown,
@@ -64,6 +64,7 @@ export default class IssuableBulkUpdateSidebar {
mountMoveIssuesButton();
mountStatusDropdown();
mountSubscriptionsDropdown();
+ mountAssigneesDropdown();
// Checking IS_EE and using ee_else_ce is odd, but we do it here to satisfy
// the import/no-unresolved lint rule when FOSS_ONLY=1, even though at
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index 22e9dd6a5c5..28908478e02 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -592,9 +592,6 @@ export default {
const bulkUpdateSidebar = await import('~/issuable');
bulkUpdateSidebar.initBulkUpdateSidebar('issuable_');
- const UsersSelect = (await import('~/users_select')).default;
- new UsersSelect(); // eslint-disable-line no-new
-
this.hasInitBulkEdit = true;
}
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index 90c1b31286a..b8138f34d45 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -56,7 +56,11 @@ function initDeferred() {
if (!appEl) return;
setNotification(appEl);
- document.querySelector('.js-whats-new-trigger').addEventListener('click', () => {
+
+ const triggerEl = document.querySelector('.js-whats-new-trigger');
+ if (!triggerEl) return;
+
+ triggerEl.addEventListener('click', () => {
import(/* webpackChunkName: 'whatsNewApp' */ '~/whats_new')
.then(({ default: initWhatsNew }) => {
initWhatsNew(appEl);
diff --git a/app/assets/javascripts/sidebar/mount_sidebar.js b/app/assets/javascripts/sidebar/mount_sidebar.js
index facc07e8ce5..bef4c6af6ab 100644
--- a/app/assets/javascripts/sidebar/mount_sidebar.js
+++ b/app/assets/javascripts/sidebar/mount_sidebar.js
@@ -1,21 +1,22 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/graphql_shared/constants';
-import { convertToGraphQLId } from '~/graphql_shared/utils';
+import { convertToGraphQLId, getIdFromGraphQLId } from '~/graphql_shared/utils';
import initInviteMembersModal from '~/invite_members/init_invite_members_modal';
import initInviteMembersTrigger from '~/invite_members/init_invite_members_trigger';
import { IssuableType } from '~/issues/constants';
import { gqlClient } from '~/issues/list/graphql';
import {
- isInIssuePage,
isInDesignPage,
isInIncidentPage,
+ isInIssuePage,
isInMRPage,
parseBoolean,
} from '~/lib/utils/common_utils';
import { __ } from '~/locale';
import { apolloProvider } from '~/graphql_shared/issuable_client';
import Translate from '~/vue_shared/translate';
+import UserSelect from '~/vue_shared/components/user_select/user_select.vue';
import CollapsedAssigneeList from './components/assignees/collapsed_assignee_list.vue';
import SidebarAssignees from './components/assignees/sidebar_assignees.vue';
import SidebarAssigneesWidget from './components/assignees/sidebar_assignees_widget.vue';
@@ -725,6 +726,70 @@ export function mountMoveIssueButton() {
});
}
+export function mountAssigneesDropdown() {
+ const el = document.querySelector('.js-assignee-dropdown');
+ const assigneeIdsInput = document.querySelector('.js-assignee-ids-input');
+
+ if (!el || !assigneeIdsInput) {
+ return null;
+ }
+
+ const { fullPath } = el.dataset;
+ const currentUser = {
+ id: gon?.current_user_id,
+ username: gon?.current_username,
+ name: gon?.current_user_fullname,
+ avatarUrl: gon?.current_user_avatar_url,
+ };
+
+ return new Vue({
+ el,
+ apolloProvider,
+ data() {
+ return {
+ selectedUserName: '',
+ value: [],
+ };
+ },
+ methods: {
+ onSelectedUnassigned() {
+ assigneeIdsInput.value = 0;
+ this.value = [];
+ this.selectedUserName = __('Unassigned');
+ },
+ onSelected(selected) {
+ assigneeIdsInput.value = selected.map((user) => getIdFromGraphQLId(user.id));
+ this.value = selected;
+ this.selectedUserName = selected.map((user) => user.name).join(', ');
+ },
+ },
+ render(h) {
+ const component = this;
+
+ return h(UserSelect, {
+ props: {
+ text: component.selectedUserName || __('Select assignee'),
+ headerText: __('Assign to'),
+ fullPath,
+ currentUser,
+ value: component.value,
+ },
+ on: {
+ input(selected) {
+ if (!selected.length) {
+ component.onSelectedUnassigned();
+ return;
+ }
+
+ component.onSelected(selected);
+ },
+ },
+ class: 'gl-w-full',
+ });
+ },
+ });
+}
+
const isAssigneesWidgetShown =
(isInIssuePage() || isInDesignPage() || isInMRPage()) && gon.features.issueAssigneesWidget;
diff --git a/app/assets/javascripts/super_sidebar/components/bottom_bar.vue b/app/assets/javascripts/super_sidebar/components/bottom_bar.vue
deleted file mode 100644
index fea29458f45..00000000000
--- a/app/assets/javascripts/super_sidebar/components/bottom_bar.vue
+++ /dev/null
@@ -1,24 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-import { __ } from '~/locale';
-
-export default {
- components: {
- GlIcon,
- },
- i18n: {
- help: __('Help'),
- new: __('New'),
- },
-};
-</script>
-
-<template>
- <div class="bottom-links gl-p-3">
- <a href="#" class="gl-text-black-normal"
- ><gl-icon name="question-o" class="gl-mr-3 gl-text-gray-300 gl-text-black-normal!" />{{
- $options.i18n.help
- }}</a
- >
- </div>
-</template>
diff --git a/app/assets/javascripts/super_sidebar/components/help_center.vue b/app/assets/javascripts/super_sidebar/components/help_center.vue
new file mode 100644
index 00000000000..6c82a4bbf86
--- /dev/null
+++ b/app/assets/javascripts/super_sidebar/components/help_center.vue
@@ -0,0 +1,88 @@
+<script>
+import { GlDisclosureDropdown } from '@gitlab/ui';
+import { helpPagePath } from '~/helpers/help_page_helper';
+import { PROMO_URL } from 'jh_else_ce/lib/utils/url_utility';
+import { __ } from '~/locale';
+
+export default {
+ components: {
+ GlDisclosureDropdown,
+ },
+ i18n: {
+ help: __('Help'),
+ support: __('Support'),
+ docs: __('GitLab documentation'),
+ plans: __('Compare GitLab plans'),
+ forum: __('Community forum'),
+ contribute: __('Contribute to GitLab'),
+ feedback: __('Provide feedback'),
+ shortcuts: __('Keyboard shortcuts'),
+ whatsnew: __("What's new"),
+ },
+ props: {
+ sidebarData: {
+ type: Object,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ items: [
+ {
+ items: [
+ { text: this.$options.i18n.help, href: helpPagePath() },
+ { text: this.$options.i18n.support, href: this.sidebarData.support_path },
+ { text: this.$options.i18n.docs, href: 'https://docs.gitlab.com' },
+ { text: this.$options.i18n.plans, href: `${PROMO_URL}/pricing` },
+ { text: this.$options.i18n.forum, href: 'https://forum.gitlab.com/' },
+ {
+ text: this.$options.i18n.contribute,
+ href: helpPagePath('', { anchor: 'contributing-to-gitlab' }),
+ },
+ { text: this.$options.i18n.feedback, href: 'https://about.gitlab.com/submit-feedback' },
+ ],
+ },
+ {
+ items: [
+ { text: this.$options.i18n.shortcuts, action: this.showKeyboardShortcuts },
+ this.sidebarData.display_whats_new && {
+ text: this.$options.i18n.whatsnew,
+ action: this.showWhatsNew,
+ },
+ ].filter(Boolean),
+ },
+ ],
+ };
+ },
+ methods: {
+ showKeyboardShortcuts() {
+ this.$refs.dropdown.close();
+ window?.toggleShortcutsHelp();
+ },
+ async showWhatsNew() {
+ this.$refs.dropdown.close();
+ if (!this.toggleWhatsNewDrawer) {
+ const appEl = document.getElementById('whats-new-app');
+ const { default: toggleWhatsNewDrawer } = await import(
+ /* webpackChunkName: 'whatsNewApp' */ '~/whats_new'
+ );
+ this.toggleWhatsNewDrawer = toggleWhatsNewDrawer;
+ this.toggleWhatsNewDrawer(appEl);
+ } else {
+ this.toggleWhatsNewDrawer();
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-disclosure-dropdown
+ ref="dropdown"
+ icon="question-o"
+ :items="items"
+ :toggle-text="$options.i18n.help"
+ category="tertiary"
+ no-caret
+ />
+</template>
diff --git a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
index 78d761e9a59..c4b769dcf24 100644
--- a/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
+++ b/app/assets/javascripts/super_sidebar/components/super_sidebar.vue
@@ -4,7 +4,7 @@ import { context } from '../mock_data';
import UserBar from './user_bar.vue';
import ContextSwitcherToggle from './context_switcher_toggle.vue';
import ContextSwitcher from './context_switcher.vue';
-import BottomBar from './bottom_bar.vue';
+import HelpCenter from './help_center.vue';
export default {
context,
@@ -13,7 +13,7 @@ export default {
UserBar,
ContextSwitcherToggle,
ContextSwitcher,
- BottomBar,
+ HelpCenter,
},
props: {
sidebarData: {
@@ -32,7 +32,7 @@ export default {
<template>
<aside
id="super-sidebar"
- class="super-sidebar gl-fixed gl-bottom-0 gl-left-0 gl-display-flex gl-flex-direction-column gl-bg-gray-10 gl-border-r gl-border-gray-a-08 gl-z-index-9999"
+ class="super-sidebar gl-fixed gl-bottom-0 gl-left-0 gl-display-flex gl-flex-direction-column gl-bg-gray-10 gl-border-r gl-border-gray-a-08"
data-testid="super-sidebar"
>
<user-bar :sidebar-data="sidebarData" />
@@ -43,8 +43,8 @@ export default {
<context-switcher />
</gl-collapse>
</div>
- <div class="gl-px-3">
- <bottom-bar />
+ <div class="gl-p-3">
+ <help-center :sidebar-data="sidebarData" />
</div>
</div>
</aside>
diff --git a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
index 86a99b8f0ed..0c8cd7fd95c 100644
--- a/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
+++ b/app/assets/javascripts/vue_shared/components/user_select/user_select.vue
@@ -2,11 +2,11 @@
import { debounce } from 'lodash';
import {
GlDropdown,
- GlDropdownForm,
GlDropdownDivider,
+ GlDropdownForm,
GlDropdownItem,
- GlSearchBoxByType,
GlLoadingIcon,
+ GlSearchBoxByType,
GlTooltipDirective,
} from '@gitlab/ui';
import { __ } from '~/locale';
@@ -47,7 +47,8 @@ export default {
},
iid: {
type: String,
- required: true,
+ required: false,
+ default: null,
},
value: {
type: Array,
@@ -167,13 +168,10 @@ export default {
return this.$apollo.queries.searchUsers.loading || this.$apollo.queries.participants.loading;
},
users() {
- if (!this.participants) {
- return [];
- }
-
- const filteredParticipants = this.participants.filter(
- (user) => user.name.includes(this.search) || user.username.includes(this.search),
- );
+ const filteredParticipants =
+ this.participants?.filter(
+ (user) => user.name.includes(this.search) || user.username.includes(this.search),
+ ) || [];
// TODO this de-duplication is temporary (BE fix required)
// https://gitlab.com/gitlab-org/gitlab/-/issues/327822
@@ -254,6 +252,10 @@ export default {
this.$emit('input', selected);
}
},
+ unassign() {
+ this.$emit('input', []);
+ this.$refs.dropdown.hide();
+ },
unselect(name) {
const selected = this.value.filter((user) => user.username !== name);
this.$emit('input', selected);
@@ -323,7 +325,7 @@ export default {
:is-checked="selectedIsEmpty"
is-check-centered
data-testid="unassign"
- @click.native.capture.stop="$emit('input', [])"
+ @click.native.capture.stop="unassign"
>
<span :class="selectedIsEmpty ? 'gl-pl-0' : 'gl-pl-6'" class="gl-font-weight-bold">{{
$options.i18n.unassigned
diff --git a/app/assets/javascripts/whats_new/utils/notification.js b/app/assets/javascripts/whats_new/utils/notification.js
index 41aff202f48..f9b725ed429 100644
--- a/app/assets/javascripts/whats_new/utils/notification.js
+++ b/app/assets/javascripts/whats_new/utils/notification.js
@@ -5,6 +5,8 @@ export const getVersionDigest = (appEl) => appEl.dataset.versionDigest;
export const setNotification = (appEl) => {
const versionDigest = getVersionDigest(appEl);
const notificationEl = document.querySelector('.header-help');
+ if (!notificationEl) return;
+
let notificationCountEl = notificationEl.querySelector('.js-whats-new-notification-count');
if (localStorage.getItem(STORAGE_KEY) === versionDigest) {
diff --git a/app/assets/stylesheets/framework/super_sidebar.scss b/app/assets/stylesheets/framework/super_sidebar.scss
index 59a9df9ede0..575fbc03f46 100644
--- a/app/assets/stylesheets/framework/super_sidebar.scss
+++ b/app/assets/stylesheets/framework/super_sidebar.scss
@@ -1,6 +1,7 @@
.super-sidebar {
top: 0;
width: $contextual-sidebar-width;
+ z-index: 600;
.user-bar {
background-color: $t-gray-a-04;
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index 5a19ce6149a..2ca6a46906c 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -42,7 +42,9 @@ module SidebarsHelper
assigned_open_merge_requests_count: user.assigned_open_merge_requests_count,
todos_pending_count: user.todos_pending_count,
issues_dashboard_path: issues_dashboard_path(assignee_username: user.username),
- create_new_menu_groups: create_new_menu_groups(group: group, project: project)
+ create_new_menu_groups: create_new_menu_groups(group: group, project: project),
+ support_path: support_url,
+ display_whats_new: display_whats_new?
}
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 9f0cd96a8f8..50696c7b5e1 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -92,10 +92,9 @@ module Issuable
validates :author, presence: true
validates :title, presence: true, length: { maximum: TITLE_LENGTH_MAX }
- # we validate the description against DESCRIPTION_LENGTH_MAX only for Issuables being created
- # to avoid breaking the existing Issuables which may have their descriptions longer
- validates :description, length: { maximum: DESCRIPTION_LENGTH_MAX }, allow_blank: true, on: :create
- validate :description_max_length_for_new_records_is_valid, on: :update
+ # we validate the description against DESCRIPTION_LENGTH_MAX only for Issuables being created and on updates if
+ # the description changes to avoid breaking the existing Issuables which may have their descriptions longer
+ validates :description, bytesize: { maximum: -> { DESCRIPTION_LENGTH_MAX } }, if: :validate_description_length?
validate :validate_assignee_size_length, unless: :importing?
before_validation :truncate_description_on_import!
@@ -229,10 +228,14 @@ module Issuable
private
- def description_max_length_for_new_records_is_valid
- if new_record? && description.length > Issuable::DESCRIPTION_LENGTH_MAX
- errors.add(:description, :too_long, count: Issuable::DESCRIPTION_LENGTH_MAX)
- end
+ def validate_description_length?
+ return false unless description_changed?
+
+ previous_description = changes['description'].first
+ # previous_description will be nil for new records
+ return true if previous_description.blank?
+
+ previous_description.bytesize <= DESCRIPTION_LENGTH_MAX
end
def truncate_description_on_import!
diff --git a/app/models/concerns/sanitizable.rb b/app/models/concerns/sanitizable.rb
index 05756beb404..653d7a4875d 100644
--- a/app/models/concerns/sanitizable.rb
+++ b/app/models/concerns/sanitizable.rb
@@ -45,6 +45,15 @@ module Sanitizable
unless input.to_s == CGI.unescapeHTML(input.to_s)
record.errors.add(attr, 'cannot contain escaped HTML entities')
end
+
+ # This method raises an exception on failure so perform this
+ # last if multiple errors should be returned.
+ Gitlab::Utils.check_path_traversal!(input.to_s)
+
+ rescue Gitlab::Utils::DoubleEncodingError
+ record.errors.add(attr, 'cannot contain escaped components')
+ rescue Gitlab::Utils::PathTraversalAttackError
+ record.errors.add(attr, "cannot contain a path traversal component")
end
end
end
diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb
index 7f65fb3a378..aeb4d7a5694 100644
--- a/app/models/namespace_setting.rb
+++ b/app/models/namespace_setting.rb
@@ -14,10 +14,11 @@ class NamespaceSetting < ApplicationRecord
validates :enabled_git_access_protocol, inclusion: { in: enabled_git_access_protocols.keys }
- validate :default_branch_name_content
validate :allow_mfa_for_group
validate :allow_resource_access_token_creation_for_group
+ sanitizes! :default_branch_name
+
before_validation :normalize_default_branch_name
chronic_duration_attr :runner_token_expiration_interval_human_readable, :runner_token_expiration_interval
@@ -45,8 +46,6 @@ class NamespaceSetting < ApplicationRecord
NAMESPACE_SETTINGS_PARAMS
end
- sanitizes! :default_branch_name
-
def prevent_sharing_groups_outside_hierarchy
return super if namespace.root?
@@ -85,14 +84,6 @@ class NamespaceSetting < ApplicationRecord
self.default_branch_name = default_branch_name.presence
end
- def default_branch_name_content
- return if default_branch_name.nil?
-
- if default_branch_name.blank?
- errors.add(:default_branch_name, "can not be an empty string")
- end
- end
-
def allow_mfa_for_group
if namespace&.subgroup? && allow_mfa_for_subgroups == false
errors.add(:allow_mfa_for_subgroups, _('is not allowed since the group is not top-level group.'))
diff --git a/app/services/packages/helm/extract_file_metadata_service.rb b/app/services/packages/helm/extract_file_metadata_service.rb
index e7373d8ea8f..77efa65f1d1 100644
--- a/app/services/packages/helm/extract_file_metadata_service.rb
+++ b/app/services/packages/helm/extract_file_metadata_service.rb
@@ -7,6 +7,10 @@ module Packages
class ExtractFileMetadataService
ExtractionError = Class.new(StandardError)
+ # Charts must be smaller than 1M because of the storage limitations of Kubernetes objects.
+ # based on https://helm.sh/docs/chart_template_guide/accessing_files/
+ MAX_FILE_SIZE = 1.megabytes.freeze
+
def initialize(package_file)
@package_file = package_file
end
@@ -42,6 +46,7 @@ module Packages
end
raise ExtractionError, 'Chart.yaml not found within a directory' unless chart_yaml
+ raise ExtractionError, 'Chart.yaml too big' if chart_yaml.size > MAX_FILE_SIZE
chart_yaml.read
ensure
diff --git a/app/views/admin/application_settings/appearances/_form.html.haml b/app/views/admin/application_settings/appearances/_form.html.haml
index 5f51e91436c..c20a86b9f9c 100644
--- a/app/views/admin/application_settings/appearances/_form.html.haml
+++ b/app/views/admin/application_settings/appearances/_form.html.haml
@@ -72,7 +72,7 @@
= f.hidden_field :logo_cache
= f.file_field :logo, class: "", accept: 'image/*'
.form-text.text-muted
- = _('Maximum file size is 1MB. Pages are optimized for a 640x360 px logo.')
+ = _('Maximum file size is 1 MB. Pages are optimized for a 128x128 px logo.')
%hr
.row
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index 8e39ef7301d..d2ed70d6b48 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -4,6 +4,10 @@
- if show_super_sidebar?
- sidebar_data = super_sidebar_context(current_user, group: @group, project: @project).to_json
%aside.js-super-sidebar.nav-sidebar{ data: { root_path: root_path, sidebar: sidebar_data, toggle_new_nav_endpoint: profile_preferences_url } }
+
+ - if display_whats_new?
+ #whats-new-app{ data: { version_digest: whats_new_version_digest } }
+
- elsif defined?(nav) && nav
= render "layouts/nav/sidebar/#{nav}"
.content-wrapper.content-wrapper-margin{ class: "#{@content_wrapper_class}" }
diff --git a/app/views/shared/issuable/_bulk_update_sidebar.html.haml b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
index da8477f4b2e..b125fe34464 100644
--- a/app/views/shared/issuable/_bulk_update_sidebar.html.haml
+++ b/app/views/shared/issuable/_bulk_update_sidebar.html.haml
@@ -20,9 +20,8 @@
.title
= _('Assignee')
.filter-item
- - field_name = "update[assignee_ids][]"
- = dropdown_tag(_("Select assignee"), options: { toggle_class: "js-user-search js-update-assignee js-filter-submit js-filter-bulk-update", title: _("Assign to"), filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable",
- placeholder: _("Search authors"), data: { first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: @project.id, field_name: field_name } })
+ %input.js-assignee-ids-input{ type: "hidden", name: "update[assignee_ids][]" }
+ .js-assignee-dropdown{ data: { full_path: @project.full_path } }
- if is_issue
= render_if_exists 'shared/issuable/epic_dropdown', parent: @project.group
.block