Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2025-04-25 12:09:51 +00:00
parent 2a069cd8b6
commit d13bafb390
101 changed files with 563 additions and 204 deletions

View File

@ -13,3 +13,4 @@ spec/frontend/lib/utils/secret_detection_spec.js:generic-api-key:55
spec/frontend/lib/utils/secret_detection_spec.js:generic-api-key:57
spec/frontend/lib/utils/secret_detection_spec.js:gitlab-pat:28
d39da82ae304b8813a8fb2c79c3d7cd6f173590e:doc/api/groups.md:gitlab-pat:2342
doc/api/packages/conan_v2.md:jwt:82

View File

@ -292,10 +292,9 @@ export const VALUE_STREAM_METRIC_METADATA = {
docsLink: helpPagePath('user/group/issues_analytics/_index'),
},
[CONTRIBUTOR_METRICS.COUNT]: {
description: s__(
'ValueStreamAnalytics|Number of monthly unique users with contributions in the group.',
),
description: s__('ValueStreamAnalytics|Number of monthly unique users with contributions.'),
groupLink: '-/contribution_analytics',
projectLink: '-/graphs/master?ref_type=heads',
docsLink: helpPagePath('user/profile/contributions_calendar.html', {
anchor: 'user-contribution-events',
}),

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import { orderBy } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import {
convertObjectPropsToCamelCase,
convertObjectPropsToSnakeCase,

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import { memoize } from 'lodash';
import axios from '~/lib/utils/axios_utils';
export const hasSelection = (tiptapEditor) => {
const { from, to } = tiptapEditor.state.selection;

View File

@ -1,4 +1,4 @@
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { initToggle } from '~/toggles';
import toast from '~/vue_shared/plugins/global_toast';
import {

View File

@ -1,4 +1,4 @@
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { initToggle } from '~/toggles';
import toast from '~/vue_shared/plugins/global_toast';
import {

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import Visibility from 'visibilityjs';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
import Poll from '~/lib/utils/poll';
import { __ } from '~/locale';

View File

@ -1,8 +1,8 @@
<script>
import { GlAlert, GlForm } from '@gitlab/ui';
import axios from 'axios';
// eslint-disable-next-line no-restricted-imports
import { mapState, mapActions, mapGetters } from 'vuex';
import axios from '~/lib/utils/axios_utils';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { s__ } from '~/locale';
import SafeHtml from '~/vue_shared/directives/safe_html';

View File

@ -1,7 +1,7 @@
<script>
import { GlAvatarLabeled, GlCollapsibleListbox } from '@gitlab/ui';
import axios from 'axios';
import { debounce } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
import { getGroups, getDescendentGroups, getProjectShareLocations } from '~/rest_api';
import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils';

View File

@ -1,4 +1,4 @@
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { buildApiUrl } from '~/api/api_utils';
import { GITLAB_COM_BASE_PATH } from '~/jira_connect/subscriptions/constants';

View File

@ -1,3 +1,5 @@
// This is the only file allowed to import directly from the package.
// eslint-disable-next-line no-restricted-imports
import axios from 'axios';
import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_interceptor';
import setupAxiosStartupCalls from './axios_startup_calls';

View File

@ -1,4 +1,4 @@
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { roleDropdownItems, initialSelectedRole } from '~/members/utils';
import {
GROUP_LINK_ACCESS_LEVEL_PROPERTY_NAME,

View File

@ -1,6 +1,6 @@
<script>
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { createAlert } from '~/alert';
import { __ } from '~/locale';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';

View File

@ -1,6 +1,6 @@
<script>
import { GlFilteredSearchSuggestion } from '@gitlab/ui';
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { createAlert } from '~/alert';
import { __ } from '~/locale';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';

View File

@ -132,7 +132,7 @@ export default {
duoFeedbackText() {
return sprintf(
__(
'Rate this response %{separator} %{codeStart}%{botUser}%{codeEnd} in reply for more questions',
'Rate this response %{separator} Mention %{codeStart}%{botUser}%{codeEnd} to continue the conversation.',
),
{
separator: '•',

View File

@ -1,5 +1,5 @@
import Vue from 'vue';
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { pinia } from '~/pinia/instance';
import { DiffFile } from '~/rapid_diffs/diff_file';
import FileBrowserToggle from '~/diffs/components/file_browser_toggle.vue';

View File

@ -12,9 +12,19 @@ export default {
default: true,
},
},
computed: {
pageKey() {
return this.$route.params.iid || this.$route.name;
},
},
};
</script>
<template>
<router-view :new-comment-template-paths="newCommentTemplatePaths" :with-tabs="withTabs" />
<router-view
:key="pageKey"
:new-comment-template-paths="newCommentTemplatePaths"
:with-tabs="withTabs"
data-testid="work-item-router-view"
/>
</template>

View File

@ -5,6 +5,7 @@ import { s__, __ } from '~/locale';
import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
import { STATE_OPEN, WORK_ITEM_TYPE_NAME_TASK, i18n } from '~/work_items/constants';
import { getDraft, clearDraft, updateDraft } from '~/lib/utils/autosave';
import gfmEventHub from '~/vue_shared/components/markdown/eventhub';
import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
import glAbilitiesMixin from '~/vue_shared/mixins/gl_abilities_mixin';
import MarkdownEditor from '~/vue_shared/components/markdown/markdown_editor.vue';
@ -40,6 +41,7 @@ export default {
constantOptions: {
markdownDocsPath: helpPagePath('user/markdown'),
},
gfmEventHub,
components: {
CommentFieldLayout,
GlButton,
@ -376,6 +378,7 @@ export default {
:autofocus="autofocus"
:restricted-tool-bar-items="restrictedToolBarItems"
@input="setCommentText"
@keydown.up="$options.gfmEventHub.$emit('edit-current-user-last-note', $event)"
@keydown.meta.enter="submitForm()"
@keydown.ctrl.enter="submitForm()"
@keydown.esc.stop="cancelEditing"

View File

@ -286,7 +286,12 @@ export default {
@cancelEditing="$emit('cancelEditing')"
@error="$emit('error', $event)"
/>
<timeline-entry-item v-else :data-note-id="noteId" class="note note-discussion gl-px-0">
<timeline-entry-item
v-else
:data-note-id="noteId"
:data-discussion-id="discussionId"
class="note note-discussion gl-px-0"
>
<div class="timeline-content">
<div class="discussion">
<div class="discussion-body">

View File

@ -9,6 +9,7 @@ import { updateDraft, clearDraft } from '~/lib/utils/autosave';
import { renderMarkdown } from '~/notes/utils';
import { getLocationHash } from '~/lib/utils/url_utility';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import gfmEventHub from '~/vue_shared/components/markdown/eventhub';
import EditedAt from '~/issues/show/components/edited.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import NoteHeader from '~/notes/components/note_header.vue';
@ -216,6 +217,12 @@ export default {
return this.note.discussion.resolvedBy;
},
},
mounted() {
gfmEventHub.$on('edit-note', this.handleEditNote);
},
beforeDestroy() {
gfmEventHub.$off('edit-note', this.handleEditNote);
},
apollo: {
workItem: {
@ -247,6 +254,11 @@ export default {
this.isEditing = true;
updateDraft(this.autosaveKey, this.note.body);
},
handleEditNote({ note }) {
if (this.hasAdminPermission && note.id === this.note.id) {
this.startEditing();
}
},
async updateNote({ commentText, executeOptimisticResponse = true }) {
try {
this.isEditing = false;

View File

@ -7,6 +7,7 @@ import {
TYPENAME_DISCUSSION_NOTE,
TYPENAME_NOTE,
TYPENAME_GROUP,
TYPENAME_USER,
} from '~/graphql_shared/constants';
import { Mousetrap } from '~/lib/mousetrap';
import { ISSUABLE_COMMENT_OR_REPLY, keysFor } from '~/behaviors/shortcuts/keybindings';
@ -239,6 +240,11 @@ export default {
return visibleNotes;
},
userComments() {
return this.notesArray
.flatMap((discussion) => discussion.notes.nodes)
.filter((note) => !note.system);
},
commentsDisabled() {
return this.discussionFilter === WORK_ITEM_NOTES_FILTER_ONLY_HISTORY;
},
@ -278,11 +284,13 @@ export default {
}
if (this.canCreateNote) {
Mousetrap.bind(keysFor(ISSUABLE_COMMENT_OR_REPLY), (e) => this.quoteReply(e));
gfmEventHub.$on('edit-current-user-last-note', this.editCurrentUserLastNote);
}
},
beforeDestroy() {
if (this.canCreateNote) {
Mousetrap.unbind(keysFor(ISSUABLE_COMMENT_OR_REPLY), this.quoteReply);
gfmEventHub.$off('edit-current-user-last-note', this.editCurrentUserLastNote);
}
},
apollo: {
@ -385,6 +393,36 @@ export default {
},
},
methods: {
editCurrentUserLastNote(e) {
const currentUserId = convertToGraphQLId(TYPENAME_USER, gon.current_user_id);
const isToplevelCommentForm = Boolean(e.target.closest('.js-comment-form'));
let availableNotes = [];
if (isToplevelCommentForm) {
// User hit `Up` key from top-level comment form, populate all the comments,
// also ensure to reverse them only if sort order is set to newest-first (DESC).
availableNotes = this.formAtTop ? [...this.userComments] : [...this.userComments].reverse();
} else {
// User hit `Up` key from a comment form within an existing thread, populate
// all the comments, from this thread, and reverse order so the latest comments come first.
const discussionId = convertToGraphQLId(
TYPENAME_DISCUSSION_NOTE,
e.target.closest('.js-timeline-entry').dataset.discussionId,
);
availableNotes = [
...this.notesArray.find((discussion) => discussion.id === discussionId).notes.nodes,
].reverse();
}
// Find current user's last note.
const currentUserLastNote = availableNotes.find((note) => note.author.id === currentUserId);
if (!currentUserLastNote) return;
gfmEventHub.$emit('edit-note', {
note: currentUserLastNote,
});
},
getDiscussionIdFromSelection() {
const selection = window.getSelection();
if (selection.rangeCount <= 0) return null;

View File

@ -6,7 +6,7 @@
= webpack_bundle_tag 'analytics'
= javascript_tag do
:plain
window.snowplowOptions = #{Gitlab::Tracking.options(@group).to_json}
window.snowplowOptions = #{Gitlab::Tracking.frontend_client_options(@group).to_json}
gl = window.gl || {};
gl.snowplowStandardContext = #{Gitlab::Tracking::StandardContext.new(

View File

@ -13,13 +13,12 @@ if Gitlab::Runtime.console?
init_autocomplete
end
unless ::Gitlab.next_rails?
def init_autocomplete
return unless Rails.env.production?
def init_autocomplete
return if ::Gitlab.next_rails?
return unless Rails.env.production?
# IRB_USE_AUTOCOMPLETE was added in https://github.com/ruby/irb/pull/469
IRB.conf[:USE_AUTOCOMPLETE] = ENV.fetch("IRB_USE_AUTOCOMPLETE", "false") == "true"
end
# IRB_USE_AUTOCOMPLETE was added in https://github.com/ruby/irb/pull/469
IRB.conf[:USE_AUTOCOMPLETE] = ENV.fetch("IRB_USE_AUTOCOMPLETE", "false") == "true"
end
end

View File

@ -5,4 +5,4 @@ feature_category: vulnerability_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/177993
milestone: '17.11'
queued_migration_version: 20250404035239
finalized_by: # version of the migration that finalized this BBM
finalized_by: 20250422130050

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
class FinalizeBackfillArchivedAndTraversalIdsToVulnerabilityStatistics < Gitlab::Database::Migration[2.2]
milestone '18.0'
disable_ddl_transaction!
restrict_gitlab_migration gitlab_schema: :gitlab_sec
MIGRATION_NAME = "BackfillArchivedAndTraversalIdsToVulnerabilityStatistics"
def up
ensure_batched_background_migration_is_finished(
job_class_name: MIGRATION_NAME,
table_name: :vulnerability_statistics,
column_name: :id,
job_arguments: []
)
end
def down
# no-op
end
end

View File

@ -0,0 +1 @@
f4023de9e67c216e18bf1a89e1b39383ed086cc5e77335222bdb3c69e396ae9c

View File

@ -18,6 +18,8 @@ Prerequisites:
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/133015) in GitLab 16.7 [with a flag](../administration/feature_flags.md) named `access_rest_chat`. Disabled by default. This feature is internal-only.
- [Added additional_context parameter](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/162650) in GitLab 17.4 [with a flag](../administration/feature_flags.md) named `duo_additional_context`. Disabled by default. This feature is internal-only.
- [Enabled on GitLab.com and GitLab Self-Managed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/181305) in GitLab 17.9 [with a flag](../administration/feature_flags.md) named `duo_additional_context`.
- [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/514559) in GitLab 18.0. Feature flag `duo_additional_context` removed in GitLab 18.0.
{{< /history >}}

View File

@ -13774,6 +13774,28 @@ paths:
tags:
- composer_packages
operationId: getApiV4ProjectsIdPackagesComposerArchives*packageName
"/api/v4/projects/{id}/packages/conan/v1/users/authenticate":
get:
summary: Authenticate user against conan CLI
description: This feature was introduced in GitLab 12.2
produces:
- application/json
parameters:
- in: path
name: id
description: The ID or URL-encoded path of the project
type: string
required: true
responses:
'200':
description: Authenticate user against conan CLI
'401':
description: Unauthorized
'404':
description: Not Found
tags:
- conan_packages
operationId: getApiV4ProjectsIdPackagesConanV1UsersAuthenticate
"/api/v4/projects/{id}/packages/conan/v1/users/check_credentials":
get:
summary: Check for valid user credentials per conan CLI
@ -13844,28 +13866,6 @@ paths:
tags:
- conan_packages
operationId: getApiV4ProjectsIdPackagesConanV1Ping
"/api/v4/projects/{id}/packages/conan/v1/users/authenticate":
get:
summary: Authenticate user against conan CLI
description: This feature was introduced in GitLab 12.2
produces:
- application/json
parameters:
- in: path
name: id
description: The ID or URL-encoded path of the project
type: string
required: true
responses:
'200':
description: Authenticate user against conan CLI
'401':
description: Unauthorized
'404':
description: Not Found
tags:
- conan_packages
operationId: getApiV4ProjectsIdPackagesConanV1UsersAuthenticate
? "/api/v4/projects/{id}/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/packages/{conan_package_reference}"
: get:
summary: Package Snapshot
@ -14801,6 +14801,28 @@ paths:
tags:
- conan_packages
operationId: putApiV4ProjectsIdPackagesConanV1FilesPackageNamePackageVersionPackageUsernamePackageChannelRecipeRevisionPackageConanPackageReferencePackageRevisionFileNameAuthorize
"/api/v4/projects/{id}/packages/conan/v2/users/authenticate":
get:
summary: Authenticate user against conan CLI
description: This feature was introduced in GitLab 12.2
produces:
- application/json
parameters:
- in: path
name: id
description: The ID or URL-encoded path of the project
type: string
required: true
responses:
'200':
description: Authenticate user against conan CLI
'401':
description: Unauthorized
'404':
description: Not Found
tags:
- conan_packages
operationId: getApiV4ProjectsIdPackagesConanV2UsersAuthenticate
"/api/v4/projects/{id}/packages/conan/v2/users/check_credentials":
get:
summary: Check for valid user credentials per conan CLI
@ -38191,6 +38213,22 @@ paths:
tags:
- composer_packages
operationId: getApiV4GroupIdPackagesComposer*packageName
"/api/v4/packages/conan/v1/users/authenticate":
get:
summary: Authenticate user against conan CLI
description: This feature was introduced in GitLab 12.2
produces:
- application/json
responses:
'200':
description: Authenticate user against conan CLI
'401':
description: Unauthorized
'404':
description: Not Found
tags:
- conan_packages
operationId: getApiV4PackagesConanV1UsersAuthenticate
"/api/v4/packages/conan/v1/users/check_credentials":
get:
summary: Check for valid user credentials per conan CLI
@ -38244,22 +38282,6 @@ paths:
tags:
- conan_packages
operationId: getApiV4PackagesConanV1Ping
"/api/v4/packages/conan/v1/users/authenticate":
get:
summary: Authenticate user against conan CLI
description: This feature was introduced in GitLab 12.2
produces:
- application/json
responses:
'200':
description: Authenticate user against conan CLI
'401':
description: Unauthorized
'404':
description: Not Found
tags:
- conan_packages
operationId: getApiV4PackagesConanV1UsersAuthenticate
? "/api/v4/packages/conan/v1/conans/{package_name}/{package_version}/{package_username}/{package_channel}/packages/{conan_package_reference}"
: get:
summary: Package Snapshot

View File

@ -46,7 +46,41 @@ These endpoints all return `404 Not Found`.
## Create an authentication token
Creates a JSON Web Token (JWT) for use as a Bearer header in other requests using the Conan v1 [`/authenticate`](conan_v1.md#create-an-authentication-token) endpoint.
Creates a JSON Web Token (JWT) for use as a Bearer header in other requests.
```shell
"Authorization: Bearer <token>
```
The Conan 2 package manager client automatically uses this token.
```plaintext
GET /projects/:id/packages/conan/v2/users/authenticate
```
| Attribute | Type | Required | Description |
| --------- | ------ | ------------- | ---------------------------------------------------------------------------- |
| `id` | string | Conditionally | The project ID or full project path. Required only for the project endpoint. |
Generate a base64-encoded Basic Auth token:
```shell
echo -n "<username>:<personal_access_token>"|base64
```
Use the base64-encoded Basic Auth token to get a JWT token:
```shell
curl --request GET \
--header 'Authorization: Basic <base64-encoded-token>' \
--url "https://gitlab.example.com/api/v4/packages/conan/v2/users/authenticate"
```
Example response:
```shell
eyJhbGciOiJIUzI1NiIiheR5cCI6IkpXVCJ9.eyJhY2Nlc3NfdG9rZW4iOjMyMTQyMzAsqaVzZXJfaWQiOjQwNTkyNTQsImp0aSI6IjdlNzBiZTNjLWFlNWQtNDEyOC1hMmIyLWZiOThhZWM0MWM2OSIsImlhd3r1MTYxNjYyMzQzNSwibmJmIjoxNjE2NjIzNDMwLCJleHAiOjE2MTY2MjcwMzV9.QF0Q3ZIB2GW5zNKyMSIe0HIFOITjEsZEioR-27Rtu7E
```
## Verify authentication credentials

View File

@ -127,6 +127,7 @@ The following endpoints are available for CI/CD job tokens.
| None | | `GET /projects/:id/packages/conan/v1/users/authenticate` | Authenticate user against conan CLI |
| None | | `GET /projects/:id/packages/conan/v1/users/check_credentials` | Check for valid user credentials per conan CLI |
| None | | `GET /projects/:id/packages/conan/v2/conans/search` | Search for packages |
| None | | `GET /projects/:id/packages/conan/v2/users/authenticate` | Authenticate user against conan CLI |
| None | | `GET /projects/:id/packages/conan/v2/users/check_credentials` | Check for valid user credentials per conan CLI |
| None | | `GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name` | Get details about a repository tag |
| None | | `GET /projects/:id/registry/repositories/:repository_id/tags` | List tags of a repository |

View File

@ -95,6 +95,7 @@ To view metrics on the Overview panel, the [background aggregation](#enable-or-d
{{< history >}}
- Contributor count metric at the group level [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/433353) to GitLab.com in GitLab 16.9.
- Contributor count metric at the project level [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/474119) to GitLab.com in GitLab 18.0.
{{< /history >}}
@ -275,6 +276,7 @@ Prerequisites:
- You must have at least the Reporter role for the project.
- Overview background aggregation for Value Streams Dashboards must be enabled.
- To view the contributor count metric in the comparison panel, you must [set up ClickHouse](../../integration/clickhouse.md).
To view the Value Streams Dashboard as an analytics dashboard for a project:

View File

@ -94,7 +94,7 @@ In addition, Chat is aware of different information, depending on where you use
| Selected lines in editor | Selected code | {{< icon name="check-circle-filled" >}} Yes | {{< icon name="check-circle-filled" >}} Yes | With the lines selected, ask about `this code` or `this file`. Chat is not aware of the file; you must select the lines you want to ask about. |
| Epics | Epic details | {{< icon name="dash-circle" >}} No | {{< icon name="check-circle-filled" >}} Yes | Ask about the URL. |
| Issues | Issue details | {{< icon name="dash-circle" >}} No | {{< icon name="check-circle-filled" >}} Yes | Ask about the URL. |
| Files | File content | {{< icon name="dash-circle" >}} No | {{< icon name="check-circle-filled" >}} Yes | Use the `/include` command to search for project files to add to Duo Chat's context. After you've added the files, you can ask Duo Chat questions about the file contents. Available for VS Code and JetBrains IDEs. For more information, see [Ask about specific files](examples.md#ask-about-specific-files-in-the-ide). |
| Files | File content | {{< icon name="check-circle-filled" >}} Yes | {{< icon name="check-circle-filled" >}} Yes | Use the `/include` command to search for project files to add to Duo Chat's context. After you've added the files, you can ask Duo Chat questions about the file contents. Available for VS Code and JetBrains IDEs. For more information, see [Ask about specific files](examples.md#ask-about-specific-files-in-the-ide). |
In addition, in the IDEs, when you use any of the slash commands,
like `/explain`, `/refactor`, `/fix`, or `/tests,` Duo Chat has access to the

View File

@ -346,8 +346,7 @@ Programming languages that require compiling the source code may throw cryptic e
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/477258) in GitLab 17.7 [with flags](../../administration/feature_flags.md) named `duo_additional_context` and `duo_include_context_file`. Disabled by default.
- [Enabled](https://gitlab.com/groups/gitlab-org/-/epics/15227) for [self-hosted model configuration](../../administration/gitlab_duo_self_hosted/_index.md#self-hosted-ai-gateway-and-llms) as well as the [default GitLab external AI vendor configuration](../../administration/gitlab_duo_self_hosted/_index.md#gitlabcom-ai-gateway-with-default-gitlab-external-vendor-llms) in GitLab 17.9.
- [Enabled on GitLab.com, GitLab Self-Managed, and GitLab Dedicated](https://gitlab.com/groups/gitlab-org/-/epics/15183) in GitLab 17.9.
- `duo_additional_context` flag [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/508741) in GitLab 17.10.
- [Enabled on GitLab.com and GitLab Self-Managed](https://gitlab.com/groups/gitlab-org/-/epics/15183) in GitLab 17.9.
{{< /history >}}

View File

@ -5,7 +5,7 @@ import localRules from 'eslint-plugin-local-rules';
import js from '@eslint/js';
import { FlatCompat } from '@eslint/eslintrc';
import * as graphqlEslint from '@graphql-eslint/eslint-plugin';
import * as todoLists from './.eslint_todo/index.mjs'
import * as todoLists from './.eslint_todo/index.mjs';
const { dirname } = import.meta;
const compat = new FlatCompat({
@ -69,6 +69,27 @@ const jestConfig = {
},
};
/** An object to make it easier to reuse restricted imports */
const restrictedImports = {
axios: {
name: 'axios',
message: 'Import axios from ~/lib/utils/axios_utils instead.',
},
mousetrap: {
name: 'mousetrap',
message: 'Import { Mousetrap } from ~/lib/mousetrap instead.',
},
sentry: {
name: '@sentry/browser',
message: 'Use "import * as Sentry from \'~/sentry/sentry_browser_wrapper\';" instead',
},
vuex: {
name: 'vuex',
message:
'See our documentation on "Migrating from VueX" for tips on how to avoid adding new VueX stores.',
},
};
export default [
{
ignores: [
@ -341,19 +362,10 @@ export default [
'error',
{
paths: [
{
name: 'mousetrap',
message: 'Import { Mousetrap } from ~/lib/mousetrap instead.',
},
{
name: 'vuex',
message:
'See our documentation on "Migrating from VueX" for tips on how to avoid adding new VueX stores.',
},
{
name: '@sentry/browser',
message: 'Use "import * as Sentry from \'~/sentry/sentry_browser_wrapper\';" instead',
},
restrictedImports.axios,
restrictedImports.mousetrap,
restrictedImports.sentry,
restrictedImports.vuex,
],
patterns: [
@ -522,19 +534,10 @@ export default [
'error',
{
paths: [
{
name: 'mousetrap',
message: 'Import { Mousetrap } from ~/lib/mousetrap instead.',
},
{
name: 'vuex',
message:
'See our documentation on "Migrating from VueX" for tips on how to avoid adding new VueX stores.',
},
{
name: '@sentry/browser',
message: 'Use "import * as Sentry from \'~/sentry/sentry_browser_wrapper\';" instead',
},
restrictedImports.axios,
restrictedImports.mousetrap,
restrictedImports.sentry,
restrictedImports.vuex,
{
name: '~/locale',
importNames: ['__', 's__'],
@ -703,7 +706,17 @@ export default [
},
},
// Consumer specs config
/*
contracts specs are a little different, as they are not "normal" jest specs.
They are actually executing `jest` and e.g. do proper non-mocked calls with axios in order
to check API contracts.
They also do not directly execute library code, so some of our usual linting rules for app code
like no-restricted-imports or i18n rules make no sense here and we can disable them.
For reference: https://docs.gitlab.com/development/testing_guide/contract/
*/
{
files: ['{,ee/}spec/contracts/consumer/**/*.js'],
@ -713,6 +726,7 @@ export default [
rules: {
'@gitlab/require-i18n-strings': 'off',
'no-restricted-imports': 'off',
},
},
...jhConfigs,

View File

@ -57,6 +57,25 @@ module API
format :txt
content_type :txt, 'text/plain'
desc 'Authenticate user against conan CLI' do
detail 'This feature was introduced in GitLab 12.2'
success code: 200
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
route_setting :authorization, skip_job_token_policies: true
get 'authenticate', urgency: :low do
unauthorized! unless token
token.to_jwt
end
desc 'Check for valid user credentials per conan CLI' do
detail 'This feature was introduced in GitLab 12.4'
success code: 200

View File

@ -41,34 +41,6 @@ module API
header 'X-Conan-Server-Capabilities', x_conan_server_capabilities_header.join(',')
end
namespace 'users' do
before do
authenticate!
end
format :txt
content_type :txt, 'text/plain'
desc 'Authenticate user against conan CLI' do
detail 'This feature was introduced in GitLab 12.2'
success code: 200
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not Found' }
]
tags %w[conan_packages]
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
route_setting :authorization, skip_job_token_policies: true
get 'authenticate', urgency: :low do
unauthorized! unless token
token.to_jwt
end
end
params do
requires :package_name, type: String, regexp: SharedEndpoints::PACKAGE_COMPONENT_REGEX,
desc: 'Package name', documentation: { example: 'my-package' }

View File

@ -33,8 +33,8 @@ module Gitlab
track_struct_event(tracker, category, action, label: label, property: property, value: value, contexts: contexts)
end
def options(group)
tracker.options(group)
def frontend_client_options(group)
tracker.frontend_client_options(group)
end
def collector_hostname

View File

@ -40,16 +40,18 @@ module Gitlab
emitter.input(payload)
end
def options(group)
def frontend_client_options(group)
additional_features = Feature.enabled?(:additional_snowplow_tracking, group, type: :ops)
# Using camel case as these keys will be used only in JavaScript
{
namespace: SNOWPLOW_NAMESPACE,
hostname: hostname,
cookie_domain: cookie_domain,
app_id: app_id,
form_tracking: additional_features,
link_click_tracking: additional_features
}.transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
cookieDomain: cookie_domain,
appId: app_id,
formTracking: additional_features,
linkClickTracking: additional_features
}
end
def enabled?

View File

@ -11,13 +11,13 @@ module Gitlab
DEFAULT_URI = 'http://localhost:9090'
override :options
def options(group)
super.update(
override :frontend_client_options
def frontend_client_options(group)
super.merge(
protocol: uri.scheme,
port: uri.port,
force_secure_tracker: false
).transform_keys! { |key| key.to_s.camelize(:lower).to_sym }
forceSecureTracker: false # Using camel case as this key will be used only in JavaScript
)
end
override :enabled?

View File

@ -5732,6 +5732,9 @@ msgstr ""
msgid "AiPowered|Enable AI logs"
msgstr ""
msgid "AiPowered|Enabled"
msgstr ""
msgid "AiPowered|Enabling self-hosted beta models and features is your acceptance of the %{linkStart}GitLab Testing Agreement%{linkEnd}."
msgstr ""
@ -5765,6 +5768,9 @@ msgstr ""
msgid "AiPowered|GitLab Duo Chat conversation expiration"
msgstr ""
msgid "AiPowered|GitLab Duo Core available to all users"
msgstr ""
msgid "AiPowered|GitLab Duo Self-Hosted"
msgstr ""
@ -5792,6 +5798,9 @@ msgstr ""
msgid "AiPowered|Monitor, manage, and customize AI features to ensure efficient utilization and alignment."
msgstr ""
msgid "AiPowered|Not enabled"
msgstr ""
msgid "AiPowered|Off by default"
msgstr ""
@ -37044,6 +37053,9 @@ msgstr ""
msgid "MemberRole|Are you sure you want to delete this role?"
msgstr ""
msgid "MemberRole|Automatically sync your LDAP directory to custom admin roles."
msgstr ""
msgid "MemberRole|Base role"
msgstr ""
@ -37059,6 +37071,9 @@ msgstr ""
msgid "MemberRole|Could not fetch available permissions."
msgstr ""
msgid "MemberRole|Could not load LDAP synchronizations. Please refresh the page to try again."
msgstr ""
msgid "MemberRole|Could not update role."
msgstr ""
@ -37080,6 +37095,9 @@ msgstr ""
msgid "MemberRole|Custom admin role"
msgstr ""
msgid "MemberRole|Custom admin role:"
msgstr ""
msgid "MemberRole|Custom member role"
msgstr ""
@ -37158,6 +37176,9 @@ msgstr ""
msgid "MemberRole|Failed to save role: %{error}"
msgstr ""
msgid "MemberRole|Group cn:"
msgstr ""
msgid "MemberRole|Learn more about %{linkStart}available custom permissions%{linkEnd}."
msgstr ""
@ -37182,6 +37203,9 @@ msgstr ""
msgid "MemberRole|New role"
msgstr ""
msgid "MemberRole|No active LDAP synchronizations. Add synchronization to connect your LDAP directory with custom admin roles."
msgstr ""
msgid "MemberRole|No description"
msgstr ""
@ -37191,6 +37215,9 @@ msgstr ""
msgid "MemberRole|Permission"
msgstr ""
msgid "MemberRole|Remove sync"
msgstr ""
msgid "MemberRole|Reverted to LDAP group sync settings. The role will be updated after the next LDAP sync."
msgstr ""
@ -37239,6 +37266,12 @@ msgstr ""
msgid "MemberRole|Select at least one permission."
msgstr ""
msgid "MemberRole|Server:"
msgstr ""
msgid "MemberRole|Sync all"
msgstr ""
msgid "MemberRole|The CSV report contains a list of users, assigned role and access in all groups, subgroups, and projects. When the export is completed, it will be sent as an attachment to %{email}."
msgstr ""
@ -37293,6 +37326,12 @@ msgstr ""
msgid "MemberRole|Use LDAP sync role"
msgstr ""
msgid "MemberRole|User filter:"
msgstr ""
msgid "MemberRole|View LDAP synced users"
msgstr ""
msgid "MemberRole|View permissions"
msgstr ""
@ -49594,7 +49633,7 @@ msgstr ""
msgid "Rate the review"
msgstr ""
msgid "Rate this response %{separator} %{codeStart}%{botUser}%{codeEnd} in reply for more questions"
msgid "Rate this response %{separator} Mention %{codeStart}%{botUser}%{codeEnd} to continue the conversation."
msgstr ""
msgid "Raw blob request rate limit per minute"
@ -66193,7 +66232,7 @@ msgstr ""
msgid "ValueStreamAnalytics|Number of merge requests merged by month."
msgstr ""
msgid "ValueStreamAnalytics|Number of monthly unique users with contributions in the group."
msgid "ValueStreamAnalytics|Number of monthly unique users with contributions."
msgstr ""
msgid "ValueStreamAnalytics|Number of new issues created."

View File

@ -52,7 +52,6 @@ ee/spec/frontend/requirements/components/requirement_item_spec.js
ee/spec/frontend/requirements/components/requirements_root_spec.js
ee/spec/frontend/roadmap/components/roadmap_shell_spec.js
ee/spec/frontend/roles_and_permissions/components/role_selector_spec.js
ee/spec/frontend/security_configuration/components/app_spec.js
ee/spec/frontend/sidebar/components/sidebar_dropdown_widget_spec.js
ee/spec/frontend/status_checks/components/modal_create_spec.js
ee/spec/frontend/status_checks/mount_spec.js

View File

@ -1,7 +1,7 @@
import { GlPagination, GlTable } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import InactiveAccessTokenTableApp from '~/access_tokens/components/inactive_access_token_table_app.vue';
import { HTTP_STATUS_OK, HTTP_STATUS_INTERNAL_SERVER_ERROR } from '~/lib/utils/http_status';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/admin/statistics_panel/store/actions';
import * as types from '~/admin/statistics_panel/store/mutation_types';

View File

@ -7,9 +7,9 @@ import {
GlLink,
} from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import mockAlerts from 'jest/vue_shared/alert_details/mocks/alerts.json';

View File

@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import axios from '~/lib/utils/axios_utils';
import {
filterMilestones,
filterLabels,

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper';
import * as actions from '~/analytics/cycle_analytics/store/actions';
import * as getters from '~/analytics/cycle_analytics/store/getters';

View File

@ -2,7 +2,7 @@ import { GlAlert, GlLink, GlSprintf, GlLoadingIcon } from '@gitlab/ui';
import { EditorContent, Editor } from '@tiptap/vue-2';
import { nextTick } from 'vue';
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { CONTENT_EDITOR_PASTE } from '~/vue_shared/constants';
import markdownEditorEventHub from '~/vue_shared/components/markdown/eventhub';

View File

@ -1,7 +1,7 @@
import fs from 'fs';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { builders } from 'prosemirror-test-builder';
import axios from '~/lib/utils/axios_utils';
import Attachment from '~/content_editor/extensions/attachment';
import DrawioDiagram from '~/content_editor/extensions/drawio_diagram';
import Image from '~/content_editor/extensions/image';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import AutocompleteHelper, {
defaultSorter,
customSorter,

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { uploadFile } from '~/content_editor/services/upload_helpers';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilteredSearchDropdownManager from '~/filtered_search/filtered_search_dropdown_manager';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import FilteredSearchSpecHelper from 'helpers/filtered_search_spec_helper';
import waitForPromises from 'helpers/wait_for_promises';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import getIdeProject from 'ee_else_ce/ide/queries/get_ide_project.query.graphql';
import Api from '~/api';
import services from '~/ide/services';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper';
import { file } from 'jest/ide/helpers';
import { commitActionTypes, PERMISSION_CREATE_MR } from '~/ide/constants';

View File

@ -1,7 +1,7 @@
import { GlAlert, GlForm } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { setHTMLFixture } from 'helpers/fixtures';
import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper';
import { I18N_FETCH_TEST_SETTINGS_DEFAULT_ERROR_MESSAGE } from '~/integrations/constants';
import {

View File

@ -1,7 +1,7 @@
import { nextTick } from 'vue';
import { GlAvatarLabeled, GlCollapsibleListbox } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
import { getGroups } from '~/api/groups_api';
import { getProjectShareLocations } from '~/api/projects_api';

View File

@ -1,7 +1,7 @@
import { GlModal, GlSprintf, GlFormInputGroup, GlButton } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import IssuableByEmail from '~/issuable/components/issuable_by_email.vue';

View File

@ -1,3 +1,8 @@
/*
We need to import axios directly here, the shared lib already applies
the interceptor we are trying to test.
*/
// eslint-disable-next-line no-restricted-imports
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import setupAxiosStartupCalls from '~/lib/utils/axios_startup_calls';

View File

@ -1,6 +1,6 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { noop } from 'lodash';
import axios from '~/lib/utils/axios_utils';
import { useFakeDate } from 'helpers/fake_date';
import testAction from 'helpers/vuex_action_helper';
import { members, group, modalData } from 'jest/members/mock_data';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Cookies from '~/lib/utils/cookies';
import { HTTP_STATUS_BAD_REQUEST, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import { useMockLocationHelper } from 'helpers/mock_window_location_helper';

View File

@ -1,10 +1,10 @@
import { GlLoadingIcon, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Vue from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
import MilestoneCombobox from '~/milestones/components/milestone_combobox.vue';
import createStore from '~/milestones/stores/';

View File

@ -1,4 +1,4 @@
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { uploadModel } from '~/ml/model_registry/services/upload_model';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';

View File

@ -338,8 +338,9 @@ describe('issue_note_body component', () => {
const result = wrapper.vm.duoFeedbackText;
expect(result).toContain('Rate this response');
expect(result).toContain('Mention');
expect(result).toContain('@GitLabDuo');
expect(result).toContain('in reply for more questions');
expect(result).toContain('to continue the conversation.');
});
});
});

View File

@ -1,7 +1,7 @@
import { GlSprintf, GlModal, GlFormGroup, GlFormCheckbox, GlLoadingIcon } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';

View File

@ -1,6 +1,6 @@
import { GlButton, GlButtonGroup, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import waitForPromises from 'helpers/wait_for_promises';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper';
import Api from '~/api';
import { createAlert } from '~/alert';

View File

@ -6,11 +6,11 @@ import {
GlFormRadio,
GlSprintf,
} from '@gitlab/ui';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
import { kebabCase, merge } from 'lodash';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import axios from '~/lib/utils/axios_utils';
import { createAlert } from '~/alert';
import * as urlUtility from '~/lib/utils/url_utility';
import ForkForm from '~/pages/projects/forks/new/components/fork_form.vue';

View File

@ -8,8 +8,8 @@ import {
GlFormCheckbox,
} from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { mockTracking } from 'helpers/tracking_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import WikiForm from '~/pages/shared/wikis/components/wiki_form.vue';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper';
import { createAlert } from '~/alert';
import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';

View File

@ -1,5 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
import axios from 'axios';
import axios from '~/lib/utils/axios_utils';
import { resetHTMLFixture, setHTMLFixture } from 'helpers/fixtures';
import { initFileBrowser } from '~/rapid_diffs/app/init_file_browser';
import createEventHub from '~/helpers/event_hub_factory';

View File

@ -1,6 +1,5 @@
import { GlLoadingIcon, GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { merge, last } from 'lodash';
// eslint-disable-next-line no-restricted-imports
@ -10,6 +9,7 @@ import commit from 'test_fixtures/api/commits/commit.json';
import branches from 'test_fixtures/api/branches/branches.json';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { trimText } from 'helpers/text_helper';
import axios from '~/lib/utils/axios_utils';
import {
HTTP_STATUS_INTERNAL_SERVER_ERROR,
HTTP_STATUS_NOT_FOUND,

View File

@ -1,4 +1,3 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { merge } from 'lodash';
// eslint-disable-next-line no-restricted-imports
@ -6,6 +5,7 @@ import Vuex from 'vuex';
import { nextTick } from 'vue';
import { GlDatepicker, GlFormCheckbox } from '@gitlab/ui';
import originalOneReleaseForEditingQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/one_release_for_editing.query.graphql.json';
import axios from '~/lib/utils/axios_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import createMilestoneComboboxState from '~/milestones/stores/state';
import { convertOneReleaseGraphQLResponse } from '~/releases/util';

View File

@ -1,8 +1,8 @@
import { GlButton, GlFormInput, GlFormTextarea } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import { shallowMount } from '@vue/test-utils';
import axios from '~/lib/utils/axios_utils';
import TagCreate from '~/releases/components/tag_create.vue';
import RefSelector from '~/ref/components/ref_selector.vue';
import createStore from '~/releases/stores';

View File

@ -1,7 +1,7 @@
import { GlFormGroup, GlTruncate, GlPopover } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import TagFieldNew from '~/releases/components/tag_field_new.vue';
import TagSearch from '~/releases/components/tag_search.vue';

View File

@ -1,8 +1,8 @@
import { GlButton, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import { mount } from '@vue/test-utils';
import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
import { DEFAULT_PER_PAGE } from '~/api';
import { sprintf } from '~/locale';

View File

@ -3,9 +3,9 @@ import { mount, shallowMount } from '@vue/test-utils';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import Vue, { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import VueApollo from 'vue-apollo';
import axios from '~/lib/utils/axios_utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert } from '~/alert';

View File

@ -1,6 +1,6 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMount } from '@vue/test-utils';
import axios from '~/lib/utils/axios_utils';
import DeleteBlobModal from '~/repository/components/delete_blob_modal.vue';
import CommitChangesModal from '~/repository/components/commit_changes_modal.vue';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';

View File

@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
import { HTTP_STATUS_OK, HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status';
import * as urlUtility from '~/lib/utils/url_utility';

View File

@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import { createAlert } from '~/alert';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';

View File

@ -1,7 +1,7 @@
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import axios from '~/lib/utils/axios_utils';
import { splitIntoChunks } from '~/vue_shared/components/source_viewer/workers/highlight_utils';
import highlightMixin from '~/repository/mixins/highlight_mixin';
import waitForPromises from 'helpers/wait_for_promises';

View File

@ -1,6 +1,6 @@
import { GlForm, GlFormInput } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createAlert, VARIANT_SUCCESS } from '~/alert';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { createAlert, VARIANT_SUCCESS } from '~/alert';
import { HTTP_STATUS_NOT_FOUND, HTTP_STATUS_OK } from '~/lib/utils/http_status';

View File

@ -1,9 +1,9 @@
import Vue, { nextTick } from 'vue';
import axios from 'axios';
import AxiosMockAdapter from 'axios-mock-adapter';
import VueApollo from 'vue-apollo';
import { PiniaVuePlugin } from 'pinia';
import { createTestingPinia } from '@pinia/testing';
import axios from '~/lib/utils/axios_utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';

View File

@ -1,6 +1,6 @@
import axios from 'axios';
import { GlLoadingIcon, GlSprintf } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import waitForPromises from 'helpers/wait_for_promises';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import MemoryUsage from '~/vue_merge_request_widget/components/deployment/memory_usage.vue';

View File

@ -2,8 +2,8 @@ import Vue, { nextTick } from 'vue';
import { GlLoadingIcon } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { trimText } from 'helpers/text_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';

View File

@ -2,8 +2,8 @@ import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { GlAlert, GlLoadingIcon } from '@gitlab/ui';
import { mount, shallowMount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';

View File

@ -1,6 +1,6 @@
import { shallowMount, mount } from '@vue/test-utils';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import AlertSidebar from '~/vue_shared/alert_details/components/alert_sidebar.vue';
import SidebarAssignees from '~/vue_shared/alert_details/components/sidebar/sidebar_assignees.vue';
import SidebarStatus from '~/vue_shared/alert_details/components/sidebar/sidebar_status.vue';

View File

@ -1,8 +1,8 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlAvatar, GlDropdownItem } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import SidebarAssignee from '~/vue_shared/alert_details/components/sidebar/sidebar_assignee.vue';

View File

@ -1,5 +1,5 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import testAction from 'helpers/vuex_action_helper';
import { mockBranches } from 'jest/vue_shared/components/filtered_search_bar/mock_data';
import Api from '~/api';

View File

@ -1,8 +1,8 @@
import axios from 'axios';
import Autosize from 'autosize';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import { GlAlert } from '@gitlab/ui';
import axios from '~/lib/utils/axios_utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import {
EDITING_MODE_MARKDOWN_FIELD,

View File

@ -1,14 +1,24 @@
import { shallowMount } from '@vue/test-utils';
import App from '~/work_items/components/app.vue';
import { ROUTES } from '~/work_items/constants';
describe('Work Items Application', () => {
let wrapper;
const createComponent = () => {
const DEFAULT_ROUTE_MOCK = {
path: '/',
name: ROUTES.index,
params: {},
};
const createComponent = (routeMock = DEFAULT_ROUTE_MOCK) => {
wrapper = shallowMount(App, {
stubs: {
'router-view': true,
},
mocks: {
$route: routeMock,
},
});
};

View File

@ -9,6 +9,7 @@ import * as autosave from '~/lib/utils/autosave';
import { ESC_KEY, ENTER_KEY } from '~/lib/utils/keys';
import { STATE_OPEN, i18n } from '~/work_items/constants';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import gfmEventHub from '~/vue_shared/components/markdown/eventhub';
import HelpIcon from '~/vue_shared/components/help_icon/help_icon.vue';
import workItemEmailParticipantsByIidQuery from '~/work_items/graphql/notes/work_item_email_participants_by_iid.query.graphql';
import * as confirmViaGlModal from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal';
@ -299,6 +300,19 @@ describe('Work item comment form component', () => {
});
});
it('emits edit-current-user-last-note on `up` keypress', async () => {
await createComponent();
jest.spyOn(gfmEventHub, '$emit').mockImplementation(jest.fn());
findMarkdownEditor().vm.$emit('keydown', new KeyboardEvent('keydown', { key: 'ArrowUp' }));
expect(gfmEventHub.$emit).toHaveBeenCalledWith(
'edit-current-user-last-note',
expect.any(Object),
);
});
it('cancels editing on clicking cancel button', async () => {
createComponent();
findCancelButton().vm.$emit('click');

View File

@ -3,6 +3,7 @@ import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import gfmEventHub from '~/vue_shared/components/markdown/eventhub';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
import ToggleRepliesWidget from '~/notes/components/toggle_replies_widget.vue';
import WorkItemDiscussion from '~/work_items/components/notes/work_item_discussion.vue';
import WorkItemNote from '~/work_items/components/notes/work_item_note.vue';
@ -29,6 +30,7 @@ describe('Work Item Discussion', () => {
const findToggleRepliesWidget = () => wrapper.findComponent(ToggleRepliesWidget);
const findAllThreads = () => wrapper.findAllComponents(WorkItemNote);
const findTimelineEntryItem = () => wrapper.findComponent(TimelineEntryItem);
const findThreadAtIndex = (index) => findAllThreads().at(index);
const findWorkItemAddNote = () => wrapper.findComponent(WorkItemAddNote);
const findWorkItemNoteReplying = () => wrapper.findComponent(WorkItemNoteReplying);
@ -102,6 +104,17 @@ describe('Work Item Discussion', () => {
});
});
it('should render timeline-entry-item with required data attributes', () => {
const expectedDiscussion =
mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[0].notes.nodes[0];
expect(findTimelineEntryItem().attributes()).toEqual({
class: expect.any(String),
'data-note-id': expectedDiscussion.id.split('/').pop(),
'data-discussion-id': expectedDiscussion.discussion.id,
});
});
it('should show the toggle replies widget', () => {
expect(findToggleRepliesWidget().exists()).toBe(true);
});

View File

@ -6,6 +6,7 @@ import mockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { updateDraft, clearDraft } from '~/lib/utils/autosave';
import EditedAt from '~/issues/show/components/edited.vue';
import gfmEventHub from '~/vue_shared/components/markdown/eventhub';
import WorkItemNote from '~/work_items/components/notes/work_item_note.vue';
import WorkItemNoteAwardsList from '~/work_items/components/notes/work_item_note_awards_list.vue';
import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue';
@ -104,6 +105,7 @@ describe('Work Item Note', () => {
isFirstNote,
workItemType: 'Task',
markdownPreviewPath: '/group/project/preview_markdown?target_type=WorkItem',
uploadsPath: '/test-project-path/uploads',
autocompleteDataSources: {},
assignees,
},
@ -510,4 +512,41 @@ end`;
expect(findNoteActions().props('showAssignUnassign')).toBe(false);
});
});
describe('markdown-editor event-hub edit-note event', () => {
const setupComponent = async ({ id = mockWorkItemCommentNote.id, adminNote = true } = {}) => {
createComponent({
note: {
...mockWorkItemCommentNote,
id,
userPermissions: {
...mockWorkItemCommentNote.userPermissions,
adminNote,
},
},
});
gfmEventHub.$emit('edit-note', { note: mockWorkItemCommentNote });
await nextTick();
};
it('enables editing on the note for a matching note when adminNote is true', async () => {
await setupComponent();
expect(wrapper.emitted('startEditing')).toHaveLength(1);
expect(findCommentForm().exists()).toBe(true);
});
it.each`
setupProps | description
${{ adminNote: false }} | ${'a matching note when adminNote is false'}
${{ id: 'gid://gitlab/Note/100' }} | ${'non-matching note when adminNote is true'}
`('does not enable editing on the note for $description', async ({ setupProps }) => {
await setupComponent(setupProps);
expect(wrapper.emitted('startEditing')).toBeUndefined();
expect(findCommentForm().exists()).toBe(false);
});
});
});

View File

@ -1,9 +1,9 @@
import Vue, { nextTick } from 'vue';
import { GlForm, GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import axios from 'axios';
import VueApollo from 'vue-apollo';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue';

View File

@ -618,7 +618,7 @@ describe('WorkItemNotes component', () => {
});
});
describe('reply shortcut', () => {
describe('r key (reply) shortcut', () => {
const triggerReplyShortcut = async () => {
Mousetrap.trigger(keysFor(ISSUABLE_COMMENT_OR_REPLY)[0]);
await nextTick();
@ -670,4 +670,77 @@ describe('WorkItemNotes component', () => {
},
);
});
describe('up-arrow key (edit last note) shortcut', () => {
const setupComponent = async ({
notesResponse = mockWorkItemNotesResponseWithComments(),
} = {}) => {
window.gon.current_user_id = 1;
jest.clearAllMocks();
jest.spyOn(gfmEventHub, '$emit').mockImplementation(jest.fn());
jest.spyOn(gfmEventHub, '$on').mockImplementation(jest.fn());
createComponent({
defaultWorkItemNotesQueryHandler: jest.fn().mockResolvedValue(notesResponse),
canCreateNote: true,
});
await waitForPromises();
};
it('attaches `edit-current-user-last-note` event listener on mount', async () => {
await setupComponent();
expect(gfmEventHub.$on).toHaveBeenCalledWith(
'edit-current-user-last-note',
wrapper.vm.editCurrentUserLastNote,
);
});
it('emits `edit-note` on markdown-editor event-hub with last user note', async () => {
const mockLastNote =
mockWorkItemNotesWidgetResponseWithComments.discussions.nodes[1].notes.nodes[0];
await setupComponent();
const registeredHandler = gfmEventHub.$on.mock.calls.find(
(call) => call[0] === 'edit-current-user-last-note',
)[1];
const mockEvent = {
target: wrapper.findComponent(WorkItemAddNote).element,
};
await registeredHandler(mockEvent);
expect(gfmEventHub.$emit).toHaveBeenCalledWith('edit-note', {
note: {
...mockLastNote,
discussion: {
...mockLastNote.discussion,
resolvable: false,
},
},
});
});
it('does not emit `edit-note` on markdown-editor event-hub when no last user note is found', async () => {
await setupComponent({
notesResponse: mockWorkItemNotesByIidResponse,
});
const registeredHandler = gfmEventHub.$on.mock.calls.find(
(call) => call[0] === 'edit-current-user-last-note',
)[1];
const mockEvent = {
target: wrapper.findComponent(WorkItemAddNote).element,
};
await registeredHandler(mockEvent);
expect(gfmEventHub.$emit).not.toHaveBeenCalledWith('edit-note');
});
});
});

View File

@ -1,4 +1,3 @@
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import Dropzone from 'dropzone';
import $ from 'jquery';
@ -6,6 +5,7 @@ import htmlSnippetsShow from 'test_fixtures/snippets/show.html';
import { Mousetrap } from '~/lib/mousetrap';
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
import GLForm from '~/gl_form';
import axios from '~/lib/utils/axios_utils';
import * as utils from '~/lib/utils/common_utils';
import { HTTP_STATUS_OK } from '~/lib/utils/http_status';
import ZenMode from '~/zen_mode';

View File

@ -56,7 +56,7 @@ RSpec.describe Gitlab::Tracking::Destinations::SnowplowMicro, feature_category:
end
end
describe '#options' do
describe '#frontend_client_options' do
let_it_be(:group) { create :group }
before do
@ -64,15 +64,15 @@ RSpec.describe Gitlab::Tracking::Destinations::SnowplowMicro, feature_category:
end
it 'includes protocol with the correct value' do
expect(subject.options(group)[:protocol]).to eq 'http'
expect(subject.frontend_client_options(group)[:protocol]).to eq 'http'
end
it 'includes port with the correct value' do
expect(subject.options(group)[:port]).to eq 9091
expect(subject.frontend_client_options(group)[:port]).to eq 9091
end
it 'includes forceSecureTracker with value false' do
expect(subject.options(group)[:forceSecureTracker]).to eq false
expect(subject.frontend_client_options(group)[:forceSecureTracker]).to eq false
end
end
end

View File

@ -20,20 +20,20 @@ RSpec.describe Gitlab::Tracking, feature_category: :application_instrumentation
it { is_expected.to delegate_method(:flush).to(:tracker) }
describe '.options' do
describe '.frontend_client_options' do
shared_examples 'delegates to destination' do |klass|
before do
allow_next_instance_of(klass) do |instance|
allow(instance).to receive(:options).and_call_original
allow(instance).to receive(:frontend_client_options).and_call_original
end
end
it "delegates to #{klass} destination" do
expect_next_instance_of(klass) do |instance|
expect(instance).to receive(:options)
expect(instance).to receive(:frontend_client_options)
end
subject.options(nil)
subject.frontend_client_options(nil)
end
end
@ -53,7 +53,7 @@ RSpec.describe Gitlab::Tracking, feature_category: :application_instrumentation
linkClickTracking: true
}
expect(subject.options(nil)).to match(expected_fields)
expect(subject.frontend_client_options(nil)).to match(expected_fields)
end
end
@ -70,7 +70,7 @@ RSpec.describe Gitlab::Tracking, feature_category: :application_instrumentation
linkClickTracking: true
}
expect(subject.options(nil)).to match(expected_fields)
expect(subject.frontend_client_options(nil)).to match(expected_fields)
end
end
@ -98,7 +98,7 @@ RSpec.describe Gitlab::Tracking, feature_category: :application_instrumentation
it 'when feature flag is disabled' do
stub_feature_flags(additional_snowplow_tracking: false)
expect(subject.options(nil)).to include(
expect(subject.frontend_client_options(nil)).to include(
formTracking: false,
linkClickTracking: false
)

Some files were not shown because too many files have changed in this diff Show More