Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-05 18:10:08 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-05 18:10:08 +0300
commit6659634b2bdc3ba362574541985c6852ad1574a4 (patch)
tree495e2462917ef620d379d0b7296d44e6c4b84bbc
parentb35f7ce1f3f12bf7b673c9d29002e14d0c83f35f (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue2
-rw-r--r--app/assets/javascripts/authentication/password/components/password_input.vue6
-rw-r--r--app/assets/javascripts/authentication/password/index.js3
-rw-r--r--app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue1
-rw-r--r--app/assets/javascripts/behaviors/index.js3
-rw-r--r--app/assets/javascripts/behaviors/shortcuts.js36
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/index.js16
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js78
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js8
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js10
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js9
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js9
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js10
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js10
-rw-r--r--app/assets/javascripts/blob/filepath_form/components/template_selector.vue1
-rw-r--r--app/assets/javascripts/issues/index.js5
-rw-r--r--app/assets/javascripts/ml/model_registry/components/model_version_detail.vue21
-rw-r--r--app/assets/javascripts/ml/model_registry/translations.js2
-rw-r--r--app/assets/javascripts/pages/groups/boards/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/shared/group_details.js3
-rw-r--r--app/assets/javascripts/pages/projects/activity/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/artifacts/browse/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/artifacts/file/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/boards/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/commit/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/commits/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/find_file/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/init_blob.js7
-rw-r--r--app/assets/javascripts/pages/projects/issues/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/index/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js3
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js3
-rw-r--r--app/assets/javascripts/pages/projects/network/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/tree/show/index.js3
-rw-r--r--app/assets/javascripts/pages/shared/wikis/wikis.js3
-rw-r--r--app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue4
-rw-r--r--app/assets/javascripts/repository/components/blob_controls.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/awards_list.vue4
-rw-r--r--app/components/projects/ml/show_ml_model_component.rb8
-rw-r--r--app/components/projects/ml/show_ml_model_version_component.rb12
-rw-r--r--app/finders/groups/custom_emoji_finder.rb26
-rw-r--r--app/graphql/resolvers/analytics/cycle_analytics/stages_resolver.rb28
-rw-r--r--app/graphql/resolvers/analytics/cycle_analytics/value_streams_resolver.rb20
-rw-r--r--app/graphql/resolvers/custom_emoji_resolver.rb23
-rw-r--r--app/graphql/resolvers/work_items/ancestors_resolver.rb2
-rw-r--r--app/graphql/types/analytics/cycle_analytics/value_stream_type.rb5
-rw-r--r--app/graphql/types/analytics/cycle_analytics/value_streams/stage_type.rb54
-rw-r--r--app/graphql/types/group_type.rb7
-rw-r--r--app/graphql/types/project_type.rb5
-rw-r--r--app/graphql/types/security/codequality_reports_comparer/degradation_type.rb2
-rw-r--r--app/models/analytics/cycle_analytics/value_stream.rb7
-rw-r--r--app/models/application_setting_implementation.rb1
-rw-r--r--app/models/award_emoji.rb3
-rw-r--r--app/models/custom_emoji.rb31
-rw-r--r--app/serializers/award_emoji_entity.rb1
-rw-r--r--app/services/ml/find_or_create_model_version_service.rb10
-rw-r--r--app/validators/gitlab/emoji_name_validator.rb6
-rw-r--r--app/views/admin/application_settings/general.html.haml5
-rw-r--r--app/views/projects/ml/model_versions/show.html.haml2
-rw-r--r--app/views/projects/ml/models/show.html.haml2
-rw-r--r--app/workers/users/deactivate_dormant_users_worker.rb6
-rw-r--r--config/feature_flags/development/updated_ai_powered_features_menu_for_sm.yml8
-rw-r--r--config/feature_flags/development/use_sync_service_token_worker.yml4
-rw-r--r--config/gitlab_loose_foreign_keys.yml6
-rw-r--r--config/initializers/custom_roles.rb5
-rw-r--r--db/post_migrate/20231127174335_remove_ignored_application_settings_columns.rb28
-rw-r--r--db/schema_migrations/202311271743351
-rw-r--r--db/structure.sql6
-rw-r--r--doc/administration/package_information/postgresql_versions.md3
-rw-r--r--doc/api/graphql/reference/index.md63
-rw-r--r--doc/development/documentation/styleguide/index.md14
-rw-r--r--doc/update/versions/gitlab_16_changes.md12
-rw-r--r--lib/banzai/filter/custom_emoji_filter.rb12
-rw-r--r--locale/gitlab.pot21
-rw-r--r--package.json4
-rw-r--r--qa/qa/page/file/form.rb4
-rw-r--r--spec/components/projects/ml/show_ml_model_component_spec.rb26
-rw-r--r--spec/components/projects/ml/show_ml_model_version_component_spec.rb31
-rw-r--r--spec/db/schema_spec.rb1
-rw-r--r--spec/factories/ml/model_versions.rb4
-rw-r--r--spec/finders/groups/custom_emoji_finder_spec.rb65
-rw-r--r--spec/frontend/authentication/password/components/password_input_spec.js1
-rw-r--r--spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js12
-rw-r--r--spec/frontend/behaviors/shortcuts/shortcuts_spec.js (renamed from spec/frontend/shortcuts_spec.js)123
-rw-r--r--spec/frontend/ml/model_registry/components/model_version_detail_spec.js52
-rw-r--r--spec/frontend/ml/model_registry/mock_data.js10
-rw-r--r--spec/frontend/repository/components/blob_controls_spec.js3
-rw-r--r--spec/graphql/types/analytics/cycle_analytics/value_stream_type_spec.rb2
-rw-r--r--spec/graphql/types/analytics/cycle_analytics/value_streams/stage_type_spec.rb15
-rw-r--r--spec/graphql/types/group_type_spec.rb33
-rw-r--r--spec/graphql/types/project_type_spec.rb4
-rw-r--r--spec/lib/banzai/filter/custom_emoji_filter_spec.rb8
-rw-r--r--spec/models/award_emoji_spec.rb11
-rw-r--r--spec/models/custom_emoji_spec.rb41
-rw-r--r--spec/models/ml/candidate_spec.rb4
-rw-r--r--spec/requests/api/graphql/custom_emoji_query_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/value_streams_spec.rb105
-rw-r--r--spec/serializers/discussion_entity_spec.rb7
-rw-r--r--spec/services/ml/create_candidate_service_spec.rb2
-rw-r--r--spec/views/admin/application_settings/general.html.haml_spec.rb3
-rw-r--r--spec/workers/users/deactivate_dormant_users_worker_spec.rb91
-rw-r--r--yarn.lock47
104 files changed, 1163 insertions, 303 deletions
diff --git a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
index 662451c5eb4..62924dcd0a8 100644
--- a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
+++ b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue
@@ -281,7 +281,7 @@ export default {
:shape="$options.AVATAR_SHAPE_OPTION_RECT"
/>
<div>
- <div data-testid="project-name" data-qa-selector="project_name">{{ item.name }}</div>
+ <div data-testid="project-name">{{ item.name }}</div>
<div class="gl-text-gray-500" data-testid="project-full-path">
{{ item.fullPath }}
</div>
diff --git a/app/assets/javascripts/authentication/password/components/password_input.vue b/app/assets/javascripts/authentication/password/components/password_input.vue
index 6e3af96cf33..7f2a2beaa47 100644
--- a/app/assets/javascripts/authentication/password/components/password_input.vue
+++ b/app/assets/javascripts/authentication/password/components/password_input.vue
@@ -27,11 +27,6 @@ export default {
required: false,
default: null,
},
- qaSelector: {
- type: String,
- required: false,
- default: null,
- },
testid: {
type: String,
required: false,
@@ -80,7 +75,6 @@ export default {
:autocomplete="autocomplete"
:name="name"
:minlength="minimumPasswordLength"
- :data-qa-selector="qaSelector"
:data-testid="testid"
:title="title"
:type="type"
diff --git a/app/assets/javascripts/authentication/password/index.js b/app/assets/javascripts/authentication/password/index.js
index a4f2d038cf7..903512a7b53 100644
--- a/app/assets/javascripts/authentication/password/index.js
+++ b/app/assets/javascripts/authentication/password/index.js
@@ -9,7 +9,7 @@ export const initPasswordInput = () => {
}
const { form } = el;
- const { title, id, minimumPasswordLength, qaSelector, testid, autocomplete, name } = el.dataset;
+ const { title, id, minimumPasswordLength, testid, autocomplete, name } = el.dataset;
// eslint-disable-next-line no-new
new Vue({
@@ -21,7 +21,6 @@ export const initPasswordInput = () => {
title,
id,
minimumPasswordLength,
- qaSelector,
testid,
autocomplete,
name,
diff --git a/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue b/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue
index 907b68e6ffc..e97846bae29 100644
--- a/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue
+++ b/app/assets/javascripts/authentication/two_factor_auth/components/manage_two_factor_form.vue
@@ -119,7 +119,6 @@ export default {
type="password"
name="current_password"
:state="currentPasswordState"
- data-qa-selector="current_password_field"
/>
</gl-form-group>
diff --git a/app/assets/javascripts/behaviors/index.js b/app/assets/javascripts/behaviors/index.js
index 84ff8fa7f33..fe3868fdd04 100644
--- a/app/assets/javascripts/behaviors/index.js
+++ b/app/assets/javascripts/behaviors/index.js
@@ -6,9 +6,9 @@ import installGlEmojiElement from './gl_emoji';
import initCopyAsGFM from './markdown/copy_as_gfm';
import './quick_submit';
import './requires_input';
-import initPageShortcuts from './shortcuts';
import { initToastMessages } from './toasts';
import { initGlobalAlerts } from './global_alerts';
+import './shortcuts';
import './toggler_behavior';
import './preview_markdown';
@@ -17,7 +17,6 @@ installGlEmojiElement();
initCopyAsGFM();
initCopyToClipboard();
-initPageShortcuts();
initCollapseSidebarOnWindowResize();
initToastMessages();
diff --git a/app/assets/javascripts/behaviors/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts.js
deleted file mode 100644
index 22a8be92e52..00000000000
--- a/app/assets/javascripts/behaviors/shortcuts.js
+++ /dev/null
@@ -1,36 +0,0 @@
-export default function initPageShortcuts() {
- const { page } = document.body.dataset;
- const pagesWithCustomShortcuts = [
- 'projects:activity',
- 'projects:artifacts:browse',
- 'projects:artifacts:file',
- 'projects:blame:show',
- 'projects:blob:show',
- 'projects:commit:show',
- 'projects:commits:show',
- 'projects:find_file:show',
- 'projects:issues:edit',
- 'projects:issues:index',
- 'projects:issues:new',
- 'projects:issues:show',
- 'projects:merge_requests:creations:diffs',
- 'projects:merge_requests:creations:new',
- 'projects:merge_requests:edit',
- 'projects:merge_requests:index',
- 'projects:merge_requests:show',
- 'projects:network:show',
- 'projects:show',
- 'projects:tree:show',
- 'groups:show',
- ];
-
- // the pages above have their own shortcuts sub-classes instantiated elsewhere
- // TODO: replace this whitelist with something more automated/maintainable
- // https://gitlab.com/gitlab-org/gitlab/-/issues/392845
- if (page && !pagesWithCustomShortcuts.includes(page)) {
- import(/* webpackChunkName: 'shortcutsBundle' */ './shortcuts/shortcuts')
- .then(({ default: Shortcuts }) => new Shortcuts())
- .catch(() => {});
- }
- return false;
-}
diff --git a/app/assets/javascripts/behaviors/shortcuts/index.js b/app/assets/javascripts/behaviors/shortcuts/index.js
new file mode 100644
index 00000000000..cc6d8a23f68
--- /dev/null
+++ b/app/assets/javascripts/behaviors/shortcuts/index.js
@@ -0,0 +1,16 @@
+const shortcutsPromise = import(/* webpackChunkName: 'shortcutsBundle' */ './shortcuts')
+ .then(({ default: Shortcuts }) => new Shortcuts())
+ .catch(() => {});
+
+export const addShortcutsExtension = (ShortcutExtension, ...args) =>
+ shortcutsPromise.then((shortcuts) => shortcuts.addExtension(ShortcutExtension, args));
+
+export const resetShortcutsForTests = async () => {
+ if (process.env.NODE_ENV === 'test') {
+ const { Mousetrap, clearStopCallbacksForTests } = await import('~/lib/mousetrap');
+ clearStopCallbacksForTests();
+ Mousetrap.reset();
+ const shortcuts = await shortcutsPromise;
+ shortcuts.extensions.clear();
+ }
+};
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index 1d6819d4b04..e05694c0907 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -63,11 +63,17 @@ function getToolbarBtnToShortcutsMap($textarea) {
export default class Shortcuts {
constructor() {
+ if (process.env.NODE_ENV !== 'production' && this.constructor !== Shortcuts) {
+ // eslint-disable-next-line @gitlab/require-i18n-strings
+ throw new Error('Shortcuts cannot be subclassed.');
+ }
+
+ this.extensions = new Map();
this.onToggleHelp = this.onToggleHelp.bind(this);
this.helpModalElement = null;
this.helpModalVueInstance = null;
- this.bindCommands([
+ this.addAll([
[TOGGLE_KEYBOARD_SHORTCUTS_DIALOG, this.onToggleHelp],
[START_SEARCH, Shortcuts.focusSearch],
[FOCUS_FILTER_BAR, this.focusFilter.bind(this)],
@@ -94,16 +100,12 @@ export default class Shortcuts {
const findFileURL = document.body.dataset.findFile;
if (typeof findFileURL !== 'undefined' && findFileURL !== null) {
- this.bindCommand(GO_TO_PROJECT_FIND_FILE, () => {
+ this.add(GO_TO_PROJECT_FIND_FILE, () => {
visitUrl(findFileURL);
});
}
- const shortcutsModalTriggerEvent = 'click.shortcutsModalTrigger';
- // eslint-disable-next-line @gitlab/no-global-event-off
- $(document)
- .off(shortcutsModalTriggerEvent)
- .on(shortcutsModalTriggerEvent, '.js-shortcuts-modal-trigger', this.onToggleHelp);
+ $(document).on('click', '.js-shortcuts-modal-trigger', this.onToggleHelp);
if (shouldDisableShortcuts()) {
disableShortcuts();
@@ -111,6 +113,62 @@ export default class Shortcuts {
}
/**
+ * Instantiate a legacy shortcut extension class.
+ *
+ * NOTE: The preferred approach for adding shortcuts is described in
+ * https://docs.gitlab.com/ee/development/fe_guide/keyboard_shortcuts.html.
+ * This method is only for existing legacy shortcut classes.
+ *
+ * A shortcut extension class packages up several shortcuts and behaviors for
+ * a page or set of pages. They are considered legacy because they usually do
+ * not follow modern best practices. For instance, they may hook into the UI
+ * in brittle ways, e.g.. querySelectors.
+ *
+ * Extension classes can declare dependencies on other shortcut extension
+ * classes by listing them in a static `dependencies` property. This is
+ * essentially a reimplementation of the previous subclassing approach, but
+ * with idempotency: a shortcut extension class can now only be added at most
+ * one time.
+ *
+ * Extension classes are instantiated and given the Shortcuts singleton
+ * instance as their first argument. If the class constructor needs
+ * additional arguments, pass them via the second argument as an array.
+ *
+ * See https://gitlab.com/gitlab-org/gitlab/-/issues/392845 for more context.
+ *
+ * @param {Function} Extension The extension class to add/instantiate.
+ * @param {Array} [args] A list of additional args to pass to the extension
+ * class constructor.
+ * @param {Set} [extensionsCurrentlyLoading] For internal use only. Do not
+ * use.
+ * @returns The instantiated shortcut extension class.
+ */
+ addExtension(Extension, args = [], extensionsCurrentlyLoading = new Set()) {
+ extensionsCurrentlyLoading.add(Extension);
+
+ let instance = this.extensions.get(Extension);
+ if (!instance) {
+ for (const Dep of Extension.dependencies ?? []) {
+ if (extensionsCurrentlyLoading.has(Dep) || Dep === Shortcuts) {
+ // We've encountered a circular dependency, so stop recursing.
+ // eslint-disable-next-line no-continue
+ continue;
+ }
+
+ extensionsCurrentlyLoading.add(Dep);
+
+ this.addExtension(Dep, [], extensionsCurrentlyLoading);
+ }
+
+ instance = new Extension(this, ...args);
+ this.extensions.set(Extension, instance);
+ }
+
+ extensionsCurrentlyLoading.delete(Extension);
+ return instance;
+ }
+
+ /**
* Bind the keyboard shortcut(s) defined by the given command to the given
* callback.
*
@@ -120,7 +178,7 @@ export default class Shortcuts {
* @returns {void}
*/
// eslint-disable-next-line class-methods-use-this
- bindCommand(command, callback) {
+ add(command, callback) {
Mousetrap.bind(keysFor(command), callback);
}
@@ -132,8 +190,8 @@ export default class Shortcuts {
* command/callback pairs.
* @returns {void}
*/
- bindCommands(commandsAndCallbacks) {
- commandsAndCallbacks.forEach((commandAndCallback) => this.bindCommand(...commandAndCallback));
+ addAll(commandsAndCallbacks) {
+ commandsAndCallbacks.forEach((commandAndCallback) => this.add(...commandAndCallback));
}
onToggleHelp(e) {
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
index 65ae67d156f..a0bfd337d10 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_blob.js
@@ -7,7 +7,6 @@ import {
getShaFromUrl,
} from '~/lib/utils/url_utility';
import { updateRefPortionOfTitle } from '~/repository/utils/title';
-import Shortcuts from './shortcuts';
const defaults = {
fileBlobPermalinkUrl: null,
@@ -19,15 +18,14 @@ function eventHasModifierKeys(event) {
return event.ctrlKey || event.metaKey || event.shiftKey;
}
-export default class ShortcutsBlob extends Shortcuts {
- constructor(opts) {
+export default class ShortcutsBlob {
+ constructor(shortcuts, opts) {
const options = { ...defaults, ...opts };
- super();
this.options = options;
this.shortcircuitPermalinkButton();
- this.bindCommand(PROJECT_FILES_GO_TO_PERMALINK, this.moveToFilePermalink.bind(this));
+ shortcuts.add(PROJECT_FILES_GO_TO_PERMALINK, this.moveToFilePermalink.bind(this));
}
moveToFilePermalink() {
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
index f26878cf161..393d0165a07 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_find_file.js
@@ -8,10 +8,8 @@ import {
import { addStopCallback } from '~/lib/mousetrap';
import ShortcutsNavigation from './shortcuts_navigation';
-export default class ShortcutsFindFile extends ShortcutsNavigation {
- constructor(projectFindFile) {
- super();
-
+export default class ShortcutsFindFile {
+ constructor(shortcuts, projectFindFile) {
addStopCallback((e, element, combo) => {
if (
element === projectFindFile.inputElement[0] &&
@@ -28,11 +26,13 @@ export default class ShortcutsFindFile extends ShortcutsNavigation {
return undefined;
});
- this.bindCommands([
+ shortcuts.addAll([
[PROJECT_FILES_MOVE_SELECTION_UP, projectFindFile.selectRowUp],
[PROJECT_FILES_MOVE_SELECTION_DOWN, projectFindFile.selectRowDown],
[PROJECT_FILES_GO_BACK, projectFindFile.goToTree],
[PROJECT_FILES_OPEN_SELECTION, projectFindFile.goToBlob],
]);
}
+
+ static dependencies = [ShortcutsNavigation];
}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
index b0e515ac19d..cde6d59b210 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_issuable.js
@@ -16,12 +16,9 @@ import {
MR_COPY_SOURCE_BRANCH_NAME,
ISSUABLE_COPY_REF,
} from './keybindings';
-import Shortcuts from './shortcuts';
-
-export default class ShortcutsIssuable extends Shortcuts {
- constructor() {
- super();
+export default class ShortcutsIssuable {
+ constructor(shortcuts) {
this.branchInMemoryButton = document.createElement('button');
this.branchClipboardInstance = new ClipboardJS(this.branchInMemoryButton);
this.branchClipboardInstance.on('success', () => {
@@ -40,7 +37,7 @@ export default class ShortcutsIssuable extends Shortcuts {
toast(s__('GlobalShortcuts|Unable to copy the reference at this time.'));
});
- this.bindCommands([
+ shortcuts.addAll([
[ISSUE_MR_CHANGE_ASSIGNEE, () => ShortcutsIssuable.openSidebarDropdown('assignee')],
[ISSUE_MR_CHANGE_MILESTONE, () => ShortcutsIssuable.openSidebarDropdown('milestone')],
[ISSUABLE_CHANGE_LABEL, () => ShortcutsIssuable.openSidebarDropdown('labels')],
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
index 4691a4228e6..bae50c02599 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_navigation.js
@@ -21,13 +21,10 @@ import {
PROJECT_FILES_GO_TO_COMPARE,
NEW_ISSUE,
} from './keybindings';
-import Shortcuts from './shortcuts';
-export default class ShortcutsNavigation extends Shortcuts {
- constructor() {
- super();
-
- this.bindCommands([
+export default class ShortcutsNavigation {
+ constructor(shortcuts) {
+ shortcuts.addAll([
[GO_TO_PROJECT_OVERVIEW, () => findAndFollowLink('.shortcuts-project')],
[GO_TO_PROJECT_ACTIVITY_FEED, () => findAndFollowLink('.shortcuts-project-activity')],
[GO_TO_PROJECT_RELEASES, () => findAndFollowLink('.shortcuts-deployments-releases')],
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js
index 02c6af53fc2..eee8c1acf1a 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_network.js
@@ -8,11 +8,9 @@ import {
} from './keybindings';
import ShortcutsNavigation from './shortcuts_navigation';
-export default class ShortcutsNetwork extends ShortcutsNavigation {
- constructor(graph) {
- super();
-
- this.bindCommands([
+export default class ShortcutsNetwork {
+ constructor(shortcuts, graph) {
+ shortcuts.addAll([
[REPO_GRAPH_SCROLL_LEFT, graph.scrollLeft],
[REPO_GRAPH_SCROLL_RIGHT, graph.scrollRight],
[REPO_GRAPH_SCROLL_UP, graph.scrollUp],
@@ -21,4 +19,6 @@ export default class ShortcutsNetwork extends ShortcutsNavigation {
[REPO_GRAPH_SCROLL_BOTTOM, graph.scrollBottom],
]);
}
+
+ static dependencies = [ShortcutsNavigation];
}
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
index 62d612cfa6d..5f45331bf76 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
@@ -2,13 +2,13 @@ import findAndFollowLink from '~/lib/utils/navigation_utility';
import { EDIT_WIKI_PAGE } from './keybindings';
import ShortcutsNavigation from './shortcuts_navigation';
-export default class ShortcutsWiki extends ShortcutsNavigation {
- constructor() {
- super();
-
- this.bindCommand(EDIT_WIKI_PAGE, ShortcutsWiki.editWiki);
+export default class ShortcutsWiki {
+ constructor(shortcuts) {
+ shortcuts.add(EDIT_WIKI_PAGE, ShortcutsWiki.editWiki);
}
+ static dependencies = [ShortcutsNavigation];
+
static editWiki() {
findAndFollowLink('.js-wiki-edit');
}
diff --git a/app/assets/javascripts/blob/filepath_form/components/template_selector.vue b/app/assets/javascripts/blob/filepath_form/components/template_selector.vue
index 379d5e38197..e9f54639fdd 100644
--- a/app/assets/javascripts/blob/filepath_form/components/template_selector.vue
+++ b/app/assets/javascripts/blob/filepath_form/components/template_selector.vue
@@ -149,7 +149,6 @@ export default {
block
class="gl-font-regular"
data-testid="template-selector"
- data-qa-selector="template_selector"
:toggle-text="dropdownToggleText"
:search-placeholder="$options.i18n.searchPlaceholder"
:items="dropdownItems"
diff --git a/app/assets/javascripts/issues/index.js b/app/assets/javascripts/issues/index.js
index eea5207801c..b7b39d0ce08 100644
--- a/app/assets/javascripts/issues/index.js
+++ b/app/assets/javascripts/issues/index.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import IssuableForm from 'ee_else_ce/issuable/issuable_form';
import IssuableLabelSelector from '~/issuable/issuable_label_selector';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import { initIssuableSidebar } from '~/issuable';
@@ -22,7 +23,7 @@ export function initForm() {
new IssuableForm($('.issue-form')); // eslint-disable-line no-new
IssuableLabelSelector();
new LabelsSelect(); // eslint-disable-line no-new
- new ShortcutsNavigation(); // eslint-disable-line no-new
+ addShortcutsExtension(ShortcutsNavigation);
initTitleSuggestions();
initTypePopover();
@@ -32,7 +33,7 @@ export function initForm() {
export function initShow() {
new Issue(); // eslint-disable-line no-new
- new ShortcutsIssuable(); // eslint-disable-line no-new
+ addShortcutsExtension(ShortcutsIssuable);
new ZenMode(); // eslint-disable-line no-new
initAwardsApp(document.getElementById('js-vue-awards-block'));
diff --git a/app/assets/javascripts/ml/model_registry/components/model_version_detail.vue b/app/assets/javascripts/ml/model_registry/components/model_version_detail.vue
index 19d91df43b2..8d3e8cf2023 100644
--- a/app/assets/javascripts/ml/model_registry/components/model_version_detail.vue
+++ b/app/assets/javascripts/ml/model_registry/components/model_version_detail.vue
@@ -1,12 +1,15 @@
<script>
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { TYPENAME_PACKAGES_PACKAGE } from '~/graphql_shared/constants';
+import * as i18n from '../translations';
+import CandidateDetail from './candidate_detail.vue';
export default {
name: 'ModelVersionDetail',
components: {
PackageFiles: () =>
import('~/packages_and_registries/package_registry/components/details/package_files.vue'),
+ CandidateDetail,
},
props: {
modelVersion: {
@@ -25,14 +28,21 @@ export default {
return 'ml_model';
},
},
+ i18n,
};
</script>
<template>
<div>
- <p>
+ <h3 class="gl-font-lg gl-mt-5">{{ $options.i18n.DESCRIPTION_LABEL }}</h3>
+
+ <div v-if="modelVersion.description">
{{ modelVersion.description }}
- </p>
+ </div>
+ <div v-else class="gl-text-secondary">
+ {{ $options.i18n.NO_DESCRIPTION_PROVIDED_LABEL }}
+ </div>
+
<template v-if="modelVersion.packageId">
<package-files
:package-id="packageId"
@@ -40,5 +50,12 @@ export default {
:package-type="packageType"
/>
</template>
+
+ <div class="gl-mt-5">
+ <span class="gl-font-weight-bold">{{ $options.i18n.MLFLOW_ID_LABEL }}:</span>
+ {{ modelVersion.candidate.info.eid }}
+ </div>
+
+ <candidate-detail :candidate="modelVersion.candidate" :show-info-section="false" />
</div>
</template>
diff --git a/app/assets/javascripts/ml/model_registry/translations.js b/app/assets/javascripts/ml/model_registry/translations.js
index bbafde0b943..f4ff0f4aa97 100644
--- a/app/assets/javascripts/ml/model_registry/translations.js
+++ b/app/assets/javascripts/ml/model_registry/translations.js
@@ -15,6 +15,8 @@ export const NO_MODELS_LABEL = s__('MlModelRegistry|No models registered in this
export const modelsCountLabel = (modelCount) =>
n__('MlModelRegistry|%d model', 'MlModelRegistry|%d models', modelCount);
+export const DESCRIPTION_LABEL = __('Description');
+export const NO_DESCRIPTION_PROVIDED_LABEL = s__('MlModelRegistry|No description provided');
export const INFO_LABEL = s__('MlModelRegistry|Info');
export const ID_LABEL = s__('MlModelRegistry|ID');
export const MLFLOW_ID_LABEL = s__('MlModelRegistry|MLflow run ID');
diff --git a/app/assets/javascripts/pages/groups/boards/index.js b/app/assets/javascripts/pages/groups/boards/index.js
index 23f5b083589..a591fed3d9b 100644
--- a/app/assets/javascripts/pages/groups/boards/index.js
+++ b/app/assets/javascripts/pages/groups/boards/index.js
@@ -1,5 +1,6 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import initBoards from '~/boards';
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
initBoards();
diff --git a/app/assets/javascripts/pages/groups/shared/group_details.js b/app/assets/javascripts/pages/groups/shared/group_details.js
index 5d9eafe5672..46040cd6706 100644
--- a/app/assets/javascripts/pages/groups/shared/group_details.js
+++ b/app/assets/javascripts/pages/groups/shared/group_details.js
@@ -1,10 +1,11 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import initInviteMembersBanner from '~/groups/init_invite_members_banner';
import initNotificationsDropdown from '~/notifications';
import ProjectsList from '~/projects_list';
export default function initGroupDetails() {
- new ShortcutsNavigation(); // eslint-disable-line no-new
+ addShortcutsExtension(ShortcutsNavigation);
initNotificationsDropdown();
diff --git a/app/assets/javascripts/pages/projects/activity/index.js b/app/assets/javascripts/pages/projects/activity/index.js
index 03fbad0f1ec..3138026e1db 100644
--- a/app/assets/javascripts/pages/projects/activity/index.js
+++ b/app/assets/javascripts/pages/projects/activity/index.js
@@ -1,5 +1,6 @@
import Activities from '~/activities';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
new Activities(); // eslint-disable-line no-new
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
diff --git a/app/assets/javascripts/pages/projects/artifacts/browse/index.js b/app/assets/javascripts/pages/projects/artifacts/browse/index.js
index 60680ec7d1d..47cf348eb4d 100644
--- a/app/assets/javascripts/pages/projects/artifacts/browse/index.js
+++ b/app/assets/javascripts/pages/projects/artifacts/browse/index.js
@@ -1,5 +1,6 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import BuildArtifacts from '~/build_artifacts';
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
new BuildArtifacts(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/artifacts/file/index.js b/app/assets/javascripts/pages/projects/artifacts/file/index.js
index 07ee4d686cc..3bc3b9dabbc 100644
--- a/app/assets/javascripts/pages/projects/artifacts/file/index.js
+++ b/app/assets/javascripts/pages/projects/artifacts/file/index.js
@@ -1,5 +1,6 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import { BlobViewer } from '~/blob/viewer/index';
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
new BlobViewer(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/boards/index.js b/app/assets/javascripts/pages/projects/boards/index.js
index 23f5b083589..a591fed3d9b 100644
--- a/app/assets/javascripts/pages/projects/boards/index.js
+++ b/app/assets/javascripts/pages/projects/boards/index.js
@@ -1,5 +1,6 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import initBoards from '~/boards';
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
initBoards();
diff --git a/app/assets/javascripts/pages/projects/commit/show/index.js b/app/assets/javascripts/pages/projects/commit/show/index.js
index c9f5895c7a3..d875f28433e 100644
--- a/app/assets/javascripts/pages/projects/commit/show/index.js
+++ b/app/assets/javascripts/pages/projects/commit/show/index.js
@@ -2,6 +2,7 @@
import $ from 'jquery';
import Vue from 'vue';
import loadAwardsHandler from '~/awards_handler';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import Diff from '~/diff';
import { createAlert } from '~/alert';
@@ -20,7 +21,7 @@ import { initReportAbuse } from '~/projects/report_abuse';
initDiffStatsDropdown();
new ZenMode();
-new ShortcutsNavigation();
+addShortcutsExtension(ShortcutsNavigation);
initCommitBoxInfo();
diff --git a/app/assets/javascripts/pages/projects/commits/show/index.js b/app/assets/javascripts/pages/projects/commits/show/index.js
index f5ecf9be591..e3b22bbfee0 100644
--- a/app/assets/javascripts/pages/projects/commits/show/index.js
+++ b/app/assets/javascripts/pages/projects/commits/show/index.js
@@ -1,10 +1,11 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import CommitsList from '~/commits';
import GpgBadges from '~/gpg_badges';
import { mountCommits, initCommitsRefSwitcher } from '~/projects/commits';
new CommitsList(document.querySelector('.js-project-commits-show').dataset.commitsLimit); // eslint-disable-line no-new
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
GpgBadges.fetch();
mountCommits(document.getElementById('js-author-dropdown'));
initCommitsRefSwitcher();
diff --git a/app/assets/javascripts/pages/projects/find_file/show/index.js b/app/assets/javascripts/pages/projects/find_file/show/index.js
index 22c21430e8b..4df84ac167c 100644
--- a/app/assets/javascripts/pages/projects/find_file/show/index.js
+++ b/app/assets/javascripts/pages/projects/find_file/show/index.js
@@ -1,4 +1,5 @@
import $ from 'jquery';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsFindFile from '~/behaviors/shortcuts/shortcuts_find_file';
import ProjectFindFile from '~/projects/project_find_file';
import InitBlobRefSwitcher from '../ref_switcher';
@@ -11,4 +12,4 @@ const projectFindFile = new ProjectFindFile($('.file-finder-holder'), {
refType: findElement.dataset.refType,
});
projectFindFile.load(findElement.dataset.fileFindUrl);
-new ShortcutsFindFile(projectFindFile); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsFindFile, projectFindFile);
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index 1075241e172..dc00036864f 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,5 +1,6 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import Project from './project';
new Project(); // eslint-disable-line no-new
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
diff --git a/app/assets/javascripts/pages/projects/init_blob.js b/app/assets/javascripts/pages/projects/init_blob.js
index 244d1d5590e..6e3e1a35bd2 100644
--- a/app/assets/javascripts/pages/projects/init_blob.js
+++ b/app/assets/javascripts/pages/projects/init_blob.js
@@ -1,3 +1,4 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import BlobForkSuggestion from '~/blob/blob_fork_suggestion';
@@ -18,10 +19,8 @@ export default () => {
const fileBlobPermalinkUrl =
fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
- new ShortcutsNavigation(); // eslint-disable-line no-new
-
- // eslint-disable-next-line no-new
- new ShortcutsBlob({
+ addShortcutsExtension(ShortcutsNavigation);
+ addShortcutsExtension(ShortcutsBlob, {
fileBlobPermalinkUrl,
fileBlobPermalinkUrlElement,
});
diff --git a/app/assets/javascripts/pages/projects/issues/index/index.js b/app/assets/javascripts/pages/projects/issues/index/index.js
index b320d8a61c2..322eaa845ec 100644
--- a/app/assets/javascripts/pages/projects/issues/index/index.js
+++ b/app/assets/javascripts/pages/projects/issues/index/index.js
@@ -1,6 +1,7 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import { mountIssuesListApp, mountJiraIssuesListApp } from '~/issues/list';
mountIssuesListApp();
mountJiraIssuesListApp();
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
diff --git a/app/assets/javascripts/pages/projects/merge_requests/index/index.js b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
index 3ae8018714a..a37c18e41ab 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/index/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/index/index.js
@@ -1,4 +1,5 @@
import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import { FILTERED_SEARCH } from '~/filtered_search/constants';
@@ -16,7 +17,7 @@ initFilteredSearch({
useDefaultState: true,
});
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
initIssuableByEmail();
initCsvImportExportButtons();
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
index 599fd225de9..0e66c3521dd 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request.js
@@ -3,12 +3,13 @@
import $ from 'jquery';
import IssuableForm from 'ee_else_ce/issuable/issuable_form';
import IssuableLabelSelector from '~/issuable/issuable_label_selector';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import LabelsSelect from '~/labels/labels_select';
import { mountMilestoneDropdown } from '~/sidebar/mount_sidebar';
export default () => {
- new ShortcutsNavigation();
+ addShortcutsExtension(ShortcutsNavigation);
new IssuableForm($('.merge-request-form'));
IssuableLabelSelector();
new LabelsSelect();
diff --git a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
index af1635221ab..1cac330520f 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/init_merge_request_show.js
@@ -1,6 +1,7 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { s__ } from '~/locale';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
import { initPipelineCountListener } from '~/commit/pipelines/utils';
import { initIssuableSidebar } from '~/issuable';
@@ -18,7 +19,7 @@ import getStateQuery from './queries/get_state.query.graphql';
export default function initMergeRequestShow(store) {
new ZenMode(); // eslint-disable-line no-new
initPipelineCountListener(document.querySelector('#commit-pipeline-table-view'));
- new ShortcutsIssuable(true); // eslint-disable-line no-new
+ addShortcutsExtension(ShortcutsIssuable);
initSourcegraph();
initIssuableSidebar();
initAwardsApp(document.getElementById('js-vue-awards-block'));
diff --git a/app/assets/javascripts/pages/projects/network/show/index.js b/app/assets/javascripts/pages/projects/network/show/index.js
index a669ea5baaf..58b703bdfda 100644
--- a/app/assets/javascripts/pages/projects/network/show/index.js
+++ b/app/assets/javascripts/pages/projects/network/show/index.js
@@ -1,6 +1,7 @@
import $ from 'jquery';
import Vue from 'vue';
import { visitUrl, joinPaths } from '~/lib/utils/url_utility';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNetwork from '~/behaviors/shortcuts/shortcuts_network';
import RefSelector from '~/ref/components/ref_selector.vue';
import Network from '../network';
@@ -44,6 +45,5 @@ initRefSwitcher();
commit_id: $('.network-graph').attr('data-commit-id'),
});
- // eslint-disable-next-line no-new
- new ShortcutsNetwork(networkGraph.branch_graph);
+ addShortcutsExtension(ShortcutsNetwork, networkGraph.branch_graph);
})();
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 98c58515d24..d0dd27f9fbf 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -1,3 +1,4 @@
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import initClustersDeprecationAlert from '~/projects/clusters_deprecation_alert';
import leaveByUrl from '~/namespaces/leave_by_url';
@@ -38,7 +39,7 @@ leaveByUrl('project');
initVueNotificationsDropdown();
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
initUploadFileTrigger();
initClustersDeprecationAlert();
diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js
index d87f8898c63..edecb798686 100644
--- a/app/assets/javascripts/pages/projects/tree/show/index.js
+++ b/app/assets/javascripts/pages/projects/tree/show/index.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import initTree from 'ee_else_ce/repository';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import NewCommitForm from '~/new_commit_form';
import initAmbiguousRefModal from '~/ref/init_ambiguous_ref_modal';
@@ -7,4 +8,4 @@ import initAmbiguousRefModal from '~/ref/init_ambiguous_ref_modal';
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
initTree();
initAmbiguousRefModal();
-new ShortcutsNavigation(); // eslint-disable-line no-new
+addShortcutsExtension(ShortcutsNavigation);
diff --git a/app/assets/javascripts/pages/shared/wikis/wikis.js b/app/assets/javascripts/pages/shared/wikis/wikis.js
index b32cc700e16..c98cda92a94 100644
--- a/app/assets/javascripts/pages/shared/wikis/wikis.js
+++ b/app/assets/javascripts/pages/shared/wikis/wikis.js
@@ -1,5 +1,6 @@
import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils';
import Tracking from '~/tracking';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsWiki from '~/behaviors/shortcuts/shortcuts_wiki';
const TRACKING_EVENT_NAME = 'view_wiki_page';
@@ -72,6 +73,6 @@ export default class Wikis {
}
static initShortcuts() {
- new ShortcutsWiki(); // eslint-disable-line no-new
+ addShortcutsExtension(ShortcutsWiki);
}
}
diff --git a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
index 0a5fa288828..9aca74c9863 100644
--- a/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
+++ b/app/assets/javascripts/projects/settings/repository/branch_rules/components/branch_rule.vue
@@ -12,8 +12,8 @@ export const i18n = {
statusChecks: s__('BranchRules|%{total} status %{subject}'),
approvalRules: s__('BranchRules|%{total} approval %{subject}'),
matchingBranches: s__('BranchRules|%{total} matching %{subject}'),
- pushAccessLevels: s__('BranchRules|Allowed to merge'),
- mergeAccessLevels: s__('BranchRules|Allowed to push and merge'),
+ pushAccessLevels: s__('BranchRules|Allowed to push and merge'),
+ mergeAccessLevels: s__('BranchRules|Allowed to merge'),
};
export default {
diff --git a/app/assets/javascripts/repository/components/blob_controls.vue b/app/assets/javascripts/repository/components/blob_controls.vue
index 7a64792d476..c64100f4f36 100644
--- a/app/assets/javascripts/repository/components/blob_controls.vue
+++ b/app/assets/javascripts/repository/components/blob_controls.vue
@@ -4,6 +4,7 @@ import { __ } from '~/locale';
import { createAlert } from '~/alert';
import getRefMixin from '~/repository/mixins/get_ref';
import initSourcegraph from '~/sourcegraph';
+import { addShortcutsExtension } from '~/behaviors/shortcuts';
import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob';
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
import { updateElementsVisibility } from '../utils/dom';
@@ -105,8 +106,7 @@ export default {
);
const fileBlobPermalinkUrl =
fileBlobPermalinkUrlElement && fileBlobPermalinkUrlElement.getAttribute('href');
- // eslint-disable-next-line no-new
- new ShortcutsBlob({
+ addShortcutsExtension(ShortcutsBlob, {
fileBlobPermalinkUrl,
fileBlobPermalinkUrlElement,
});
diff --git a/app/assets/javascripts/vue_shared/components/awards_list.vue b/app/assets/javascripts/vue_shared/components/awards_list.vue
index 59f03b41144..3c19df9c196 100644
--- a/app/assets/javascripts/vue_shared/components/awards_list.vue
+++ b/app/assets/javascripts/vue_shared/components/awards_list.vue
@@ -94,14 +94,12 @@ export default {
return awardList.some((award) => award.user.id === this.currentUserId);
},
createAwardList(name, list) {
- const url = list.length ? list[0].url : null;
-
return {
name,
list,
title: this.getAwardListTitle(list, name),
classes: this.getAwardClassBindings(list),
- html: glEmojiTag(name, { url }),
+ html: glEmojiTag(name),
};
},
getAwardListTitle(awardsList, name) {
diff --git a/app/components/projects/ml/show_ml_model_component.rb b/app/components/projects/ml/show_ml_model_component.rb
index 38a81a5837d..03300f01f64 100644
--- a/app/components/projects/ml/show_ml_model_component.rb
+++ b/app/components/projects/ml/show_ml_model_component.rb
@@ -3,10 +3,11 @@
module Projects
module Ml
class ShowMlModelComponent < ViewComponent::Base
- attr_reader :model
+ attr_reader :model, :current_user
- def initialize(model:)
+ def initialize(model:, current_user:)
@model = model.present
+ @current_user = current_user
end
private
@@ -35,7 +36,8 @@ module Projects
version: model_version.version,
description: model_version.description,
project_path: project_path(model_version.project),
- package_id: model_version.package_id
+ package_id: model_version.package_id,
+ **::Ml::CandidateDetailsPresenter.new(model_version.candidate, current_user).present
}
end
end
diff --git a/app/components/projects/ml/show_ml_model_version_component.rb b/app/components/projects/ml/show_ml_model_version_component.rb
index 0e39a1cbcc6..a4c641f6d66 100644
--- a/app/components/projects/ml/show_ml_model_version_component.rb
+++ b/app/components/projects/ml/show_ml_model_version_component.rb
@@ -3,11 +3,12 @@
module Projects
module Ml
class ShowMlModelVersionComponent < ViewComponent::Base
- attr_reader :model_version, :model
+ attr_reader :model_version, :model, :current_user
- def initialize(model_version:)
+ def initialize(model_version:, current_user:)
@model_version = model_version.present
@model = model_version.model.present
+ @current_user = current_user
end
private
@@ -24,12 +25,17 @@ module Projects
model: {
name: model.name,
path: model.path
- }
+ },
+ **candidate_data
}
}
Gitlab::Json.generate(vm.deep_transform_keys { |k| k.to_s.camelize(:lower) })
end
+
+ def candidate_data
+ ::Ml::CandidateDetailsPresenter.new(model_version.candidate, current_user).present
+ end
end
end
end
diff --git a/app/finders/groups/custom_emoji_finder.rb b/app/finders/groups/custom_emoji_finder.rb
new file mode 100644
index 00000000000..80a4e948f8b
--- /dev/null
+++ b/app/finders/groups/custom_emoji_finder.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Groups
+ class CustomEmojiFinder < Base
+ include FinderWithGroupHierarchy
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(group, params = {})
+ @group = group
+ @params = params
+ @skip_authorization = true
+ end
+
+ def execute
+ return CustomEmoji.none if Feature.disabled?(:custom_emoji, group)
+
+ return CustomEmoji.for_resource(group) unless params[:include_ancestor_groups]
+
+ CustomEmoji.for_namespaces(group_ids_for(group))
+ end
+
+ private
+
+ attr_reader :group, :params, :skip_authorization
+ end
+end
diff --git a/app/graphql/resolvers/analytics/cycle_analytics/stages_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/stages_resolver.rb
new file mode 100644
index 00000000000..d14aae7002e
--- /dev/null
+++ b/app/graphql/resolvers/analytics/cycle_analytics/stages_resolver.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Analytics
+ module CycleAnalytics
+ class StagesResolver < BaseResolver
+ type [Types::Analytics::CycleAnalytics::ValueStreams::StageType], null: true
+
+ def resolve
+ response =
+ ::Analytics::CycleAnalytics::Stages::ListService.new(
+ parent: namespace,
+ current_user: current_user,
+ params: { value_stream: object }
+ ).execute
+
+ response[:stages]
+ end
+
+ def namespace
+ object.project.project_namespace
+ end
+ end
+ end
+ end
+end
+
+Resolvers::Analytics::CycleAnalytics::StagesResolver.prepend_mod
diff --git a/app/graphql/resolvers/analytics/cycle_analytics/value_streams_resolver.rb b/app/graphql/resolvers/analytics/cycle_analytics/value_streams_resolver.rb
new file mode 100644
index 00000000000..f3e3da86169
--- /dev/null
+++ b/app/graphql/resolvers/analytics/cycle_analytics/value_streams_resolver.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Resolvers
+ module Analytics
+ module CycleAnalytics
+ class ValueStreamsResolver < BaseResolver
+ type Types::Analytics::CycleAnalytics::ValueStreamType.connection_type, null: true
+
+ def resolve
+ # FOSS only have default value stream available
+ [
+ ::Analytics::CycleAnalytics::ValueStream.build_default_value_stream(object.project_namespace)
+ ]
+ end
+ end
+ end
+ end
+end
+
+Resolvers::Analytics::CycleAnalytics::ValueStreamsResolver.prepend_mod
diff --git a/app/graphql/resolvers/custom_emoji_resolver.rb b/app/graphql/resolvers/custom_emoji_resolver.rb
new file mode 100644
index 00000000000..1e39fafe486
--- /dev/null
+++ b/app/graphql/resolvers/custom_emoji_resolver.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class CustomEmojiResolver < BaseResolver
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ authorizes_object!
+
+ authorize :read_custom_emoji
+
+ argument :include_ancestor_groups,
+ GraphQL::Types::Boolean,
+ required: false,
+ default_value: false,
+ description: 'Includes custom emoji from parent groups.'
+
+ type Types::CustomEmojiType, null: true
+
+ def resolve(**args)
+ Groups::CustomEmojiFinder.new(object, args).execute
+ end
+ end
+end
diff --git a/app/graphql/resolvers/work_items/ancestors_resolver.rb b/app/graphql/resolvers/work_items/ancestors_resolver.rb
index 21e7b097738..efd01700a31 100644
--- a/app/graphql/resolvers/work_items/ancestors_resolver.rb
+++ b/app/graphql/resolvers/work_items/ancestors_resolver.rb
@@ -38,6 +38,8 @@ module Resolvers
end
def preload_resource_parents(work_items)
+ return unless current_user
+
projects = work_items.filter_map(&:project)
namespaces = work_items.filter_map(&:namespace)
group_namespaces = namespaces.select { |n| n.type == ::Group.sti_name }
diff --git a/app/graphql/types/analytics/cycle_analytics/value_stream_type.rb b/app/graphql/types/analytics/cycle_analytics/value_stream_type.rb
index 16ce9b82718..900d2873789 100644
--- a/app/graphql/types/analytics/cycle_analytics/value_stream_type.rb
+++ b/app/graphql/types/analytics/cycle_analytics/value_stream_type.rb
@@ -26,6 +26,11 @@ module Types
null: true,
description: 'Project the value stream belongs to, returns empty if it belongs to a group.',
alpha: { milestone: '15.6' }
+
+ field :stages,
+ null: true,
+ resolver: Resolvers::Analytics::CycleAnalytics::StagesResolver,
+ description: 'Value Stream stages.'
end
end
end
diff --git a/app/graphql/types/analytics/cycle_analytics/value_streams/stage_type.rb b/app/graphql/types/analytics/cycle_analytics/value_streams/stage_type.rb
new file mode 100644
index 00000000000..c8fdf8513be
--- /dev/null
+++ b/app/graphql/types/analytics/cycle_analytics/value_streams/stage_type.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Types
+ module Analytics
+ module CycleAnalytics
+ module ValueStreams
+ # rubocop: disable Graphql/AuthorizeTypes -- # Already authorized in parent value stream type.
+ class StageType < BaseObject
+ graphql_name 'ValueStreamStage'
+
+ field :name,
+ GraphQL::Types::String,
+ null: false,
+ description: 'Name of the stage.'
+
+ field :hidden,
+ GraphQL::Types::Boolean,
+ null: false,
+ description: 'Whether the stage is hidden.'
+
+ field :custom,
+ GraphQL::Types::Boolean,
+ null: false,
+ description: 'Whether the stage is customized.'
+
+ field :start_event_identifier,
+ StageEventEnum,
+ null: false,
+ description: 'Start event identifier.'
+
+ field :end_event_identifier,
+ StageEventEnum,
+ null: false,
+ description: 'End event identifier.'
+
+ def start_event_identifier
+ events_enum[object.start_event_identifier]
+ end
+
+ def end_event_identifier
+ events_enum[object.end_event_identifier]
+ end
+
+ def events_enum
+ Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum.with_indifferent_access
+ end
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+ end
+ end
+end
+
+Types::Analytics::CycleAnalytics::ValueStreams::StageType.prepend_mod
diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb
index 74e7f256b44..a4eba3c63ae 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -21,7 +21,8 @@ module Types
field :custom_emoji,
type: Types::CustomEmojiType.connection_type,
null: true,
- description: 'Custom emoji within this namespace.',
+ resolver: Resolvers::CustomEmojiResolver,
+ description: 'Custom emoji in this namespace.',
alpha: { milestone: '13.6' }
field :share_with_group_lock,
@@ -330,10 +331,6 @@ module Types
group.dependency_proxy_setting || group.create_dependency_proxy_setting
end
- def custom_emoji
- object.custom_emoji if Feature.enabled?(:custom_emoji)
- end
-
private
def group
diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb
index 97db338ad1c..befac90ef42 100644
--- a/app/graphql/types/project_type.rb
+++ b/app/graphql/types/project_type.rb
@@ -651,6 +651,11 @@ module Types
description: 'Detailed import status of the project.',
method: :import_state
+ field :value_streams,
+ description: 'Value streams available to the project.',
+ null: true,
+ resolver: Resolvers::Analytics::CycleAnalytics::ValueStreamsResolver
+
def timelog_categories
object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories)
end
diff --git a/app/graphql/types/security/codequality_reports_comparer/degradation_type.rb b/app/graphql/types/security/codequality_reports_comparer/degradation_type.rb
index 7dd47611a2e..d4aca0a3792 100644
--- a/app/graphql/types/security/codequality_reports_comparer/degradation_type.rb
+++ b/app/graphql/types/security/codequality_reports_comparer/degradation_type.rb
@@ -35,7 +35,7 @@ module Types
description: 'URL to the file along with line number.'
field :engine_name, GraphQL::Types::String,
- null: false,
+ null: true,
description: 'Code quality plugin that reported the degradation.'
end
# rubocop: enable Graphql/AuthorizeTypes
diff --git a/app/models/analytics/cycle_analytics/value_stream.rb b/app/models/analytics/cycle_analytics/value_stream.rb
index 681d5828906..4d1d764755e 100644
--- a/app/models/analytics/cycle_analytics/value_stream.rb
+++ b/app/models/analytics/cycle_analytics/value_stream.rb
@@ -42,8 +42,11 @@ module Analytics
namespace.project
end
- def at_group_level?
- project.nil?
+ def to_global_id
+ return super if persisted?
+
+ # Returns default name as id for built in value stream at FOSS level
+ name
end
private
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 16991937e2f..bb1368f6dc1 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -110,6 +110,7 @@ module ApplicationSettingImplementation
housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10,
import_sources: Settings.gitlab['import_sources'],
+ instance_level_ai_beta_features_enabled: false,
instance_level_code_suggestions_enabled: false,
invisible_captcha_enabled: false,
issues_create_limit: 300,
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index 4dc26a2d197..9c1005e19c7 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -66,7 +66,8 @@ class AwardEmoji < ApplicationRecord
def url
return if TanukiEmoji.find_by_alpha_code(name)
- CustomEmoji.for_resource(resource_parent).by_name(name).select(:url).first&.url
+ Groups::CustomEmojiFinder.new(resource_parent, { include_ancestor_groups: true }).execute
+ .by_name(name)&.select(:url)&.first&.url
end
def expire_cache
diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb
index c704795130b..a3318cd0bd8 100644
--- a/app/models/custom_emoji.rb
+++ b/app/models/custom_emoji.rb
@@ -25,20 +25,33 @@ class CustomEmoji < ApplicationRecord
format: { with: /\A#{NAME_REGEXP}\z/o }
scope :by_name, -> (names) { where(name: names) }
+ scope :for_namespaces, -> (namespace_ids) do
+ order = Gitlab::Pagination::Keyset::Order.build([
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'name',
+ order_expression: CustomEmoji.arel_table[:name].asc,
+ nullable: :not_nullable,
+ distinct: true
+ ),
+ Gitlab::Pagination::Keyset::ColumnOrderDefinition.new(
+ attribute_name: 'current_namespace',
+ order_expression: Arel.sql("CASE WHEN namespace_id = #{namespace_ids.first} THEN 0 ELSE 1 END").asc,
+ nullable: :not_nullable,
+ add_to_projections: true
+ )
+ ])
+ where(namespace_id: namespace_ids)
+ .select("DISTINCT ON (name) *")
+ .order(order)
+ end
alias_attribute :url, :file # this might need a change in https://gitlab.com/gitlab-org/gitlab/-/issues/230467
- # Find custom emoji for the given resource.
- # A resource can be either a Project or a Group, or anything responding to #root_ancestor.
- # Usually it's the return value of #resource_parent on any model.
scope :for_resource, -> (resource) do
- return none if resource.nil?
-
- namespace = resource.root_ancestor
-
- return none if namespace.nil? || Feature.disabled?(:custom_emoji, namespace)
+ return none if resource.nil? || Feature.disabled?(:custom_emoji, resource)
+ return none unless resource.is_a?(Group)
- namespace.custom_emoji
+ resource.custom_emoji
end
private
diff --git a/app/serializers/award_emoji_entity.rb b/app/serializers/award_emoji_entity.rb
index 6ca782d8203..212931a2fa9 100644
--- a/app/serializers/award_emoji_entity.rb
+++ b/app/serializers/award_emoji_entity.rb
@@ -3,5 +3,4 @@
class AwardEmojiEntity < Grape::Entity
expose :name
expose :user, using: API::Entities::UserSafe
- expose :url
end
diff --git a/app/services/ml/find_or_create_model_version_service.rb b/app/services/ml/find_or_create_model_version_service.rb
index a5e9bf997cc..1c6f5bb96dd 100644
--- a/app/services/ml/find_or_create_model_version_service.rb
+++ b/app/services/ml/find_or_create_model_version_service.rb
@@ -15,10 +15,12 @@ module Ml
model_version = Ml::ModelVersion.find_or_create!(model, @version, @package, @description)
- model_version.candidate = ::Ml::CreateCandidateService.new(
- model.default_experiment,
- { model_version: model_version }
- ).execute
+ unless model_version.candidate
+ model_version.candidate = ::Ml::CreateCandidateService.new(
+ model.default_experiment,
+ { model_version: model_version }
+ ).execute
+ end
model_version
end
diff --git a/app/validators/gitlab/emoji_name_validator.rb b/app/validators/gitlab/emoji_name_validator.rb
index c034a79214b..68743530d83 100644
--- a/app/validators/gitlab/emoji_name_validator.rb
+++ b/app/validators/gitlab/emoji_name_validator.rb
@@ -25,8 +25,12 @@ module Gitlab
def valid_custom_emoji?(record, value)
resource = record.try(:resource_parent)
+ namespace = resource.try(:namespace)
- CustomEmoji.for_resource(resource).by_name(value.to_s).any?
+ return unless resource.is_a?(Group) || namespace.is_a?(Group)
+
+ Groups::CustomEmojiFinder.new(resource, { include_ancestor_groups: true }).execute
+ .by_name(value.to_s).any?
end
end
end
diff --git a/app/views/admin/application_settings/general.html.haml b/app/views/admin/application_settings/general.html.haml
index d84fbe94f65..0ba883872a1 100644
--- a/app/views/admin/application_settings/general.html.haml
+++ b/app/views/admin/application_settings/general.html.haml
@@ -120,4 +120,7 @@
= render_if_exists 'admin/application_settings/add_license'
= render 'admin/application_settings/jira_connect'
= render 'admin/application_settings/slack'
-= render_if_exists 'admin/application_settings/ai_access'
+- if Feature.enabled?(:updated_ai_powered_features_menu_for_sm)
+ = render_if_exists 'admin/application_settings/ai_powered'
+- else
+ = render_if_exists 'admin/application_settings/ai_access'
diff --git a/app/views/projects/ml/model_versions/show.html.haml b/app/views/projects/ml/model_versions/show.html.haml
index 0b3d5462a89..1b4bdd29842 100644
--- a/app/views/projects/ml/model_versions/show.html.haml
+++ b/app/views/projects/ml/model_versions/show.html.haml
@@ -3,4 +3,4 @@
- breadcrumb_title @model_version.version
- page_title "#{@model_version.name} / #{@model_version.version}"
-= render(Projects::Ml::ShowMlModelVersionComponent.new(model_version: @model_version))
+= render(Projects::Ml::ShowMlModelVersionComponent.new(model_version: @model_version, current_user: current_user))
diff --git a/app/views/projects/ml/models/show.html.haml b/app/views/projects/ml/models/show.html.haml
index be611e55304..e0067143450 100644
--- a/app/views/projects/ml/models/show.html.haml
+++ b/app/views/projects/ml/models/show.html.haml
@@ -2,4 +2,4 @@
- breadcrumb_title @model.name
- page_title @model.name
-= render(Projects::Ml::ShowMlModelComponent.new(model: @model))
+= render(Projects::Ml::ShowMlModelComponent.new(model: @model, current_user: current_user))
diff --git a/app/workers/users/deactivate_dormant_users_worker.rb b/app/workers/users/deactivate_dormant_users_worker.rb
index 33c54f07521..5cd1d2938ee 100644
--- a/app/workers/users/deactivate_dormant_users_worker.rb
+++ b/app/workers/users/deactivate_dormant_users_worker.rb
@@ -18,8 +18,10 @@ module Users
admin_bot = Users::Internal.admin_bot
return unless admin_bot
- deactivate_users(User.dormant, admin_bot)
- deactivate_users(User.with_no_activity, admin_bot)
+ Gitlab::Auth::CurrentUserMode.bypass_session!(admin_bot.id) do
+ deactivate_users(User.dormant, admin_bot)
+ deactivate_users(User.with_no_activity, admin_bot)
+ end
end
private
diff --git a/config/feature_flags/development/updated_ai_powered_features_menu_for_sm.yml b/config/feature_flags/development/updated_ai_powered_features_menu_for_sm.yml
new file mode 100644
index 00000000000..64377eacd5d
--- /dev/null
+++ b/config/feature_flags/development/updated_ai_powered_features_menu_for_sm.yml
@@ -0,0 +1,8 @@
+---
+name: updated_ai_powered_features_menu_for_sm
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138337
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/433255
+milestone: '16.7'
+type: development
+group: group::cloud connector
+default_enabled: false
diff --git a/config/feature_flags/development/use_sync_service_token_worker.yml b/config/feature_flags/development/use_sync_service_token_worker.yml
index 9adda88ea4e..162387468ae 100644
--- a/config/feature_flags/development/use_sync_service_token_worker.yml
+++ b/config/feature_flags/development/use_sync_service_token_worker.yml
@@ -1,8 +1,8 @@
---
name: use_sync_service_token_worker
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135486
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/136078
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/431608
milestone: '16.7'
type: development
group: group::cloud connector
-default_enabled: true
+default_enabled: false
diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml
index d2477740fba..ea501630e77 100644
--- a/config/gitlab_loose_foreign_keys.yml
+++ b/config/gitlab_loose_foreign_keys.yml
@@ -10,15 +10,9 @@ application_settings:
- table: users
column: usage_stats_set_by_user_id
on_delete: async_nullify
- - table: namespaces
- column: instance_administrators_group_id
- on_delete: async_nullify
- table: projects
column: file_template_project_id
on_delete: async_nullify
- - table: projects
- column: instance_administration_project_id
- on_delete: async_nullify
- table: namespaces
column: custom_project_templates_group_id
on_delete: async_nullify
diff --git a/config/initializers/custom_roles.rb b/config/initializers/custom_roles.rb
new file mode 100644
index 00000000000..e2a61655db6
--- /dev/null
+++ b/config/initializers/custom_roles.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+return unless Gitlab.ee?
+
+Gitlab::CustomRoles::Definition.load_abilities!
diff --git a/db/post_migrate/20231127174335_remove_ignored_application_settings_columns.rb b/db/post_migrate/20231127174335_remove_ignored_application_settings_columns.rb
new file mode 100644
index 00000000000..07cabb93d96
--- /dev/null
+++ b/db/post_migrate/20231127174335_remove_ignored_application_settings_columns.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class RemoveIgnoredApplicationSettingsColumns < Gitlab::Database::Migration[2.2]
+ milestone '16.7'
+
+ disable_ddl_transaction!
+
+ PROJECT_INDEX_NAME = 'index_applicationsettings_on_instance_administration_project_id'
+ GROUP_INDEX_NAME = 'index_application_settings_on_instance_administrators_group_id'
+
+ def up
+ remove_column(:application_settings, :instance_administration_project_id)
+ remove_column(:application_settings, :instance_administrators_group_id)
+ end
+
+ def down
+ unless column_exists?(:users, :instance_administration_project_id)
+ add_column(:application_settings, :instance_administration_project_id, :bigint)
+ end
+
+ unless column_exists?(:users, :instance_administrators_group_id)
+ add_column(:application_settings, :instance_administrators_group_id, :integer)
+ end
+
+ add_concurrent_index(:application_settings, :instance_administration_project_id, name: PROJECT_INDEX_NAME)
+ add_concurrent_index(:application_settings, :instance_administrators_group_id, name: GROUP_INDEX_NAME)
+ end
+end
diff --git a/db/schema_migrations/20231127174335 b/db/schema_migrations/20231127174335
new file mode 100644
index 00000000000..5a47c758a30
--- /dev/null
+++ b/db/schema_migrations/20231127174335
@@ -0,0 +1 @@
+a12b08baa00906fad3acd0f3c0490d1fc6880eb627f7c2cc025edf481c8f9e0b \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 60906ed74e7..b5de92bf170 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -11894,7 +11894,6 @@ CREATE TABLE application_settings (
raw_blob_request_limit integer DEFAULT 300 NOT NULL,
allow_local_requests_from_web_hooks_and_services boolean DEFAULT false NOT NULL,
allow_local_requests_from_system_hooks boolean DEFAULT true NOT NULL,
- instance_administration_project_id bigint,
asset_proxy_enabled boolean DEFAULT false NOT NULL,
asset_proxy_url character varying,
encrypted_asset_proxy_secret_key text,
@@ -11940,7 +11939,6 @@ CREATE TABLE application_settings (
encrypted_slack_app_verification_token_iv character varying(255),
force_pages_access_control boolean DEFAULT false NOT NULL,
updating_name_disabled_for_users boolean DEFAULT false NOT NULL,
- instance_administrators_group_id integer,
elasticsearch_indexed_field_length_limit integer DEFAULT 0 NOT NULL,
elasticsearch_max_bulk_size_mb smallint DEFAULT 10 NOT NULL,
elasticsearch_max_bulk_concurrency smallint DEFAULT 10 NOT NULL,
@@ -31716,16 +31714,12 @@ CREATE INDEX index_application_settings_on_custom_project_templates_group_id ON
CREATE INDEX index_application_settings_on_file_template_project_id ON application_settings USING btree (file_template_project_id);
-CREATE INDEX index_application_settings_on_instance_administrators_group_id ON application_settings USING btree (instance_administrators_group_id);
-
CREATE UNIQUE INDEX index_application_settings_on_push_rule_id ON application_settings USING btree (push_rule_id);
CREATE INDEX index_application_settings_on_usage_stats_set_by_user_id ON application_settings USING btree (usage_stats_set_by_user_id);
CREATE INDEX index_application_settings_web_ide_oauth_application_id ON application_settings USING btree (web_ide_oauth_application_id);
-CREATE INDEX index_applicationsettings_on_instance_administration_project_id ON application_settings USING btree (instance_administration_project_id);
-
CREATE INDEX index_approval_group_rules_groups_on_group_id ON approval_group_rules_groups USING btree (group_id);
CREATE INDEX index_approval_group_rules_on_scan_result_policy_id ON approval_group_rules USING btree (scan_result_policy_id);
diff --git a/doc/administration/package_information/postgresql_versions.md b/doc/administration/package_information/postgresql_versions.md
index 3a499be43b3..76d7244ac3e 100644
--- a/doc/administration/package_information/postgresql_versions.md
+++ b/doc/administration/package_information/postgresql_versions.md
@@ -29,8 +29,9 @@ The lowest supported PostgreSQL versions are listed in the
Read more about update policies and warnings in the PostgreSQL
[upgrade docs](https://docs.gitlab.com/omnibus/settings/database.html#upgrade-packaged-postgresql-server).
-| GitLab version | PostgreSQL versions | Default version for fresh installs | Default version for upgrades | Notes |
+| First GitLab version | PostgreSQL versions | Default version for fresh installs | Default version for upgrades | Notes |
| -------------- | ------------------- | ---------------------------------- | ---------------------------- | ----- |
+| 16.4.3, 16.5.3, 16.6.1 | 13.12, 14.9 | 13.12 | 13.12 | |
| 16.2.0 | 13.11, 14.8 | 13.11 | 13.11 | For upgrades, users can manually upgrade to 14.8 following the [upgrade documentation](https://docs.gitlab.com/omnibus/settings/database.html#gitlab-162-and-later). |
| 16.0.2 | 13.11 | 13.11 | 13.11 | |
| 16.0.0 | 13.8 | 13.8 | 13.8 | |
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index d5e80e0549f..693349cf049 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -13442,6 +13442,29 @@ The edge type for [`UserCore`](#usercore).
| <a id="usercoreedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="usercoreedgenode"></a>`node` | [`UserCore`](#usercore) | The item at the end of the edge. |
+#### `ValueStreamConnection`
+
+The connection type for [`ValueStream`](#valuestream).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="valuestreamconnectionedges"></a>`edges` | [`[ValueStreamEdge]`](#valuestreamedge) | A list of edges. |
+| <a id="valuestreamconnectionnodes"></a>`nodes` | [`[ValueStream]`](#valuestream) | A list of nodes. |
+| <a id="valuestreamconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `ValueStreamEdge`
+
+The edge type for [`ValueStream`](#valuestream).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="valuestreamedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| <a id="valuestreamedgenode"></a>`node` | [`ValueStream`](#valuestream) | The item at the end of the edge. |
+
#### `VulnerabilitiesCountByDayConnection`
The connection type for [`VulnerabilitiesCountByDay`](#vulnerabilitiescountbyday).
@@ -15980,7 +16003,7 @@ Represents a degradation on the compared codequality report.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="codequalityreportscomparerreportdegradationdescription"></a>`description` | [`String!`](#string) | Description of the code quality degradation. |
-| <a id="codequalityreportscomparerreportdegradationenginename"></a>`engineName` | [`String!`](#string) | Code quality plugin that reported the degradation. |
+| <a id="codequalityreportscomparerreportdegradationenginename"></a>`engineName` | [`String`](#string) | Code quality plugin that reported the degradation. |
| <a id="codequalityreportscomparerreportdegradationfilepath"></a>`filePath` | [`String!`](#string) | Relative path to the file containing the code quality degradation. |
| <a id="codequalityreportscomparerreportdegradationfingerprint"></a>`fingerprint` | [`String!`](#string) | Unique fingerprint to identify the code quality degradation. For example, an MD5 hash. |
| <a id="codequalityreportscomparerreportdegradationline"></a>`line` | [`Int!`](#int) | Line on which the code quality degradation occurred. |
@@ -18978,7 +19001,6 @@ GPG signature for a signed commit.
| <a id="groupcontainerrepositoriescount"></a>`containerRepositoriesCount` | [`Int!`](#int) | Number of container repositories in the group. |
| <a id="groupcontainslockedprojects"></a>`containsLockedProjects` | [`Boolean!`](#boolean) | Includes at least one project where the repository size exceeds the limit. This only applies to namespaces under Project limit enforcement. |
| <a id="groupcrossprojectpipelineavailable"></a>`crossProjectPipelineAvailable` | [`Boolean!`](#boolean) | Indicates if the cross_project_pipeline feature is available for the namespace. |
-| <a id="groupcustomemoji"></a>`customEmoji` **{warning-solid}** | [`CustomEmojiConnection`](#customemojiconnection) | **Introduced** in 13.6. This feature is an Experiment. It can be changed or removed at any time. Custom emoji within this namespace. |
| <a id="groupdependencyproxyblobcount"></a>`dependencyProxyBlobCount` | [`Int!`](#int) | Number of dependency proxy blobs cached in the group. |
| <a id="groupdependencyproxyblobs"></a>`dependencyProxyBlobs` | [`DependencyProxyBlobConnection`](#dependencyproxyblobconnection) | Dependency Proxy blobs. (see [Connections](#connections)) |
| <a id="groupdependencyproxyimagecount"></a>`dependencyProxyImageCount` | [`Int!`](#int) | Number of dependency proxy images cached in the group. |
@@ -19027,6 +19049,7 @@ GPG signature for a signed commit.
| <a id="grouptotalrepositorysizeexcess"></a>`totalRepositorySizeExcess` | [`Float`](#float) | Total excess repository size of all projects in the root namespace in bytes. This only applies to namespaces under Project limit enforcement. |
| <a id="grouptwofactorgraceperiod"></a>`twoFactorGracePeriod` | [`Int`](#int) | Time before two-factor authentication is enforced. |
| <a id="groupuserpermissions"></a>`userPermissions` | [`GroupPermissions!`](#grouppermissions) | Permissions for the current user on the resource. |
+| <a id="groupvaluestreams"></a>`valueStreams` | [`ValueStreamConnection`](#valuestreamconnection) | Value streams available to the group. (see [Connections](#connections)) |
| <a id="groupvisibility"></a>`visibility` | [`String`](#string) | Visibility of the namespace. |
| <a id="groupvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the project vulnerabilities of the group and its subgroups. (see [Connections](#connections)) |
| <a id="groupweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the group. |
@@ -19270,6 +19293,26 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="groupcontributionsfrom"></a>`from` | [`ISO8601Date!`](#iso8601date) | Start date of the reporting time range. |
| <a id="groupcontributionsto"></a>`to` | [`ISO8601Date!`](#iso8601date) | End date of the reporting time range. The end date must be within 93 days after the start date. |
+##### `Group.customEmoji`
+
+Custom emoji in this namespace.
+
+WARNING:
+**Introduced** in 13.6.
+This feature is an Experiment. It can be changed or removed at any time.
+
+Returns [`CustomEmojiConnection`](#customemojiconnection).
+
+This field returns a [connection](#connections). It accepts the
+four standard [pagination arguments](#connection-pagination-arguments):
+`before: String`, `after: String`, `first: Int`, `last: Int`.
+
+###### Arguments
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="groupcustomemojiincludeancestorgroups"></a>`includeAncestorGroups` | [`Boolean`](#boolean) | Includes custom emoji from parent groups. |
+
##### `Group.customizableDashboardVisualizations`
Visualizations of the group or associated configuration project.
@@ -23891,6 +23934,7 @@ Represents vulnerability finding of a security report on the pipeline.
| <a id="projecttrackingkey"></a>`trackingKey` **{warning-solid}** | [`String`](#string) | **Introduced** in 16.0. This feature is an Experiment. It can be changed or removed at any time. Tracking key assigned to the project. |
| <a id="projectuseraccessauthorizedagents"></a>`userAccessAuthorizedAgents` | [`ClusterAgentAuthorizationUserAccessConnection`](#clusteragentauthorizationuseraccessconnection) | Authorized cluster agents for the project through user_access keyword. (see [Connections](#connections)) |
| <a id="projectuserpermissions"></a>`userPermissions` | [`ProjectPermissions!`](#projectpermissions) | Permissions for the current user on the resource. |
+| <a id="projectvaluestreams"></a>`valueStreams` | [`ValueStreamConnection`](#valuestreamconnection) | Value streams available to the project. (see [Connections](#connections)) |
| <a id="projectvisibility"></a>`visibility` | [`String`](#string) | Visibility of the project. |
| <a id="projectvulnerabilityimages"></a>`vulnerabilityImages` | [`VulnerabilityContainerImageConnection`](#vulnerabilitycontainerimageconnection) | Container images reported on the project vulnerabilities. (see [Connections](#connections)) |
| <a id="projectvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the project vulnerabilities. (see [Connections](#connections)) |
@@ -27608,6 +27652,7 @@ fields relate to interactions between the two entities.
| <a id="valuestreamname"></a>`name` | [`String!`](#string) | Name of the value stream. |
| <a id="valuestreamnamespace"></a>`namespace` | [`Namespace!`](#namespace) | Namespace the value stream belongs to. |
| <a id="valuestreamproject"></a>`project` **{warning-solid}** | [`Project`](#project) | **Introduced** in 15.6. This feature is an Experiment. It can be changed or removed at any time. Project the value stream belongs to, returns empty if it belongs to a group. |
+| <a id="valuestreamstages"></a>`stages` | [`[ValueStreamStage!]`](#valuestreamstage) | Value Stream stages. |
### `ValueStreamAnalyticsMetric`
@@ -27644,6 +27689,20 @@ Represents a recorded measurement (object count) for the requested group.
| <a id="valuestreammetriclinktypename"></a>`name` | [`String!`](#string) | Name of the link group. |
| <a id="valuestreammetriclinktypeurl"></a>`url` | [`String!`](#string) | Drill-down URL. |
+### `ValueStreamStage`
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| <a id="valuestreamstagecustom"></a>`custom` | [`Boolean!`](#boolean) | Whether the stage is customized. |
+| <a id="valuestreamstageendeventidentifier"></a>`endEventIdentifier` | [`ValueStreamStageEvent!`](#valuestreamstageevent) | End event identifier. |
+| <a id="valuestreamstageendeventlabel"></a>`endEventLabel` | [`Label`](#label) | Label associated with end event. |
+| <a id="valuestreamstagehidden"></a>`hidden` | [`Boolean!`](#boolean) | Whether the stage is hidden. |
+| <a id="valuestreamstagename"></a>`name` | [`String!`](#string) | Name of the stage. |
+| <a id="valuestreamstagestarteventidentifier"></a>`startEventIdentifier` | [`ValueStreamStageEvent!`](#valuestreamstageevent) | Start event identifier. |
+| <a id="valuestreamstagestarteventlabel"></a>`startEventLabel` | [`Label`](#label) | Label associated with start event. |
+
### `VulnerabilitiesCountByDay`
Represents the count of vulnerabilities by severity on a particular day. This data is retained for 365 days.
diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md
index 4ca2a72fddb..6109d4d603d 100644
--- a/doc/development/documentation/styleguide/index.md
+++ b/doc/development/documentation/styleguide/index.md
@@ -189,6 +189,20 @@ Words that indicate you are not writing from a customer perspective are
[allow and enable](word_list.md#allow-enable). Try instead to use
[you](word_list.md#you-your-yours) and to speak directly to the user.
+### Building trust
+
+Product documentation should be focused on providing clear, concise information,
+without the addition of sales or marketing text.
+
+- Do not use words like [easily](word_list.md#easily) or [simply](word_list.md#simply-simple).
+- Do not use marketing phrases like "This feature will save you time and money."
+
+Instead, focus on facts and achievable goals. Be specific. For example:
+
+- The build time can decrease when you use this feature.
+- You can use this feature to save time when you create a project. The API creates the file and you
+ do not need to manually intervene.
+
### Capitalization
As a company, we tend toward lowercase.
diff --git a/doc/update/versions/gitlab_16_changes.md b/doc/update/versions/gitlab_16_changes.md
index 0227eb8b8a9..354a1596a8c 100644
--- a/doc/update/versions/gitlab_16_changes.md
+++ b/doc/update/versions/gitlab_16_changes.md
@@ -102,9 +102,9 @@ Specific information applies to installations using Geo:
**Affected releases**:
| Affected minor releases | Affected patch releases | Fixed in |
- | ------ | ------ | ------ |
- | 16.4 | All | None |
- | 16.5 | All | None |
+ | ----------------------- | ----------------------- | -------- |
+ | 16.4 | 16.4.0 - 16.4.2 | 16.4.3 |
+ | 16.5 | 16.5.0 - 16.5.1 | 16.5.2 |
- After [Group Wiki](../../user/project/wiki/group.md) verification was added in GitLab 16.3, missing Group Wiki repositories are being incorrectly flagged as failing verification. This issue is not a result of an actual replication/verification failure but an invalid internal state for these missing repositories inside Geo and results in errors in the logs and the verification progress reporting a failed state for these Group Wiki repositories.
@@ -232,9 +232,9 @@ Specific information applies to installations using Geo:
**Affected releases**:
| Affected minor releases | Affected patch releases | Fixed in |
- | ------ | ------ | ------ |
- | 16.4 | All | None |
- | 16.5 | All | None |
+ | ----------------------- | ----------------------- | -------- |
+ | 16.4 | 16.4.0 - 16.4.2 | 16.4.3 |
+ | 16.5 | 16.5.0 - 16.5.1 | 16.5.2 |
- An [issue](https://gitlab.com/gitlab-org/gitlab/-/issues/419370) with sync states getting stuck in pending state results in replication being stuck indefinitely for impacted items leading to risk of data loss in the event of a failover. This mostly impact repository syncs but can also can also affect container registry syncs. You are advised to upgrade to a fixed version to avoid risk of data loss.
diff --git a/lib/banzai/filter/custom_emoji_filter.rb b/lib/banzai/filter/custom_emoji_filter.rb
index dddaaebc9de..4dd6bada306 100644
--- a/lib/banzai/filter/custom_emoji_filter.rb
+++ b/lib/banzai/filter/custom_emoji_filter.rb
@@ -48,10 +48,9 @@ module Banzai
private
def has_custom_emoji?
- strong_memoize(:has_custom_emoji) do
- CustomEmoji.for_resource(resource_parent).any?
- end
+ all_custom_emoji&.any?
end
+ strong_memoize_attr :has_custom_emoji?
def resource_parent
context[:project] || context[:group]
@@ -62,9 +61,12 @@ module Banzai
end
def all_custom_emoji
- @all_custom_emoji ||=
- CustomEmoji.for_resource(resource_parent).by_name(custom_emoji_candidates).index_by(&:name)
+ Groups::CustomEmojiFinder.new(resource_parent, { include_ancestor_groups: true })
+ .execute
+ .by_name(custom_emoji_candidates)
+ .index_by(&:name)
end
+ strong_memoize_attr :all_custom_emoji
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 66468232633..e9121dbb371 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1917,6 +1917,18 @@ msgstr ""
msgid "AI-generated summary"
msgstr ""
+msgid "AIPoweredSM|AI-powered features"
+msgstr ""
+
+msgid "AIPoweredSM|By enabling this feature, you agree to the %{terms_link_start}GitLab Testing Agreement%{link_end}."
+msgstr ""
+
+msgid "AIPoweredSM|Enable %{link_start}AI-powered features%{link_end} for this instance."
+msgstr ""
+
+msgid "AIPoweredSM|Enable Experiment and Beta AI-powered features"
+msgstr ""
+
msgid "AISummary|Generates a summary of all comments"
msgstr ""
@@ -11936,6 +11948,12 @@ msgstr ""
msgid "CodeSuggestionsSM|Code Suggestions"
msgstr ""
+msgid "CodeSuggestionsSM|Code Suggestions %{beta}"
+msgstr ""
+
+msgid "CodeSuggestionsSM|Enable Code Suggestions for this instance"
+msgstr ""
+
msgid "CodeSuggestionsSM|Enable Code Suggestions for this instance %{beta}"
msgstr ""
@@ -30861,6 +30879,9 @@ msgstr ""
msgid "MlModelRegistry|Model registry"
msgstr ""
+msgid "MlModelRegistry|No description provided"
+msgstr ""
+
msgid "MlModelRegistry|No logged metadata"
msgstr ""
diff --git a/package.json b/package.json
index 62ae74702ee..7f96ffcbaa2 100644
--- a/package.json
+++ b/package.json
@@ -210,7 +210,7 @@
"visibilityjs": "^1.2.4",
"vue": "2.7.15",
"vue-apollo": "^3.0.7",
- "vue-loader": "15.10.2",
+ "vue-loader": "15.11.1",
"vue-observe-visibility": "^1.0.0",
"vue-resize": "^1.0.1",
"vue-router": "3.6.5",
@@ -223,7 +223,7 @@
"web-streams-polyfill": "^3.2.1",
"web-vitals": "^0.2.4",
"webpack": "^4.47.0",
- "webpack-bundle-analyzer": "^4.9.1",
+ "webpack-bundle-analyzer": "^4.10.1",
"webpack-cli": "^4.10.0",
"webpack-stats-plugin": "^0.3.1",
"worker-loader": "^3.0.8",
diff --git a/qa/qa/page/file/form.rb b/qa/qa/page/file/form.rb
index 30cd4f11bb4..61216f7b28d 100644
--- a/qa/qa/page/file/form.rb
+++ b/qa/qa/page/file/form.rb
@@ -15,7 +15,7 @@ module QA
end
view 'app/assets/javascripts/blob/filepath_form/components/template_selector.vue' do
- element :template_selector
+ element 'template-selector'
end
def add_name(name)
@@ -35,7 +35,7 @@ module QA
def select_template(template_type, template)
case template_type
when '.gitignore', '.gitlab-ci.yml', 'Dockerfile', 'LICENSE'
- click_element :template_selector
+ click_element 'template-selector'
else
raise %(Unsupported template_type "#{template_type}". Please confirm that it is a valid option.)
end
diff --git a/spec/components/projects/ml/show_ml_model_component_spec.rb b/spec/components/projects/ml/show_ml_model_component_spec.rb
index 02fad55e0be..ed989421679 100644
--- a/spec/components/projects/ml/show_ml_model_component_spec.rb
+++ b/spec/components/projects/ml/show_ml_model_component_spec.rb
@@ -3,11 +3,14 @@
require "spec_helper"
RSpec.describe Projects::Ml::ShowMlModelComponent, type: :component, feature_category: :mlops do
- let_it_be(:project) { build_stubbed(:project) }
- let_it_be(:model1) { build_stubbed(:ml_models, :with_latest_version_and_package, project: project) }
+ let_it_be(:project) { create(:project) } # rubocop:disable RSpec/FactoryBot/AvoidCreate -- build_stubbed breaks because it doesn't create iids properly.
+ let_it_be(:model1) { create(:ml_models, :with_latest_version_and_package, project: project) } # rubocop:disable RSpec/FactoryBot/AvoidCreate -- build_stubbed breaks because it doesn't create iids properly.
+
+ let_it_be(:experiment) { model1.default_experiment }
+ let_it_be(:candidate) { model1.latest_version.candidate }
subject(:component) do
- described_class.new(model: model1)
+ described_class.new(model: model1, current_user: model1.user)
end
describe 'rendered' do
@@ -28,7 +31,22 @@ RSpec.describe Projects::Ml::ShowMlModelComponent, type: :component, feature_cat
'version' => model1.latest_version.version,
'description' => model1.latest_version.description,
'projectPath' => "/#{project.full_path}",
- 'packageId' => model1.latest_version.package_id
+ 'packageId' => model1.latest_version.package_id,
+ 'candidate' => {
+ 'info' => {
+ 'iid' => candidate.iid,
+ 'eid' => candidate.eid,
+ 'pathToArtifact' => nil,
+ 'experimentName' => candidate.experiment.name,
+ 'pathToExperiment' => "/#{project.full_path}/-/ml/experiments/#{experiment.iid}",
+ 'status' => 'running',
+ 'path' => "/#{project.full_path}/-/ml/candidates/#{candidate.iid}",
+ 'ciJob' => nil
+ },
+ 'metrics' => [],
+ 'params' => [],
+ 'metadata' => []
+ }
},
'versionCount' => 1
}
diff --git a/spec/components/projects/ml/show_ml_model_version_component_spec.rb b/spec/components/projects/ml/show_ml_model_version_component_spec.rb
index a7dad5e4b2b..89f0c8633c2 100644
--- a/spec/components/projects/ml/show_ml_model_version_component_spec.rb
+++ b/spec/components/projects/ml/show_ml_model_version_component_spec.rb
@@ -3,12 +3,20 @@
require "spec_helper"
RSpec.describe Projects::Ml::ShowMlModelVersionComponent, type: :component, feature_category: :mlops do
- let_it_be(:project) { build_stubbed(:project) }
- let_it_be(:model) { build_stubbed(:ml_models, project: project) }
- let_it_be(:version) { build_stubbed(:ml_model_versions, :with_package, model: model, description: 'abc') }
+ let_it_be(:project) { create(:project) } # rubocop:disable RSpec/FactoryBot/AvoidCreate -- build_stubbed breaks because it doesn't create iids properly.
+ let_it_be(:user) { project.owner }
+ let_it_be(:model) { create(:ml_models, project: project) } # rubocop:disable RSpec/FactoryBot/AvoidCreate -- build_stubbed breaks because it doesn't create iids properly.
+ let_it_be(:experiment) { model.default_experiment }
+ let_it_be(:candidate) do
+ create(:ml_candidates, :with_artifact, experiment: experiment, user: user, project: project) # rubocop:disable RSpec/FactoryBot/AvoidCreate -- build_stubbed breaks because it doesn't create iids properly.
+ end
+
+ let_it_be(:version) do
+ build_stubbed(:ml_model_versions, :with_package, model: model, candidate: candidate, description: 'abc')
+ end
subject(:component) do
- described_class.new(model_version: version)
+ described_class.new(model_version: version, current_user: user)
end
describe 'rendered' do
@@ -30,6 +38,21 @@ RSpec.describe Projects::Ml::ShowMlModelVersionComponent, type: :component, feat
'model' => {
'name' => model.name,
'path' => "/#{project.full_path}/-/ml/models/#{model.id}"
+ },
+ 'candidate' => {
+ 'info' => {
+ 'iid' => candidate.iid,
+ 'eid' => candidate.eid,
+ 'pathToArtifact' => "/#{project.full_path}/-/packages/#{candidate.artifact.id}",
+ 'experimentName' => candidate.experiment.name,
+ 'pathToExperiment' => "/#{project.full_path}/-/ml/experiments/#{experiment.iid}",
+ 'status' => 'running',
+ 'path' => "/#{project.full_path}/-/ml/candidates/#{candidate.iid}",
+ 'ciJob' => nil
+ },
+ 'metrics' => [],
+ 'params' => [],
+ 'metadata' => []
}
}
})
diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb
index 09f39506448..92835d21bb6 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -10,6 +10,7 @@ RSpec.describe 'Database schema', feature_category: :database do
let(:columns_name_with_jsonb) { retrieve_columns_name_with_jsonb }
IGNORED_INDEXES_ON_FKS = {
+ application_settings: %w[instance_administration_project_id instance_administrators_group_id],
# `search_index_id index_type` is the composite foreign key configured for `search_namespace_index_assignments`,
# but in Search::NamespaceIndexAssignment model, only `search_index_id` is used as foreign key and indexed
search_namespace_index_assignments: [%w[search_index_id index_type]],
diff --git a/spec/factories/ml/model_versions.rb b/spec/factories/ml/model_versions.rb
index a097640b134..fd7ed857ee2 100644
--- a/spec/factories/ml/model_versions.rb
+++ b/spec/factories/ml/model_versions.rb
@@ -8,6 +8,10 @@ FactoryBot.define do
project { model.project }
description { 'Some description' }
+ candidate do
+ association :ml_candidates, experiment: model.default_experiment, project: project, model_version: instance
+ end
+
trait :with_package do
package do
association :ml_model_package, name: model.name, version: version, project: project
diff --git a/spec/finders/groups/custom_emoji_finder_spec.rb b/spec/finders/groups/custom_emoji_finder_spec.rb
new file mode 100644
index 00000000000..f1044997d4f
--- /dev/null
+++ b/spec/finders/groups/custom_emoji_finder_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Groups::CustomEmojiFinder, feature_category: :code_review_workflow do
+ describe '#execute' do
+ let(:params) { {} }
+
+ subject(:execute) { described_class.new(group, params).execute }
+
+ context 'when inside a group' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:custom_emoji) { create(:custom_emoji, group: group) }
+
+ it 'returns custom emoji from group' do
+ expect(execute).to contain_exactly(custom_emoji)
+ end
+ end
+
+ context 'when group is nil' do
+ let_it_be(:group) { nil }
+
+ it 'returns nil' do
+ expect(execute).to be_empty
+ end
+ end
+
+ context 'when group is a subgroup' do
+ let_it_be(:parent) { create(:group) }
+ let_it_be(:group) { create(:group, parent: parent) }
+ let_it_be(:custom_emoji) { create(:custom_emoji, group: group) }
+
+ it 'returns custom emoji' do
+ expect(described_class.new(group, params).execute).to contain_exactly(custom_emoji)
+ end
+ end
+
+ describe 'when custom emoji is in parent group' do
+ let_it_be(:parent) { create(:group) }
+ let_it_be(:group) { create(:group, parent: parent) }
+ let_it_be(:custom_emoji) { create(:custom_emoji, group: parent) }
+ let(:params) { { include_ancestor_groups: true } }
+
+ it 'returns custom emoji' do
+ expect(execute).to contain_exactly(custom_emoji)
+ end
+
+ context 'when params is empty' do
+ let(:params) { {} }
+
+ it 'returns empty record' do
+ expect(execute).to eq([])
+ end
+ end
+
+ context 'when include_ancestor_groups is false' do
+ let(:params) { { include_ancestor_groups: false } }
+
+ it 'returns empty record' do
+ expect(execute).to eq([])
+ end
+ end
+ end
+ end
+end
diff --git a/spec/frontend/authentication/password/components/password_input_spec.js b/spec/frontend/authentication/password/components/password_input_spec.js
index 5b2a9da993b..62438e824cf 100644
--- a/spec/frontend/authentication/password/components/password_input_spec.js
+++ b/spec/frontend/authentication/password/components/password_input_spec.js
@@ -9,7 +9,6 @@ describe('PasswordInput', () => {
title: 'This field is required',
id: 'new_user_password',
minimumPasswordLength: '8',
- qaSelector: 'new_user_password_field',
testid: 'new_user_password',
autocomplete: 'new-password',
name: 'new_user',
diff --git a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
index ae7f5416c0c..6db99e796d6 100644
--- a/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
+++ b/spec/frontend/behaviors/shortcuts/shortcuts_issuable_spec.js
@@ -30,14 +30,11 @@ describe('ShortcutsIssuable', () => {
</div>`,
);
document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
-
- window.shortcut = new ShortcutsIssuable(true);
});
afterEach(() => {
$(FORM_SELECTOR).remove();
- delete window.shortcut;
resetHTMLFixture();
});
@@ -55,6 +52,15 @@ describe('ShortcutsIssuable', () => {
});
};
+ it('sets up commands on instantiation', () => {
+ const mockShortcutsInstance = { addAll: jest.fn() };
+
+ // eslint-disable-next-line no-new
+ new ShortcutsIssuable(mockShortcutsInstance);
+
+ expect(mockShortcutsInstance.addAll).toHaveBeenCalled();
+ });
+
describe('with empty selection', () => {
it('does not return an error', () => {
ShortcutsIssuable.replyWithSelectedText(true);
diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/behaviors/shortcuts/shortcuts_spec.js
index a348ee77be1..5f71eb24758 100644
--- a/spec/frontend/shortcuts_spec.js
+++ b/spec/frontend/behaviors/shortcuts/shortcuts_spec.js
@@ -37,6 +37,16 @@ describe('Shortcuts', () => {
resetHTMLFixture();
});
+ it('does not allow subclassing', () => {
+ const createSubclass = () => {
+ class Subclass extends Shortcuts {}
+
+ return new Subclass();
+ };
+
+ expect(createSubclass).toThrow(/cannot be subclassed/);
+ });
+
describe('markdown shortcuts', () => {
let shortcutElements;
@@ -121,12 +131,12 @@ describe('Shortcuts', () => {
});
});
- describe('bindCommand(s)', () => {
- it('bindCommand calls Mousetrap.bind correctly', () => {
+ describe('adding shortcuts', () => {
+ it('add calls Mousetrap.bind correctly', () => {
const mockCommand = { defaultKeys: ['m'] };
const mockCallback = () => {};
- shortcuts.bindCommand(mockCommand, mockCallback);
+ shortcuts.add(mockCommand, mockCallback);
expect(Mousetrap.prototype.bind).toHaveBeenCalledTimes(1);
const [callArguments] = Mousetrap.prototype.bind.mock.calls;
@@ -134,13 +144,13 @@ describe('Shortcuts', () => {
expect(callArguments[1]).toBe(mockCallback);
});
- it('bindCommands calls Mousetrap.bind correctly', () => {
+ it('addAll calls Mousetrap.bind correctly', () => {
const mockCommandsAndCallbacks = [
[{ defaultKeys: ['1'] }, () => {}],
[{ defaultKeys: ['2'] }, () => {}],
];
- shortcuts.bindCommands(mockCommandsAndCallbacks);
+ shortcuts.addAll(mockCommandsAndCallbacks);
expect(Mousetrap.prototype.bind).toHaveBeenCalledTimes(mockCommandsAndCallbacks.length);
const { calls } = Mousetrap.prototype.bind.mock;
@@ -151,4 +161,107 @@ describe('Shortcuts', () => {
});
});
});
+
+ describe('addExtension', () => {
+ it('instantiates the given extension', () => {
+ const MockExtension = jest.fn();
+
+ const returnValue = shortcuts.addExtension(MockExtension, ['foo']);
+
+ expect(MockExtension).toHaveBeenCalledTimes(1);
+ expect(MockExtension).toHaveBeenCalledWith(shortcuts, 'foo');
+ expect(returnValue).toBe(MockExtension.mock.instances[0]);
+ });
+
+ it('instantiates declared dependencies', () => {
+ const MockDependency = jest.fn();
+ const MockExtension = jest.fn();
+
+ MockExtension.dependencies = [MockDependency];
+
+ const returnValue = shortcuts.addExtension(MockExtension, ['foo']);
+
+ expect(MockDependency).toHaveBeenCalledTimes(1);
+ expect(MockDependency.mock.instances).toHaveLength(1);
+ expect(MockDependency).toHaveBeenCalledWith(shortcuts);
+
+ expect(returnValue).toBe(MockExtension.mock.instances[0]);
+ });
+
+ it('does not instantiate an extension more than once', () => {
+ const MockExtension = jest.fn();
+
+ const returnValue = shortcuts.addExtension(MockExtension, ['foo']);
+ const secondReturnValue = shortcuts.addExtension(MockExtension, ['bar']);
+
+ expect(MockExtension).toHaveBeenCalledTimes(1);
+ expect(MockExtension).toHaveBeenCalledWith(shortcuts, 'foo');
+ expect(returnValue).toBe(MockExtension.mock.instances[0]);
+ expect(secondReturnValue).toBe(MockExtension.mock.instances[0]);
+ });
+
+ it('allows extensions to redundantly depend on Shortcuts', () => {
+ const MockExtension = jest.fn();
+ MockExtension.dependencies = [Shortcuts];
+
+ shortcuts.addExtension(MockExtension);
+
+ expect(MockExtension).toHaveBeenCalledTimes(1);
+ expect(MockExtension).toHaveBeenCalledWith(shortcuts);
+
+ // Ensure it wasn't instantiated
+ expect(shortcuts.extensions.has(Shortcuts)).toBe(false);
+ });
+
+ it('allows extensions to incorrectly depend on themselves', () => {
+ const A = jest.fn();
+ A.dependencies = [A];
+ shortcuts.addExtension(A);
+ expect(A).toHaveBeenCalledTimes(1);
+ expect(A).toHaveBeenCalledWith(shortcuts);
+ });
+
+ it('handles extensions with circular dependencies', () => {
+ const A = jest.fn();
+ const B = jest.fn();
+ const C = jest.fn();
+
+ A.dependencies = [B];
+ B.dependencies = [C];
+ C.dependencies = [A];
+
+ shortcuts.addExtension(A);
+
+ expect(A).toHaveBeenCalledTimes(1);
+ expect(B).toHaveBeenCalledTimes(1);
+ expect(C).toHaveBeenCalledTimes(1);
+ });
+
+ it('handles complex (diamond) dependency graphs', () => {
+ const X = jest.fn();
+ const A = jest.fn();
+ const C = jest.fn();
+ const D = jest.fn();
+ const E = jest.fn();
+
+ // Form this dependency graph:
+ //
+ // X ───► A ───► C
+ // │ ▲
+ // └────► D ─────┘
+ // │
+ // └────► E
+ X.dependencies = [A, D];
+ A.dependencies = [C];
+ D.dependencies = [C, E];
+
+ shortcuts.addExtension(X);
+
+ expect(X).toHaveBeenCalledTimes(1);
+ expect(A).toHaveBeenCalledTimes(1);
+ expect(C).toHaveBeenCalledTimes(1);
+ expect(D).toHaveBeenCalledTimes(1);
+ expect(E).toHaveBeenCalledTimes(1);
+ });
+ });
});
diff --git a/spec/frontend/ml/model_registry/components/model_version_detail_spec.js b/spec/frontend/ml/model_registry/components/model_version_detail_spec.js
index aeb9d13ad97..d1874346ad7 100644
--- a/spec/frontend/ml/model_registry/components/model_version_detail_spec.js
+++ b/spec/frontend/ml/model_registry/components/model_version_detail_spec.js
@@ -3,6 +3,7 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo';
import ModelVersionDetail from '~/ml/model_registry/components/model_version_detail.vue';
import PackageFiles from '~/packages_and_registries/package_registry/components/details/package_files.vue';
+import CandidateDetail from '~/ml/model_registry/components/candidate_detail.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import { makeModelVersion, MODEL_VERSION } from '../mock_data';
@@ -15,36 +16,51 @@ const createWrapper = (modelVersion = MODEL_VERSION) => {
};
const findPackageFiles = () => wrapper.findComponent(PackageFiles);
+const findCandidateDetail = () => wrapper.findComponent(CandidateDetail);
describe('ml/model_registry/components/model_version_detail.vue', () => {
- describe('description', () => {
+ describe('base behaviour', () => {
beforeEach(() => createWrapper());
it('shows the description', () => {
expect(wrapper.text()).toContain(MODEL_VERSION.description);
});
- });
- describe('package files', () => {
- describe('if package exists', () => {
- beforeEach(() => createWrapper());
-
- it('renders files', () => {
- expect(findPackageFiles().props()).toEqual({
- packageId: 'gid://gitlab/Packages::Package/12',
- projectPath: MODEL_VERSION.projectPath,
- packageType: 'ml_model',
- canDelete: false,
- });
- });
+ it('shows the candidate', () => {
+ expect(findCandidateDetail().props('candidate')).toBe(MODEL_VERSION.candidate);
});
- describe('if package does not exist', () => {
- beforeEach(() => createWrapper(makeModelVersion({ packageId: 0 })));
+ it('shows the mlflow label string', () => {
+ expect(wrapper.text()).toContain('MLflow run ID');
+ });
- it('does not render files', () => {
- expect(findPackageFiles().exists()).toBe(false);
+ it('shows the mlflow id', () => {
+ expect(wrapper.text()).toContain(MODEL_VERSION.candidate.info.eid);
+ });
+
+ it('renders files', () => {
+ expect(findPackageFiles().props()).toEqual({
+ packageId: 'gid://gitlab/Packages::Package/12',
+ projectPath: MODEL_VERSION.projectPath,
+ packageType: 'ml_model',
+ canDelete: false,
});
});
});
+
+ describe('if package does not exist', () => {
+ beforeEach(() => createWrapper(makeModelVersion({ packageId: 0 })));
+
+ it('does not render files', () => {
+ expect(findPackageFiles().exists()).toBe(false);
+ });
+ });
+
+ describe('if model version does not have description', () => {
+ beforeEach(() => createWrapper(makeModelVersion({ description: null })));
+
+ it('renders no description provided label', () => {
+ expect(wrapper.text()).toContain('No description provided');
+ });
+ });
});
diff --git a/spec/frontend/ml/model_registry/mock_data.js b/spec/frontend/ml/model_registry/mock_data.js
index 07cf59388ea..78e22eda7b9 100644
--- a/spec/frontend/ml/model_registry/mock_data.js
+++ b/spec/frontend/ml/model_registry/mock_data.js
@@ -56,12 +56,18 @@ export const makeModel = ({ latestVersion } = { latestVersion: LATEST_VERSION })
export const MODEL = makeModel();
-export const makeModelVersion = ({ version = '1.2.3', model = MODEL, packageId = 12 } = {}) => ({
+export const makeModelVersion = ({
+ version = '1.2.3',
+ model = MODEL,
+ packageId = 12,
+ description = 'Model version description',
+} = {}) => ({
version,
model,
packageId,
- description: 'Model version description',
+ description,
projectPath: 'path/to/project',
+ candidate: newCandidate(),
});
export const MODEL_VERSION = makeModelVersion();
diff --git a/spec/frontend/repository/components/blob_controls_spec.js b/spec/frontend/repository/components/blob_controls_spec.js
index 3ced5f6c4d2..53ebabebf1d 100644
--- a/spec/frontend/repository/components/blob_controls_spec.js
+++ b/spec/frontend/repository/components/blob_controls_spec.js
@@ -8,6 +8,7 @@ import blobControlsQuery from '~/repository/queries/blob_controls.query.graphql'
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createRouter from '~/repository/router';
import { updateElementsVisibility } from '~/repository/utils/dom';
+import { resetShortcutsForTests } from '~/behaviors/shortcuts';
import ShortcutsBlob from '~/behaviors/shortcuts/shortcuts_blob';
import BlobLinePermalinkUpdater from '~/blob/blob_line_permalink_updater';
import { blobControlsDataMock, refMock } from '../mock_data';
@@ -32,6 +33,8 @@ const createComponent = async () => {
mockResolver = jest.fn().mockResolvedValue({ data: { project } });
+ await resetShortcutsForTests();
+
wrapper = shallowMountExtended(BlobControls, {
router,
apolloProvider: createMockApollo([[blobControlsQuery, mockResolver]]),
diff --git a/spec/graphql/types/analytics/cycle_analytics/value_stream_type_spec.rb b/spec/graphql/types/analytics/cycle_analytics/value_stream_type_spec.rb
index 5e2638210d3..1d5a8dbebd6 100644
--- a/spec/graphql/types/analytics/cycle_analytics/value_stream_type_spec.rb
+++ b/spec/graphql/types/analytics/cycle_analytics/value_stream_type_spec.rb
@@ -7,5 +7,5 @@ RSpec.describe Types::Analytics::CycleAnalytics::ValueStreamType, feature_catego
specify { expect(described_class).to require_graphql_authorizations(:read_cycle_analytics) }
- specify { expect(described_class).to have_graphql_fields(:id, :name, :namespace, :project) }
+ specify { expect(described_class).to have_graphql_fields(:id, :name, :namespace, :project, :stages) }
end
diff --git a/spec/graphql/types/analytics/cycle_analytics/value_streams/stage_type_spec.rb b/spec/graphql/types/analytics/cycle_analytics/value_streams/stage_type_spec.rb
new file mode 100644
index 00000000000..92276647e1b
--- /dev/null
+++ b/spec/graphql/types/analytics/cycle_analytics/value_streams/stage_type_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Types::Analytics::CycleAnalytics::ValueStreams::StageType, feature_category: :value_stream_management do
+ let(:fields) do
+ %i[
+ name start_event_identifier
+ end_event_identifier hidden custom
+ ]
+ end
+
+ specify { expect(described_class.graphql_name).to eq('ValueStreamStage') }
+ specify { expect(described_class).to have_graphql_fields(fields).at_least }
+end
diff --git a/spec/graphql/types/group_type_spec.rb b/spec/graphql/types/group_type_spec.rb
index 6622551f063..d3f9053faf3 100644
--- a/spec/graphql/types/group_type_spec.rb
+++ b/spec/graphql/types/group_type_spec.rb
@@ -125,4 +125,37 @@ RSpec.describe GitlabSchema.types['Group'] do
expect { clean_state_query }.not_to exceed_all_query_limit(control)
end
end
+
+ describe 'custom emoji' do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let_it_be(:custom_emoji) { create(:custom_emoji, group: group) }
+ let_it_be(:custom_emoji_subgroup) { create(:custom_emoji, group: subgroup) }
+ let(:query) do
+ %(
+ query {
+ group(fullPath: "#{subgroup.full_path}") {
+ customEmoji(includeAncestorGroups: true) {
+ nodes {
+ id
+ }
+ }
+ }
+ }
+ )
+ end
+
+ before_all do
+ group.add_reporter(user)
+ end
+
+ describe 'when includeAncestorGroups is true' do
+ it 'returns emoji from ancestor groups' do
+ result = GitlabSchema.execute(query, context: { current_user: user }).as_json
+
+ expect(result.dig('data', 'group', 'customEmoji', 'nodes').count).to eq(2)
+ end
+ end
+ end
end
diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb
index fc3dd28c297..3965312316b 100644
--- a/spec/graphql/types/project_type_spec.rb
+++ b/spec/graphql/types/project_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe GitlabSchema.types['Project'] do
+RSpec.describe GitlabSchema.types['Project'], feature_category: :groups_and_projects do
include GraphqlHelpers
include ProjectForksHelper
using RSpec::Parameterized::TableSyntax
@@ -41,7 +41,7 @@ RSpec.describe GitlabSchema.types['Project'] do
recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables
timelog_categories fork_targets branch_rules ci_config_variables pipeline_schedules languages
incident_management_timeline_event_tags visible_forks inherited_ci_variables autocomplete_users
- ci_cd_settings detailed_import_status
+ ci_cd_settings detailed_import_status value_streams
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/lib/banzai/filter/custom_emoji_filter_spec.rb b/spec/lib/banzai/filter/custom_emoji_filter_spec.rb
index 7fd25eac81b..4fc9d9dd4f6 100644
--- a/spec/lib/banzai/filter/custom_emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/custom_emoji_filter_spec.rb
@@ -55,4 +55,12 @@ RSpec.describe Banzai::Filter::CustomEmojiFilter, feature_category: :team_planni
filter('<p>:tanuki:</p> <p>:party-parrot:</p>')
end.not_to exceed_all_query_limit(control_count.count)
end
+
+ it 'uses custom emoji from ancestor group' do
+ subgroup = create(:group, parent: group)
+
+ doc = filter('<p>:tanuki:</p>', group: subgroup)
+
+ expect(doc.css('gl-emoji').size).to eq 1
+ end
end
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb
index 87abd8a676d..a901453ba9f 100644
--- a/spec/models/award_emoji_spec.rb
+++ b/spec/models/award_emoji_spec.rb
@@ -319,6 +319,17 @@ RSpec.describe AwardEmoji, feature_category: :team_planning do
expect(new_award.url).to eq(custom_emoji.url)
end
+ describe 'when inside subgroup' do
+ let_it_be(:subgroup) { create(:group, parent: custom_emoji.group) }
+ let_it_be(:project) { create(:project, namespace: subgroup) }
+
+ it 'is set for custom emoji' do
+ new_award = build_award(custom_emoji.name)
+
+ expect(new_award.url).to eq(custom_emoji.url)
+ end
+ end
+
context 'feature flag disabled' do
before do
stub_feature_flags(custom_emoji: false)
diff --git a/spec/models/custom_emoji_spec.rb b/spec/models/custom_emoji_spec.rb
index 15655d08556..cbdf05cf28f 100644
--- a/spec/models/custom_emoji_spec.rb
+++ b/spec/models/custom_emoji_spec.rb
@@ -48,4 +48,45 @@ RSpec.describe CustomEmoji do
expect(emoji.errors.messages).to eq(file: ["is blocked: Only allowed schemes are http, https"])
end
end
+
+ describe '#for_resource' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:custom_emoji) { create(:custom_emoji, namespace: group) }
+
+ context 'when custom_emoji feature flag is disabled' do
+ before do
+ stub_feature_flags(custom_emoji: false)
+ end
+
+ it { expect(described_class.for_resource(group)).to eq([]) }
+ end
+
+ context 'when group is nil' do
+ let_it_be(:group) { nil }
+
+ it { expect(described_class.for_resource(group)).to eq([]) }
+ end
+
+ context 'when resource is a project' do
+ let_it_be(:project) { create(:project) }
+
+ it { expect(described_class.for_resource(project)).to eq([]) }
+ end
+
+ it { expect(described_class.for_resource(group)).to eq([custom_emoji]) }
+ end
+
+ describe '#for_namespaces' do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:custom_emoji) { create(:custom_emoji, namespace: group, name: 'parrot') }
+
+ it { expect(described_class.for_namespaces([group.id])).to eq([custom_emoji]) }
+
+ context 'with subgroup' do
+ let_it_be(:subgroup) { create(:group, parent: group) }
+ let_it_be(:subgroup_emoji) { create(:custom_emoji, namespace: subgroup, name: 'parrot') }
+
+ it { expect(described_class.for_namespaces([subgroup.id, group.id])).to eq([subgroup_emoji]) }
+ end
+ end
end
diff --git a/spec/models/ml/candidate_spec.rb b/spec/models/ml/candidate_spec.rb
index 503d3514a72..678224a3c8e 100644
--- a/spec/models/ml/candidate_spec.rb
+++ b/spec/models/ml/candidate_spec.rb
@@ -38,8 +38,8 @@ RSpec.describe Ml::Candidate, factory_default: :keep, feature_category: :mlops d
describe 'validation' do
let_it_be(:model) { create(:ml_models, project: candidate.project) }
- let_it_be(:model_version1) { create(:ml_model_versions, model: model) }
- let_it_be(:model_version2) { create(:ml_model_versions, model: model) }
+ let_it_be(:model_version1) { create(:ml_model_versions, model: model, candidate: nil) }
+ let_it_be(:model_version2) { create(:ml_model_versions, model: model, candidate: nil) }
let_it_be(:validation_candidate) do
create(:ml_candidates, model_version: model_version1, project: candidate.project)
end
diff --git a/spec/requests/api/graphql/custom_emoji_query_spec.rb b/spec/requests/api/graphql/custom_emoji_query_spec.rb
index 1858ea831dd..c89ad0002b4 100644
--- a/spec/requests/api/graphql/custom_emoji_query_spec.rb
+++ b/spec/requests/api/graphql/custom_emoji_query_spec.rb
@@ -35,14 +35,14 @@ RSpec.describe 'getting custom emoji within namespace', feature_category: :share
expect(graphql_data['group']['customEmoji']['nodes'].first['name']).to eq(custom_emoji.name)
end
- it 'returns nil custom emoji when the custom_emoji feature flag is disabled' do
+ it 'returns empty array when the custom_emoji feature flag is disabled' do
stub_feature_flags(custom_emoji: false)
post_graphql(custom_emoji_query(group), current_user: current_user)
expect(response).to have_gitlab_http_status(:ok)
expect(graphql_data['group']).to be_present
- expect(graphql_data['group']['customEmoji']).to be_nil
+ expect(graphql_data['group']['customEmoji']['nodes']).to eq([])
end
it 'returns nil group when unauthorised' do
diff --git a/spec/requests/api/graphql/project/value_streams_spec.rb b/spec/requests/api/graphql/project/value_streams_spec.rb
new file mode 100644
index 00000000000..01e937c1e47
--- /dev/null
+++ b/spec/requests/api/graphql/project/value_streams_spec.rb
@@ -0,0 +1,105 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Project.value_streams', feature_category: :value_stream_management do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:query) do
+ <<~QUERY
+ query($fullPath: ID!) {
+ project(fullPath: $fullPath) {
+ valueStreams {
+ nodes {
+ name
+ stages {
+ name
+ startEventIdentifier
+ endEventIdentifier
+ }
+ }
+ }
+ }
+ }
+ QUERY
+ end
+
+ context 'when user has permissions to read value streams' do
+ let(:expected_value_stream) do
+ {
+ 'project' => {
+ 'valueStreams' => {
+ 'nodes' => [
+ {
+ 'name' => 'default',
+ 'stages' => expected_stages
+ }
+ ]
+ }
+ }
+ }
+ end
+
+ let(:expected_stages) do
+ [
+ {
+ 'name' => 'issue',
+ 'startEventIdentifier' => 'ISSUE_CREATED',
+ 'endEventIdentifier' => 'ISSUE_STAGE_END'
+ },
+ {
+ 'name' => 'plan',
+ 'startEventIdentifier' => 'PLAN_STAGE_START',
+ 'endEventIdentifier' => 'ISSUE_FIRST_MENTIONED_IN_COMMIT'
+ },
+ {
+ 'name' => 'code',
+ 'startEventIdentifier' => 'CODE_STAGE_START',
+ 'endEventIdentifier' => 'MERGE_REQUEST_CREATED'
+ },
+ {
+ 'name' => 'test',
+ 'startEventIdentifier' => 'MERGE_REQUEST_LAST_BUILD_STARTED',
+ 'endEventIdentifier' => 'MERGE_REQUEST_LAST_BUILD_FINISHED'
+ },
+ {
+ 'name' => 'review',
+ 'startEventIdentifier' => 'MERGE_REQUEST_CREATED',
+ 'endEventIdentifier' => 'MERGE_REQUEST_MERGED'
+ },
+ {
+ 'name' => 'staging',
+ 'startEventIdentifier' => 'MERGE_REQUEST_MERGED',
+ 'endEventIdentifier' => 'MERGE_REQUEST_FIRST_DEPLOYED_TO_PRODUCTION'
+ }
+ ]
+ end
+
+ before_all do
+ project.add_guest(user)
+ end
+
+ before do
+ post_graphql(query, current_user: user, variables: { fullPath: project.full_path })
+ end
+
+ it_behaves_like 'a working graphql query'
+
+ it 'returns only `default` value stream' do
+ expect(graphql_data).to eq(expected_value_stream)
+ end
+ end
+
+ context 'when user does not have permission to read value streams' do
+ before do
+ post_graphql(query, current_user: user, variables: { fullPath: project.full_path })
+ end
+
+ it 'returns nil' do
+ expect(graphql_data_at(:project, :valueStreams)).to be_nil
+ end
+ end
+end
diff --git a/spec/serializers/discussion_entity_spec.rb b/spec/serializers/discussion_entity_spec.rb
index 0fe10ed2c6d..4b818ce35e6 100644
--- a/spec/serializers/discussion_entity_spec.rb
+++ b/spec/serializers/discussion_entity_spec.rb
@@ -53,13 +53,6 @@ RSpec.describe DiscussionEntity do
.to match_schema('entities/note_user_entity')
end
- it 'exposes the url for custom award emoji' do
- custom_emoji = create(:custom_emoji, group: group)
- create(:award_emoji, awardable: note, name: custom_emoji.name)
-
- expect(subject[:notes].last[:award_emoji].first.keys).to include(:url)
- end
-
context 'when is LegacyDiffDiscussion' do
let(:discussion) { create(:legacy_diff_note_on_merge_request, noteable: note.noteable, project: project).to_discussion }
diff --git a/spec/services/ml/create_candidate_service_spec.rb b/spec/services/ml/create_candidate_service_spec.rb
index fb3456b0bcc..b1a053711d7 100644
--- a/spec/services/ml/create_candidate_service_spec.rb
+++ b/spec/services/ml/create_candidate_service_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe ::Ml::CreateCandidateService, feature_category: :mlops do
describe '#execute' do
- let_it_be(:model_version) { create(:ml_model_versions) }
+ let_it_be(:model_version) { create(:ml_model_versions, candidate: nil) }
let_it_be(:experiment) { create(:ml_experiments, project: model_version.project) }
let(:params) { {} }
diff --git a/spec/views/admin/application_settings/general.html.haml_spec.rb b/spec/views/admin/application_settings/general.html.haml_spec.rb
index 99564003d59..ad581ee6093 100644
--- a/spec/views/admin/application_settings/general.html.haml_spec.rb
+++ b/spec/views/admin/application_settings/general.html.haml_spec.rb
@@ -115,7 +115,7 @@ RSpec.describe 'admin/application_settings/general.html.haml' do
end
# for the licensed tests, refer to ee/spec/views/admin/application_settings/general.html.haml_spec.rb
- describe 'instance-level code suggestions settings', :without_license, feature_category: :code_suggestions do
+ describe 'instance-level ai-powered settings', :without_license, feature_category: :code_suggestions do
before do
allow(::Gitlab).to receive(:org_or_com?).and_return(gitlab_org_or_com?)
@@ -125,6 +125,7 @@ RSpec.describe 'admin/application_settings/general.html.haml' do
shared_examples 'does not render the form' do
it 'does not render the form' do
expect(rendered).not_to have_field('application_setting_instance_level_code_suggestions_enabled')
+ expect(rendered).not_to have_field('application_setting_instance_level_ai_beta_features_enabled')
end
end
diff --git a/spec/workers/users/deactivate_dormant_users_worker_spec.rb b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
index c28be165fd7..574dc922a36 100644
--- a/spec/workers/users/deactivate_dormant_users_worker_spec.rb
+++ b/spec/workers/users/deactivate_dormant_users_worker_spec.rb
@@ -10,34 +10,27 @@ RSpec.describe Users::DeactivateDormantUsersWorker, feature_category: :seat_cost
let_it_be(:inactive) { create(:user, last_activity_on: nil, created_at: User::MINIMUM_DAYS_CREATED.days.ago.to_date) }
let_it_be(:inactive_recently_created) { create(:user, last_activity_on: nil, created_at: (User::MINIMUM_DAYS_CREATED - 1).days.ago.to_date) }
- let(:admin_bot) { create(:user, :admin_bot) }
- let(:deactivation_service) { instance_spy(Users::DeactivateService) }
-
- before do
- allow(Users::DeactivateService).to receive(:new).and_return(deactivation_service)
- end
-
subject(:worker) { described_class.new }
it 'does not run for SaaS', :saas do
- # Now makes a call to current settings to determine period of dormancy
-
worker.perform
- expect(deactivation_service).not_to have_received(:execute)
- end
-
- context 'when automatic deactivation of dormant users is enabled' do
- before do
- stub_application_setting(deactivate_dormant_users: true)
+ expect_any_instance_of(::Users::DeactivateService) do |deactivation_service|
+ expect(deactivation_service).not_to have_received(:execute)
end
+ end
- it 'deactivates dormant users' do
- worker.perform
-
- expect(deactivation_service).to have_received(:execute).twice
+ shared_examples 'deactivates dormant users' do
+ specify do
+ expect { worker.perform }
+ .to change { dormant.reload.state }
+ .to('deactivated')
+ .and change { inactive.reload.state }
+ .to('deactivated')
end
+ end
+ shared_examples 'deactivates certain user types' do
where(:user_type, :expected_state) do
:human | 'deactivated'
:support_bot | 'active'
@@ -52,33 +45,69 @@ RSpec.describe Users::DeactivateDormantUsersWorker, feature_category: :seat_cost
end
with_them do
- it 'deactivates certain user types' do
+ specify do
user = create(:user, user_type: user_type, state: :active, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date)
worker.perform
- if expected_state == 'deactivated'
- expect(deactivation_service).to have_received(:execute).with(user)
- else
- expect(deactivation_service).not_to have_received(:execute).with(user)
+ expect_any_instance_of(::Users::DeactivateService) do |deactivation_service|
+ if expected_state == 'deactivated'
+ expect(deactivation_service).to receive(:execute).with(user).and_call_original
+ else
+ expect(deactivation_service).not_to have_received(:execute).with(user)
+ end
end
+
+ expect(user.reload.state).to eq expected_state
end
end
+ end
- it 'does not deactivate non-active users' do
+ shared_examples 'does not deactivate non-active users' do
+ specify do
human_user = create(:user, user_type: :human, state: :blocked, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date)
service_user = create(:user, user_type: :service_user, state: :blocked, last_activity_on: Gitlab::CurrentSettings.deactivate_dormant_users_period.days.ago.to_date)
worker.perform
- expect(deactivation_service).not_to have_received(:execute).with(human_user)
- expect(deactivation_service).not_to have_received(:execute).with(service_user)
+ expect_any_instance_of(::Users::DeactivateService) do |deactivation_service|
+ expect(deactivation_service).not_to have_received(:execute).with(human_user)
+ expect(deactivation_service).not_to have_received(:execute).with(service_user)
+ end
end
+ end
- it 'does not deactivate recently created users' do
+ shared_examples 'does not deactivate recently created users' do
+ specify do
worker.perform
- expect(deactivation_service).not_to have_received(:execute).with(inactive_recently_created)
+ expect_any_instance_of(::Users::DeactivateService) do |deactivation_service|
+ expect(deactivation_service).not_to have_received(:execute).with(inactive_recently_created)
+ end
+ end
+ end
+
+ context 'when automatic deactivation of dormant users is enabled' do
+ before do
+ stub_application_setting(deactivate_dormant_users: true)
+ end
+
+ context 'when admin mode is not enabled', :do_not_mock_admin_mode_setting do
+ include_examples 'deactivates dormant users'
+ include_examples 'deactivates certain user types'
+ include_examples 'does not deactivate non-active users'
+ include_examples 'does not deactivate recently created users'
+ end
+
+ context 'when admin mode is enabled', :request_store do
+ before do
+ stub_application_setting(admin_mode: true)
+ end
+
+ include_examples 'deactivates dormant users'
+ include_examples 'deactivates certain user types'
+ include_examples 'does not deactivate non-active users'
+ include_examples 'does not deactivate recently created users'
end
end
@@ -90,7 +119,9 @@ RSpec.describe Users::DeactivateDormantUsersWorker, feature_category: :seat_cost
it 'does nothing' do
worker.perform
- expect(deactivation_service).not_to have_received(:execute)
+ expect_any_instance_of(::Users::DeactivateService) do |deactivation_service|
+ expect(deactivation_service).not_to have_received(:execute)
+ end
end
end
end
diff --git a/yarn.lock b/yarn.lock
index 3ed1e4f7f55..b72a2afd272 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5393,6 +5393,11 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
+debounce@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.1.tgz#38881d8f4166a5c5848020c11827b834bcb3e0a5"
+ integrity sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==
+
debug@2.6.9, debug@^2.2.0, debug@^2.3.3:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@@ -7449,10 +7454,10 @@ html-entities@^2.3.2:
resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.2.tgz#760b404685cb1d794e4f4b744332e3b00dcfe488"
integrity sha512-c3Ab/url5ksaT0WyleslpBEthOzWhrjQbg75y7XUsfSzi3Dgzt0l8w5e7DylRn15MTlMMD58dTfzddNS2kcAjQ==
-html-escaper@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.0.tgz#71e87f931de3fe09e56661ab9a29aadec707b491"
- integrity sha512-a4u9BeERWGu/S8JiWEAQcdrg9v4QArtP9keViQjGMdff20fBdd8waotXaNmODqBe6uZ3Nafi7K/ho4gCQHV3Ig==
+html-escaper@^2.0.0, html-escaper@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
+ integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
html-tags@^3.3.1:
version "3.3.1"
@@ -8890,11 +8895,6 @@ lodash.debounce@^4.0.8:
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
-lodash.escape@^4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
- integrity sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==
-
lodash.find@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.find/-/lodash.find-4.6.0.tgz#cb0704d47ab71789ffa0de8b97dd926fb88b13b1"
@@ -8980,11 +8980,6 @@ lodash.pick@^4.4.0:
resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3"
integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=
-lodash.pullall@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/lodash.pullall/-/lodash.pullall-4.2.0.tgz#9d98b8518b7c965b0fae4099bd9fb7df8bbf38ba"
- integrity sha512-VhqxBKH0ZxPpLhiu68YD1KnHmbhQJQctcipvmFnqIBDYzcIHzf3Zpu0tpeOKtR4x76p9yohc506eGdOjTmyIBg==
-
lodash.snakecase@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
@@ -13488,10 +13483,10 @@ vue-hot-reload-api@^2.3.0:
hash-sum "^2.0.0"
loader-utils "^2.0.0"
-vue-loader@15.10.2:
- version "15.10.2"
- resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.10.2.tgz#6dccfda8661caa7f5415806a5e386fd3258d8112"
- integrity sha512-ndeSe/8KQc/nlA7TJ+OBhv2qalmj1s+uBs7yHDRFaAXscFTApBzY9F1jES3bautmgWjDlDct0fw8rPuySDLwxw==
+vue-loader@15.11.1:
+ version "15.11.1"
+ resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.11.1.tgz#dee91169211276ed43c5715caef88a56b1f497b0"
+ integrity sha512-0iw4VchYLePqJfJu9s62ACWUXeSqM30SQqlIftbYWM3C+jpPcEHKSPUZBLjSF9au4HTHQ/naF6OGnO3Q/qGR3Q==
dependencies:
"@vue/component-compiler-utils" "^3.1.0"
hash-sum "^1.0.2"
@@ -13692,24 +13687,20 @@ webidl-conversions@^7.0.0:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a"
integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==
-webpack-bundle-analyzer@^4.9.1:
- version "4.9.1"
- resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.9.1.tgz#d00bbf3f17500c10985084f22f1a2bf45cb2f09d"
- integrity sha512-jnd6EoYrf9yMxCyYDPj8eutJvtjQNp8PHmni/e/ulydHBWhT5J3menXt3HEkScsu9YqMAcG4CfFjs3rj5pVU1w==
+webpack-bundle-analyzer@^4.10.1:
+ version "4.10.1"
+ resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.1.tgz#84b7473b630a7b8c21c741f81d8fe4593208b454"
+ integrity sha512-s3P7pgexgT/HTUSYgxJyn28A+99mmLq4HsJepMPzu0R8ImJc52QNqaFYW1Z2z2uIb1/J3eYgaAWVpaC+v/1aAQ==
dependencies:
"@discoveryjs/json-ext" "0.5.7"
acorn "^8.0.4"
acorn-walk "^8.0.0"
commander "^7.2.0"
+ debounce "^1.2.1"
escape-string-regexp "^4.0.0"
gzip-size "^6.0.0"
+ html-escaper "^2.0.2"
is-plain-object "^5.0.0"
- lodash.debounce "^4.0.8"
- lodash.escape "^4.0.1"
- lodash.flatten "^4.4.0"
- lodash.invokemap "^4.6.0"
- lodash.pullall "^4.2.0"
- lodash.uniqby "^4.7.0"
opener "^1.5.2"
picocolors "^1.0.0"
sirv "^2.0.3"