From 81240d18170bb149bd5d9cd481eeb88b97c0dab8 Mon Sep 17 00:00:00 2001
From: GitLab Bot
Date: Tue, 10 Sep 2024 12:08:24 +0000
Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master
---
.haml-lint.yml | 4 +-
.../gitlab/feature_flag_without_actor.yml | 2 +-
.rubocop_todo/gitlab/namespaced_class.yml | 2 +-
.../components/notes/abuse_report_note.vue | 2 +-
.../notes/abuse_report_note.fragment.graphql | 6 +-
...e_report_note_permissions.fragment.graphql | 3 -
.../create_abuse_report_note.mutation.graphql | 7 -
.../update_abuse_report_note.mutation.graphql | 5 -
.../common/private/job_action_component.vue | 1 +
.../diffs/stores/legacy_diffs/actions.js | 593 +++++++++---------
.../diffs/stores/legacy_diffs/getters.js | 304 +++++----
.../diffs/stores/legacy_diffs/index.js | 2 +-
.../diffs/stores/legacy_diffs/mutations.js | 227 ++++---
.../graphql_shared/possible_types.json | 11 +-
.../mr_notes/store/legacy_mr_notes.js | 3 +
.../components/multiline_comment_form.vue | 1 +
.../notes/store/legacy_notes/index.js | 3 +
app/assets/stylesheets/framework/awards.scss | 13 -
app/assets/stylesheets/framework/blocks.scss | 9 -
.../stylesheets/framework/breadcrumbs.scss | 22 -
.../framework/broadcast_messages.scss | 6 -
app/assets/stylesheets/framework/buttons.scss | 4 -
app/assets/stylesheets/framework/common.scss | 17 +-
app/assets/stylesheets/framework/diffs.scss | 15 -
.../stylesheets/framework/dropdowns.scss | 60 +-
.../framework/vue_transitions.scss | 5 +
.../resolvers/noteable/notes_resolver.rb | 3 -
app/graphql/types/abuse_report_type.rb | 7 +-
.../notes/abuse_report/discussion_type.rb | 23 +
.../types/notes/abuse_report/note_type.rb | 27 +
.../types/notes/base_discussion_interface.rb | 27 +
.../types/notes/base_note_interface.rb | 65 ++
app/graphql/types/notes/discussion_type.rb | 17 +-
app/graphql/types/notes/note_type.rb | 52 +-
app/graphql/types/notes/noteable_interface.rb | 5 -
app/models/anti_abuse/reports/discussion.rb | 2 +
app/models/anti_abuse/reports/note.rb | 13 +
app/models/concerns/notes/discussion.rb | 2 +-
.../integrations/instance_integration.rb | 7 +
app/models/note.rb | 4 +
.../anti_abuse/reports/note_policy.rb | 9 +
...ernal_authorization_service_form.html.haml | 2 +-
.../impersonation_tokens/index.html.haml | 2 +-
.../_resource_access_token_creation.html.haml | 4 +-
.../settings/_two_factor_auth.html.haml | 2 +-
.../settings/access_tokens/index.html.haml | 4 +-
.../profiles/two_factor_auths/show.html.haml | 6 +-
.../settings/access_tokens/_form.html.haml | 2 +-
.../settings/access_tokens/index.html.haml | 2 +-
...tor_auth_recovery_settings_check.html.haml | 2 +-
.../personal_access_tokens/index.html.haml | 2 +-
.../types/self_hosted_model_destroyed.yml | 9 +
config/initializers/gitlab_experiment.rb | 2 +-
db/docs/instance_integrations.yml | 10 +
...ory_pipeline_access_to_project_settings.rb | 10 +
...3210_create_instance_integrations_table.rb | 42 ++
db/schema_migrations/20240828103148 | 1 +
db/schema_migrations/20240829163210 | 1 +
db/structure.sql | 48 ++
doc/api/graphql/reference/index.md | 172 ++++-
doc/ci/pipelines/pipeline_types.md | 22 +-
doc/ci/secrets/akeyless.md | 1 +
doc/update/versions/gitlab_17_changes.md | 22 +-
doc/user/compliance/audit_event_types.md | 1 +
doc/user/group/saml_sso/index.md | 2 +-
haml_lint/linter/documentation_links.rb | 2 +-
lib/banzai/filter/base_sanitization_filter.rb | 5 +-
lib/banzai/filter/external_link_filter.rb | 6 +-
lib/banzai/filter/gollum_tags_filter.rb | 1 +
.../references/abstract_reference_filter.rb | 2 -
lib/banzai/filter/sanitize_link_filter.rb | 42 ++
lib/banzai/filter/spaced_link_filter.rb | 4 +-
lib/banzai/pipeline/ascii_doc_pipeline.rb | 1 +
.../pipeline/broadcast_message_pipeline.rb | 1 +
lib/banzai/pipeline/emoji_pipeline.rb | 1 +
lib/banzai/pipeline/gfm_pipeline.rb | 3 +-
.../timeline_event_pipeline.rb | 1 +
lib/banzai/pipeline/label_pipeline.rb | 1 +
lib/banzai/pipeline/markup_pipeline.rb | 1 +
lib/banzai/pipeline/single_line_pipeline.rb | 1 +
lib/gitlab/ci/config/external/file/project.rb | 8 +-
lib/gitlab/ci/project_config.rb | 2 +-
lib/gitlab/ci/project_config/source.rb | 4 +-
lib/gitlab/experiment/rollout/feature.rb | 82 ---
lib/gitlab/experiment_feature_rollout.rb | 78 +++
lib/gitlab/url_builder.rb | 6 +
locale/gitlab.pot | 41 +-
package.json | 2 +-
...ab+web-ide+0.0.1-dev-20240909013227.patch} | 2 +-
.../integrations/instance_integrations.rb | 7 +
.../profiles/two_factor_auths_spec.rb | 2 +-
.../notes/abuse_report_note_spec.js | 17 +-
spec/frontend/admin/abuse_report/mock_data.js | 180 +++++-
.../graph/components/action_component_spec.js | 18 +-
spec/frontend/ide/web_ide_assets_spec.js | 21 +
spec/graphql/types/abuse_report_type_spec.rb | 11 +
.../abuse_report/discussion_type_spec.rb | 25 +
.../notes/abuse_report/note_type_spec.rb | 32 +
spec/graphql/types/notes/note_type_spec.rb | 39 +-
.../types/notes/noteable_interface_spec.rb | 1 -
.../linter/documentation_links_spec.rb | 22 +-
...adcast_message_sanitization_filter_spec.rb | 3 -
.../filter/external_link_filter_spec.rb | 5 +-
spec/lib/banzai/filter/math_filter_spec.rb | 1 +
.../banzai/filter/sanitization_filter_spec.rb | 5 +-
.../filter/sanitize_link_filter_spec.rb | 17 +
.../broadcast_message_pipeline_spec.rb | 2 +
.../pipeline/description_pipeline_spec.rb | 2 +
.../banzai/pipeline/email_pipeline_spec.rb | 2 +
.../banzai/pipeline/emoji_pipeline_spec.rb | 2 +
.../lib/banzai/pipeline/full_pipeline_spec.rb | 2 +
spec/lib/banzai/pipeline/gfm_pipeline_spec.rb | 2 +
.../timeline_event_pipeline_spec.rb | 3 +
.../service_desk_email_pipeline_spec.rb | 2 +
.../lib/banzai/pipeline/wiki_pipeline_spec.rb | 2 +
....rb => experiment_feature_rollout_spec.rb} | 2 +-
spec/lib/gitlab/import_export/all_models.yml | 2 +-
spec/lib/gitlab/url_builder_spec.rb | 1 +
spec/models/anti_abuse/reports/note_spec.rb | 18 +
.../requests/api/graphql/abuse_report_spec.rb | 29 +
spec/requests/api/project_attributes.yml | 1 +
spec/support/rspec_order_todo.yml | 2 +-
.../filters/filter_timeout_shared_examples.rb | 9 +
.../sanitization_filter_shared_examples.rb | 53 +-
workhorse/go.mod | 4 +-
workhorse/go.sum | 8 +-
yarn.lock | 39 +-
127 files changed, 1766 insertions(+), 1085 deletions(-)
delete mode 100644 app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note_permissions.fragment.graphql
create mode 100644 app/assets/javascripts/mr_notes/store/legacy_mr_notes.js
create mode 100644 app/assets/javascripts/notes/store/legacy_notes/index.js
create mode 100644 app/graphql/types/notes/abuse_report/discussion_type.rb
create mode 100644 app/graphql/types/notes/abuse_report/note_type.rb
create mode 100644 app/graphql/types/notes/base_discussion_interface.rb
create mode 100644 app/graphql/types/notes/base_note_interface.rb
create mode 100644 app/models/integrations/instance_integration.rb
create mode 100644 app/policies/anti_abuse/reports/note_policy.rb
create mode 100644 config/audit_events/types/self_hosted_model_destroyed.yml
create mode 100644 db/docs/instance_integrations.yml
create mode 100644 db/migrate/20240828103148_add_spp_repository_pipeline_access_to_project_settings.rb
create mode 100644 db/migrate/20240829163210_create_instance_integrations_table.rb
create mode 100644 db/schema_migrations/20240828103148
create mode 100644 db/schema_migrations/20240829163210
create mode 100644 lib/banzai/filter/sanitize_link_filter.rb
delete mode 100644 lib/gitlab/experiment/rollout/feature.rb
create mode 100644 lib/gitlab/experiment_feature_rollout.rb
rename patches/{@gitlab+web-ide+0.0.1-dev-20240816130114.patch => @gitlab+web-ide+0.0.1-dev-20240909013227.patch} (99%)
create mode 100644 spec/factories/integrations/instance_integrations.rb
create mode 100644 spec/graphql/types/abuse_report_type_spec.rb
create mode 100644 spec/graphql/types/notes/abuse_report/discussion_type_spec.rb
create mode 100644 spec/graphql/types/notes/abuse_report/note_type_spec.rb
create mode 100644 spec/lib/banzai/filter/sanitize_link_filter_spec.rb
rename spec/lib/gitlab/{experiment/rollout/feature_spec.rb => experiment_feature_rollout_spec.rb} (97%)
diff --git a/.haml-lint.yml b/.haml-lint.yml
index faa858687b7..fbd85412637 100644
--- a/.haml-lint.yml
+++ b/.haml-lint.yml
@@ -30,7 +30,9 @@ linters:
max_consecutive: 2
DocumentationLinks:
- enabled: true
+ # Will be enabled again after offenses are resolved.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/419673
+ enabled: false
include:
- 'app/views/**/*.haml'
- 'ee/app/views/**/*.haml'
diff --git a/.rubocop_todo/gitlab/feature_flag_without_actor.yml b/.rubocop_todo/gitlab/feature_flag_without_actor.yml
index b0d5aa61bb8..1d9feedd44d 100644
--- a/.rubocop_todo/gitlab/feature_flag_without_actor.yml
+++ b/.rubocop_todo/gitlab/feature_flag_without_actor.yml
@@ -205,7 +205,7 @@ Gitlab/FeatureFlagWithoutActor:
- 'lib/gitlab/database/query_analyzers/prevent_cross_database_modification.rb'
- 'lib/gitlab/database/query_analyzers/prevent_set_operator_mismatch.rb'
- 'lib/gitlab/database/reindexing.rb'
- - 'lib/gitlab/experiment/rollout/feature.rb'
+ - 'lib/gitlab/experiment_feature_rollout.rb'
- 'lib/gitlab/git/diff.rb'
- 'lib/gitlab/git/repository.rb'
- 'lib/gitlab/git/user.rb'
diff --git a/.rubocop_todo/gitlab/namespaced_class.yml b/.rubocop_todo/gitlab/namespaced_class.yml
index 9f0c960d769..3153bf08f25 100644
--- a/.rubocop_todo/gitlab/namespaced_class.yml
+++ b/.rubocop_todo/gitlab/namespaced_class.yml
@@ -1088,7 +1088,7 @@ Gitlab/NamespacedClass:
- 'lib/gitlab/environment_logger.rb'
- 'lib/gitlab/exceptions_app.rb'
- 'lib/gitlab/exclusive_lease.rb'
- - 'lib/gitlab/experiment/rollout/feature.rb'
+ - 'lib/gitlab/experiment_feature_rollout.rb'
- 'lib/gitlab/fake_application_settings.rb'
- 'lib/gitlab/favicon.rb'
- 'lib/gitlab/feature_categories.rb'
diff --git a/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note.vue b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note.vue
index 1c7b7f09f34..c3c2184b904 100644
--- a/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note.vue
+++ b/app/assets/javascripts/admin/abuse_report/components/notes/abuse_report_note.vue
@@ -55,7 +55,7 @@ export default {
return getIdFromGraphQLId(this.author.id);
},
showEditButton() {
- return this.note.userPermissions.resolveNote;
+ return false;
},
editedAtClasses() {
return this.showReplyButton ? 'gl-text-secondary gl-pl-3' : 'gl-text-secondary gl-pl-8';
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note.fragment.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note.fragment.graphql
index 84b57b4ed79..d75c3f1bc51 100644
--- a/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note.fragment.graphql
+++ b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note.fragment.graphql
@@ -1,7 +1,6 @@
#import "~/graphql_shared/fragments/author.fragment.graphql"
-#import "./abuse_report_note_permissions.fragment.graphql"
-fragment AbuseReportNote on Note {
+fragment AbuseReportNote on AbuseReportNote {
id
body
bodyHtml
@@ -16,9 +15,6 @@ fragment AbuseReportNote on Note {
...Author
webPath
}
- userPermissions {
- ...AbuseReportNotePermissions
- }
discussion {
id
notes {
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note_permissions.fragment.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note_permissions.fragment.graphql
deleted file mode 100644
index 31ca24e675f..00000000000
--- a/app/assets/javascripts/admin/abuse_report/graphql/notes/abuse_report_note_permissions.fragment.graphql
+++ /dev/null
@@ -1,3 +0,0 @@
-fragment AbuseReportNotePermissions on NotePermissions {
- resolveNote
-}
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/create_abuse_report_note.mutation.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/create_abuse_report_note.mutation.graphql
index 53ac9468e08..bc97e38ca63 100644
--- a/app/assets/javascripts/admin/abuse_report/graphql/notes/create_abuse_report_note.mutation.graphql
+++ b/app/assets/javascripts/admin/abuse_report/graphql/notes/create_abuse_report_note.mutation.graphql
@@ -1,16 +1,9 @@
-#import "./abuse_report_note.fragment.graphql"
-
mutation createAbuseReportNote($input: CreateNoteInput!) {
createNote(input: $input) {
note {
id
discussion {
id
- notes {
- nodes {
- ...AbuseReportNote
- }
- }
}
}
errors
diff --git a/app/assets/javascripts/admin/abuse_report/graphql/notes/update_abuse_report_note.mutation.graphql b/app/assets/javascripts/admin/abuse_report/graphql/notes/update_abuse_report_note.mutation.graphql
index e11165074c9..b0a0a48025e 100644
--- a/app/assets/javascripts/admin/abuse_report/graphql/notes/update_abuse_report_note.mutation.graphql
+++ b/app/assets/javascripts/admin/abuse_report/graphql/notes/update_abuse_report_note.mutation.graphql
@@ -1,10 +1,5 @@
-#import "./abuse_report_note.fragment.graphql"
-
mutation updateAbuseReportNote($input: UpdateNoteInput!) {
updateNote(input: $input) {
- note {
- ...AbuseReportNote
- }
errors
}
}
diff --git a/app/assets/javascripts/ci/common/private/job_action_component.vue b/app/assets/javascripts/ci/common/private/job_action_component.vue
index ad1493679d1..b8333c55712 100644
--- a/app/assets/javascripts/ci/common/private/job_action_component.vue
+++ b/app/assets/javascripts/ci/common/private/job_action_component.vue
@@ -98,6 +98,7 @@ export default {
.post(`${this.link}.json`)
.then(() => {
this.isLoading = false;
+ this.isDisabled = false;
this.$emit('pipelineActionRequestComplete');
})
diff --git a/app/assets/javascripts/diffs/stores/legacy_diffs/actions.js b/app/assets/javascripts/diffs/stores/legacy_diffs/actions.js
index d5fd55f4214..a134a7273b2 100644
--- a/app/assets/javascripts/diffs/stores/legacy_diffs/actions.js
+++ b/app/assets/javascripts/diffs/stores/legacy_diffs/actions.js
@@ -21,6 +21,7 @@ import { generateTreeList } from '~/diffs/utils/tree_worker_utils';
import { sortTree } from '~/ide/stores/utils';
import { detectAndConfirmSensitiveTokens } from '~/lib/utils/secret_detection';
import { isCollapsed } from '~/diffs/utils/diff_file';
+import { useNotes } from '~/notes/store/legacy_notes';
import {
INLINE_DIFF_VIEW_TYPE,
DIFF_VIEW_COOKIE_NAME,
@@ -80,7 +81,7 @@ import {
findDiffFile,
} from '../../store/utils';
-export const setBaseConfig = ({ commit }, options) => {
+export function setBaseConfig(options) {
const {
endpoint,
endpointMetadata,
@@ -97,7 +98,7 @@ export const setBaseConfig = ({ commit }, options) => {
diffViewType,
perPage,
} = options;
- commit(types.SET_BASE_CONFIG, {
+ this[types.SET_BASE_CONFIG]({
endpoint,
endpointMetadata,
endpointBatch,
@@ -117,12 +118,12 @@ export const setBaseConfig = ({ commit }, options) => {
Array.from(new Set(Object.values(mrReviews).flat())).forEach((id) => {
const viewedId = id.replace(/^hash:/, '');
- commit(types.SET_DIFF_FILE_VIEWED, { id: viewedId, seen: true });
+ this[types.SET_DIFF_FILE_VIEWED]({ id: viewedId, seen: true });
});
-};
+}
-export const prefetchSingleFile = async ({ state, getters, commit }, treeEntry) => {
- const url = new URL(state.endpointBatch, 'https://gitlab.com');
+export async function prefetchSingleFile(treeEntry) {
+ const url = new URL(this.endpointBatch, 'https://gitlab.com');
const diffId = getParameterValues('diff_id', url)[0];
const startSha = getParameterValues('start_sha', url)[0];
@@ -130,14 +131,14 @@ export const prefetchSingleFile = async ({ state, getters, commit }, treeEntry)
treeEntry &&
!treeEntry.diffLoaded &&
!treeEntry.diffLoading &&
- !getters.getDiffFileByHash(treeEntry.fileHash)
+ !this.getDiffFileByHash(treeEntry.fileHash)
) {
const urlParams = {
old_path: treeEntry.filePaths.old,
new_path: treeEntry.filePaths.new,
- w: state.showWhitespace ? '0' : '1',
+ w: this.showWhitespace ? '0' : '1',
view: 'inline',
- commit_id: getters.commitId,
+ commit_id: this.commitId,
diff_head: true,
};
@@ -149,45 +150,45 @@ export const prefetchSingleFile = async ({ state, getters, commit }, treeEntry)
urlParams.start_sha = startSha;
}
- commit(types.TREE_ENTRY_DIFF_LOADING, { path: treeEntry.filePaths.new });
+ this[types.TREE_ENTRY_DIFF_LOADING]({ path: treeEntry.filePaths.new });
try {
const { data: diffData } = await axios.get(
- mergeUrlParams({ ...urlParams }, state.endpointDiffForPath),
+ mergeUrlParams({ ...urlParams }, this.endpointDiffForPath),
);
- commit(types.SET_DIFF_DATA_BATCH, { diff_files: diffData.diff_files });
+ this[types.SET_DIFF_DATA_BATCH]({ diff_files: diffData.diff_files });
eventHub.$emit('diffFilesModified');
} catch (e) {
- commit(types.TREE_ENTRY_DIFF_LOADING, { path: treeEntry.filePaths.new, loading: false });
+ this[types.TREE_ENTRY_DIFF_LOADING]({ path: treeEntry.filePaths.new, loading: false });
}
}
-};
+}
-export const fetchFileByFile = async ({ state, getters, commit }) => {
+export async function fetchFileByFile() {
const isNoteLink = isUrlHashNoteLink(window?.location?.hash);
- const id = parseUrlHashAsFileHash(window?.location?.hash, state.currentDiffFileId);
- const url = new URL(state.endpointBatch, 'https://gitlab.com');
+ const id = parseUrlHashAsFileHash(window?.location?.hash, this.currentDiffFileId);
+ const url = new URL(this.endpointBatch, 'https://gitlab.com');
const diffId = getParameterValues('diff_id', url)[0];
const startSha = getParameterValues('start_sha', url)[0];
const treeEntry = id
- ? getters.flatBlobsList.find(({ fileHash }) => fileHash === id)
- : getters.flatBlobsList[0];
+ ? this.flatBlobsList.find(({ fileHash }) => fileHash === id)
+ : this.flatBlobsList[0];
eventHub.$emit(EVT_PERF_MARK_DIFF_FILES_START);
- if (treeEntry && !treeEntry.diffLoaded && !getters.getDiffFileByHash(id)) {
+ if (treeEntry && !treeEntry.diffLoaded && !this.getDiffFileByHash(id)) {
// Overloading "batch" loading indicators so the UI stays mostly the same
- commit(types.SET_BATCH_LOADING_STATE, 'loading');
- commit(types.SET_RETRIEVING_BATCHES, true);
+ this[types.SET_BATCH_LOADING_STATE]('loading');
+ this[types.SET_RETRIEVING_BATCHES](true);
const urlParams = {
old_path: treeEntry.filePaths.old,
new_path: treeEntry.filePaths.new,
- w: state.showWhitespace ? '0' : '1',
+ w: this.showWhitespace ? '0' : '1',
view: 'inline',
- commit_id: getters.commitId,
+ commit_id: this.commitId,
diff_head: true,
};
@@ -200,35 +201,35 @@ export const fetchFileByFile = async ({ state, getters, commit }) => {
}
axios
- .get(mergeUrlParams({ ...urlParams }, state.endpointDiffForPath))
+ .get(mergeUrlParams({ ...urlParams }, this.endpointDiffForPath))
.then(({ data: diffData }) => {
- commit(types.SET_DIFF_DATA_BATCH, { diff_files: diffData.diff_files });
+ this[types.SET_DIFF_DATA_BATCH]({ diff_files: diffData.diff_files });
- if (!isNoteLink && !state.currentDiffFileId) {
- commit(types.SET_CURRENT_DIFF_FILE, state.diffFiles[0]?.file_hash || '');
+ if (!isNoteLink && !this.currentDiffFileId) {
+ this[types.SET_CURRENT_DIFF_FILE](this.diffFiles[0]?.file_hash || '');
}
- commit(types.SET_BATCH_LOADING_STATE, 'loaded');
+ this[types.SET_BATCH_LOADING_STATE]('loaded');
eventHub.$emit('diffFilesModified');
})
.catch(() => {
- commit(types.SET_BATCH_LOADING_STATE, 'error');
+ this[types.SET_BATCH_LOADING_STATE]('error');
})
.finally(() => {
- commit(types.SET_RETRIEVING_BATCHES, false);
+ this[types.SET_RETRIEVING_BATCHES](false);
});
}
-};
+}
-export const fetchDiffFilesBatch = ({ commit, state, dispatch }, linkedFileLoading = false) => {
- let perPage = state.viewDiffsFileByFile ? 1 : state.perPage;
+export function fetchDiffFilesBatch(linkedFileLoading = false) {
+ let perPage = this.viewDiffsFileByFile ? 1 : this.perPage;
let increaseAmount = 1.4;
const startPage = 0;
const id = window?.location?.hash;
const isNoteLink = id.indexOf('#note') === 0;
const urlParams = {
- w: state.showWhitespace ? '0' : '1',
+ w: this.showWhitespace ? '0' : '1',
view: 'inline',
};
const hash = window.location.hash.replace('#', '').split('diff-content-').pop();
@@ -236,22 +237,22 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }, linkedFileLoadi
let scrolledVirtualScroller = hash === '';
if (!linkedFileLoading) {
- commit(types.SET_BATCH_LOADING_STATE, 'loading');
- commit(types.SET_RETRIEVING_BATCHES, true);
+ this[types.SET_BATCH_LOADING_STATE]('loading');
+ this[types.SET_RETRIEVING_BATCHES](true);
}
eventHub.$emit(EVT_PERF_MARK_DIFF_FILES_START);
const getBatch = (page = startPage) =>
axios
- .get(mergeUrlParams({ ...urlParams, page, per_page: perPage }, state.endpointBatch))
+ .get(mergeUrlParams({ ...urlParams, page, per_page: perPage }, this.endpointBatch))
.then(({ data: { pagination, diff_files: diffFiles } }) => {
totalLoaded += diffFiles.length;
- commit(types.SET_DIFF_DATA_BATCH, { diff_files: diffFiles });
- commit(types.SET_BATCH_LOADING_STATE, 'loaded');
+ this[types.SET_DIFF_DATA_BATCH]({ diff_files: diffFiles });
+ this[types.SET_BATCH_LOADING_STATE]('loaded');
if (!scrolledVirtualScroller && !linkedFileLoading) {
- const index = state.diffFiles.findIndex(
+ const index = this.diffFiles.findIndex(
(f) =>
f.file_hash === hash || f[INLINE_DIFF_LINES_KEY].find((l) => l.line_code === hash),
);
@@ -262,38 +263,38 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }, linkedFileLoadi
}
}
- if (!isNoteLink && !state.currentDiffFileId) {
- commit(types.SET_CURRENT_DIFF_FILE, diffFiles[0]?.file_hash);
+ if (!isNoteLink && !this.currentDiffFileId) {
+ this[types.SET_CURRENT_DIFF_FILE](diffFiles[0]?.file_hash);
}
if (isNoteLink) {
- dispatch('setCurrentDiffFileIdFromNote', id.split('_').pop());
+ this.setCurrentDiffFileIdFromNote(id.split('_').pop());
}
if (totalLoaded === pagination.total_pages || pagination.total_pages === null) {
- commit(types.SET_RETRIEVING_BATCHES, false);
+ this[types.SET_RETRIEVING_BATCHES](false);
eventHub.$emit('doneLoadingBatches');
// We need to check that the currentDiffFileId points to a file that exists
if (
- state.currentDiffFileId &&
- !state.diffFiles.some((f) => f.file_hash === state.currentDiffFileId) &&
+ this.currentDiffFileId &&
+ !this.diffFiles.some((f) => f.file_hash === this.currentDiffFileId) &&
!isNoteLink
) {
- commit(types.SET_CURRENT_DIFF_FILE, state.diffFiles[0].file_hash);
+ this[types.SET_CURRENT_DIFF_FILE](this.diffFiles[0].file_hash);
}
- if (state.diffFiles?.length) {
+ if (this.diffFiles?.length) {
// eslint-disable-next-line promise/catch-or-return,promise/no-nesting
import('~/code_navigation').then((m) =>
m.default({
- blobs: state.diffFiles
+ blobs: this.diffFiles
.filter((f) => f.code_navigation_path)
.map((f) => ({
path: f.new_path,
codeNavigationPath: f.code_navigation_path,
})),
- definitionPathPrefix: state.definitionPathPrefix,
+ definitionPathPrefix: this.definitionPathPrefix,
}),
);
}
@@ -315,24 +316,24 @@ export const fetchDiffFilesBatch = ({ commit, state, dispatch }, linkedFileLoadi
return null;
})
.catch((error) => {
- commit(types.SET_RETRIEVING_BATCHES, false);
- commit(types.SET_BATCH_LOADING_STATE, 'error');
+ this[types.SET_RETRIEVING_BATCHES](false);
+ this[types.SET_BATCH_LOADING_STATE]('error');
throw error;
});
return getBatch();
-};
+}
-export const fetchDiffFilesMeta = ({ commit, state }) => {
+export function fetchDiffFilesMeta() {
const urlParams = {
view: 'inline',
- w: state.showWhitespace ? '0' : '1',
+ w: this.showWhitespace ? '0' : '1',
};
- commit(types.SET_LOADING, true);
+ this[types.SET_LOADING](true);
return axios
- .get(mergeUrlParams(urlParams, state.endpointMetadata))
+ .get(mergeUrlParams(urlParams, this.endpointMetadata))
.then(({ data }) => {
const strippedData = { ...data };
delete strippedData.diff_files;
@@ -345,14 +346,14 @@ export const fetchDiffFilesMeta = ({ commit, state }) => {
});
}
- commit(types.SET_LOADING, false);
- commit(types.SET_MERGE_REQUEST_DIFFS, data.merge_request_diffs || []);
- commit(types.SET_DIFF_METADATA, strippedData);
+ this[types.SET_LOADING](false);
+ this[types.SET_MERGE_REQUEST_DIFFS](data.merge_request_diffs || []);
+ this[types.SET_DIFF_METADATA](strippedData);
eventHub.$emit(EVT_PERF_MARK_FILE_TREE_START);
const { treeEntries, tree } = generateTreeList(data.diff_files);
eventHub.$emit(EVT_PERF_MARK_FILE_TREE_END);
- commit(types.SET_TREE_DATA, {
+ this[types.SET_TREE_DATA]({
treeEntries,
tree: sortTree(tree),
});
@@ -371,28 +372,28 @@ export const fetchDiffFilesMeta = ({ commit, state }) => {
throw error;
}
});
-};
+}
-export function prefetchFileNeighbors({ getters, dispatch }) {
- const { flatBlobsList: allBlobs, currentDiffIndex: currentIndex } = getters;
+export function prefetchFileNeighbors() {
+ const { flatBlobsList: allBlobs, currentDiffIndex: currentIndex } = this;
const previous = Math.max(currentIndex - 1, 0);
const next = Math.min(allBlobs.length - 1, currentIndex + 1);
- dispatch('prefetchSingleFile', allBlobs[next]);
- dispatch('prefetchSingleFile', allBlobs[previous]);
+ this.prefetchSingleFile(allBlobs[next]);
+ this.prefetchSingleFile(allBlobs[previous]);
}
-export const fetchCoverageFiles = ({ commit, state }) => {
+export function fetchCoverageFiles() {
const coveragePoll = new Poll({
resource: {
getCoverageReports: (endpoint) => axios.get(endpoint),
},
- data: state.endpointCoverage,
+ data: this.endpointCoverage,
method: 'getCoverageReports',
successCallback: ({ status, data }) => {
if (status === HTTP_STATUS_OK) {
- commit(types.SET_COVERAGE_DATA, data);
+ this[types.SET_COVERAGE_DATA](data);
coveragePoll.stop();
}
@@ -404,35 +405,33 @@ export const fetchCoverageFiles = ({ commit, state }) => {
});
coveragePoll.makeRequest();
-};
+}
-export const setHighlightedRow = ({ commit }, { lineCode, event }) => {
+export function setHighlightedRow({ lineCode, event }) {
if (event && event.target.href) {
event.preventDefault();
window.history.replaceState(null, undefined, removeParams(['file'], event.target.href));
}
const fileHash = lineCode.split('_')[0];
- commit(types.SET_HIGHLIGHTED_ROW, lineCode);
- commit(types.SET_CURRENT_DIFF_FILE, fileHash);
+ this[types.SET_HIGHLIGHTED_ROW](lineCode);
+ this[types.SET_CURRENT_DIFF_FILE](fileHash);
handleLocationHash();
-};
+}
// This is adding line discussions to the actual lines in the diff tree
// once for parallel and once for inline mode
-export const assignDiscussionsToDiff = (
- { commit, state, rootState, dispatch },
- discussions = rootState.notes.discussions,
-) => {
+export function assignDiscussionsToDiff(discussions) {
+ const targetDiscussions = discussions || useNotes().notes.discussions;
const id = window?.location?.hash;
const isNoteLink = id.indexOf('#note') === 0;
- const diffPositionByLineCode = getDiffPositionByLineCode(state.diffFiles);
+ const diffPositionByLineCode = getDiffPositionByLineCode(this.diffFiles);
const hash = getLocationHash();
- discussions
+ targetDiscussions
.filter((discussion) => discussion.diff_discussion)
.forEach((discussion) => {
- commit(types.SET_LINE_DISCUSSIONS_FOR_FILE, {
+ this[types.SET_LINE_DISCUSSIONS_FOR_FILE]({
discussion,
diffPositionByLineCode,
hash,
@@ -440,15 +439,15 @@ export const assignDiscussionsToDiff = (
});
if (isNoteLink) {
- dispatch('setCurrentDiffFileIdFromNote', id.split('_').pop());
+ this.setCurrentDiffFileIdFromNote(id.split('_').pop());
}
Vue.nextTick(() => {
eventHub.$emit(EVT_DISCUSSIONS_ASSIGNED);
});
-};
+}
-export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => {
+export function removeDiscussionsFromDiff(removeDiscussion) {
if (!removeDiscussion.diff_file) return;
const {
@@ -456,25 +455,25 @@ export const removeDiscussionsFromDiff = ({ commit }, removeDiscussion) => {
line_code: lineCode,
id,
} = removeDiscussion;
- commit(types.REMOVE_LINE_DISCUSSIONS_FOR_FILE, { fileHash, lineCode, id });
-};
+ this[types.REMOVE_LINE_DISCUSSIONS_FOR_FILE]({ fileHash, lineCode, id });
+}
-export const toggleLineDiscussions = ({ commit }, options) => {
- commit(types.TOGGLE_LINE_DISCUSSIONS, options);
-};
+export function toggleLineDiscussions(options) {
+ this[types.TOGGLE_LINE_DISCUSSIONS](options);
+}
-export const renderFileForDiscussionId = ({ commit, rootState, state }, discussionId) => {
- const discussion = rootState.notes.discussions.find((d) => d.id === discussionId);
+export function renderFileForDiscussionId(discussionId) {
+ const discussion = useNotes().notes.discussions.find((d) => d.id === discussionId);
if (discussion && discussion.diff_file) {
- const file = state.diffFiles.find((f) => f.file_hash === discussion.diff_file.file_hash);
+ const file = this.diffFiles.find((f) => f.file_hash === discussion.diff_file.file_hash);
if (file) {
if (file.viewer.automaticallyCollapsed) {
notesEventHub.$emit(`loadCollapsedDiff/${file.file_hash}`);
scrollToElement(document.getElementById(file.file_hash));
} else if (file.viewer.manuallyCollapsed) {
- commit(types.SET_FILE_COLLAPSED, {
+ this[types.SET_FILE_COLLAPSED]({
filePath: file.file_path,
collapsed: false,
trigger: DIFF_FILE_AUTOMATIC_COLLAPSE,
@@ -485,10 +484,10 @@ export const renderFileForDiscussionId = ({ commit, rootState, state }, discussi
}
}
}
-};
+}
-export const setDiffViewType = ({ commit }, diffViewType) => {
- commit(types.SET_DIFF_VIEW_TYPE, diffViewType);
+export function setDiffViewType(diffViewType) {
+ this[types.SET_DIFF_VIEW_TYPE](diffViewType);
setCookie(DIFF_VIEW_COOKIE_NAME, diffViewType);
const url = mergeUrlParams({ view: diffViewType }, window.location.href);
@@ -500,10 +499,10 @@ export const setDiffViewType = ({ commit }, diffViewType) => {
? TRACKING_DIFF_VIEW_INLINE
: TRACKING_DIFF_VIEW_PARALLEL,
]);
-};
+}
-export const showCommentForm = ({ commit }, { lineCode, fileHash }) => {
- commit(types.TOGGLE_LINE_HAS_FORM, { lineCode, fileHash, hasForm: true });
+export function showCommentForm({ lineCode, fileHash }) {
+ this[types.TOGGLE_LINE_HAS_FORM]({ lineCode, fileHash, hasForm: true });
// The comment form for diffs gets focussed differently due to the way the virtual scroller
// works. If we focus the comment form on mount and the comment form gets removed and then
@@ -526,13 +525,13 @@ export const showCommentForm = ({ commit }, { lineCode, fileHash }) => {
window.scrollBy(0, Math.floor(Math.abs(overflowBottom)) + 150);
}
});
-};
+}
-export const cancelCommentForm = ({ commit }, { lineCode, fileHash }) => {
- commit(types.TOGGLE_LINE_HAS_FORM, { lineCode, fileHash, hasForm: false });
-};
+export function cancelCommentForm({ lineCode, fileHash }) {
+ this[types.TOGGLE_LINE_HAS_FORM]({ lineCode, fileHash, hasForm: false });
+}
-export const loadMoreLines = ({ commit }, options) => {
+export function loadMoreLines(options) {
const { endpoint, params, lineNumbers, fileHash, isExpandDown, nextLineNumbers } = options;
params.from_merge_request = true;
@@ -540,7 +539,7 @@ export const loadMoreLines = ({ commit }, options) => {
return axios.get(endpoint, { params }).then((res) => {
const contextLines = res.data || [];
- commit(types.ADD_CONTEXT_LINES, {
+ this[types.ADD_CONTEXT_LINES]({
lineNumbers,
contextLines,
params,
@@ -549,17 +548,17 @@ export const loadMoreLines = ({ commit }, options) => {
nextLineNumbers,
});
});
-};
+}
-export const scrollToLineIfNeededInline = (_, line) => {
+export function scrollToLineIfNeededInline(line) {
const hash = getLocationHash();
if (hash && line.line_code === hash) {
handleLocationHash();
}
-};
+}
-export const scrollToLineIfNeededParallel = (_, line) => {
+export function scrollToLineIfNeededParallel(line) {
const hash = getLocationHash();
if (
@@ -568,13 +567,13 @@ export const scrollToLineIfNeededParallel = (_, line) => {
) {
handleLocationHash();
}
-};
+}
-export const loadCollapsedDiff = ({ commit, getters, state }, { file, params = {} }) => {
- const versionPath = state.mergeRequestDiff?.version_path;
+export function loadCollapsedDiff({ file, params = {} }) {
+ const versionPath = this.mergeRequestDiff?.version_path;
const loadParams = {
- commit_id: getters.commitId,
- w: state.showWhitespace ? '0' : '1',
+ commit_id: this.commitId,
+ w: this.showWhitespace ? '0' : '1',
...params,
};
@@ -586,23 +585,23 @@ export const loadCollapsedDiff = ({ commit, getters, state }, { file, params = {
}
return axios.get(file.load_collapsed_diff_url, { params: loadParams }).then((res) => {
- commit(types.ADD_COLLAPSED_DIFFS, {
+ this[types.ADD_COLLAPSED_DIFFS]({
file,
data: res.data,
});
});
-};
+}
/**
* Toggles the file discussions after user clicked on the toggle discussions button.
* @param {Object} discussion
*/
-export const toggleFileDiscussion = ({ commit }, discussion) => {
- commit(types.TOGGLE_FILE_DISCUSSION_EXPAND, { discussion });
-};
+export function toggleFileDiscussion(discussion) {
+ this[types.TOGGLE_FILE_DISCUSSION_EXPAND]({ discussion });
+}
-export const toggleFileDiscussionWrappers = ({ commit, getters }, diff) => {
- const discussionWrappersExpanded = getters.diffHasExpandedDiscussions(diff);
+export function toggleFileDiscussionWrappers(diff) {
+ const discussionWrappersExpanded = this.diffHasExpandedDiscussions(diff);
const lineCodesWithDiscussions = new Set();
const lineHasDiscussion = (line) => Boolean(line?.discussions.length);
const registerDiscussionLine = (line) => lineCodesWithDiscussions.add(line.line_code);
@@ -611,7 +610,7 @@ export const toggleFileDiscussionWrappers = ({ commit, getters }, diff) => {
if (lineCodesWithDiscussions.size) {
Array.from(lineCodesWithDiscussions).forEach((lineCode) => {
- commit(types.TOGGLE_LINE_DISCUSSIONS, {
+ this[types.TOGGLE_LINE_DISCUSSIONS]({
fileHash: diff.file_hash,
expanded: !discussionWrappersExpanded,
lineCode,
@@ -622,20 +621,20 @@ export const toggleFileDiscussionWrappers = ({ commit, getters }, diff) => {
if (diff.discussions.length) {
diff.discussions.forEach((discussion) => {
if (discussion.position?.position_type === FILE_DIFF_POSITION_TYPE) {
- commit(types.TOGGLE_FILE_DISCUSSION_EXPAND, {
+ this[types.TOGGLE_FILE_DISCUSSION_EXPAND]({
discussion,
expandedOnDiff: !discussionWrappersExpanded,
});
}
});
}
-};
+}
-export const saveDiffDiscussion = async ({ state, dispatch }, { note, formData }) => {
+export async function saveDiffDiscussion({ note, formData }) {
const postData = getNoteFormData({
- commit: state.commit,
+ commit: this.commit,
note,
- showWhitespace: state.showWhitespace,
+ showWhitespace: this.showWhitespace,
...formData,
});
@@ -644,57 +643,58 @@ export const saveDiffDiscussion = async ({ state, dispatch }, { note, formData }
return null;
}
- return dispatch('saveNote', postData, { root: true })
- .then((result) => dispatch('updateDiscussion', result.discussion, { root: true }))
- .then((discussion) => dispatch('assignDiscussionsToDiff', [discussion]))
- .then(() => dispatch('updateResolvableDiscussionsCounts', null, { root: true }))
- .then(() => dispatch('closeDiffFileCommentForm', formData.diffFile.file_hash))
+ return useNotes()
+ .saveNote(postData)
+ .then((result) => useNotes().updateDiscussion(result.discussion))
+ .then((discussion) => this.assignDiscussionsToDiff([discussion]))
+ .then(() => useNotes().updateResolvableDiscussionsCounts(null))
+ .then(() => this.closeDiffFileCommentForm(formData.diffFile.file_hash))
.then(() => {
if (formData.positionType === FILE_DIFF_POSITION_TYPE) {
- dispatch('toggleFileCommentForm', formData.diffFile.file_path);
+ this.toggleFileCommentForm(formData.diffFile.file_path);
}
});
-};
+}
-export const toggleTreeOpen = ({ commit }, path) => {
- commit(types.TOGGLE_FOLDER_OPEN, path);
-};
+export function toggleTreeOpen(path) {
+ this[types.TOGGLE_FOLDER_OPEN](path);
+}
-export const setCurrentFileHash = ({ commit }, hash) => {
- commit(types.SET_CURRENT_DIFF_FILE, hash);
-};
+export function setCurrentFileHash(hash) {
+ this[types.SET_CURRENT_DIFF_FILE](hash);
+}
-export const goToFile = ({ state, commit, dispatch, getters }, { path }) => {
- if (!state.viewDiffsFileByFile) {
- dispatch('scrollToFile', { path });
+export function goToFile({ path }) {
+ if (!this.viewDiffsFileByFile) {
+ this.scrollToFile({ path });
} else {
- if (!state.treeEntries[path]) return;
+ if (!this.treeEntries[path]) return;
- dispatch('unlinkFile');
+ this.unlinkFile();
- const { fileHash } = state.treeEntries[path];
+ const { fileHash } = this.treeEntries[path];
- commit(types.SET_CURRENT_DIFF_FILE, fileHash);
+ this[types.SET_CURRENT_DIFF_FILE](fileHash);
const newUrl = new URL(window.location);
newUrl.hash = fileHash;
historyPushState(newUrl, { skipScrolling: true });
scrollToElement('.diff-files-holder', { duration: 0 });
- if (!getters.isTreePathLoaded(path)) {
- dispatch('fetchFileByFile');
+ if (!this.isTreePathLoaded(path)) {
+ this.fetchFileByFile();
}
}
-};
+}
-export const scrollToFile = ({ state, commit, getters }, { path }) => {
- if (!state.treeEntries[path]) return;
+export function scrollToFile({ path }) {
+ if (!this.treeEntries[path]) return;
- const { fileHash } = state.treeEntries[path];
+ const { fileHash } = this.treeEntries[path];
- commit(types.SET_CURRENT_DIFF_FILE, fileHash);
+ this[types.SET_CURRENT_DIFF_FILE](fileHash);
- if (getters.isVirtualScrollingEnabled) {
+ if (this.isVirtualScrollingEnabled) {
eventHub.$emit('scrollToFileHash', fileHash);
setTimeout(() => {
@@ -707,36 +707,36 @@ export const scrollToFile = ({ state, commit, getters }, { path }) => {
handleLocationHash();
});
}
-};
+}
-export const setShowTreeList = ({ commit }, { showTreeList, saving = true }) => {
- commit(types.SET_SHOW_TREE_LIST, showTreeList);
+export function setShowTreeList({ showTreeList, saving = true }) {
+ this[types.SET_SHOW_TREE_LIST](showTreeList);
if (saving) {
localStorage.setItem(MR_TREE_SHOW_KEY, showTreeList);
}
-};
+}
-export const toggleTreeList = ({ state, commit }) => {
- commit(types.SET_SHOW_TREE_LIST, !state.showTreeList);
-};
+export function toggleTreeList() {
+ this[types.SET_SHOW_TREE_LIST](!this.showTreeList);
+}
-export const openDiffFileCommentForm = ({ commit, getters }, formData) => {
- const form = getters.getCommentFormForDiffFile(formData.fileHash);
+export function openDiffFileCommentForm(formData) {
+ const form = this.getCommentFormForDiffFile(formData.fileHash);
if (form) {
- commit(types.UPDATE_DIFF_FILE_COMMENT_FORM, formData);
+ this[types.UPDATE_DIFF_FILE_COMMENT_FORM](formData);
} else {
- commit(types.OPEN_DIFF_FILE_COMMENT_FORM, formData);
+ this[types.OPEN_DIFF_FILE_COMMENT_FORM](formData);
}
-};
+}
-export const closeDiffFileCommentForm = ({ commit }, fileHash) => {
- commit(types.CLOSE_DIFF_FILE_COMMENT_FORM, fileHash);
-};
+export function closeDiffFileCommentForm(fileHash) {
+ this[types.CLOSE_DIFF_FILE_COMMENT_FORM](fileHash);
+}
-export const setRenderTreeList = ({ commit }, { renderTreeList, trackClick = true }) => {
- commit(types.SET_RENDER_TREE_LIST, renderTreeList);
+export function setRenderTreeList({ renderTreeList, trackClick = true }) {
+ this[types.SET_RENDER_TREE_LIST](renderTreeList);
localStorage.setItem(TREE_LIST_STORAGE_KEY, renderTreeList);
@@ -751,17 +751,19 @@ export const setRenderTreeList = ({ commit }, { renderTreeList, trackClick = tru
queueRedisHllEvents(events);
}
-};
+}
-export const setShowWhitespace = async (
- { state, commit },
- { url, showWhitespace, updateDatabase = true, trackClick = true },
-) => {
+export async function setShowWhitespace({
+ url,
+ showWhitespace,
+ updateDatabase = true,
+ trackClick = true,
+}) {
if (updateDatabase && Boolean(window.gon?.current_user_id)) {
- await axios.put(url || state.endpointUpdateUser, { show_whitespace_in_diffs: showWhitespace });
+ await axios.put(url || this.endpointUpdateUser, { show_whitespace_in_diffs: showWhitespace });
}
- commit(types.SET_SHOW_WHITESPACE, showWhitespace);
+ this[types.SET_SHOW_WHITESPACE](showWhitespace);
notesEventHub.$emit('refetchDiffData');
if (trackClick) {
@@ -775,20 +777,20 @@ export const setShowWhitespace = async (
queueRedisHllEvents(events);
}
-};
+}
-export const toggleFileFinder = ({ commit }, visible) => {
- commit(types.TOGGLE_FILE_FINDER_VISIBLE, visible);
-};
+export function toggleFileFinder(visible) {
+ this[types.TOGGLE_FILE_FINDER_VISIBLE](visible);
+}
-export const receiveFullDiffError = ({ commit }, filePath) => {
- commit(types.RECEIVE_FULL_DIFF_ERROR, filePath);
+export function receiveFullDiffError(filePath) {
+ this[types.RECEIVE_FULL_DIFF_ERROR](filePath);
createAlert({
message: ERROR_LOADING_FULL_DIFF,
});
-};
+}
-export const setExpandedDiffLines = ({ commit }, { file, data }) => {
+export function setExpandedDiffLines({ file, data }) {
const expandedDiffLines = convertExpandLines({
diffLines: file[INLINE_DIFF_LINES_KEY],
typeKey: TYPE_KEY,
@@ -805,11 +807,11 @@ export const setExpandedDiffLines = ({ commit }, { file, data }) => {
if (expandedDiffLines.length > MAX_RENDERING_DIFF_LINES) {
let index = START_RENDERING_INDEX;
- commit(types.SET_CURRENT_VIEW_DIFF_FILE_LINES, {
+ this[types.SET_CURRENT_VIEW_DIFF_FILE_LINES]({
filePath: file.file_path,
lines: expandedDiffLines.slice(0, index),
});
- commit(types.TOGGLE_DIFF_FILE_RENDERING_MORE, file.file_path);
+ this[types.TOGGLE_DIFF_FILE_RENDERING_MORE](file.file_path);
const idleCb = (t) => {
const startIndex = index;
@@ -822,7 +824,7 @@ export const setExpandedDiffLines = ({ commit }, { file, data }) => {
const line = expandedDiffLines[index];
if (line) {
- commit(types.ADD_CURRENT_VIEW_DIFF_FILE_LINES, { filePath: file.file_path, line });
+ this[types.ADD_CURRENT_VIEW_DIFF_FILE_LINES]({ filePath: file.file_path, line });
index += 1;
}
}
@@ -830,21 +832,21 @@ export const setExpandedDiffLines = ({ commit }, { file, data }) => {
if (index !== expandedDiffLines.length) {
idleCallback(idleCb);
} else {
- commit(types.TOGGLE_DIFF_FILE_RENDERING_MORE, file.file_path);
+ this[types.TOGGLE_DIFF_FILE_RENDERING_MORE](file.file_path);
}
};
idleCallback(idleCb);
} else {
- commit(types.SET_CURRENT_VIEW_DIFF_FILE_LINES, {
+ this[types.SET_CURRENT_VIEW_DIFF_FILE_LINES]({
filePath: file.file_path,
lines: expandedDiffLines,
});
}
-};
+}
-export const fetchFullDiff = ({ commit, dispatch }, file) =>
- axios
+export function fetchFullDiff(file) {
+ return axios
.get(file.context_lines_path, {
params: {
full: true,
@@ -852,27 +854,28 @@ export const fetchFullDiff = ({ commit, dispatch }, file) =>
},
})
.then(({ data }) => {
- commit(types.RECEIVE_FULL_DIFF_SUCCESS, { filePath: file.file_path });
+ this[types.RECEIVE_FULL_DIFF_SUCCESS]({ filePath: file.file_path });
- dispatch('setExpandedDiffLines', { file, data });
+ this.setExpandedDiffLines({ file, data });
})
- .catch(() => dispatch('receiveFullDiffError', file.file_path));
+ .catch(() => this.receiveFullDiffError(file.file_path));
+}
-export const toggleFullDiff = ({ dispatch, commit, getters, state }, filePath) => {
- const file = state.diffFiles.find((f) => f.file_path === filePath);
+export function toggleFullDiff(filePath) {
+ const file = this.diffFiles.find((f) => f.file_path === filePath);
- commit(types.REQUEST_FULL_DIFF, filePath);
+ this[types.REQUEST_FULL_DIFF](filePath);
if (file.isShowingFullFile) {
- dispatch('loadCollapsedDiff', { file })
- .then(() => dispatch('assignDiscussionsToDiff', getters.getDiffFileDiscussions(file)))
- .catch(() => dispatch('receiveFullDiffError', filePath));
+ this.loadCollapsedDiff({ file })
+ .then(() => this.assignDiscussionsToDiff(this.getDiffFileDiscussions(file)))
+ .catch(() => this.receiveFullDiffError(filePath));
} else {
- dispatch('fetchFullDiff', file);
+ this.fetchFullDiff(file);
}
-};
+}
-export function switchToFullDiffFromRenamedFile({ commit }, { diffFile }) {
+export function switchToFullDiffFromRenamedFile({ diffFile }) {
return axios
.get(diffFile.context_lines_path, {
params: {
@@ -890,7 +893,7 @@ export function switchToFullDiffFromRenamedFile({ commit }, { diffFile }) {
}),
);
- commit(types.SET_DIFF_FILE_VIEWER, {
+ this[types.SET_DIFF_FILE_VIEWER]({
filePath: diffFile.file_path,
viewer: {
...diffFile.alternate_viewer,
@@ -899,64 +902,65 @@ export function switchToFullDiffFromRenamedFile({ commit }, { diffFile }) {
forceOpen: false,
},
});
- commit(types.SET_CURRENT_VIEW_DIFF_FILE_LINES, { filePath: diffFile.file_path, lines });
+ this[types.SET_CURRENT_VIEW_DIFF_FILE_LINES]({ filePath: diffFile.file_path, lines });
});
}
-export const setFileCollapsedByUser = ({ commit }, { filePath, collapsed }) => {
- commit(types.SET_FILE_COLLAPSED, { filePath, collapsed, trigger: DIFF_FILE_MANUAL_COLLAPSE });
-};
-
-export const setFileCollapsedAutomatically = ({ commit }, { filePath, collapsed }) => {
- commit(types.SET_FILE_COLLAPSED, { filePath, collapsed, trigger: DIFF_FILE_AUTOMATIC_COLLAPSE });
-};
-
-export function setFileForcedOpen({ commit }, { filePath, forced }) {
- commit(types.SET_FILE_FORCED_OPEN, { filePath, forced });
+export function setFileCollapsedByUser({ filePath, collapsed }) {
+ this[types.SET_FILE_COLLAPSED]({ filePath, collapsed, trigger: DIFF_FILE_MANUAL_COLLAPSE });
}
-export const setSuggestPopoverDismissed = ({ commit, state }) =>
- axios
- .post(state.dismissEndpoint, {
+export function setFileCollapsedAutomatically({ filePath, collapsed }) {
+ this[types.SET_FILE_COLLAPSED]({ filePath, collapsed, trigger: DIFF_FILE_AUTOMATIC_COLLAPSE });
+}
+
+export function setFileForcedOpen({ filePath, forced }) {
+ this[types.SET_FILE_FORCED_OPEN]({ filePath, forced });
+}
+
+export function setSuggestPopoverDismissed() {
+ return axios
+ .post(this.dismissEndpoint, {
feature_name: 'suggest_popover_dismissed',
})
.then(() => {
- commit(types.SET_SHOW_SUGGEST_POPOVER);
+ this[types.SET_SHOW_SUGGEST_POPOVER]();
})
.catch(() => {
createAlert({
message: ERROR_DISMISSING_SUGESTION_POPOVER,
});
});
+}
-export function changeCurrentCommit({ dispatch, commit, state }, { commitId }) {
+export function changeCurrentCommit({ commitId }) {
if (!commitId) {
return Promise.reject(new Error('`commitId` is a required argument'));
}
- if (!state.commit) {
+ if (!this.commit) {
return Promise.reject(new Error('`state` must already contain a valid `commit`')); // eslint-disable-line @gitlab/require-i18n-strings
}
// this is less than ideal, see: https://gitlab.com/gitlab-org/gitlab/-/issues/215421
- const commitRE = new RegExp(state.commit.id, 'g');
+ const commitRE = new RegExp(this.commit.id, 'g');
- commit(types.SET_DIFF_FILES, []);
- commit(types.SET_BASE_CONFIG, {
- ...state,
- endpoint: state.endpoint.replace(commitRE, commitId),
- endpointBatch: state.endpointBatch.replace(commitRE, commitId),
- endpointMetadata: state.endpointMetadata.replace(commitRE, commitId),
+ this[types.SET_DIFF_FILES]([]);
+ this[types.SET_BASE_CONFIG]({
+ ...this.$state,
+ endpoint: this.endpoint.replace(commitRE, commitId),
+ endpointBatch: this.endpointBatch.replace(commitRE, commitId),
+ endpointMetadata: this.endpointMetadata.replace(commitRE, commitId),
});
- return dispatch('fetchDiffFilesMeta');
+ return this.fetchDiffFilesMeta();
}
-export function moveToNeighboringCommit({ dispatch, state }, { direction }) {
- const previousCommitId = state.commit?.prev_commit_id;
- const nextCommitId = state.commit?.next_commit_id;
+export function moveToNeighboringCommit({ direction }) {
+ const previousCommitId = this.commit?.prev_commit_id;
+ const nextCommitId = this.commit?.next_commit_id;
const canMove = {
- next: !state.isLoading && nextCommitId,
- previous: !state.isLoading && previousCommitId,
+ next: !this.isLoading && nextCommitId,
+ previous: !this.isLoading && previousCommitId,
};
let commitId;
@@ -967,18 +971,18 @@ export function moveToNeighboringCommit({ dispatch, state }, { direction }) {
}
if (commitId) {
- dispatch('changeCurrentCommit', { commitId });
+ this.changeCurrentCommit({ commitId });
}
}
-export const rereadNoteHash = ({ state, dispatch }) => {
+export function rereadNoteHash() {
const urlHash = window?.location?.hash;
if (isUrlHashNoteLink(urlHash)) {
- dispatch('setCurrentDiffFileIdFromNote', urlHash.split('_').pop())
+ this.setCurrentDiffFileIdFromNote(urlHash.split('_').pop())
.then(() => {
- if (state.viewDiffsFileByFile) {
- dispatch('fetchFileByFile');
+ if (this.viewDiffsFileByFile) {
+ this.fetchFileByFile();
}
})
.catch(() => {
@@ -987,35 +991,35 @@ export const rereadNoteHash = ({ state, dispatch }) => {
});
});
}
-};
+}
-export const setCurrentDiffFileIdFromNote = ({ commit, getters, rootGetters }, noteId) => {
- const note = rootGetters.notesById[noteId];
+export function setCurrentDiffFileIdFromNote(noteId) {
+ const note = useNotes().notesById[noteId];
if (!note) return;
- const fileHash = rootGetters.getDiscussion(note.discussion_id).diff_file?.file_hash;
+ const fileHash = useNotes().getDiscussion(note.discussion_id).diff_file?.file_hash;
- if (fileHash && getters.flatBlobsList.some((f) => f.fileHash === fileHash)) {
- commit(types.SET_CURRENT_DIFF_FILE, fileHash);
+ if (fileHash && this.flatBlobsList.some((f) => f.fileHash === fileHash)) {
+ this[types.SET_CURRENT_DIFF_FILE](fileHash);
}
-};
+}
-export const navigateToDiffFileIndex = ({ state, getters, commit, dispatch }, index) => {
- dispatch('unlinkFile');
+export function navigateToDiffFileIndex(index) {
+ this.unlinkFile();
- const { fileHash } = getters.flatBlobsList[index];
+ const { fileHash } = this.flatBlobsList[index];
document.location.hash = fileHash;
- commit(types.SET_CURRENT_DIFF_FILE, fileHash);
+ this[types.SET_CURRENT_DIFF_FILE](fileHash);
- if (state.viewDiffsFileByFile) {
- dispatch('fetchFileByFile');
+ if (this.viewDiffsFileByFile) {
+ this.fetchFileByFile();
}
-};
+}
-export const setFileByFile = ({ state, commit }, { fileByFile }) => {
- commit(types.SET_FILE_BY_FILE, fileByFile);
+export function setFileByFile({ fileByFile }) {
+ this[types.SET_FILE_BY_FILE](fileByFile);
const events = [TRACKING_CLICK_SINGLE_FILE_SETTING];
@@ -1028,7 +1032,7 @@ export const setFileByFile = ({ state, commit }, { fileByFile }) => {
queueRedisHllEvents(events);
return axios
- .put(state.endpointUpdateUser, {
+ .put(this.endpointUpdateUser, {
view_diffs_file_by_file: fileByFile,
})
.then(() => {
@@ -1038,38 +1042,41 @@ export const setFileByFile = ({ state, commit }, { fileByFile }) => {
// eventually handle errors appropriately.
// console.warn('Saving the file-by-fil user preference failed.');
});
-};
+}
-export function reviewFile({ commit, state }, { file, reviewed = true }) {
+export function reviewFile({ file, reviewed = true }) {
const { mrPath } = getDerivedMergeRequestInformation({ endpoint: file.load_collapsed_diff_url });
- const reviews = markFileReview(state.mrReviews, file, reviewed);
+ const reviews = markFileReview(this.mrReviews, file, reviewed);
setReviewsForMergeRequest(mrPath, reviews);
- commit(types.SET_DIFF_FILE_VIEWED, { id: file.file_hash, seen: reviewed });
- commit(types.SET_MR_FILE_REVIEWS, reviews);
+ this[types.SET_DIFF_FILE_VIEWED]({ id: file.file_hash, seen: reviewed });
+ this[types.SET_MR_FILE_REVIEWS](reviews);
}
-export const disableVirtualScroller = ({ commit }) => commit(types.DISABLE_VIRTUAL_SCROLLING);
+export function disableVirtualScroller() {
+ this[types.DISABLE_VIRTUAL_SCROLLING]();
+}
-export const toggleFileCommentForm = ({ state, commit }, filePath) => {
- const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+export function toggleFileCommentForm(filePath) {
+ const file = findDiffFile(this.diffFiles, filePath, 'file_path');
if (isCollapsed(file)) {
- commit(types.SET_FILE_COMMENT_FORM, { filePath, expanded: true });
+ this[types.SET_FILE_COMMENT_FORM]({ filePath, expanded: true });
} else {
- commit(types.TOGGLE_FILE_COMMENT_FORM, filePath);
+ this[types.TOGGLE_FILE_COMMENT_FORM](filePath);
}
- commit(types.SET_FILE_COLLAPSED, { filePath, collapsed: false });
-};
+ this[types.SET_FILE_COLLAPSED]({ filePath, collapsed: false });
+}
-export const addDraftToFile = ({ commit }, { filePath, draft }) =>
- commit(types.ADD_DRAFT_TO_FILE, { filePath, draft });
+export function addDraftToFile({ filePath, draft }) {
+ return this[types.ADD_DRAFT_TO_FILE]({ filePath, draft });
+}
-export const fetchLinkedFile = ({ state, commit }, linkedFileUrl) => {
+export function fetchLinkedFile(linkedFileUrl) {
const isNoteLink = isUrlHashNoteLink(window?.location?.hash);
- commit(types.SET_BATCH_LOADING_STATE, 'loading');
- commit(types.SET_RETRIEVING_BATCHES, true);
+ this[types.SET_BATCH_LOADING_STATE]('loading');
+ this[types.SET_RETRIEVING_BATCHES](true);
return axios
.get(linkedFileUrl)
@@ -1079,14 +1086,14 @@ export const fetchLinkedFile = ({ state, commit }, linkedFileUrl) => {
// we must store linked file in the `diffs`, otherwise collapsing and commenting on a file won't work
// once the same file arrives in a file batch we must only update its' position
// we also must not update file's position since it's loaded out of order
- commit(types.SET_DIFF_DATA_BATCH, { diff_files: diffData.diff_files, updatePosition: false });
- commit(types.SET_LINKED_FILE_HASH, file_hash);
+ this[types.SET_DIFF_DATA_BATCH]({ diff_files: diffData.diff_files, updatePosition: false });
+ this[types.SET_LINKED_FILE_HASH](file_hash);
- if (!isNoteLink && !state.currentDiffFileId) {
- commit(types.SET_CURRENT_DIFF_FILE, file_hash);
+ if (!isNoteLink && !this.currentDiffFileId) {
+ this[types.SET_CURRENT_DIFF_FILE](file_hash);
}
- commit(types.SET_BATCH_LOADING_STATE, 'loaded');
+ this[types.SET_BATCH_LOADING_STATE]('loaded');
setTimeout(() => {
handleLocationHash();
@@ -1095,23 +1102,23 @@ export const fetchLinkedFile = ({ state, commit }, linkedFileUrl) => {
eventHub.$emit('diffFilesModified');
})
.catch((error) => {
- commit(types.SET_BATCH_LOADING_STATE, 'error');
+ this[types.SET_BATCH_LOADING_STATE]('error');
throw error;
})
.finally(() => {
- commit(types.SET_RETRIEVING_BATCHES, false);
+ this[types.SET_RETRIEVING_BATCHES](false);
});
-};
+}
-export const unlinkFile = ({ getters, commit }) => {
- if (!getters.linkedFile) return;
- commit(types.SET_LINKED_FILE_HASH, null);
+export function unlinkFile() {
+ if (!this.linkedFile) return;
+ this[types.SET_LINKED_FILE_HASH](null);
const newUrl = new URL(window.location);
newUrl.searchParams.delete('file');
newUrl.hash = '';
window.history.replaceState(null, undefined, newUrl);
-};
+}
-export const toggleAllDiffDiscussions = ({ commit, getters }) => {
- commit(types.SET_EXPAND_ALL_DIFF_DISCUSSIONS, !getters.allDiffDiscussionsExpanded);
-};
+export function toggleAllDiffDiscussions() {
+ this[types.SET_EXPAND_ALL_DIFF_DISCUSSIONS](!this.allDiffDiscussionsExpanded);
+}
diff --git a/app/assets/javascripts/diffs/stores/legacy_diffs/getters.js b/app/assets/javascripts/diffs/stores/legacy_diffs/getters.js
index 123fdd56bb3..425dc5deddd 100644
--- a/app/assets/javascripts/diffs/stores/legacy_diffs/getters.js
+++ b/app/assets/javascripts/diffs/stores/legacy_diffs/getters.js
@@ -7,77 +7,93 @@ import {
INLINE_DIFF_LINES_KEY,
DIFF_COMPARE_BASE_VERSION_INDEX,
DIFF_COMPARE_HEAD_VERSION_INDEX,
-} from '../../constants';
+} from '~/diffs/constants';
+import { useNotes } from '~/notes/store/legacy_notes';
+import { useMrNotes } from '~/mr_notes/store/legacy_mr_notes';
import { computeSuggestionCommitMessage } from '../../utils/suggestions';
import { parallelizeDiffLines } from '../../store/utils';
-export const isParallelView = (state) => state.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
+export function isParallelView() {
+ return this.diffViewType === PARALLEL_DIFF_VIEW_TYPE;
+}
-export const isInlineView = (state) => state.diffViewType === INLINE_DIFF_VIEW_TYPE;
+export function isInlineView() {
+ return this.diffViewType === INLINE_DIFF_VIEW_TYPE;
+}
-export const whichCollapsedTypes = (state) => {
- const automatic = state.diffFiles.some((file) => file.viewer?.automaticallyCollapsed);
- const manual = state.diffFiles.some((file) => file.viewer?.manuallyCollapsed);
+export function whichCollapsedTypes() {
+ const automatic = this.diffFiles.some((file) => file.viewer?.automaticallyCollapsed);
+ const manual = this.diffFiles.some((file) => file.viewer?.manuallyCollapsed);
return {
any: automatic || manual,
automatic,
manual,
};
-};
+}
-export const commitId = (state) => (state.commit && state.commit.id ? state.commit.id : null);
+export function commitId() {
+ return this.commit && this.commit.id ? this.commit.id : null;
+}
/**
* Checks if the diff has all discussions expanded
* @param {Object} diff
* @returns {Boolean}
*/
-export const diffHasAllExpandedDiscussions = (state, getters) => (diff) => {
- const discussions = getters.getDiffFileDiscussions(diff);
+export function diffHasAllExpandedDiscussions() {
+ return (diff) => {
+ const discussions = this.getDiffFileDiscussions(diff);
- return (
- (discussions && discussions.length && discussions.every((discussion) => discussion.expanded)) ||
- false
- );
-};
+ return (
+ (discussions &&
+ discussions.length &&
+ discussions.every((discussion) => discussion.expanded)) ||
+ false
+ );
+ };
+}
/**
* Checks if the diff has all discussions collapsed
* @param {Object} diff
* @returns {Boolean}
*/
-export const diffHasAllCollapsedDiscussions = (state, getters) => (diff) => {
- const discussions = getters.getDiffFileDiscussions(diff);
+export function diffHasAllCollapsedDiscussions() {
+ return (diff) => {
+ const discussions = this.getDiffFileDiscussions(diff);
- return (
- (discussions &&
- discussions.length &&
- discussions.every((discussion) => !discussion.expanded)) ||
- false
- );
-};
+ return (
+ (discussions &&
+ discussions.length &&
+ discussions.every((discussion) => !discussion.expanded)) ||
+ false
+ );
+ };
+}
/**
* Checks if the diff has any open discussions
* @param {Object} diff
* @returns {Boolean}
*/
-export const diffHasExpandedDiscussions = () => (diff) => {
- const diffLineDiscussionsExpanded = diff[INLINE_DIFF_LINES_KEY].filter(
- (l) => l.discussions.length >= 1,
- ).some((l) => l.discussionsExpanded);
- const diffFileDiscussionsExpanded = diff.discussions?.some((d) => d.expandedOnDiff);
+export function diffHasExpandedDiscussions() {
+ return (diff) => {
+ const diffLineDiscussionsExpanded = diff[INLINE_DIFF_LINES_KEY].filter(
+ (l) => l.discussions.length >= 1,
+ ).some((l) => l.discussionsExpanded);
+ const diffFileDiscussionsExpanded = diff.discussions?.some((d) => d.expandedOnDiff);
- return diffFileDiscussionsExpanded || diffLineDiscussionsExpanded;
-};
+ return diffFileDiscussionsExpanded || diffLineDiscussionsExpanded;
+ };
+}
/**
* Checks if every diff has every discussion open
* @returns {Boolean}
*/
-export const allDiffDiscussionsExpanded = (state) => {
- return state.diffFiles.every((diff) => {
+export function allDiffDiscussionsExpanded() {
+ return this.diffFiles.every((diff) => {
const highlightedLines = diff[INLINE_DIFF_LINES_KEY];
if (highlightedLines.length) {
return highlightedLines
@@ -89,45 +105,51 @@ export const allDiffDiscussionsExpanded = (state) => {
}
return true;
});
-};
+}
/**
* Checks if the diff has any discussion
* @param {Boolean} diff
* @returns {Boolean}
*/
-export const diffHasDiscussions = () => (diff) => {
- return (
- diff.discussions?.length >= 1 ||
- diff[INLINE_DIFF_LINES_KEY].some((l) => l.discussions.length >= 1)
- );
-};
+export function diffHasDiscussions() {
+ return (diff) => {
+ return (
+ diff.discussions?.length >= 1 ||
+ diff[INLINE_DIFF_LINES_KEY].some((l) => l.discussions.length >= 1)
+ );
+ };
+}
/**
* Returns an array with the discussions of the given diff
* @param {Object} diff
* @returns {Array}
*/
-// eslint-disable-next-line max-params
-export const getDiffFileDiscussions = (state, getters, rootState, rootGetters) => (diff) =>
- rootGetters.discussions.filter(
- (discussion) => discussion.diff_discussion && discussion.diff_file.file_hash === diff.file_hash,
- ) || [];
+export function getDiffFileDiscussions() {
+ return (diff) =>
+ useNotes().discussions.filter(
+ (discussion) =>
+ discussion.diff_discussion && discussion.diff_file.file_hash === diff.file_hash,
+ ) || [];
+}
-export const getDiffFileByHash = (state) => (fileHash) =>
- state.diffFiles.find((file) => file.file_hash === fileHash);
+export function getDiffFileByHash() {
+ return (fileHash) => this.diffFiles.find((file) => file.file_hash === fileHash);
+}
-export function isTreePathLoaded(state) {
+export function isTreePathLoaded() {
return (path) => {
- return Boolean(state.treeEntries[path]?.diffLoaded);
+ return Boolean(this.treeEntries[path]?.diffLoaded);
};
}
-export const flatBlobsList = (state) =>
- Object.values(state.treeEntries).filter((f) => f.type === 'blob');
+export function flatBlobsList() {
+ return Object.values(this.treeEntries).filter((f) => f.type === 'blob');
+}
-export const allBlobs = (state, getters) =>
- getters.flatBlobsList.reduce((acc, file) => {
+export function allBlobs() {
+ return this.flatBlobsList.reduce((acc, file) => {
const { parentPath } = file;
if (parentPath && !acc.some((f) => f.path === parentPath)) {
@@ -142,9 +164,11 @@ export const allBlobs = (state, getters) =>
return acc;
}, []);
+}
-export const getCommentFormForDiffFile = (state) => (fileHash) =>
- state.commentForms.find((form) => form.fileHash === fileHash);
+export function getCommentFormForDiffFile() {
+ return (fileHash) => this.commentForms.find((form) => form.fileHash === fileHash);
+}
/**
* Returns the test coverage hits for a specific line of a given file
@@ -152,114 +176,131 @@ export const getCommentFormForDiffFile = (state) => (fileHash) =>
* @param {number} line
* @returns {number}
*/
-export const fileLineCoverage = (state) => (file, line) => {
- if (!state.coverageFiles.files) return {};
- const fileCoverage = state.coverageFiles.files[file];
- if (!fileCoverage) return {};
- const lineCoverage = fileCoverage[String(line)];
+export function fileLineCoverage() {
+ return (file, line) => {
+ if (!this.coverageFiles.files) return {};
+ const fileCoverage = this.coverageFiles.files[file];
+ if (!fileCoverage) return {};
+ const lineCoverage = fileCoverage[String(line)];
- if (lineCoverage === 0) {
- return { text: __('No test coverage'), class: 'no-coverage' };
- }
- if (lineCoverage >= 0) {
- return {
- text: n__('Test coverage: %d hit', 'Test coverage: %d hits', lineCoverage),
- class: 'coverage',
- };
- }
- return {};
-};
+ if (lineCoverage === 0) {
+ return { text: __('No test coverage'), class: 'no-coverage' };
+ }
+ if (lineCoverage >= 0) {
+ return {
+ text: n__('Test coverage: %d hit', 'Test coverage: %d hits', lineCoverage),
+ class: 'coverage',
+ };
+ }
+ return {};
+ };
+}
/**
* Returns index of a currently selected diff in diffFiles
* @returns {number}
*/
-export const currentDiffIndex = (state) =>
- Math.max(
+export function currentDiffIndex() {
+ return Math.max(
0,
- flatBlobsList(state).findIndex((diff) => diff.fileHash === state.currentDiffFileId),
+ this.flatBlobsList.findIndex((diff) => diff.fileHash === this.currentDiffFileId),
);
+}
-export const diffLines = (state) => (file) => {
- return parallelizeDiffLines(
- file.highlighted_diff_lines || [],
- state.diffViewType === INLINE_DIFF_VIEW_TYPE,
- );
-};
+export function diffLines() {
+ return (file) => {
+ return parallelizeDiffLines(
+ file.highlighted_diff_lines || [],
+ this.diffViewType === INLINE_DIFF_VIEW_TYPE,
+ );
+ };
+}
-export function suggestionCommitMessage(state, _, rootState) {
- return (values = {}) =>
- computeSuggestionCommitMessage({
- message: state.defaultSuggestionCommitMessage,
+export function suggestionCommitMessage() {
+ return (values = {}) => {
+ const { mrMetadata } = useMrNotes().page;
+ return computeSuggestionCommitMessage({
+ message: this.defaultSuggestionCommitMessage,
values: {
- branch_name: rootState.page.mrMetadata.branch_name,
- project_path: rootState.page.mrMetadata.project_path,
- project_name: rootState.page.mrMetadata.project_name,
- username: rootState.page.mrMetadata.username,
- user_full_name: rootState.page.mrMetadata.user_full_name,
+ branch_name: mrMetadata.branch_name,
+ project_path: mrMetadata.project_path,
+ project_name: mrMetadata.project_name,
+ username: mrMetadata.username,
+ user_full_name: mrMetadata.user_full_name,
...values,
},
});
+ };
}
-export const isVirtualScrollingEnabled = (state) => {
- if (state.disableVirtualScroller || getParameterValues('virtual_scrolling')[0] === 'false') {
+export function isVirtualScrollingEnabled() {
+ if (this.disableVirtualScroller || getParameterValues('virtual_scrolling')[0] === 'false') {
return false;
}
- return !state.viewDiffsFileByFile;
-};
+ return !this.viewDiffsFileByFile;
+}
-export const isBatchLoading = (state) => state.batchLoadingState === 'loading';
-export const isBatchLoadingError = (state) => state.batchLoadingState === 'error';
+export function isBatchLoading() {
+ return this.batchLoadingState === 'loading';
+}
+export function isBatchLoadingError() {
+ return this.batchLoadingState === 'error';
+}
-export const diffFiles = (state, getters) => {
- const { linkedFile } = getters;
- if (linkedFile) {
- const diffs = state.diffFiles.slice(0);
- diffs.splice(diffs.indexOf(linkedFile), 1);
- return [linkedFile, ...diffs];
+export function diffFilesFiltered() {
+ const { linkedFile: file } = this;
+ if (file) {
+ const diffs = this.diffFiles.slice(0);
+ diffs.splice(diffs.indexOf(file), 1);
+ return [file, ...diffs];
}
- return state.diffFiles;
-};
+ return this.diffFiles;
+}
-export const linkedFile = (state) => {
- if (!state.linkedFileHash) return null;
- return state.diffFiles.find((file) => file.file_hash === state.linkedFileHash);
-};
+export function linkedFile() {
+ if (!this.linkedFileHash) return null;
+ return this.diffFiles.find((file) => file.file_hash === this.linkedFileHash);
+}
-export const selectedTargetIndex = (state) =>
- state.startVersion?.version_index || DIFF_COMPARE_BASE_VERSION_INDEX;
+export function selectedTargetIndex() {
+ return this.startVersion?.version_index || DIFF_COMPARE_BASE_VERSION_INDEX;
+}
-export const selectedSourceIndex = (state) => state.mergeRequestDiff.version_index;
+export function selectedSourceIndex() {
+ if (!this.mergeRequestDiff) return undefined;
+ return this.mergeRequestDiff.version_index;
+}
-export const selectedContextCommitsDiffs = (state) =>
- state.contextCommitsDiff && state.contextCommitsDiff.showing_context_commits_diff;
+export function selectedContextCommitsDiff() {
+ return this.contextCommitsDiff && this.contextCommitsDiff.showing_context_commits_diff;
+}
-export const diffCompareDropdownTargetVersions = (state, getters) => {
+export function diffCompareDropdownTargetVersions() {
+ if (!this.mergeRequestDiff) return [];
// startVersion only exists if the user has selected a version other
// than "base" so if startVersion is null then base must be selected
const diffHeadParam = getParameterByName('diff_head');
const diffHead = parseBoolean(diffHeadParam) || !diffHeadParam;
- const isBaseSelected = !state.startVersion;
- const isHeadSelected = !state.startVersion && diffHead;
+ const isBaseSelected = !this.startVersion;
+ const isHeadSelected = !this.startVersion && diffHead;
let baseVersion = null;
- if (!state.mergeRequestDiff.head_version_path) {
+ if (!this.mergeRequestDiff.head_version_path) {
baseVersion = {
- versionName: state.targetBranchName,
+ versionName: this.targetBranchName,
version_index: DIFF_COMPARE_BASE_VERSION_INDEX,
- href: state.mergeRequestDiff.base_version_path,
+ href: this.mergeRequestDiff.base_version_path,
isBase: true,
selected: isBaseSelected,
};
}
const headVersion = {
- versionName: state.targetBranchName,
+ versionName: this.targetBranchName,
version_index: DIFF_COMPARE_HEAD_VERSION_INDEX,
- href: state.mergeRequestDiff.head_version_path,
+ href: this.mergeRequestDiff.head_version_path,
isHead: true,
selected: isHeadSelected,
};
@@ -268,21 +309,21 @@ export const diffCompareDropdownTargetVersions = (state, getters) => {
return {
href: v.compare_path,
versionName: sprintf(__(`version %{versionIndex}`), { versionIndex: v.version_index }),
- selected: v.version_index === getters.selectedTargetIndex,
+ selected: v.version_index === this.selectedTargetIndex,
...v,
};
};
return [
- ...state.mergeRequestDiffs.slice(1).map(formatVersion),
+ ...this.mergeRequestDiffs.slice(1).map(formatVersion),
baseVersion,
- state.mergeRequestDiff.head_version_path && headVersion,
+ this.mergeRequestDiff.head_version_path && headVersion,
].filter((a) => a);
-};
+}
-export const diffCompareDropdownSourceVersions = (state, getters) => {
+export function diffCompareDropdownSourceVersions() {
// Appended properties here are to make the compare_dropdown_layout easier to reason about
- const versions = state.mergeRequestDiffs.map((v, i) => {
+ const versions = this.mergeRequestDiffs.map((v, i) => {
const isLatestVersion = i === 0;
return {
@@ -293,20 +334,19 @@ export const diffCompareDropdownSourceVersions = (state, getters) => {
versionName: isLatestVersion
? __('latest version')
: sprintf(__(`version %{versionIndex}`), { versionIndex: v.version_index }),
- selected:
- v.version_index === getters.selectedSourceIndex && !getters.selectedContextCommitsDiffs,
+ selected: v.version_index === this.selectedSourceIndex && !this.selectedContextCommitsDiffs,
};
});
- const { contextCommitsDiff } = state;
+ const { contextCommitsDiff } = this;
if (contextCommitsDiff) {
versions.push({
href: contextCommitsDiff.diffs_path,
commitsText: n__(`%d commit`, `%d commits`, contextCommitsDiff.commits_count),
versionName: __('previously merged commits'),
- selected: getters.selectedContextCommitsDiffs,
- addDivider: state.mergeRequestDiffs.length > 0,
+ selected: this.selectedContextCommitsDiffs,
+ addDivider: this.mergeRequestDiffs.length > 0,
});
}
return versions;
-};
+}
diff --git a/app/assets/javascripts/diffs/stores/legacy_diffs/index.js b/app/assets/javascripts/diffs/stores/legacy_diffs/index.js
index 7baef00273c..45b1dd047c7 100644
--- a/app/assets/javascripts/diffs/stores/legacy_diffs/index.js
+++ b/app/assets/javascripts/diffs/stores/legacy_diffs/index.js
@@ -47,7 +47,7 @@ export const useLegacyDiffs = defineStore('legacyDiffs', {
mrReviews: {},
latestDiff: true,
disableVirtualScroller: false,
- pinnedFileHash: null,
+ linkedFileHash: null,
};
},
actions: {
diff --git a/app/assets/javascripts/diffs/stores/legacy_diffs/mutations.js b/app/assets/javascripts/diffs/stores/legacy_diffs/mutations.js
index de963004b07..d56b24dd9d6 100644
--- a/app/assets/javascripts/diffs/stores/legacy_diffs/mutations.js
+++ b/app/assets/javascripts/diffs/stores/legacy_diffs/mutations.js
@@ -18,12 +18,8 @@ import {
markTreeEntriesLoaded,
} from '../../store/utils';
-function updateDiffFilesInState(state, files) {
- return Object.assign(state, { diffFiles: files });
-}
-
export default {
- [types.SET_BASE_CONFIG](state, options) {
+ [types.SET_BASE_CONFIG](options) {
const {
endpoint,
endpointMetadata,
@@ -40,7 +36,7 @@ export default {
diffViewType,
perPage,
} = options;
- Object.assign(state, {
+ Object.assign(this, {
endpoint,
endpointMetadata,
endpointBatch,
@@ -58,79 +54,79 @@ export default {
});
},
- [types.SET_LOADING](state, isLoading) {
- Object.assign(state, { isLoading });
+ [types.SET_LOADING](isLoading) {
+ Object.assign(this, { isLoading });
},
- [types.SET_BATCH_LOADING_STATE](state, batchLoadingState) {
- Object.assign(state, { batchLoadingState });
+ [types.SET_BATCH_LOADING_STATE](batchLoadingState) {
+ Object.assign(this, { batchLoadingState });
},
- [types.SET_RETRIEVING_BATCHES](state, retrievingBatches) {
- Object.assign(state, { retrievingBatches });
+ [types.SET_RETRIEVING_BATCHES](retrievingBatches) {
+ Object.assign(this, { retrievingBatches });
},
- [types.SET_DIFF_FILES](state, files) {
- updateDiffFilesInState(state, files);
+ [types.SET_DIFF_FILES](files) {
+ return Object.assign(this, { diffFiles: files });
},
- [types.SET_DIFF_METADATA](state, data) {
- Object.assign(state, {
+ [types.SET_DIFF_METADATA](data) {
+ Object.assign(this, {
...convertObjectPropsToCamelCase(data),
});
},
- [types.SET_DIFF_DATA_BATCH](state, { diff_files: diffFiles, updatePosition = true }) {
- Object.assign(state, {
+ [types.SET_DIFF_DATA_BATCH]({ diff_files: diffFiles, updatePosition = true }) {
+ Object.assign(this, {
diffFiles: prepareDiffData({
diff: { diff_files: diffFiles },
- priorFiles: state.diffFiles,
+ priorFiles: this.diffFiles,
// when a linked file is added to diffs its position may be incorrect since it's loaded out of order
// we need to ensure when we load it in batched request it updates it position
updatePosition,
}),
treeEntries: markTreeEntriesLoaded({
- priorEntries: state.treeEntries,
+ priorEntries: this.treeEntries,
loadedFiles: diffFiles,
}),
});
},
- [types.SET_DIFF_TREE_ENTRY](state, diffFile) {
- Object.assign(state, {
+ [types.SET_DIFF_TREE_ENTRY](diffFile) {
+ Object.assign(this, {
treeEntries: markTreeEntriesLoaded({
- priorEntries: state.treeEntries,
+ priorEntries: this.treeEntries,
loadedFiles: [diffFile],
}),
});
},
- [types.SET_COVERAGE_DATA](state, coverageFiles) {
- Object.assign(state, { coverageFiles, coverageLoaded: true });
+ [types.SET_COVERAGE_DATA](coverageFiles) {
+ Object.assign(this, { coverageFiles, coverageLoaded: true });
},
- [types.SET_MERGE_REQUEST_DIFFS](state, mergeRequestDiffs) {
- Object.assign(state, {
+ [types.SET_MERGE_REQUEST_DIFFS](mergeRequestDiffs) {
+ Object.assign(this, {
mergeRequestDiffs,
});
},
- [types.SET_DIFF_VIEW_TYPE](state, diffViewType) {
- Object.assign(state, { diffViewType });
+ [types.SET_DIFF_VIEW_TYPE](diffViewType) {
+ Object.assign(this, { diffViewType });
},
- [types.TOGGLE_LINE_HAS_FORM](state, { lineCode, fileHash, hasForm }) {
- const diffFile = state.diffFiles.find((f) => f.file_hash === fileHash);
+ [types.TOGGLE_LINE_HAS_FORM]({ lineCode, fileHash, hasForm }) {
+ const diffFile = this.diffFiles.find((f) => f.file_hash === fileHash);
if (!diffFile) return;
diffFile[INLINE_DIFF_LINES_KEY].find((l) => l.line_code === lineCode).hasForm = hasForm;
},
- [types.ADD_CONTEXT_LINES](state, options) {
+ [types.ADD_CONTEXT_LINES](options) {
const { lineNumbers, contextLines, fileHash, isExpandDown, nextLineNumbers } = options;
const { bottom } = options.params;
- const diffFile = findDiffFile(state.diffFiles, fileHash);
+ const diffFile = findDiffFile(this.diffFiles, fileHash);
removeMatchLine(diffFile, lineNumbers, bottom);
@@ -163,18 +159,18 @@ export default {
});
},
- [types.ADD_COLLAPSED_DIFFS](state, { file, data }) {
+ [types.ADD_COLLAPSED_DIFFS]({ file, data }) {
const files = prepareDiffData({ diff: data });
const [newFileData] = files.filter((f) => f.file_hash === file.file_hash);
- const selectedFile = state.diffFiles.find((f) => f.file_hash === file.file_hash);
+ const selectedFile = this.diffFiles.find((f) => f.file_hash === file.file_hash);
Object.assign(selectedFile, {
...newFileData,
whitespaceOnlyChange: selectedFile.whitespaceOnlyChange,
});
},
- [types.SET_LINE_DISCUSSIONS_FOR_FILE](state, { discussion, diffPositionByLineCode, hash }) {
- const { latestDiff } = state;
+ [types.SET_LINE_DISCUSSIONS_FOR_FILE]({ discussion, diffPositionByLineCode, hash }) {
+ const { latestDiff } = this;
const originalStartLineCode = discussion.original_position?.line_range?.start?.line_code;
const positionType = discussion.position?.position_type;
const discussionLineCodes = [
@@ -205,7 +201,7 @@ export default {
const addDiscussion = (discussions) =>
discussions.filter(({ id }) => discussion.id !== id).concat(discussion);
- const file = state.diffFiles.find((diff) => diff.file_hash === fileHash);
+ const file = this.diffFiles.find((diff) => diff.file_hash === fileHash);
// a file batch might not be loaded yet when we try to add a discussion
if (!file) return;
const diffLines = file[INLINE_DIFF_LINES_KEY];
@@ -227,19 +223,19 @@ export default {
}
},
- [types.TOGGLE_FILE_DISCUSSION_EXPAND](
- state,
- { discussion, expandedOnDiff = !discussion.expandedOnDiff },
- ) {
+ [types.TOGGLE_FILE_DISCUSSION_EXPAND]({
+ discussion,
+ expandedOnDiff = !discussion.expandedOnDiff,
+ }) {
Object.assign(discussion, { expandedOnDiff });
const fileHash = discussion.diff_file.file_hash;
- const diff = state.diffFiles.find((f) => f.file_hash === fileHash);
+ const diff = this.diffFiles.find((f) => f.file_hash === fileHash);
// trigger Vue reactivity
Object.assign(diff, { discussions: [...diff.discussions] });
},
- [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE](state, { fileHash, lineCode }) {
- const selectedFile = state.diffFiles.find((f) => f.file_hash === fileHash);
+ [types.REMOVE_LINE_DISCUSSIONS_FOR_FILE]({ fileHash, lineCode }) {
+ const selectedFile = this.diffFiles.find((f) => f.file_hash === fileHash);
if (selectedFile) {
updateLineInFile(selectedFile, lineCode, (line) =>
Object.assign(line, {
@@ -255,17 +251,17 @@ export default {
}
},
- [types.TOGGLE_LINE_DISCUSSIONS](state, { fileHash, lineCode, expanded }) {
- const selectedFile = state.diffFiles.find((f) => f.file_hash === fileHash);
+ [types.TOGGLE_LINE_DISCUSSIONS]({ fileHash, lineCode, expanded }) {
+ const selectedFile = this.diffFiles.find((f) => f.file_hash === fileHash);
updateLineInFile(selectedFile, lineCode, (line) => {
Object.assign(line, { discussionsExpanded: expanded });
});
},
- [types.SET_EXPAND_ALL_DIFF_DISCUSSIONS](state, expanded) {
+ [types.SET_EXPAND_ALL_DIFF_DISCUSSIONS](expanded) {
const lineHasDiscussion = (line) => Boolean(line.discussions?.length);
- state.diffFiles.forEach((file) => {
+ this.diffFiles.forEach((file) => {
const highlightedLines = file[INLINE_DIFF_LINES_KEY];
if (highlightedLines.length) {
const discussionLines = highlightedLines.filter(lineHasDiscussion);
@@ -284,33 +280,33 @@ export default {
});
},
- [types.TOGGLE_FOLDER_OPEN](state, path) {
- state.treeEntries[path].opened = !state.treeEntries[path].opened;
+ [types.TOGGLE_FOLDER_OPEN](path) {
+ this.treeEntries[path].opened = !this.treeEntries[path].opened;
},
- [types.TREE_ENTRY_DIFF_LOADING](state, { path, loading = true }) {
- state.treeEntries[path].diffLoading = loading;
+ [types.TREE_ENTRY_DIFF_LOADING]({ path, loading = true }) {
+ this.treeEntries[path].diffLoading = loading;
},
- [types.SET_SHOW_TREE_LIST](state, showTreeList) {
- state.showTreeList = showTreeList;
+ [types.SET_SHOW_TREE_LIST](showTreeList) {
+ this.showTreeList = showTreeList;
},
- [types.SET_CURRENT_DIFF_FILE](state, fileId) {
- state.currentDiffFileId = fileId;
+ [types.SET_CURRENT_DIFF_FILE](fileId) {
+ this.currentDiffFileId = fileId;
},
- [types.SET_DIFF_FILE_VIEWED](state, { id, seen }) {
- state.viewedDiffFileIds = {
- ...state.viewedDiffFileIds,
+ [types.SET_DIFF_FILE_VIEWED]({ id, seen }) {
+ this.viewedDiffFileIds = {
+ ...this.viewedDiffFileIds,
[id]: seen,
};
},
- [types.OPEN_DIFF_FILE_COMMENT_FORM](state, formData) {
- state.commentForms.push({
+ [types.OPEN_DIFF_FILE_COMMENT_FORM](formData) {
+ this.commentForms.push({
...formData,
});
},
- [types.UPDATE_DIFF_FILE_COMMENT_FORM](state, formData) {
+ [types.UPDATE_DIFF_FILE_COMMENT_FORM](formData) {
const { fileHash } = formData;
- state.commentForms = state.commentForms.map((form) => {
+ this.commentForms = this.commentForms.map((form) => {
if (form.fileHash === fileHash) {
return {
...formData,
@@ -320,48 +316,45 @@ export default {
return form;
});
},
- [types.CLOSE_DIFF_FILE_COMMENT_FORM](state, fileHash) {
- state.commentForms = state.commentForms.filter((form) => form.fileHash !== fileHash);
+ [types.CLOSE_DIFF_FILE_COMMENT_FORM](fileHash) {
+ this.commentForms = this.commentForms.filter((form) => form.fileHash !== fileHash);
},
- [types.SET_HIGHLIGHTED_ROW](state, lineCode) {
- state.highlightedRow = lineCode;
+ [types.SET_HIGHLIGHTED_ROW](lineCode) {
+ this.highlightedRow = lineCode;
},
- [types.SET_TREE_DATA](state, { treeEntries, tree }) {
- state.treeEntries = treeEntries;
- state.tree = tree;
- state.isTreeLoaded = true;
+ [types.SET_TREE_DATA]({ treeEntries, tree }) {
+ this.treeEntries = treeEntries;
+ this.tree = tree;
+ this.isTreeLoaded = true;
},
- [types.SET_RENDER_TREE_LIST](state, renderTreeList) {
- state.renderTreeList = renderTreeList;
+ [types.SET_RENDER_TREE_LIST](renderTreeList) {
+ this.renderTreeList = renderTreeList;
},
- [types.SET_SHOW_WHITESPACE](state, showWhitespace) {
- state.showWhitespace = showWhitespace;
- state.diffFiles = [];
+ [types.SET_SHOW_WHITESPACE](showWhitespace) {
+ this.showWhitespace = showWhitespace;
+ this.diffFiles = [];
},
- [types.TOGGLE_FILE_FINDER_VISIBLE](state, visible) {
- state.fileFinderVisible = visible;
+ [types.TOGGLE_FILE_FINDER_VISIBLE](visible) {
+ this.fileFinderVisible = visible;
},
- [types.REQUEST_FULL_DIFF](state, filePath) {
- const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+ [types.REQUEST_FULL_DIFF](filePath) {
+ const file = findDiffFile(this.diffFiles, filePath, 'file_path');
file.isLoadingFullFile = true;
},
- [types.RECEIVE_FULL_DIFF_ERROR](state, filePath) {
- const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+ [types.RECEIVE_FULL_DIFF_ERROR](filePath) {
+ const file = findDiffFile(this.diffFiles, filePath, 'file_path');
file.isLoadingFullFile = false;
},
- [types.RECEIVE_FULL_DIFF_SUCCESS](state, { filePath }) {
- const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+ [types.RECEIVE_FULL_DIFF_SUCCESS]({ filePath }) {
+ const file = findDiffFile(this.diffFiles, filePath, 'file_path');
file.isShowingFullFile = true;
file.isLoadingFullFile = false;
},
- [types.SET_FILE_COLLAPSED](
- state,
- { filePath, collapsed, trigger = DIFF_FILE_AUTOMATIC_COLLAPSE },
- ) {
- const file = state.diffFiles.find((f) => f.file_path === filePath);
+ [types.SET_FILE_COLLAPSED]({ filePath, collapsed, trigger = DIFF_FILE_AUTOMATIC_COLLAPSE }) {
+ const file = this.diffFiles.find((f) => f.file_path === filePath);
if (file && file.viewer) {
if (trigger === DIFF_FILE_MANUAL_COLLAPSE) {
@@ -373,58 +366,58 @@ export default {
}
}
},
- [types.SET_FILE_FORCED_OPEN](state, { filePath, forced = true }) {
- const file = state.diffFiles.find((f) => f.file_path === filePath);
+ [types.SET_FILE_FORCED_OPEN]({ filePath, forced = true }) {
+ const file = this.diffFiles.find((f) => f.file_path === filePath);
file.viewer.forceOpen = forced;
},
- [types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, lines }) {
- const file = state.diffFiles.find((f) => f.file_path === filePath);
+ [types.SET_CURRENT_VIEW_DIFF_FILE_LINES]({ filePath, lines }) {
+ const file = this.diffFiles.find((f) => f.file_path === filePath);
- file[INLINE_DIFF_LINES_KEY] = lines;
+ file[INLINE_DIFF_LINES_KEY] = [...lines];
},
- [types.ADD_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, line }) {
- const file = state.diffFiles.find((f) => f.file_path === filePath);
+ [types.ADD_CURRENT_VIEW_DIFF_FILE_LINES]({ filePath, line }) {
+ const file = this.diffFiles.find((f) => f.file_path === filePath);
file[INLINE_DIFF_LINES_KEY].push(line);
},
- [types.TOGGLE_DIFF_FILE_RENDERING_MORE](state, filePath) {
- const file = state.diffFiles.find((f) => f.file_path === filePath);
+ [types.TOGGLE_DIFF_FILE_RENDERING_MORE](filePath) {
+ const file = this.diffFiles.find((f) => f.file_path === filePath);
file.renderingLines = !file.renderingLines;
},
- [types.SET_DIFF_FILE_VIEWER](state, { filePath, viewer }) {
- const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+ [types.SET_DIFF_FILE_VIEWER]({ filePath, viewer }) {
+ const file = findDiffFile(this.diffFiles, filePath, 'file_path');
file.viewer = viewer;
},
- [types.SET_SHOW_SUGGEST_POPOVER](state) {
- state.showSuggestPopover = false;
+ [types.SET_SHOW_SUGGEST_POPOVER]() {
+ this.showSuggestPopover = false;
},
- [types.SET_FILE_BY_FILE](state, fileByFile) {
- state.viewDiffsFileByFile = fileByFile;
+ [types.SET_FILE_BY_FILE](fileByFile) {
+ this.viewDiffsFileByFile = fileByFile;
},
- [types.SET_MR_FILE_REVIEWS](state, newReviews) {
- state.mrReviews = newReviews;
+ [types.SET_MR_FILE_REVIEWS](newReviews) {
+ this.mrReviews = newReviews;
},
- [types.DISABLE_VIRTUAL_SCROLLING](state) {
- state.disableVirtualScroller = true;
+ [types.DISABLE_VIRTUAL_SCROLLING]() {
+ this.disableVirtualScroller = true;
},
- [types.TOGGLE_FILE_COMMENT_FORM](state, filePath) {
- const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+ [types.TOGGLE_FILE_COMMENT_FORM](filePath) {
+ const file = findDiffFile(this.diffFiles, filePath, 'file_path');
file.hasCommentForm = !file.hasCommentForm;
},
- [types.SET_FILE_COMMENT_FORM](state, { filePath, expanded }) {
- const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+ [types.SET_FILE_COMMENT_FORM]({ filePath, expanded }) {
+ const file = findDiffFile(this.diffFiles, filePath, 'file_path');
file.hasCommentForm = expanded;
},
- [types.ADD_DRAFT_TO_FILE](state, { filePath, draft }) {
- const file = findDiffFile(state.diffFiles, filePath, 'file_path');
+ [types.ADD_DRAFT_TO_FILE]({ filePath, draft }) {
+ const file = findDiffFile(this.diffFiles, filePath, 'file_path');
file?.drafts.push(draft);
},
- [types.SET_LINKED_FILE_HASH](state, fileHash) {
- state.linkedFileHash = fileHash;
+ [types.SET_LINKED_FILE_HASH](fileHash) {
+ this.linkedFileHash = fileHash;
},
};
diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json
index 213763d7857..81b0fad47a0 100644
--- a/app/assets/javascripts/graphql_shared/possible_types.json
+++ b/app/assets/javascripts/graphql_shared/possible_types.json
@@ -11,10 +11,18 @@
"GroupAuditEventStreamingDestination",
"InstanceAuditEventStreamingDestination"
],
+ "BaseDiscussionInterface": [
+ "AbuseReportDiscussion",
+ "Discussion"
+ ],
"BaseHeaderInterface": [
"AuditEventStreamingHeader",
"AuditEventsStreamingInstanceHeader"
],
+ "BaseNoteInterface": [
+ "AbuseReportNote",
+ "Note"
+ ],
"CiRunnerCloudProvisioning": [
"CiRunnerGoogleCloudProvisioning"
],
@@ -85,7 +93,6 @@
"ProjectMember"
],
"NoteableInterface": [
- "AbuseReport",
"AlertManagementAlert",
"BoardEpic",
"Design",
@@ -138,6 +145,8 @@
"UploadRegistry"
],
"ResolvableInterface": [
+ "AbuseReportDiscussion",
+ "AbuseReportNote",
"Discussion",
"Note"
],
diff --git a/app/assets/javascripts/mr_notes/store/legacy_mr_notes.js b/app/assets/javascripts/mr_notes/store/legacy_mr_notes.js
new file mode 100644
index 00000000000..d59b133f4c2
--- /dev/null
+++ b/app/assets/javascripts/mr_notes/store/legacy_mr_notes.js
@@ -0,0 +1,3 @@
+import { defineStore } from 'pinia';
+
+export const useMrNotes = defineStore('legacyMrNotes', {});
diff --git a/app/assets/javascripts/notes/components/multiline_comment_form.vue b/app/assets/javascripts/notes/components/multiline_comment_form.vue
index 8dbce435dc9..be655ee1f6d 100644
--- a/app/assets/javascripts/notes/components/multiline_comment_form.vue
+++ b/app/assets/javascripts/notes/components/multiline_comment_form.vue
@@ -89,6 +89,7 @@ export default {
:value="commentLineStart"
:options="commentLineOptions"
width="sm"
+ class="gl-w-auto"
@change="updateCommentLineStart"
/>
diff --git a/app/assets/javascripts/notes/store/legacy_notes/index.js b/app/assets/javascripts/notes/store/legacy_notes/index.js
new file mode 100644
index 00000000000..5c4dd4d23e5
--- /dev/null
+++ b/app/assets/javascripts/notes/store/legacy_notes/index.js
@@ -0,0 +1,3 @@
+import { defineStore } from 'pinia';
+
+export const useNotes = defineStore('legacyNotes', {});
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 66355317d18..db882f78b29 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -177,18 +177,6 @@
}
}
- &.user-authored {
- cursor: default;
- background-color: $gray-10;
- border-color: $gray-100;
- color: $gl-text-color-disabled;
-
- gl-emoji {
- opacity: 0.4;
- filter: grayscale(100%);
- }
- }
-
&.btn {
&:focus {
outline: 0;
@@ -196,7 +184,6 @@
}
&.is-loading {
- .award-control-icon-normal,
.emoji-icon {
display: none;
}
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 5d35a71f3f1..c034df6b626 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -42,11 +42,6 @@
margin-bottom: 0;
}
- &.clear-block {
- margin-bottom: $gl-padding - 1px;
- padding-bottom: $gl-padding;
- }
-
&.second-block {
margin-top: -1px;
margin-bottom: 0;
@@ -77,10 +72,6 @@
> .controls {
float: right;
}
-
- .new-branch {
- margin-top: 3px;
- }
}
.content-block-small {
diff --git a/app/assets/stylesheets/framework/breadcrumbs.scss b/app/assets/stylesheets/framework/breadcrumbs.scss
index c56cd09c5d4..2ccaeb19326 100644
--- a/app/assets/stylesheets/framework/breadcrumbs.scss
+++ b/app/assets/stylesheets/framework/breadcrumbs.scss
@@ -32,25 +32,3 @@
flex-grow: 1;
}
}
-
-/*
- * This temporarily restores the legacy breadcrumbs styles on the primary HAML breadcrumbs.
- * Those styles got changed in https://gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/3663,
- * causing a regression in this particular instance which does not use a Vue component and is
- * therefore unable to collapse overflowing items within a disclosure dropdown.
- * These temporary overrides will be removed as part of https://gitlab.com/gitlab-org/gitlab/-/issues/358113.
- */
-.tmp-breadcrumbs-fix {
- .gl-breadcrumb-list {
- flex-wrap: wrap;
- max-width: none;
-
- .gl-breadcrumb-item {
- > a {
- @include media-breakpoint-down(xs) {
- @include str-truncated($breadcrumb-max-width);
- }
- }
- }
- }
-}
diff --git a/app/assets/stylesheets/framework/broadcast_messages.scss b/app/assets/stylesheets/framework/broadcast_messages.scss
index d5cd28718aa..df5970b0e34 100644
--- a/app/assets/stylesheets/framework/broadcast_messages.scss
+++ b/app/assets/stylesheets/framework/broadcast_messages.scss
@@ -41,12 +41,6 @@
}
}
-.toggle-colors {
- input {
- min-height: 34px;
- }
-}
-
.gl-broadcast-message-content p:last-child {
margin: 0;
}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index 5df5cbbe126..ee3351e9374 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -20,10 +20,6 @@
color: $text;
border-color: $border;
- &.btn-border-color {
- border-color: $border-color;
- }
-
> .icon {
color: $text;
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 68f1452bebb..e320aa7251e 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -199,12 +199,6 @@ p.time {
// Fix issue with notes & lists creating a bunch of bottom borders.
li.note {
img { max-width: 100%; }
-
- .note-title {
- li {
- border-bottom: 0 !important;
- }
- }
}
.markdown {
@@ -287,6 +281,7 @@ li.note {
}
}
+// these classes override styles from the dropzone node package
.dropzone .dz-preview .dz-progress {
border-color: $border-color !important;
@@ -345,16 +340,6 @@ li.note {
word-wrap: break-word;
}
-.checkbox-icon-inline-wrapper {
- .checkbox {
- display: inline;
-
- label {
- display: inline;
- }
- }
-}
-
/** COMMON CLASSES **/
/**
🚨 Do not use these classes — they are deprecated and being removed. 🚨
diff --git a/app/assets/stylesheets/framework/diffs.scss b/app/assets/stylesheets/framework/diffs.scss
index 71d16b92019..c6629180e87 100644
--- a/app/assets/stylesheets/framework/diffs.scss
+++ b/app/assets/stylesheets/framework/diffs.scss
@@ -82,11 +82,6 @@ $diff-file-header: 41px;
cursor: pointer;
}
- .file-mode-changed {
- padding: 10px;
- @apply gl-text-subtle;
- }
-
.suppressed-container {
padding: ($padding-base-vertical + 5px) $padding-base-horizontal;
text-align: center;
@@ -455,12 +450,6 @@ table.code {
span {
white-space: break-spaces;
- &.context-cell {
- display: inline-block;
- width: 100%;
- height: 100%;
- }
-
&.line {
word-wrap: break-word;
}
@@ -820,10 +809,6 @@ table.code {
&[aria-expanded="false"] {
@apply gl-border-b;
}
-
- .reply-author-avatar {
- height: 1.5rem;
- }
}
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 128755d59e8..113f2935850 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -104,10 +104,6 @@
color: $gl-text-color !important;
}
- &.no-outline {
- outline: 0;
- }
-
&.large {
width: 200px;
}
@@ -241,13 +237,6 @@
display: block;
}
}
-
- .icon-play {
- fill: $gl-text-color-secondary;
- margin-right: 6px;
- height: 12px;
- width: 11px;
- }
}
.dropdown-menu {
@@ -272,11 +261,6 @@
margin-bottom: $dropdown-vertical-offset;
}
- &.dropdown-open-left {
- right: 0;
- left: auto;
- }
-
&.is-loading {
.dropdown-content {
display: none;
@@ -360,12 +344,6 @@
text-transform: capitalize;
}
- .dropdown-bold-header {
- font-weight: $gl-font-weight-bold;
- line-height: $gl-line-height;
- padding: $dropdown-item-padding-y $dropdown-item-padding-x;
- }
-
.unclickable {
cursor: not-allowed;
padding: 5px 8px;
@@ -376,10 +354,6 @@
// Expects up to 3 digits on the badge
margin-right: 40px;
}
-
- .dropdown-menu-content {
- padding: $dropdown-item-padding-y $dropdown-item-padding-x;
- }
}
.dropdown-item {
@@ -462,10 +436,6 @@
}
}
-.dropdown-menu-large {
- width: 340px;
-}
-
.dropdown-menu-full-width {
width: 100%;
}
@@ -537,7 +507,7 @@
li {
a,
button,
- .dropdown-item:not(.open-with-link) {
+ .dropdown-item {
padding: 8px 40px;
position: relative;
@@ -656,8 +626,7 @@
}
}
-.dropdown-input-field,
-.default-dropdown-input {
+.dropdown-input-field {
background-color: $input-bg;
display: block;
width: 100%;
@@ -808,31 +777,6 @@
}
}
-.dropdown-content-faded-mask {
- position: relative;
-
- .dropdown-list {
- max-height: $dropdown-max-height;
- overflow-y: auto;
- position: relative;
- }
-
- &::after {
- height: $dropdown-fade-mask-height;
- width: 100%;
- position: absolute;
- bottom: 0;
- background: linear-gradient(to top, $white 0, rgba($white, 0));
- transition: opacity $fade-mask-transition-duration $fade-mask-transition-curve;
- content: '';
- pointer-events: none;
- }
-
- &.fade-out::after {
- opacity: 0;
- }
-}
-
.labels-select-wrapper {
&.is-standalone {
min-width: $input-md-width;
diff --git a/app/assets/stylesheets/framework/vue_transitions.scss b/app/assets/stylesheets/framework/vue_transitions.scss
index a9acb823cf3..563a1a8c717 100644
--- a/app/assets/stylesheets/framework/vue_transitions.scss
+++ b/app/assets/stylesheets/framework/vue_transitions.scss
@@ -1,3 +1,8 @@
+/**
+* Transition classes are built dynamically, please read about vue-transitions
+* here before removing https://vuejs.org/guide/built-ins/transition
+**/
+
.fade-enter-active,
.fade-leave-active,
.fade-in-enter-active,
diff --git a/app/graphql/resolvers/noteable/notes_resolver.rb b/app/graphql/resolvers/noteable/notes_resolver.rb
index a8d75d8053e..b4bd1068723 100644
--- a/app/graphql/resolvers/noteable/notes_resolver.rb
+++ b/app/graphql/resolvers/noteable/notes_resolver.rb
@@ -22,9 +22,6 @@ module Resolvers
end
def resolve_with_lookahead(**args)
- # TODO: Implement as part of completion https://gitlab.com/gitlab-org/gitlab/-/issues/458264
- return [] if object.is_a?(AbuseReport)
-
notes = NotesFinder.new(current_user, build_params(args)).execute
apply_lookahead(notes)
end
diff --git a/app/graphql/types/abuse_report_type.rb b/app/graphql/types/abuse_report_type.rb
index dc40800af94..1ba3a9916cb 100644
--- a/app/graphql/types/abuse_report_type.rb
+++ b/app/graphql/types/abuse_report_type.rb
@@ -4,8 +4,6 @@ module Types
class AbuseReportType < BaseObject
graphql_name 'AbuseReport'
- implements Types::Notes::NoteableInterface
-
description 'An abuse report'
authorize :read_abuse_report
@@ -15,5 +13,10 @@ module Types
field :labels, ::Types::LabelType.connection_type,
null: true, description: 'Labels of the abuse report.'
+
+ field :discussions, ::Types::Notes::AbuseReport::DiscussionType.connection_type,
+ null: false, description: "All discussions on the noteable."
+ field :notes, ::Types::Notes::AbuseReport::NoteType.connection_type,
+ null: false, description: "All notes on the noteable."
end
end
diff --git a/app/graphql/types/notes/abuse_report/discussion_type.rb b/app/graphql/types/notes/abuse_report/discussion_type.rb
new file mode 100644
index 00000000000..b7c8cab3e59
--- /dev/null
+++ b/app/graphql/types/notes/abuse_report/discussion_type.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ module AbuseReport
+ class DiscussionType < BaseObject
+ graphql_name 'AbuseReportDiscussion'
+
+ authorize :read_note
+
+ DiscussionID = ::Types::GlobalIDType[::Discussion]
+
+ implements Types::Notes::BaseDiscussionInterface
+
+ field :abuse_report, Types::AbuseReportType, null: true,
+ description: 'Abuse report which the discussion belongs to.'
+
+ field :notes, Types::Notes::AbuseReport::NoteType.connection_type, null: false,
+ description: 'All notes in the discussion.'
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/notes/abuse_report/note_type.rb b/app/graphql/types/notes/abuse_report/note_type.rb
new file mode 100644
index 00000000000..c31caf4c9bd
--- /dev/null
+++ b/app/graphql/types/notes/abuse_report/note_type.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ module AbuseReport
+ class NoteType < BaseObject
+ graphql_name 'AbuseReportNote'
+
+ implements Types::Notes::BaseNoteInterface
+
+ authorize :read_note
+
+ field :id, ::Types::GlobalIDType[::AntiAbuse::Reports::Note],
+ null: false,
+ description: 'ID of the note.'
+
+ field :discussion, Types::Notes::AbuseReport::DiscussionType,
+ null: true,
+ description: 'Discussion the note is a part of.'
+
+ def note_project
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/notes/base_discussion_interface.rb b/app/graphql/types/notes/base_discussion_interface.rb
new file mode 100644
index 00000000000..174c74eb4f2
--- /dev/null
+++ b/app/graphql/types/notes/base_discussion_interface.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ module BaseDiscussionInterface
+ include Types::BaseInterface
+
+ DiscussionID = ::Types::GlobalIDType[::Discussion]
+
+ implements Types::ResolvableInterface
+
+ field :created_at, Types::TimeType, null: false,
+ description: "Timestamp of the discussion's creation."
+ field :id, DiscussionID, null: false,
+ description: "ID of the discussion."
+ field :reply_id, DiscussionID, null: false,
+ description: 'ID used to reply to the discussion.'
+
+ # DiscussionID.coerce_result is suitable here, but will always mark this
+ # as being a 'Discussion'. Using `GlobalId.build` guarantees that we get
+ # the correct class, and that it matches `id`.
+ def reply_id
+ ::Gitlab::GlobalId.build(object, id: object.reply_id)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/notes/base_note_interface.rb b/app/graphql/types/notes/base_note_interface.rb
new file mode 100644
index 00000000000..ff0e2be1333
--- /dev/null
+++ b/app/graphql/types/notes/base_note_interface.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ module BaseNoteInterface
+ include Types::BaseInterface
+
+ implements Types::ResolvableInterface
+
+ field :author, Types::UserType,
+ null: true,
+ description: 'User who wrote the note.'
+
+ field :award_emoji, Types::AwardEmojis::AwardEmojiType.connection_type,
+ null: true,
+ description: 'List of emoji reactions associated with the note.'
+
+ field :body, GraphQL::Types::String,
+ null: false,
+ method: :note,
+ description: 'Content of the note.'
+
+ field :body_first_line_html, GraphQL::Types::String,
+ null: false,
+ description: 'First line of the note content.'
+
+ field :body_html, GraphQL::Types::String,
+ method: :note_html,
+ null: true,
+ description: "GitLab Flavored Markdown rendering of the content of the note."
+
+ field :created_at, Types::TimeType,
+ null: false,
+ description: 'Timestamp of the note creation.'
+
+ field :last_edited_at, Types::TimeType,
+ null: true,
+ description: 'Timestamp when note was last edited.'
+
+ field :last_edited_by, Types::UserType,
+ null: true,
+ description: 'User who last edited the note.'
+
+ field :updated_at, Types::TimeType,
+ null: false,
+ description: "Timestamp of the note's last activity."
+
+ field :url, GraphQL::Types::String,
+ null: true,
+ description: 'URL to view the note in the Web UI.'
+
+ def author
+ Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
+ end
+
+ def url
+ ::Gitlab::UrlBuilder.build(object)
+ end
+
+ def body_first_line_html
+ first_line_in_markdown(object, :note, 125, project: note_project)
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/notes/discussion_type.rb b/app/graphql/types/notes/discussion_type.rb
index fa2a1a26a51..69ea3d8974c 100644
--- a/app/graphql/types/notes/discussion_type.rb
+++ b/app/graphql/types/notes/discussion_type.rb
@@ -5,29 +5,14 @@ module Types
class DiscussionType < BaseObject
graphql_name 'Discussion'
- DiscussionID = ::Types::GlobalIDType[::Discussion]
-
authorize :read_note
- implements Types::ResolvableInterface
+ implements Types::Notes::BaseDiscussionInterface
- field :created_at, Types::TimeType, null: false,
- description: "Timestamp of the discussion's creation."
- field :id, DiscussionID, null: false,
- description: "ID of this discussion."
field :noteable, Types::NoteableType, null: true,
description: 'Object which the discussion belongs to.'
field :notes, Types::Notes::NoteType.connection_type, null: false,
description: 'All notes in the discussion.'
- field :reply_id, DiscussionID, null: false,
- description: 'ID used to reply to this discussion.'
-
- # DiscussionID.coerce_result is suitable here, but will always mark this
- # as being a 'Discussion'. Using `GlobalId.build` guarantees that we get
- # the correct class, and that it matches `id`.
- def reply_id
- ::Gitlab::GlobalId.build(object, id: object.reply_id)
- end
def noteable
noteable = object.noteable
diff --git a/app/graphql/types/notes/note_type.rb b/app/graphql/types/notes/note_type.rb
index 791b76de30d..7808a45c97e 100644
--- a/app/graphql/types/notes/note_type.rb
+++ b/app/graphql/types/notes/note_type.rb
@@ -14,7 +14,7 @@ module Types
expose_permissions Types::PermissionTypes::Note
- implements Types::ResolvableInterface
+ implements Types::Notes::BaseNoteInterface
present_using NotePresenter
@@ -31,10 +31,6 @@ module Types
null: true,
description: 'Project associated with the note.'
- field :author, Types::UserType,
- null: true,
- description: 'User who wrote the note.'
-
field :system, GraphQL::Types::Boolean,
null: false,
description: 'Indicates whether the note was created by the system or by a user.'
@@ -43,19 +39,6 @@ module Types
null: true,
description: 'Name of the icon corresponding to a system note.'
- field :body, GraphQL::Types::String,
- null: false,
- method: :note,
- description: 'Content of the note.'
-
- field :body_first_line_html, GraphQL::Types::String,
- null: false,
- description: 'First line of the note content.'
-
- field :award_emoji, Types::AwardEmojis::AwardEmojiType.connection_type,
- null: true,
- description: 'List of emoji reactions associated with the note.'
-
field :imported, GraphQL::Types::Boolean,
null: true,
description: 'Indicates whether the note was imported.',
@@ -65,28 +48,12 @@ module Types
description: 'Indicates if the note is internal.',
method: :confidential?
- field :created_at, Types::TimeType,
- null: false,
- description: 'Timestamp of the note creation.'
field :discussion, Types::Notes::DiscussionType,
null: true,
description: 'Discussion the note is a part of.'
field :position, Types::Notes::DiffPositionType,
null: true,
description: 'Position of the note on a diff.'
- field :updated_at, Types::TimeType,
- null: false,
- description: "Timestamp of the note's last activity."
- field :url, GraphQL::Types::String,
- null: true,
- description: 'URL to view the note in the Web UI.'
-
- field :last_edited_at, Types::TimeType,
- null: true,
- description: 'Timestamp when note was last edited.'
- field :last_edited_by, Types::UserType,
- null: true,
- description: 'User who last edited the note.'
field :author_is_contributor, GraphQL::Types::Boolean,
null: true,
@@ -98,15 +65,6 @@ module Types
null: true,
description: 'Metadata for the given note if it is a system note.'
- field :body_html, GraphQL::Types::String,
- method: :note_html,
- null: true,
- description: "GitLab Flavored Markdown rendering of the content of the note."
-
- def url
- ::Gitlab::UrlBuilder.build(object)
- end
-
def system_note_icon_name
SystemNoteHelper.system_note_icon_name(object) if object.system?
end
@@ -115,10 +73,6 @@ module Types
Gitlab::Graphql::Loaders::BatchModelLoader.new(Project, object.project_id).find
end
- def author
- Gitlab::Graphql::Loaders::BatchModelLoader.new(User, object.author_id).find
- end
-
# We now support also SyntheticNote notes as a NoteType, but SyntheticNote does not have a real note ID,
# as SyntheticNote is generated dynamically from a ResourceEvent instance.
def id
@@ -128,8 +82,8 @@ module Types
::Gitlab::GlobalId.build(object, model_name: object.object.class.to_s, id: object.discussion_id)
end
- def body_first_line_html
- first_line_in_markdown(object, :note, 125, project: object.project)
+ def note_project
+ object.project
end
end
end
diff --git a/app/graphql/types/notes/noteable_interface.rb b/app/graphql/types/notes/noteable_interface.rb
index cfe091ed255..9971511d6ce 100644
--- a/app/graphql/types/notes/noteable_interface.rb
+++ b/app/graphql/types/notes/noteable_interface.rb
@@ -21,17 +21,12 @@ module Types
Types::DesignManagement::DesignType
when ::AlertManagement::Alert
Types::AlertManagement::AlertType
- when AbuseReport
- Types::AbuseReportType
else
raise "Unknown GraphQL type for #{object}"
end
end
def commenters
- # TODO: Implement as part of completion https://gitlab.com/gitlab-org/gitlab/-/issues/458264
- return [] if object.is_a?(AbuseReport)
-
object.commenters(user: current_user)
end
end
diff --git a/app/models/anti_abuse/reports/discussion.rb b/app/models/anti_abuse/reports/discussion.rb
index b169b619828..b4246ba7b44 100644
--- a/app/models/anti_abuse/reports/discussion.rb
+++ b/app/models/anti_abuse/reports/discussion.rb
@@ -3,6 +3,8 @@
module AntiAbuse
module Reports
class Discussion < ::Discussion
+ delegate :abuse_report, to: :first_note
+
def self.base_discussion_id(_note)
[:discussion, :abuse_report_id]
end
diff --git a/app/models/anti_abuse/reports/note.rb b/app/models/anti_abuse/reports/note.rb
index 19be2f9a65b..e64bcee3aff 100644
--- a/app/models/anti_abuse/reports/note.rb
+++ b/app/models/anti_abuse/reports/note.rb
@@ -32,6 +32,19 @@ module AntiAbuse
validates :abuse_report, presence: true
scope :fresh, -> { order_created_asc.with_order_id_asc }
+ scope :inc_relations_for_view, ->(_abuse_report = nil) do
+ relations = [
+ { author: :status }, :updated_by, :award_emoji
+ ]
+
+ includes(relations)
+ end
+
+ class << self
+ def parent_object_field
+ :abuse_report
+ end
+ end
def discussion_class(_noteable = nil)
AntiAbuse::Reports::IndividualNoteDiscussion
diff --git a/app/models/concerns/notes/discussion.rb b/app/models/concerns/notes/discussion.rb
index 57c8d7c6343..658861beaea 100644
--- a/app/models/concerns/notes/discussion.rb
+++ b/app/models/concerns/notes/discussion.rb
@@ -11,7 +11,7 @@ module Notes
class_methods do
def discussions(context_noteable = nil)
- ::Discussion.build_collection(all.includes(:noteable).fresh, context_noteable)
+ ::Discussion.build_collection(all.includes(parent_object_field).fresh, context_noteable)
end
def find_discussion(discussion_id)
diff --git a/app/models/integrations/instance_integration.rb b/app/models/integrations/instance_integration.rb
new file mode 100644
index 00000000000..9fc52fb5708
--- /dev/null
+++ b/app/models/integrations/instance_integration.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module Integrations
+ class InstanceIntegration < Integration
+ self.table_name = 'instance_integrations'
+ end
+end
diff --git a/app/models/note.rb b/app/models/note.rb
index d0cb30529a1..a0936cee8f8 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -230,6 +230,10 @@ class Note < ApplicationRecord
ActiveModel::Name.new(self, nil, 'note')
end
+ def parent_object_field
+ :noteable
+ end
+
# Group diff discussions by line code or file path.
# It is not needed to group by line code when comment is
# on an image.
diff --git a/app/policies/anti_abuse/reports/note_policy.rb b/app/policies/anti_abuse/reports/note_policy.rb
new file mode 100644
index 00000000000..522fec21800
--- /dev/null
+++ b/app/policies/anti_abuse/reports/note_policy.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module AntiAbuse
+ module Reports
+ class NotePolicy < BasePolicy
+ delegate { @subject.abuse_report }
+ end
+ end
+end
diff --git a/app/views/admin/application_settings/_external_authorization_service_form.html.haml b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
index b0cff84e50c..2ea4bbb1102 100644
--- a/app/views/admin/application_settings/_external_authorization_service_form.html.haml
+++ b/app/views/admin/application_settings/_external_authorization_service_form.html.haml
@@ -4,7 +4,7 @@
expanded: expanded) do |c|
- c.with_description do
= s_('ExternalAuthorization|External classification policy authorization.')
- = link_to _('Learn more.'), help_page_path('administration/settings/external_authorization'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('administration/settings/external_authorization.md'), target: '_blank', rel: 'noopener noreferrer'
- c.with_body do
= gitlab_ui_form_for @application_setting, url: general_admin_application_settings_path(anchor: 'js-external-auth-settings'), html: { class: 'fieldset-form', id: 'external-auth-settings' } do |f|
= form_errors(@application_setting)
diff --git a/app/views/admin/impersonation_tokens/index.html.haml b/app/views/admin/impersonation_tokens/index.html.haml
index 3171f019abb..d19a90a8c2b 100644
--- a/app/views/admin/impersonation_tokens/index.html.haml
+++ b/app/views/admin/impersonation_tokens/index.html.haml
@@ -28,7 +28,7 @@
impersonation: true,
token: @impersonation_token,
scopes: @scopes,
- help_path: help_page_path('api/rest/index', anchor: 'impersonation-tokens')
+ help_path: help_page_path('api/rest/index.md', anchor: 'impersonation-tokens')
- c.with_body do
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_impersonation_tokens.to_json } }
diff --git a/app/views/groups/settings/_resource_access_token_creation.html.haml b/app/views/groups/settings/_resource_access_token_creation.html.haml
index 7d64ab84ad2..e3a04678daa 100644
--- a/app/views/groups/settings/_resource_access_token_creation.html.haml
+++ b/app/views/groups/settings/_resource_access_token_creation.html.haml
@@ -1,8 +1,8 @@
- return unless render_setting_to_allow_project_access_token_creation?(group)
.form-group.gl-mb-3
- - project_access_tokens_link = help_page_path('user/project/settings/project_access_tokens')
- - group_access_tokens_link = help_page_path('user/group/settings/group_access_tokens')
+ - project_access_tokens_link = help_page_path('user/project/settings/project_access_tokens.md')
+ - group_access_tokens_link = help_page_path('user/group/settings/group_access_tokens.md')
- link_start_project = ''.html_safe % { url: project_access_tokens_link }
- link_start_group = ''.html_safe % { url: group_access_tokens_link }
= f.gitlab_ui_checkbox_component :resource_access_token_creation_allowed,
diff --git a/app/views/groups/settings/_two_factor_auth.html.haml b/app/views/groups/settings/_two_factor_auth.html.haml
index cf44f2b69b1..e7bd0392105 100644
--- a/app/views/groups/settings/_two_factor_auth.html.haml
+++ b/app/views/groups/settings/_two_factor_auth.html.haml
@@ -1,5 +1,5 @@
- return unless group.parent_allows_two_factor_authentication?
-- docs_link_url = help_page_path('security/two_factor_authentication', anchor: 'enforce-2fa-for-all-users-in-a-group')
+- docs_link_url = help_page_path('security/two_factor_authentication.md', anchor: 'enforce-2fa-for-all-users-in-a-group')
- docs_link_start = ''.html_safe % { url: docs_link_url }
%h5= _('Two-factor authentication')
diff --git a/app/views/groups/settings/access_tokens/index.html.haml b/app/views/groups/settings/access_tokens/index.html.haml
index 2b31feaa6d4..41d66e9f6a7 100644
--- a/app/views/groups/settings/access_tokens/index.html.haml
+++ b/app/views/groups/settings/access_tokens/index.html.haml
@@ -6,7 +6,7 @@
= render ::Layouts::SettingsSectionComponent.new(page_title, options: { class: 'js-search-settings-section' }) do |c|
- c.with_description do
- - help_link_start = ''.html_safe % { url: help_page_path('user/group/settings/group_access_tokens') }
+ - help_link_start = ''.html_safe % { url: help_page_path('user/group/settings/group_access_tokens.md') }
- if current_user.can?(:create_resource_access_tokens, @group)
= _('Generate group access tokens scoped to this group for your applications that need access to the GitLab API.')
= html_escape(_('You can also use group access tokens with Git to authenticate over HTTP(S). %{link_start}Learn more.%{link_end}')) % { link_start: help_link_start, link_end: ''.html_safe }
@@ -46,7 +46,7 @@
default_access_level: Gitlab::Access::GUEST,
prefix: :resource_access_token,
description_prefix: :group_access_token,
- help_path: help_page_path('user/group/settings/group_access_tokens', anchor: 'scopes-for-a-group-access-token')
+ help_path: help_page_path('user/group/settings/group_access_tokens.md', anchor: 'scopes-for-a-group-access-token')
- c.with_body do
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json, no_active_tokens_message: _('This group has no active access tokens.'), show_role: true } }
diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml
index 89b35c847e4..d1de043b21d 100644
--- a/app/views/profiles/two_factor_auths/show.html.haml
+++ b/app/views/profiles/two_factor_auths/show.html.haml
@@ -17,7 +17,7 @@
%p
- register_2fa_token = _('We recommend using cloud-based authenticator applications that can restore access if you lose your hardware device.')
= register_2fa_token.html_safe
- = link_to _('What are some examples?'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'enable-one-time-password'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('What are some examples?'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'enable-one-time-password'), target: '_blank', rel: 'noopener noreferrer'
.gl-p-2.gl-mb-3{ style: 'background: #fff' }
= raw @qr_code
.gl-mb-5
@@ -41,7 +41,7 @@
alert_options: { class: 'gl-mb-3' },
dismissible: false) do |c|
- c.with_body do
- = link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication_troubleshooting'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication_troubleshooting.md'), target: '_blank', rel: 'noopener noreferrer'
- if current_password_required?
.form-group
@@ -130,7 +130,7 @@
alert_options: { class: 'gl-mb-3' },
dismissible: false) do |c|
- c.with_body do
- = link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication_troubleshooting'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Try the troubleshooting steps here.'), help_page_path('user/profile/account/two_factor_authentication_troubleshooting.md'), target: '_blank', rel: 'noopener noreferrer'
.js-manage-two-factor-form{ data: { current_password_required: current_password_required?.to_s, profile_two_factor_auth_path: profile_two_factor_auth_path, profile_two_factor_auth_method: 'delete', codes_profile_two_factor_auth_path: codes_profile_two_factor_auth_path, codes_profile_two_factor_auth_method: 'post' } }
- else
%p
diff --git a/app/views/projects/settings/access_tokens/_form.html.haml b/app/views/projects/settings/access_tokens/_form.html.haml
index ee993962c7a..298b8359890 100644
--- a/app/views/projects/settings/access_tokens/_form.html.haml
+++ b/app/views/projects/settings/access_tokens/_form.html.haml
@@ -11,4 +11,4 @@
default_access_level: Gitlab::Access::GUEST,
prefix: :resource_access_token,
description_prefix: :project_access_token,
- help_path: help_page_path('user/project/settings/project_access_tokens', anchor: 'scopes-for-a-project-access-token')
+ help_path: help_page_path('user/project/settings/project_access_tokens.md', anchor: 'scopes-for-a-project-access-token')
diff --git a/app/views/projects/settings/access_tokens/index.html.haml b/app/views/projects/settings/access_tokens/index.html.haml
index 4249fec3717..3350b6e89f2 100644
--- a/app/views/projects/settings/access_tokens/index.html.haml
+++ b/app/views/projects/settings/access_tokens/index.html.haml
@@ -10,7 +10,7 @@
%h4.gl-my-0
= page_title
%p.gl-text-secondary
- - help_link_start = ''.html_safe % { url: help_page_path('user/project/settings/project_access_tokens') }
+ - help_link_start = ''.html_safe % { url: help_page_path('user/project/settings/project_access_tokens.md') }
- if current_user.can?(:create_resource_access_tokens, @project)
= _('Generate project access tokens scoped to this project for your applications that need access to the GitLab API.')
= _('You can also use project access tokens with Git to authenticate over HTTP(S). %{link_start}Learn more.%{link_end}').html_safe % { link_start: help_link_start, link_end: ''.html_safe }
diff --git a/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml b/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml
index e372dbd983c..ce2a6fc09da 100644
--- a/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml
+++ b/app/views/shared/_two_factor_auth_recovery_settings_check.html.haml
@@ -6,7 +6,7 @@
close_button_options: { data: { testid: 'close-account-recovery-regular-check-callout' }}) do |c|
- c.with_body do
= s_('Profiles|Ensure you have two-factor authentication recovery codes stored in a safe place.')
- = link_to _('Learn more.'), help_page_path('user/profile/account/two_factor_authentication', anchor: 'recovery-codes'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more.'), help_page_path('user/profile/account/two_factor_authentication.md', anchor: 'recovery-codes'), target: '_blank', rel: 'noopener noreferrer'
- c.with_actions do
= link_button_to profile_two_factor_auth_path, class: 'deferred-link gl-alert-action', variant: :confirm do
= s_('Profiles|Manage two-factor authentication')
diff --git a/app/views/user_settings/personal_access_tokens/index.html.haml b/app/views/user_settings/personal_access_tokens/index.html.haml
index e092af3215e..1a912f2aeec 100644
--- a/app/views/user_settings/personal_access_tokens/index.html.haml
+++ b/app/views/user_settings/personal_access_tokens/index.html.haml
@@ -31,7 +31,7 @@
path: user_settings_personal_access_tokens_path,
token: @personal_access_token,
scopes: @scopes,
- help_path: help_page_path('user/profile/personal_access_tokens', anchor: 'personal-access-token-scopes')
+ help_path: help_page_path('user/profile/personal_access_tokens.md', anchor: 'personal-access-token-scopes')
- c.with_body do
#js-access-token-table-app{ data: { access_token_type: type, access_token_type_plural: type_plural, initial_active_access_tokens: @active_access_tokens.to_json } }
diff --git a/config/audit_events/types/self_hosted_model_destroyed.yml b/config/audit_events/types/self_hosted_model_destroyed.yml
new file mode 100644
index 00000000000..970227e2347
--- /dev/null
+++ b/config/audit_events/types/self_hosted_model_destroyed.yml
@@ -0,0 +1,9 @@
+name: self_hosted_model_destroyed
+description: A new self-hosted model configuration was destroyed
+introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/477999
+introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165321
+feature_category: self-hosted_models
+milestone: '17.4'
+saved_to_database: true
+scope: [Instance, User]
+streamed: true
diff --git a/config/initializers/gitlab_experiment.rb b/config/initializers/gitlab_experiment.rb
index 13554fc0bfb..8494f49bde6 100644
--- a/config/initializers/gitlab_experiment.rb
+++ b/config/initializers/gitlab_experiment.rb
@@ -13,7 +13,7 @@ Gitlab::Experiment.configure do |config|
# Customize the logic of our default rollout, which shouldn't include
# assigning the control yet -- we specifically set it to false for now.
#
- config.default_rollout = Gitlab::Experiment::Rollout.resolve(:feature)
+ config.default_rollout = Gitlab::Experiment::Rollout.resolve('Gitlab::ExperimentFeatureRollout')
# Mount the engine and middleware at a gitlab friendly style path.
#
diff --git a/db/docs/instance_integrations.yml b/db/docs/instance_integrations.yml
new file mode 100644
index 00000000000..b799f7a5a5b
--- /dev/null
+++ b/db/docs/instance_integrations.yml
@@ -0,0 +1,10 @@
+---
+table_name: instance_integrations
+classes:
+- Integrations::InstanceIntegration
+feature_categories:
+- integrations
+description: Support 3rd party instance-wide integrations
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/164503
+milestone: '17.4'
+gitlab_schema: gitlab_main_clusterwide
diff --git a/db/migrate/20240828103148_add_spp_repository_pipeline_access_to_project_settings.rb b/db/migrate/20240828103148_add_spp_repository_pipeline_access_to_project_settings.rb
new file mode 100644
index 00000000000..2d1c274ab38
--- /dev/null
+++ b/db/migrate/20240828103148_add_spp_repository_pipeline_access_to_project_settings.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddSppRepositoryPipelineAccessToProjectSettings < Gitlab::Database::Migration[2.2]
+ enable_lock_retries!
+ milestone '17.4'
+
+ def change
+ add_column :project_settings, :spp_repository_pipeline_access, :boolean
+ end
+end
diff --git a/db/migrate/20240829163210_create_instance_integrations_table.rb b/db/migrate/20240829163210_create_instance_integrations_table.rb
new file mode 100644
index 00000000000..85128e8dc86
--- /dev/null
+++ b/db/migrate/20240829163210_create_instance_integrations_table.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class CreateInstanceIntegrationsTable < Gitlab::Database::Migration[2.2]
+ milestone '17.4'
+
+ # rubocop:disable Migration/EnsureFactoryForTable -- False Positive
+ def up
+ create_table :instance_integrations, id: :bigserial do |t|
+ t.timestamps_with_timezone null: false
+ t.integer :comment_detail
+ t.boolean :active, default: false, null: false
+ t.boolean :push_events, default: true
+ t.boolean :issues_events, default: true
+ t.boolean :merge_requests_events, default: true
+ t.boolean :tag_push_events, default: true
+ t.boolean :note_events, default: true, null: false
+ t.boolean :wiki_page_events, default: true
+ t.boolean :pipeline_events, default: false, null: false
+ t.boolean :confidential_issues_events, default: true, null: false
+ t.boolean :commit_events, default: true, null: false
+ t.boolean :job_events, default: false, null: false
+ t.boolean :confidential_note_events, default: true
+ t.boolean :deployment_events, default: false, null: false
+ t.boolean :comment_on_event_enabled, default: true, null: false
+ t.boolean :alert_events
+ t.boolean :vulnerability_events, default: false, null: false
+ t.boolean :archive_trace_events, default: false, null: false
+ t.boolean :incident_events, default: false, null: false
+ t.boolean :group_mention_events, default: false, null: false
+ t.boolean :group_confidential_mention_events, default: false, null: false
+ t.text :category, default: 'common', limit: 255
+ t.text :type, limit: 255
+ t.binary :encrypted_properties
+ t.binary :encrypted_properties_iv
+ end
+ end
+ # rubocop:enable Migration/EnsureFactoryForTable -- False Positive
+
+ def down
+ drop_table :instance_integrations, if_exists: true
+ end
+end
diff --git a/db/schema_migrations/20240828103148 b/db/schema_migrations/20240828103148
new file mode 100644
index 00000000000..d5a5dcbeb52
--- /dev/null
+++ b/db/schema_migrations/20240828103148
@@ -0,0 +1 @@
+3a6da002969d32ed71e3a8700a007380d20193df5f3a3591e98136a135380f4f
\ No newline at end of file
diff --git a/db/schema_migrations/20240829163210 b/db/schema_migrations/20240829163210
new file mode 100644
index 00000000000..ebc3b67fc32
--- /dev/null
+++ b/db/schema_migrations/20240829163210
@@ -0,0 +1 @@
+7f15b5117aa826e8e0dbbd334393a29c4da7bc6443308f1ceffbaa015ab999cd
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 91b21383f49..bc19fdb661f 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -12059,6 +12059,48 @@ CREATE SEQUENCE instance_audit_events_streaming_headers_id_seq
ALTER SEQUENCE instance_audit_events_streaming_headers_id_seq OWNED BY instance_audit_events_streaming_headers.id;
+CREATE TABLE instance_integrations (
+ id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ comment_detail integer,
+ active boolean DEFAULT false NOT NULL,
+ push_events boolean DEFAULT true,
+ issues_events boolean DEFAULT true,
+ merge_requests_events boolean DEFAULT true,
+ tag_push_events boolean DEFAULT true,
+ note_events boolean DEFAULT true NOT NULL,
+ wiki_page_events boolean DEFAULT true,
+ pipeline_events boolean DEFAULT false NOT NULL,
+ confidential_issues_events boolean DEFAULT true NOT NULL,
+ commit_events boolean DEFAULT true NOT NULL,
+ job_events boolean DEFAULT false NOT NULL,
+ confidential_note_events boolean DEFAULT true,
+ deployment_events boolean DEFAULT false NOT NULL,
+ comment_on_event_enabled boolean DEFAULT true NOT NULL,
+ alert_events boolean,
+ vulnerability_events boolean DEFAULT false NOT NULL,
+ archive_trace_events boolean DEFAULT false NOT NULL,
+ incident_events boolean DEFAULT false NOT NULL,
+ group_mention_events boolean DEFAULT false NOT NULL,
+ group_confidential_mention_events boolean DEFAULT false NOT NULL,
+ category text DEFAULT 'common'::text,
+ type text,
+ encrypted_properties bytea,
+ encrypted_properties_iv bytea,
+ CONSTRAINT check_611836812c CHECK ((char_length(category) <= 255)),
+ CONSTRAINT check_69b7b09aa8 CHECK ((char_length(type) <= 255))
+);
+
+CREATE SEQUENCE instance_integrations_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE instance_integrations_id_seq OWNED BY instance_integrations.id;
+
CREATE TABLE integrations (
id bigint NOT NULL,
project_id bigint,
@@ -16719,6 +16761,7 @@ CREATE TABLE project_settings (
duo_features_enabled boolean DEFAULT true NOT NULL,
require_reauthentication_to_approve boolean,
observability_alerts_enabled boolean DEFAULT true NOT NULL,
+ spp_repository_pipeline_access boolean,
CONSTRAINT check_1a30456322 CHECK ((char_length(pages_unique_domain) <= 63)),
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
CONSTRAINT check_3ca5cbffe6 CHECK ((char_length(issue_branch_template) <= 255)),
@@ -21709,6 +21752,8 @@ ALTER TABLE ONLY insights ALTER COLUMN id SET DEFAULT nextval('insights_id_seq':
ALTER TABLE ONLY instance_audit_events_streaming_headers ALTER COLUMN id SET DEFAULT nextval('instance_audit_events_streaming_headers_id_seq'::regclass);
+ALTER TABLE ONLY instance_integrations ALTER COLUMN id SET DEFAULT nextval('instance_integrations_id_seq'::regclass);
+
ALTER TABLE ONLY integrations ALTER COLUMN id SET DEFAULT nextval('integrations_id_seq'::regclass);
ALTER TABLE ONLY internal_ids ALTER COLUMN id SET DEFAULT nextval('internal_ids_id_seq'::regclass);
@@ -23973,6 +24018,9 @@ ALTER TABLE ONLY instance_audit_events
ALTER TABLE ONLY instance_audit_events_streaming_headers
ADD CONSTRAINT instance_audit_events_streaming_headers_pkey PRIMARY KEY (id);
+ALTER TABLE ONLY instance_integrations
+ ADD CONSTRAINT instance_integrations_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY integrations
ADD CONSTRAINT integrations_pkey PRIMARY KEY (id);
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 5b0ff90be95..f921c83cb21 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -10880,6 +10880,52 @@ Some of the types in the schema exist solely to model connections. Each connecti
has a distinct, named type, with a distinct named edge type. These are listed separately
below.
+#### `AbuseReportDiscussionConnection`
+
+The connection type for [`AbuseReportDiscussion`](#abusereportdiscussion).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `edges` | [`[AbuseReportDiscussionEdge]`](#abusereportdiscussionedge) | A list of edges. |
+| `nodes` | [`[AbuseReportDiscussion]`](#abusereportdiscussion) | A list of nodes. |
+| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `AbuseReportDiscussionEdge`
+
+The edge type for [`AbuseReportDiscussion`](#abusereportdiscussion).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| `node` | [`AbuseReportDiscussion`](#abusereportdiscussion) | The item at the end of the edge. |
+
+#### `AbuseReportNoteConnection`
+
+The connection type for [`AbuseReportNote`](#abusereportnote).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `edges` | [`[AbuseReportNoteEdge]`](#abusereportnoteedge) | A list of edges. |
+| `nodes` | [`[AbuseReportNote]`](#abusereportnote) | A list of nodes. |
+| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
+
+#### `AbuseReportNoteEdge`
+
+The edge type for [`AbuseReportNote`](#abusereportnote).
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `cursor` | [`String!`](#string) | A cursor for use in pagination. |
+| `node` | [`AbuseReportNote`](#abusereportnote) | The item at the end of the edge. |
+
#### `AccessLevelDeployKeyConnection`
The connection type for [`AccessLevelDeployKey`](#accessleveldeploykey).
@@ -16813,28 +16859,49 @@ An abuse report.
| Name | Type | Description |
| ---- | ---- | ----------- |
-| `commenters` | [`UserCoreConnection!`](#usercoreconnection) | All commenters on this noteable. (see [Connections](#connections)) |
-| `discussions` | [`DiscussionConnection!`](#discussionconnection) | All discussions on this noteable. (see [Connections](#connections)) |
+| `discussions` | [`AbuseReportDiscussionConnection!`](#abusereportdiscussionconnection) | All discussions on the noteable. (see [Connections](#connections)) |
| `id` | [`AbuseReportID!`](#abusereportid) | Global ID of the abuse report. |
| `labels` | [`LabelConnection`](#labelconnection) | Labels of the abuse report. (see [Connections](#connections)) |
+| `notes` | [`AbuseReportNoteConnection!`](#abusereportnoteconnection) | All notes on the noteable. (see [Connections](#connections)) |
-#### Fields with arguments
+### `AbuseReportDiscussion`
-##### `AbuseReport.notes`
-
-All notes on this noteable.
-
-Returns [`NoteConnection!`](#noteconnection).
-
-This field returns a [connection](#connections). It accepts the
-four standard [pagination arguments](#pagination-arguments):
-`before: String`, `after: String`, `first: Int`, and `last: Int`.
-
-###### Arguments
+#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
-| `filter` | [`NotesFilterType`](#notesfiltertype) | Type of notes collection: ALL_NOTES, ONLY_COMMENTS, ONLY_ACTIVITY. |
+| `abuseReport` | [`AbuseReport`](#abusereport) | Abuse report which the discussion belongs to. |
+| `createdAt` | [`Time!`](#time) | Timestamp of the discussion's creation. |
+| `id` | [`DiscussionID!`](#discussionid) | ID of the discussion. |
+| `notes` | [`AbuseReportNoteConnection!`](#abusereportnoteconnection) | All notes in the discussion. (see [Connections](#connections)) |
+| `replyId` | [`DiscussionID!`](#discussionid) | ID used to reply to the discussion. |
+| `resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
+| `resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
+| `resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
+| `resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. |
+
+### `AbuseReportNote`
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `author` | [`UserCore`](#usercore) | User who wrote the note. |
+| `awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of emoji reactions associated with the note. (see [Connections](#connections)) |
+| `body` | [`String!`](#string) | Content of the note. |
+| `bodyFirstLineHtml` | [`String!`](#string) | First line of the note content. |
+| `bodyHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of the content of the note. |
+| `createdAt` | [`Time!`](#time) | Timestamp of the note creation. |
+| `discussion` | [`AbuseReportDiscussion`](#abusereportdiscussion) | Discussion the note is a part of. |
+| `id` | [`AntiAbuseReportsNoteID!`](#antiabusereportsnoteid) | ID of the note. |
+| `lastEditedAt` | [`Time`](#time) | Timestamp when note was last edited. |
+| `lastEditedBy` | [`UserCore`](#usercore) | User who last edited the note. |
+| `resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
+| `resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
+| `resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
+| `resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. |
+| `updatedAt` | [`Time!`](#time) | Timestamp of the note's last activity. |
+| `url` | [`String`](#string) | URL to view the note in the Web UI. |
### `AccessLevel`
@@ -17355,6 +17422,19 @@ Information about a connected Agent.
| `estimatedNextUpdateAt` | [`Time`](#time) | Estimated time when the next incremental update will happen. |
| `lastUpdateAt` | [`Time`](#time) | Last incremental update time. |
+### `AiAdditionalContext`
+
+Additional context for AI message.
+
+#### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `category` | [`AiAdditionalContextCategory!`](#aiadditionalcontextcategory) | Category of the additional context. |
+| `content` | [`String!`](#string) | Content of the additional context. |
+| `id` | [`ID!`](#id) | ID of the additional context. |
+| `metadata` | [`JSON`](#json) | Metadata of the additional context. |
+
### `AiAgent`
An AI agent.
@@ -17403,6 +17483,7 @@ AI features communication message.
| Name | Type | Description |
| ---- | ---- | ----------- |
+| `additionalContext` | [`[AiAdditionalContext!]`](#aiadditionalcontext) | Additional context for the message. |
| `agentVersionId` | [`AiAgentVersionID`](#aiagentversionid) | Global ID of the agent version to answer the message. |
| `chunkId` | [`Int`](#int) | Incremental ID for a chunk from a streamed message. Null when it is not a streamed message. |
| `content` | [`String`](#string) | Raw response content. |
@@ -21570,10 +21651,10 @@ Aggregated summary of changes.
| Name | Type | Description |
| ---- | ---- | ----------- |
| `createdAt` | [`Time!`](#time) | Timestamp of the discussion's creation. |
-| `id` | [`DiscussionID!`](#discussionid) | ID of this discussion. |
+| `id` | [`DiscussionID!`](#discussionid) | ID of the discussion. |
| `noteable` | [`NoteableType`](#noteabletype) | Object which the discussion belongs to. |
| `notes` | [`NoteConnection!`](#noteconnection) | All notes in the discussion. (see [Connections](#connections)) |
-| `replyId` | [`DiscussionID!`](#discussionid) | ID used to reply to this discussion. |
+| `replyId` | [`DiscussionID!`](#discussionid) | ID used to reply to the discussion. |
| `resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
| `resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
| `resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
@@ -35427,8 +35508,11 @@ LLMs supported by the self-hosted model features.
| `CODESTRAL` | Codestral 22B: Suitable for code completion and code generation. |
| `DEEPSEEKCODER` | Deepseek Coder 1.3b, 6.7b and 33b base or instruct. |
| `MISTRAL` | Mistral 7B: Suitable for code generation and duo chat. |
+| `MISTRAL_TEXT` | Mistral-7B Text: Suitable for code completion. |
| `MIXTRAL` | Mixtral 8x7B: Suitable for code generation and duo chat. |
| `MIXTRAL_8X22B` | Mixtral 8x22B: Suitable for code generation and duo chat. |
+| `MIXTRAL_8X22B_TEXT` | Mixtral-8x22B Text: Suitable for code completion. |
+| `MIXTRAL_TEXT` | Mixtral-8x7B Text: Suitable for code completion. |
### `AiAction`
@@ -38817,6 +38901,12 @@ A `AnalyticsDevopsAdoptionEnabledNamespaceID` is a global ID. It is encoded as a
An example `AnalyticsDevopsAdoptionEnabledNamespaceID` is: `"gid://gitlab/Analytics::DevopsAdoption::EnabledNamespace/1"`.
+### `AntiAbuseReportsNoteID`
+
+A `AntiAbuseReportsNoteID` is a global ID. It is encoded as a string.
+
+An example `AntiAbuseReportsNoteID` is: `"gid://gitlab/AntiAbuse::Reports::Note/1"`.
+
### `AppSecFuzzingCoverageCorpusID`
A `AppSecFuzzingCoverageCorpusID` is a global ID. It is encoded as a string.
@@ -40001,6 +40091,25 @@ Implementations:
| `id` | [`ID!`](#id) | ID of the destination. |
| `name` | [`String!`](#string) | Name of the external destination to send audit events to. |
+#### `BaseDiscussionInterface`
+
+Implementations:
+
+- [`AbuseReportDiscussion`](#abusereportdiscussion)
+- [`Discussion`](#discussion)
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `createdAt` | [`Time!`](#time) | Timestamp of the discussion's creation. |
+| `id` | [`DiscussionID!`](#discussionid) | ID of the discussion. |
+| `replyId` | [`DiscussionID!`](#discussionid) | ID used to reply to the discussion. |
+| `resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
+| `resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
+| `resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
+| `resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. |
+
#### `BaseHeaderInterface`
Implementations:
@@ -40017,6 +40126,32 @@ Implementations:
| `key` | [`String!`](#string) | Key of the header. |
| `value` | [`String!`](#string) | Value of the header. |
+#### `BaseNoteInterface`
+
+Implementations:
+
+- [`AbuseReportNote`](#abusereportnote)
+- [`Note`](#note)
+
+##### Fields
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| `author` | [`UserCore`](#usercore) | User who wrote the note. |
+| `awardEmoji` | [`AwardEmojiConnection`](#awardemojiconnection) | List of emoji reactions associated with the note. (see [Connections](#connections)) |
+| `body` | [`String!`](#string) | Content of the note. |
+| `bodyFirstLineHtml` | [`String!`](#string) | First line of the note content. |
+| `bodyHtml` | [`String`](#string) | GitLab Flavored Markdown rendering of the content of the note. |
+| `createdAt` | [`Time!`](#time) | Timestamp of the note creation. |
+| `lastEditedAt` | [`Time`](#time) | Timestamp when note was last edited. |
+| `lastEditedBy` | [`UserCore`](#usercore) | User who last edited the note. |
+| `resolvable` | [`Boolean!`](#boolean) | Indicates if the object can be resolved. |
+| `resolved` | [`Boolean!`](#boolean) | Indicates if the object is resolved. |
+| `resolvedAt` | [`Time`](#time) | Timestamp of when the object was resolved. |
+| `resolvedBy` | [`UserCore`](#usercore) | User who resolved the object. |
+| `updatedAt` | [`Time!`](#time) | Timestamp of the note's last activity. |
+| `url` | [`String`](#string) | URL to view the note in the Web UI. |
+
#### `CiVariable`
Implementations:
@@ -40211,7 +40346,6 @@ Returns [`UserMergeRequestInteraction`](#usermergerequestinteraction).
Implementations:
-- [`AbuseReport`](#abusereport)
- [`AlertManagementAlert`](#alertmanagementalert)
- [`BoardEpic`](#boardepic)
- [`Design`](#design)
@@ -40288,6 +40422,8 @@ Implementations:
Implementations:
+- [`AbuseReportDiscussion`](#abusereportdiscussion)
+- [`AbuseReportNote`](#abusereportnote)
- [`Discussion`](#discussion)
- [`Note`](#note)
diff --git a/doc/ci/pipelines/pipeline_types.md b/doc/ci/pipelines/pipeline_types.md
index 566a4528509..c45724950b6 100644
--- a/doc/ci/pipelines/pipeline_types.md
+++ b/doc/ci/pipelines/pipeline_types.md
@@ -10,9 +10,10 @@ DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** GitLab.com, Self-managed, GitLab Dedicated
-Four types of pipelines exist:
+Multiple types of pipelines can run in a project, including:
- Branch pipelines
+- Tag pipelines
- Merge request pipelines
- Merge result pipelines
- Merge trains
@@ -32,7 +33,24 @@ Branch pipelines:
- Run when you push a new commit to a branch.
- Have access to [some predefined variables](../variables/predefined_variables.md).
- Have access to [protected variables](../variables/index.md#protect-a-cicd-variable)
- and [protected runners](../runners/configure_runners.md#prevent-runners-from-revealing-sensitive-information).
+ and [protected runners](../runners/configure_runners.md#prevent-runners-from-revealing-sensitive-information)
+ when the branch is a [protected branch](../../user/project/protected_branches.md).
+
+## Tag pipeline
+
+A pipeline can run every time you create or push a new [tag](../../user/project/repository/tags/index.md).
+
+This type of pipeline is called a *tag pipeline*.
+
+This pipeline runs by default. No configuration is required.
+
+Tag pipelines:
+
+- Run when you create/push a new tag to your repository.
+- Have access to [some predefined variables](../variables/predefined_variables.md).
+- Have access to [protected variables](../variables/index.md#protect-a-cicd-variable)
+ and [protected runners](../runners/configure_runners.md#prevent-runners-from-revealing-sensitive-information)
+ when the tag is a [protected tag](../../user/project/protected_tags.md).
## Merge request pipeline
diff --git a/doc/ci/secrets/akeyless.md b/doc/ci/secrets/akeyless.md
index 922c95cb568..a3a541d5235 100644
--- a/doc/ci/secrets/akeyless.md
+++ b/doc/ci/secrets/akeyless.md
@@ -22,6 +22,7 @@ Prerequisites:
- Save your Akeyless access ID as a [CI/CD variable in your GitLab project](../variables/index.md#for-a-project)
named `AKEYLESS_ACCESS_ID`.
+- This integration only supports [static secrets](https://docs.akeyless.io/docs/static-secrets).
To retrieve secrets from Akeyless, review the CI/CD configuration example that matches
your use case. The `akeyless:name` keyword can contain any secrets type.
diff --git a/doc/update/versions/gitlab_17_changes.md b/doc/update/versions/gitlab_17_changes.md
index fc9fcbf9c73..9f87f4e1e32 100644
--- a/doc/update/versions/gitlab_17_changes.md
+++ b/doc/update/versions/gitlab_17_changes.md
@@ -117,17 +117,6 @@ For more information, see the:
- [Deprecations and removals documentation](../../update/deprecations.md#non-expiring-access-tokens).
- [Deprecation issue](https://gitlab.com/gitlab-org/gitlab/-/issues/369122).
-## 17.4.0
-
-- Starting with GitLab 17.4, new GitLab installations have a different database schema regarding ID columns.
- - All previous integer (32 bits) ID columns (for example columns like `id`, `%_id`, `%_ids`) are now created as `bigint` (64 bits).
- - Existing installations will migrate from 32 bit to 64 bit integers in later releases when database migrations ship to perform this change.
- - If you are building a new GitLab environment to test upgrades, install GitLab 17.3 or earlier to get
- the same integer types as your existing environments. You can then upgrade to later releases to run the same
- database migrations as your existing environments. This isn't necessary if you're restoring from backup into the
- new environment as the database restore removes the existing database schema definition and uses the definition
- that's stored as part of the backup.
-
## Issues to be aware of when upgrading from 17.1 and earlier
- If the customer is using GitLab Duo and upgrading to GitLab 17.2.3 or earlier, they must do both of the following:
@@ -211,6 +200,17 @@ bits with a `certificate key too weak` error message.
Check the [GitLab documentation on securing your installation](../../security/index.md).
for more details.
+## 17.4.0
+
+- Starting with GitLab 17.4, new GitLab installations have a different database schema regarding ID columns.
+ - All previous integer (32 bits) ID columns (for example columns like `id`, `%_id`, `%_ids`) are now created as `bigint` (64 bits).
+ - Existing installations will migrate from 32 bit to 64 bit integers in later releases when database migrations ship to perform this change.
+ - If you are building a new GitLab environment to test upgrades, install GitLab 17.3 or earlier to get
+ the same integer types as your existing environments. You can then upgrade to later releases to run the same
+ database migrations as your existing environments. This isn't necessary if you're restoring from backup into the
+ new environment as the database restore removes the existing database schema definition and uses the definition
+ that's stored as part of the backup.
+
## 17.3.0
- Git 2.45.0 and later is required by Gitaly. For installations from source, you should use the [Git version provided by Gitaly](../../install/installation.md#git).
diff --git a/doc/user/compliance/audit_event_types.md b/doc/user/compliance/audit_event_types.md
index 2975ebb088a..7bb4011e38c 100644
--- a/doc/user/compliance/audit_event_types.md
+++ b/doc/user/compliance/audit_event_types.md
@@ -453,6 +453,7 @@ Audit event types belong to the following product categories.
| Name | Description | Saved to database | Streamed | Introduced in | Scope |
|:------------|:------------|:------------------|:---------|:--------------|:--------------|
+| [`self_hosted_model_destroyed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165321) | A new self-hosted model configuration was destroyed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/477999) | Instance, User |
| [`self_hosted_model_feature_changed`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165489) | A self-hosted model feature had its configuration changed | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/463215) | Project |
| [`self_hosted_model_terms_accepted`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/165480) | Terms for usage of self-hosted models were accepted | **{check-circle}** Yes | **{check-circle}** Yes | GitLab [17.4](https://gitlab.com/gitlab-org/gitlab/-/issues/477999) | Instance, User |
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index f78e125c1c3..1a820f281f1 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -264,7 +264,7 @@ After you set up your identity provider to work with GitLab, you must configure
- In GitLab 17.4 and later, **Disable password authentication for enterprise users**.
For more information, see the [Disable password authentication for enterprise users documentation](#disable-password-authentication-for-enterprise-users).
- **Enforce SSO-only authentication for web activity for this group**.
- - **Enforce SSO-only authentication for Git activity for this group**.
+ - **Enforce SSO-only authentication for Git and Dependency Proxy activity for this group**.
For more information, see the [SSO enforcement documentation](#sso-enforcement).
1. Select **Save changes**.
diff --git a/haml_lint/linter/documentation_links.rb b/haml_lint/linter/documentation_links.rb
index 9fff207a81c..5c362c47abf 100644
--- a/haml_lint/linter/documentation_links.rb
+++ b/haml_lint/linter/documentation_links.rb
@@ -60,7 +60,7 @@ module HamlLint
record_lint(node, "anchor (#{match[:anchor]}) is missing in: #{path_to_file}")
end
- record_lint(node, "remove .md extension from the link: #{link}") if link.end_with?('.md')
+ record_lint(node, "add .md extension to the link: #{link}") unless link.end_with?('.md')
end
def extract_link_and_anchor(ast_tree)
diff --git a/lib/banzai/filter/base_sanitization_filter.rb b/lib/banzai/filter/base_sanitization_filter.rb
index 2174caf239b..51dba8d1381 100644
--- a/lib/banzai/filter/base_sanitization_filter.rb
+++ b/lib/banzai/filter/base_sanitization_filter.rb
@@ -15,8 +15,6 @@ module Banzai
include Gitlab::Utils::StrongMemoize
extend Gitlab::Utils::SanitizeNodeLink
- RENDER_TIMEOUT = 5.seconds
-
UNSAFE_PROTOCOLS = %w[data javascript vbscript].freeze
def call
@@ -57,9 +55,8 @@ module Banzai
allowlist[:attributes]['img'].push('data-diagram-src')
# Allow any protocol in `a` elements
- # and then remove links with unsafe protocols
+ # and then remove links with unsafe protocols in SanitizeLinkFilter
allowlist[:protocols].delete('a')
- allowlist[:transformers].push(self.class.method(:sanitize_unsafe_links))
# Remove `rel` attribute from `a` elements
allowlist[:transformers].push(self.class.remove_rel)
diff --git a/lib/banzai/filter/external_link_filter.rb b/lib/banzai/filter/external_link_filter.rb
index 8c141dfec51..8561066ee55 100644
--- a/lib/banzai/filter/external_link_filter.rb
+++ b/lib/banzai/filter/external_link_filter.rb
@@ -2,10 +2,10 @@
module Banzai
module Filter
- # HTML Filter to modify the attributes of external links
+ # HTML Filter to modify the attributes of external links.
+ # This is considered a sanitization filter.
class ExternalLinkFilter < HTML::Pipeline::Filter
prepend Concerns::TimeoutFilterHandler
- prepend Concerns::PipelineTimingCheck
SCHEMES = ['http', 'https', nil].freeze
RTLO = "\u202E"
@@ -46,7 +46,7 @@ module Banzai
# partial un-sanitized results.
# It's ok to allow any following filters to run since this is safe HTML.
def returned_timeout_value
- HTML::Pipeline.parse(COMPLEX_MARKDOWN_MESSAGE)
+ HTML::Pipeline.parse(Banzai::Filter::SanitizeLinkFilter::TIMEOUT_MARKDOWN_MESSAGE)
end
# if this is a link to a proxied image, then `src` is already the correct
diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb
index 01f8210b485..6aad2613c86 100644
--- a/lib/banzai/filter/gollum_tags_filter.rb
+++ b/lib/banzai/filter/gollum_tags_filter.rb
@@ -77,6 +77,7 @@ module Banzai
def sanitized_content_tag(name, content, options = {})
html = content_tag(name, content, options)
node = Banzai::Filter::SanitizationFilter.new(html).call
+ node = Banzai::Filter::SanitizeLinkFilter.new(node).call
node&.children&.first
end
diff --git a/lib/banzai/filter/references/abstract_reference_filter.rb b/lib/banzai/filter/references/abstract_reference_filter.rb
index 1489f4c98d9..c8e2932bfd7 100644
--- a/lib/banzai/filter/references/abstract_reference_filter.rb
+++ b/lib/banzai/filter/references/abstract_reference_filter.rb
@@ -10,8 +10,6 @@ module Banzai
prepend Concerns::TimeoutFilterHandler
prepend Concerns::PipelineTimingCheck
- RENDER_TIMEOUT = 2.seconds
-
def initialize(doc, context = nil, result = nil)
super
diff --git a/lib/banzai/filter/sanitize_link_filter.rb b/lib/banzai/filter/sanitize_link_filter.rb
new file mode 100644
index 00000000000..3c4682987b3
--- /dev/null
+++ b/lib/banzai/filter/sanitize_link_filter.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # Validate links and remove unsafe protocols.
+ # This can be intensive, so it was split from BaseSanitizationFilter in order
+ # for it to have its own time period.
+ class SanitizeLinkFilter < HTML::Pipeline::Filter
+ prepend Concerns::TimeoutFilterHandler
+ include Gitlab::Utils::SanitizeNodeLink
+
+ # [href], [src], [data-src], [data-canonical-src]
+ CSS = Gitlab::Utils::SanitizeNodeLink::ATTRS_TO_SANITIZE.map { |x| "[#{x}]" }.join(', ')
+ XPATH = Gitlab::Utils::Nokogiri.css_to_xpath(CSS).freeze
+
+ TIMEOUT_MARKDOWN_MESSAGE =
+ <<~HTML
+ Timeout while sanitizing links - rendering aborted. Please reduce the number of links if possible.
+ HTML
+
+ def call
+ doc.xpath(self.class::XPATH).each do |el|
+ sanitize_unsafe_links({ node: el })
+ end
+
+ doc
+ end
+
+ private
+
+ def render_timeout
+ SANITIZATION_RENDER_TIMEOUT
+ end
+
+ # If sanitization times out, we can not return partial un-sanitized results.
+ # It's ok to allow any following filters to run since this is safe HTML.
+ def returned_timeout_value
+ HTML::Pipeline.parse(TIMEOUT_MARKDOWN_MESSAGE)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/spaced_link_filter.rb b/lib/banzai/filter/spaced_link_filter.rb
index 479565a9f53..d8b4f447583 100644
--- a/lib/banzai/filter/spaced_link_filter.rb
+++ b/lib/banzai/filter/spaced_link_filter.rb
@@ -17,8 +17,8 @@ module Banzai
# This is a small extension to the CommonMark spec. If they start allowing
# spaces in urls, we could then remove this filter.
#
- # Note: Filter::SanitizationFilter should always be run sometime after this filter
- # to prevent XSS attacks
+ # Note: Filter::SanitizationFilter/Filter::SanitizeLinkFilter should always be run sometime
+ # after this filter to prevent XSS attacks
#
class SpacedLinkFilter < HTML::Pipeline::Filter
prepend Concerns::PipelineTimingCheck
diff --git a/lib/banzai/pipeline/ascii_doc_pipeline.rb b/lib/banzai/pipeline/ascii_doc_pipeline.rb
index 0829d8dd556..97ceaad9e7d 100644
--- a/lib/banzai/pipeline/ascii_doc_pipeline.rb
+++ b/lib/banzai/pipeline/ascii_doc_pipeline.rb
@@ -6,6 +6,7 @@ module Banzai
def self.filters
FilterArray[
Filter::AsciiDocSanitizationFilter,
+ Filter::SanitizeLinkFilter,
Filter::CodeLanguageFilter,
Filter::GollumTagsFilter,
Filter::WikiLinkGollumFilter,
diff --git a/lib/banzai/pipeline/broadcast_message_pipeline.rb b/lib/banzai/pipeline/broadcast_message_pipeline.rb
index 91a0723a965..813d5532310 100644
--- a/lib/banzai/pipeline/broadcast_message_pipeline.rb
+++ b/lib/banzai/pipeline/broadcast_message_pipeline.rb
@@ -8,6 +8,7 @@ module Banzai
Filter::BlockquoteFenceLegacyFilter,
Filter::MarkdownFilter,
Filter::BroadcastMessageSanitizationFilter,
+ Filter::SanitizeLinkFilter,
Filter::EmojiFilter,
Filter::ColorFilter,
Filter::AutolinkFilter,
diff --git a/lib/banzai/pipeline/emoji_pipeline.rb b/lib/banzai/pipeline/emoji_pipeline.rb
index a1b522f4303..a155b336271 100644
--- a/lib/banzai/pipeline/emoji_pipeline.rb
+++ b/lib/banzai/pipeline/emoji_pipeline.rb
@@ -9,6 +9,7 @@ module Banzai
@filters ||= FilterArray[
Filter::HtmlEntityFilter,
Filter::SanitizationFilter,
+ Filter::SanitizeLinkFilter,
Filter::EmojiFilter
]
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 5d51037aae4..d1f46c51bad 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -13,9 +13,10 @@ module Banzai
@filters ||= FilterArray[
Filter::CodeLanguageFilter,
Filter::PlantumlFilter,
- # Must always be before the SanitizationFilter to prevent XSS attacks
+ # Must always be before the SanitizationFilter/SanitizeLinkFilter to prevent XSS attacks
Filter::SpacedLinkFilter,
Filter::SanitizationFilter,
+ Filter::SanitizeLinkFilter,
Filter::EscapedCharFilter,
Filter::KrokiFilter,
Filter::GollumTagsFilter,
diff --git a/lib/banzai/pipeline/incident_management/timeline_event_pipeline.rb b/lib/banzai/pipeline/incident_management/timeline_event_pipeline.rb
index b00b4dd85b5..c347ef3da15 100644
--- a/lib/banzai/pipeline/incident_management/timeline_event_pipeline.rb
+++ b/lib/banzai/pipeline/incident_management/timeline_event_pipeline.rb
@@ -12,6 +12,7 @@ module Banzai
@filters ||= FilterArray[
*super,
Filter::SanitizationFilter,
+ Filter::SanitizeLinkFilter,
*Banzai::Pipeline::GfmPipeline.reference_filters,
Filter::EmojiFilter,
Filter::ExternalLinkFilter,
diff --git a/lib/banzai/pipeline/label_pipeline.rb b/lib/banzai/pipeline/label_pipeline.rb
index ccfda2052e6..7999ca3454e 100644
--- a/lib/banzai/pipeline/label_pipeline.rb
+++ b/lib/banzai/pipeline/label_pipeline.rb
@@ -6,6 +6,7 @@ module Banzai
def self.filters
@filters ||= FilterArray[
Filter::SanitizationFilter,
+ Filter::SanitizeLinkFilter,
Filter::References::LabelReferenceFilter
]
end
diff --git a/lib/banzai/pipeline/markup_pipeline.rb b/lib/banzai/pipeline/markup_pipeline.rb
index cb421ff77b6..985000f24dd 100644
--- a/lib/banzai/pipeline/markup_pipeline.rb
+++ b/lib/banzai/pipeline/markup_pipeline.rb
@@ -6,6 +6,7 @@ module Banzai
def self.filters
@filters ||= FilterArray[
Filter::SanitizationFilter,
+ Filter::SanitizeLinkFilter,
Filter::CodeLanguageFilter,
Filter::AssetProxyFilter,
Filter::ExternalLinkFilter,
diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb
index 0de9157a2b0..e42596861c7 100644
--- a/lib/banzai/pipeline/single_line_pipeline.rb
+++ b/lib/banzai/pipeline/single_line_pipeline.rb
@@ -7,6 +7,7 @@ module Banzai
@filters ||= FilterArray[
Filter::HtmlEntityFilter,
Filter::SanitizationFilter,
+ Filter::SanitizeLinkFilter,
Filter::AssetProxyFilter,
Filter::EmojiFilter,
Filter::CustomEmojiFilter,
diff --git a/lib/gitlab/ci/config/external/file/project.rb b/lib/gitlab/ci/config/external/file/project.rb
index de726b57053..d80323589dc 100644
--- a/lib/gitlab/ci/config/external/file/project.rb
+++ b/lib/gitlab/ci/config/external/file/project.rb
@@ -90,12 +90,16 @@ module Gitlab
.batch(key: context.user) do |projects, loader, args|
projects.uniq.each do |project|
context.logger.instrument(:config_file_project_validate_access) do
- loader.call(project, Ability.allowed?(args[:key], :download_code, project))
+ loader.call(project, project_access_allowed?(args[:key], project))
end
end
end
end
+ def project_access_allowed?(user, project)
+ Ability.allowed?(user, :download_code, project)
+ end
+
def sha
return if project.nil?
@@ -179,3 +183,5 @@ module Gitlab
end
end
end
+
+Gitlab::Ci::Config::External::File::Project.prepend_mod
diff --git a/lib/gitlab/ci/project_config.rb b/lib/gitlab/ci/project_config.rb
index f92b84abfa9..bb185cf0a7d 100644
--- a/lib/gitlab/ci/project_config.rb
+++ b/lib/gitlab/ci/project_config.rb
@@ -49,7 +49,7 @@ module Gitlab
end
end
- delegate :content, :source, :url, to: :@config, allow_nil: true
+ delegate :content, :source, :url, :pipeline_policy_context, to: :@config, allow_nil: true
delegate :internal_include_prepended?, to: :@config
def exists?
diff --git a/lib/gitlab/ci/project_config/source.rb b/lib/gitlab/ci/project_config/source.rb
index 755640ad965..7288ee7fcd3 100644
--- a/lib/gitlab/ci/project_config/source.rb
+++ b/lib/gitlab/ci/project_config/source.rb
@@ -42,10 +42,12 @@ module Gitlab
nil
end
+ attr_reader :pipeline_policy_context
+
private
attr_reader :project, :sha, :custom_content, :pipeline_source, :pipeline_source_bridge, :triggered_for_branch,
- :ref, :pipeline_policy_context
+ :ref
def ci_config_path
@ci_config_path ||= project.ci_config_path_or_default
diff --git a/lib/gitlab/experiment/rollout/feature.rb b/lib/gitlab/experiment/rollout/feature.rb
deleted file mode 100644
index 0f2a1b9fb1d..00000000000
--- a/lib/gitlab/experiment/rollout/feature.rb
+++ /dev/null
@@ -1,82 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- class Experiment
- module Rollout
- class Feature < Percent
- # For this rollout strategy to consider an experiment as enabled, we
- # must:
- #
- # - have a feature flag yaml file that declares it.
- # - be in an environment that permits it.
- # - not have rolled out the feature flag at all (no percent of actors,
- # no inclusions, etc.)
- def enabled?
- return false unless feature_flag_defined?
- return false unless available?
- return false unless ::Feature.enabled?(:gitlab_experiment, type: :ops)
-
- feature_flag_instance.state != :off
- end
-
- # For assignment we first check to see if our feature flag is enabled
- # for "self". This is done by calling `#flipper_id` (used behind the
- # scenes by `Feature`). By default this is our `experiment.id` (or more
- # specifically, the context key, which is an anonymous SHA generated
- # using the details of an experiment.
- #
- # If the `Feature.enabled?` check is false, we return nil implicitly,
- # which will assign the control. Otherwise we call super, which will
- # assign a variant based on our provided distribution rules.
- # Otherwise we will assign a variant evenly across the behaviours without control.
- def execute_assignment
- super if ::Feature.enabled?(feature_flag_name, self, type: :experiment)
- end
-
- # This is what's provided to the `Feature.enabled?` call that will be
- # used to determine experiment inclusion. An experiment may provide an
- # override for this method to make the experiment work on user, group,
- # or projects.
- #
- # For example, when running an experiment on a project, you could make
- # the experiment assignable by project (using chatops) by implementing
- # a `flipper_id` method in the experiment:
- #
- # def flipper_id
- # context.project.flipper_id
- # end
- #
- # Or even cleaner, simply delegate it:
- #
- # delegate :flipper_id, to: -> { context.project }
- def flipper_id
- return experiment.flipper_id if experiment.respond_to?(:flipper_id)
-
- "Experiment;#{id}"
- end
-
- private
-
- def available?
- ApplicationExperiment.available?
- end
-
- def feature_flag_instance
- ::Feature.get(feature_flag_name) # rubocop:disable Gitlab/AvoidFeatureGet -- We are using at a lower layer here in experiment framework
- end
-
- def feature_flag_defined?
- ::Feature::Definition.get(feature_flag_name).present?
- end
-
- def feature_flag_name
- experiment.name.tr('/', '_')
- end
-
- def behavior_names
- super - [:control]
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/experiment_feature_rollout.rb b/lib/gitlab/experiment_feature_rollout.rb
new file mode 100644
index 00000000000..fccc91d5109
--- /dev/null
+++ b/lib/gitlab/experiment_feature_rollout.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class ExperimentFeatureRollout < Gitlab::Experiment::Rollout::Percent
+ # For this rollout strategy to consider an experiment as enabled, we
+ # must:
+ #
+ # - have a feature flag yaml file that declares it.
+ # - be in an environment that permits it.
+ # - not have rolled out the feature flag at all (no percent of actors,
+ # no inclusions, etc.)
+ def enabled?
+ return false unless feature_flag_defined?
+ return false unless available?
+ return false unless ::Feature.enabled?(:gitlab_experiment, type: :ops)
+
+ feature_flag_instance.state != :off
+ end
+
+ # For assignment we first check to see if our feature flag is enabled
+ # for "self". This is done by calling `#flipper_id` (used behind the
+ # scenes by `Feature`). By default this is our `experiment.id` (or more
+ # specifically, the context key, which is an anonymous SHA generated
+ # using the details of an experiment.
+ #
+ # If the `Feature.enabled?` check is false, we return nil implicitly,
+ # which will assign the control. Otherwise we call super, which will
+ # assign a variant based on our provided distribution rules.
+ # Otherwise we will assign a variant evenly across the behaviours without control.
+ def execute_assignment
+ super if ::Feature.enabled?(feature_flag_name, self, type: :experiment)
+ end
+
+ # This is what's provided to the `Feature.enabled?` call that will be
+ # used to determine experiment inclusion. An experiment may provide an
+ # override for this method to make the experiment work on user, group,
+ # or projects.
+ #
+ # For example, when running an experiment on a project, you could make
+ # the experiment assignable by project (using chatops) by implementing
+ # a `flipper_id` method in the experiment:
+ #
+ # def flipper_id
+ # context.project.flipper_id
+ # end
+ #
+ # Or even cleaner, simply delegate it:
+ #
+ # delegate :flipper_id, to: -> { context.project }
+ def flipper_id
+ return experiment.flipper_id if experiment.respond_to?(:flipper_id)
+
+ "Experiment;#{id}"
+ end
+
+ private
+
+ def available?
+ ApplicationExperiment.available?
+ end
+
+ def feature_flag_instance
+ ::Feature.get(feature_flag_name) # rubocop:disable Gitlab/AvoidFeatureGet -- We are using at a lower layer here in experiment framework
+ end
+
+ def feature_flag_defined?
+ ::Feature::Definition.get(feature_flag_name).present?
+ end
+
+ def feature_flag_name
+ experiment.name.tr('/', '_')
+ end
+
+ def behavior_names
+ super - [:control]
+ end
+ end
+end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index a1787656171..3f9ba7c5db4 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -38,6 +38,8 @@ module Gitlab
instance.milestone_url(object, **options)
when Note
note_url(object, **options)
+ when AntiAbuse::Reports::Note
+ abuse_report_note_url(object, **options)
when Release
instance.release_url(object, **options)
when Organizations::Organization
@@ -102,6 +104,10 @@ module Gitlab
end
end
+ def abuse_report_note_url(note, **options)
+ instance.admin_abuse_report_url(note.abuse_report, anchor: dom_id(note), **options)
+ end
+
def snippet_url(snippet, **options)
if options[:file].present?
file, ref = options.values_at(:file, :ref)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 10e4f4d6375..988f7e17ec9 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2276,9 +2276,6 @@ msgstr ""
msgid "AI|Explain with AI"
msgstr ""
-msgid "AI|Explain your rating to help us improve! (optional)"
-msgstr ""
-
msgid "AI|For example: Organizations should be able to forecast into the future by using value stream analytics charts. This feature would help them understand how their metrics are trending."
msgstr ""
@@ -13655,7 +13652,7 @@ msgstr ""
msgid "Commit message"
msgstr ""
-msgid "Commit message generated by AI"
+msgid "Commit message generated by GitLab Duo"
msgstr ""
msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
@@ -60375,9 +60372,6 @@ msgstr ""
msgid "Vulnerability|Comments"
msgstr ""
-msgid "Vulnerability|Could not load prompt."
-msgstr ""
-
msgid "Vulnerability|Crash address:"
msgstr ""
@@ -60408,9 +60402,6 @@ msgstr ""
msgid "Vulnerability|Evidence:"
msgstr ""
-msgid "Vulnerability|Explain this vulnerability"
-msgstr ""
-
msgid "Vulnerability|External Security Report"
msgstr ""
@@ -60423,12 +60414,6 @@ msgstr ""
msgid "Vulnerability|GitLab Security Report"
msgstr ""
-msgid "Vulnerability|GitLab has identified sensitive strings in the code snippet for the AI prompt, indicating a possible leaked secret. Please review your code before utilizing the Explain This Vulnerability feature. If you still wish to proceed and send the %{linkStart}code%{linkEnd} to the AI, click the checkbox below."
-msgstr ""
-
-msgid "Vulnerability|Hide prompt"
-msgstr ""
-
msgid "Vulnerability|Identifier"
msgstr ""
@@ -60462,9 +60447,6 @@ msgstr ""
msgid "Vulnerability|Project:"
msgstr ""
-msgid "Vulnerability|Providing the source code improves the response quality. If security is a concern, you can send basic vulnerability info for a generic example."
-msgstr ""
-
msgid "Vulnerability|Remove identifier row"
msgstr ""
@@ -60474,9 +60456,6 @@ msgstr ""
msgid "Vulnerability|Request/Response"
msgstr ""
-msgid "Vulnerability|Response generated by AI"
-msgstr ""
-
msgid "Vulnerability|Scanner:"
msgstr ""
@@ -60489,12 +60468,6 @@ msgstr ""
msgid "Vulnerability|Select a severity"
msgstr ""
-msgid "Vulnerability|Send code with prompt"
-msgstr ""
-
-msgid "Vulnerability|Sending code to AI"
-msgstr ""
-
msgid "Vulnerability|Sent request:"
msgstr ""
@@ -60507,9 +60480,6 @@ msgstr ""
msgid "Vulnerability|Severity:"
msgstr ""
-msgid "Vulnerability|Show prompt"
-msgstr ""
-
msgid "Vulnerability|Something went wrong while trying to get the source file."
msgstr ""
@@ -60558,21 +60528,12 @@ msgstr ""
msgid "Vulnerability|Vulnerable method:"
msgstr ""
-msgid "Vulnerability|Warning: possible secrets detected"
-msgstr ""
-
msgid "Vulnerability|What is code flow?"
msgstr ""
-msgid "Vulnerability|You can also %{message}."
-msgstr ""
-
msgid "Vulnerability|code_flow"
msgstr ""
-msgid "Vulnerability|use AI by asking GitLab Duo Chat to explain this vulnerability and suggest a solution"
-msgstr ""
-
msgid "WARNING:"
msgstr ""
diff --git a/package.json b/package.json
index 7d741b4bc93..542a9a878f9 100644
--- a/package.json
+++ b/package.json
@@ -75,7 +75,7 @@
"@gitlab/query-language": "^0.0.5-a-20240903",
"@gitlab/svgs": "3.112.0",
"@gitlab/ui": "91.1.2",
- "@gitlab/web-ide": "^0.0.1-dev-20240816130114",
+ "@gitlab/web-ide": "^0.0.1-dev-20240909013227",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
"@rails/actioncable": "7.0.8-4",
"@rails/ujs": "7.0.8-4",
diff --git a/patches/@gitlab+web-ide+0.0.1-dev-20240816130114.patch b/patches/@gitlab+web-ide+0.0.1-dev-20240909013227.patch
similarity index 99%
rename from patches/@gitlab+web-ide+0.0.1-dev-20240816130114.patch
rename to patches/@gitlab+web-ide+0.0.1-dev-20240909013227.patch
index 0b40e9ec7d4..815bfb36eeb 100644
--- a/patches/@gitlab+web-ide+0.0.1-dev-20240816130114.patch
+++ b/patches/@gitlab+web-ide+0.0.1-dev-20240909013227.patch
@@ -2950,5 +2950,5 @@ index 6a16dd1..99b1df4 100644
- const parentOrigin = searchParams.get('parentOrigin') || window.origin;
+ const parentOrigin = window.origin;
const salt = searchParams.get('salt');
-
+
(async function () {
diff --git a/spec/factories/integrations/instance_integrations.rb b/spec/factories/integrations/instance_integrations.rb
new file mode 100644
index 00000000000..d70b7ecf889
--- /dev/null
+++ b/spec/factories/integrations/instance_integrations.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :instance_integration, class: 'Integrations::InstanceIntegration' do
+ type { 'Integrations::InstanceIntegration' }
+ end
+end
diff --git a/spec/features/profiles/two_factor_auths_spec.rb b/spec/features/profiles/two_factor_auths_spec.rb
index e12b634828e..fa87c7dc25e 100644
--- a/spec/features/profiles/two_factor_auths_spec.rb
+++ b/spec/features/profiles/two_factor_auths_spec.rb
@@ -59,7 +59,7 @@ RSpec.describe 'Two factor auths', feature_category: :system_access do
fill_in 'pin_code', with: '123'
click_button 'Register with two-factor app'
- expect(page).to have_link('Try the troubleshooting steps here.', href: help_page_path('user/profile/account/two_factor_authentication_troubleshooting'))
+ expect(page).to have_link('Try the troubleshooting steps here.', href: help_page_path('user/profile/account/two_factor_authentication_troubleshooting.md'))
end
end
diff --git a/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js b/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js
index bc7aa8ef5de..d1f5c3bbb17 100644
--- a/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js
+++ b/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js
@@ -93,21 +93,8 @@ describe('Abuse Report Note', () => {
});
describe('Editing', () => {
- it('should show edit button when resolveNote is true', () => {
- createComponent({
- note: { ...mockNote, userPermissions: { resolveNote: true } },
- });
-
- expect(findNoteActions().props()).toMatchObject({
- showEditButton: true,
- });
- });
-
- it('should not show edit button when resolveNote is false', () => {
- createComponent({
- note: { ...mockNote, userPermissions: { resolveNote: false } },
- });
-
+ // this should be changed: https://gitlab.com/gitlab-org/gitlab/-/issues/481897
+ it('should not show edit button', () => {
expect(findNoteActions().props()).toMatchObject({
showEditButton: false,
});
diff --git a/spec/frontend/admin/abuse_report/mock_data.js b/spec/frontend/admin/abuse_report/mock_data.js
index 0f37ef218d2..8dafbcc9528 100644
--- a/spec/frontend/admin/abuse_report/mock_data.js
+++ b/spec/frontend/admin/abuse_report/mock_data.js
@@ -318,6 +318,171 @@ export const mockDiscussionWithReplies = [
},
];
+export const mockAbuseReportDiscussionWithNoReplies = [
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::Note/1',
+ body: 'Comment 1',
+ bodyHtml: '\u003cp data-sourcepos="1:1-1:9" dir="auto"\u003eComment 1\u003c/p\u003e',
+ createdAt: '2023-10-19T06:11:13Z',
+ lastEditedAt: null,
+ url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_1',
+ resolved: false,
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ webPath: '/root',
+ __typename: 'UserCore',
+ },
+ lastEditedBy: null,
+ discussion: {
+ id: 'gid://gitlab/Discussion/055af96ab917175219aec8739c911277b18ea41d',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::Note/1',
+ __typename: 'Note',
+ },
+ ],
+ __typename: 'AbuseReportNoteConnection',
+ },
+ __typename: 'AbuseReportDiscussion',
+ },
+ __typename: 'AbuseReportNote',
+ },
+];
+export const mockAbuseReportDiscussionWithReplies = [
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/2',
+ body: 'Comment 2',
+ bodyHtml: '\u003cp data-sourcepos="1:1-1:9" dir="auto"\u003eComment 2\u003c/p\u003e',
+ createdAt: '2023-10-20T07:47:21Z',
+ lastEditedAt: '2023-10-20T07:47:42Z',
+ url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_2',
+ resolved: false,
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ webPath: '/root',
+ __typename: 'UserCore',
+ },
+ lastEditedBy: null,
+ discussion: {
+ id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/2',
+ __typename: 'AbuseReportNote',
+ },
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/3',
+ __typename: 'AbuseReportNote',
+ },
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/4',
+ __typename: 'AbuseReportNote',
+ },
+ ],
+ __typename: 'AbuseReportNoteConnection',
+ },
+ __typename: 'AbuseReportDiscussion',
+ },
+ __typename: 'AbuseReportNote',
+ },
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/3',
+ body: 'Reply comment 1',
+ bodyHtml: '\u003cp data-sourcepos="1:1-1:15" dir="auto"\u003eReply comment 1\u003c/p\u003e',
+ createdAt: '2023-10-20T07:47:42Z',
+ lastEditedAt: '2023-10-20T07:47:42Z',
+ url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_3',
+ resolved: false,
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ webPath: '/root',
+ __typename: 'UserCore',
+ },
+ lastEditedBy: null,
+ discussion: {
+ id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/2',
+ __typename: 'AbuseReportNote',
+ },
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/3',
+ __typename: 'AbuseReportNote',
+ },
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/4',
+ __typename: 'AbuseReportNote',
+ },
+ ],
+ __typename: 'AbuseReportNoteConnection',
+ },
+ __typename: 'AbuseReportDiscussion',
+ },
+ __typename: 'AbuseReportNote',
+ },
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/4',
+ body: 'Reply comment 2',
+ bodyHtml: '\u003cp data-sourcepos="1:1-1:15" dir="auto"\u003eReply comment 2\u003c/p\u003e',
+ createdAt: '2023-10-20T08:26:51Z',
+ lastEditedAt: '2023-10-20T08:26:51Z',
+ url: 'http://127.0.0.1:3000/admin/abuse_reports/1#note_4',
+ resolved: false,
+ author: {
+ id: 'gid://gitlab/User/1',
+ avatarUrl:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon',
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://127.0.0.1:3000/root',
+ webPath: '/root',
+ __typename: 'UserCore',
+ },
+ lastEditedBy: null,
+ discussion: {
+ id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
+ notes: {
+ nodes: [
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/2',
+ __typename: 'AbuseReportNote',
+ },
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/3',
+ __typename: 'AbuseReportNote',
+ },
+ {
+ id: 'gid://gitlab/AntiAbuse::Reports::DiscussionNote/4',
+ __typename: 'AbuseReportNote',
+ },
+ ],
+ __typename: 'AbuseReportNoteConnection',
+ },
+ __typename: 'AbuseReportDiscussion',
+ },
+ __typename: 'AbuseReportNote',
+ },
+];
+
export const mockNotesByIdResponse = {
data: {
abuseReport: {
@@ -327,22 +492,23 @@ export const mockNotesByIdResponse = {
{
id: 'gid://gitlab/Discussion/055af96ab917175219aec8739c911277b18ea41d',
replyId:
- 'gid://gitlab/IndividualNoteDiscussion/055af96ab917175219aec8739c911277b18ea41d',
+ 'gid://gitlab/AntiAbuse::Reports::IndividualNoteDiscussion/055af96ab917175219aec8739c911277b18ea41d',
notes: {
- nodes: mockDiscussionWithNoReplies,
- __typename: 'NoteConnection',
+ nodes: mockAbuseReportDiscussionWithNoReplies,
+ __typename: 'AbuseReportNoteConnection',
},
},
{
id: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
- replyId: 'gid://gitlab/Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
+ replyId:
+ 'gid://gitlab/AntiAbuse::Reports::Discussion/9c7228e06fb0339a3d1440fcda960acfd8baa43a',
notes: {
- nodes: mockDiscussionWithReplies,
- __typename: 'NoteConnection',
+ nodes: mockAbuseReportDiscussionWithReplies,
+ __typename: 'AbuseReportNoteConnection',
},
},
],
- __typename: 'DiscussionConnection',
+ __typename: 'AbuseReportDiscussionConnection',
},
__typename: 'AbuseReport',
},
diff --git a/spec/frontend/ci/pipeline_details/graph/components/action_component_spec.js b/spec/frontend/ci/pipeline_details/graph/components/action_component_spec.js
index d204576fe8f..e1f6c65acce 100644
--- a/spec/frontend/ci/pipeline_details/graph/components/action_component_spec.js
+++ b/spec/frontend/ci/pipeline_details/graph/components/action_component_spec.js
@@ -1,4 +1,4 @@
-import { GlButton } from '@gitlab/ui';
+import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
@@ -10,7 +10,9 @@ import ActionComponent from '~/ci/common/private/job_action_component.vue';
describe('pipeline graph action component', () => {
let wrapper;
let mock;
+
const findButton = () => wrapper.findComponent(GlButton);
+ const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const defaultProps = {
tooltipText: 'bar',
@@ -69,11 +71,21 @@ describe('pipeline graph action component', () => {
expect(wrapper.emitted().pipelineActionRequestComplete).toHaveLength(1);
});
- it('renders a loading icon while waiting for request', async () => {
+ it('displays a loading icon/disabled button while waiting for request', async () => {
+ expect(findLoadingIcon().exists()).toBe(false);
+ expect(findButton().props('disabled')).toBe(false);
+
findButton().trigger('click');
await nextTick();
- expect(wrapper.find('.js-action-icon-loading').exists()).toBe(true);
+
+ expect(findLoadingIcon().exists()).toBe(true);
+ expect(findButton().props('disabled')).toBe(true);
+
+ await waitForPromises();
+
+ expect(findLoadingIcon().exists()).toBe(false);
+ expect(findButton().props('disabled')).toBe(false);
});
});
diff --git a/spec/frontend/ide/web_ide_assets_spec.js b/spec/frontend/ide/web_ide_assets_spec.js
index d59b09f4cbc..692c7f9853c 100644
--- a/spec/frontend/ide/web_ide_assets_spec.js
+++ b/spec/frontend/ide/web_ide_assets_spec.js
@@ -25,6 +25,24 @@ describe('asset patching in @gitlab/web-ide', () => {
});
const htmlChildren = allChildren.filter((x) => x.endsWith('.html'));
+ /**
+ * ## What in the world is this test doing!?
+ *
+ * This test was introduced when we were fixing a [security vulnerability][1] related to GitLab self-hosting
+ * problematic `.html` files. These files could be exploited through an `iframe` on an `evil.com` and will
+ * assume the user's cookie authentication. Boom!
+ *
+ * ## How do I know if an `.html` file is vulnerable?
+ *
+ * - The `.html` file used the `postMessage` API and allowed any `origin` which enabled any external site to
+ * open it in an `iframe` and communicate to it.
+ * - The `iframe` exposed some internal VSCode message bus that could allow arbitrary requests. So watch out for
+ * `fetch`.
+ *
+ * [1]: https://gitlab.com/gitlab-org/security/gitlab-web-ide-vscode-fork/-/issues/1#note_1905417620
+ *
+ * ========== If expectation fails and you can't see the full comment... LOOK UP! ==============
+ */
expect(htmlChildren).toEqual([
// This is the only HTML file we expect and it's protected by the other test.
'out/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html',
@@ -33,6 +51,9 @@ describe('asset patching in @gitlab/web-ide', () => {
'extensions/microsoft-authentication/media/index.html',
'extensions/gitlab-vscode-extension/webviews/security_finding/index.html',
'extensions/gitlab-vscode-extension/webviews/gitlab_duo_chat/index.html',
+ 'extensions/gitlab-vscode-extension/assets/language-server/webviews/duo-workflow/index.html',
+ 'extensions/gitlab-vscode-extension/assets/language-server/webviews/duo-chat/index.html',
+ 'extensions/gitlab-vscode-extension/assets/language-server/webviews/chat/index.html',
'extensions/github-authentication/media/index.html',
]);
});
diff --git a/spec/graphql/types/abuse_report_type_spec.rb b/spec/graphql/types/abuse_report_type_spec.rb
new file mode 100644
index 00000000000..6ee1aad12d8
--- /dev/null
+++ b/spec/graphql/types/abuse_report_type_spec.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AbuseReport'], feature_category: :insider_threat do
+ let(:fields) { %w[id labels discussions notes] }
+
+ specify { expect(described_class.graphql_name).to eq('AbuseReport') }
+
+ specify { expect(described_class).to have_graphql_fields(fields) }
+end
diff --git a/spec/graphql/types/notes/abuse_report/discussion_type_spec.rb b/spec/graphql/types/notes/abuse_report/discussion_type_spec.rb
new file mode 100644
index 00000000000..c97844881ab
--- /dev/null
+++ b/spec/graphql/types/notes/abuse_report/discussion_type_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AbuseReportDiscussion'], feature_category: :team_planning do
+ include GraphqlHelpers
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ abuse_report
+ created_at
+ id
+ notes
+ reply_id
+ resolvable
+ resolved
+ resolved_at
+ resolved_by
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+
+ specify { expect(described_class.graphql_name).to eq('AbuseReportDiscussion') }
+end
diff --git a/spec/graphql/types/notes/abuse_report/note_type_spec.rb b/spec/graphql/types/notes/abuse_report/note_type_spec.rb
new file mode 100644
index 00000000000..3945e1906a2
--- /dev/null
+++ b/spec/graphql/types/notes/abuse_report/note_type_spec.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe GitlabSchema.types['AbuseReportNote'], feature_category: :team_planning do
+ include GraphqlHelpers
+
+ it 'exposes the expected fields' do
+ expected_fields = %i[
+ author
+ body
+ body_html
+ body_first_line_html
+ award_emoji
+ created_at
+ discussion
+ id
+ resolvable
+ resolved
+ resolved_at
+ resolved_by
+ updated_at
+ url
+ last_edited_at
+ last_edited_by
+ ]
+
+ expect(described_class).to have_graphql_fields(*expected_fields)
+ end
+
+ specify { expect(described_class.graphql_name).to eq('AbuseReportNote') }
+end
diff --git a/spec/graphql/types/notes/note_type_spec.rb b/spec/graphql/types/notes/note_type_spec.rb
index dde51d84f78..d16cf5f44a7 100644
--- a/spec/graphql/types/notes/note_type_spec.rb
+++ b/spec/graphql/types/notes/note_type_spec.rb
@@ -5,6 +5,16 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Note'], feature_category: :team_planning do
include GraphqlHelpers
+ # rubocop:disable RSpec/FactoryBot/AvoidCreate -- we need the project and author for the test, id needed
+ let_it_be(:project) { create(:project, :public) }
+ let_it_be(:user) { build_stubbed(:user) }
+
+ let_it_be(:note_text) { 'note body content' }
+ let_it_be(:note) { create(:note, note: note_text, project: project) }
+ # rubocop:enable RSpec/FactoryBot/AvoidCreate
+
+ let(:batch_loader) { instance_double(Gitlab::Graphql::Loaders::BatchModelLoader) }
+
it 'exposes the expected fields' do
expected_fields = %i[
author
@@ -42,12 +52,10 @@ RSpec.describe GitlabSchema.types['Note'], feature_category: :team_planning do
specify { expect(described_class).to require_graphql_authorizations(:read_note) }
context 'when system note with issue_email_participants action', feature_category: :service_desk do
- let_it_be(:user) { build_stubbed(:user) }
let_it_be(:email) { 'user@example.com' }
let_it_be(:note_text) { "added #{email}" }
# Create project and issue separately because we need to public project.
# rubocop:disable RSpec/FactoryBot/AvoidCreate -- Notes::RenderService updates #note and #cached_markdown_version
- let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }
let_it_be(:note) do
create(:note, :system, project: project, noteable: issue, author: Users::Internal.support_bot, note: note_text)
@@ -72,9 +80,6 @@ RSpec.describe GitlabSchema.types['Note'], feature_category: :team_planning do
end
describe '#body_first_line_html' do
- let_it_be(:user) { build_stubbed(:user) }
- let_it_be(:project) { build(:project, :public) }
-
let(:note_text) { 'note body content' }
let(:note) { build(:note, note: note_text, project: project) }
@@ -109,4 +114,28 @@ RSpec.describe GitlabSchema.types['Note'], feature_category: :team_planning do
end
end
end
+
+ describe '#project' do
+ subject(:note_project) { resolve_field(:project, note, current_user: user) }
+
+ it 'fetches the project' do
+ expect(Gitlab::Graphql::Loaders::BatchModelLoader).to receive(:new).with(Project, project.id)
+ .and_return(batch_loader)
+ expect(batch_loader).to receive(:find)
+
+ note_project
+ end
+ end
+
+ describe '#author' do
+ subject(:note_author) { resolve_field(:author, note, current_user: user) }
+
+ it 'fetches the author' do
+ expect(Gitlab::Graphql::Loaders::BatchModelLoader).to receive(:new).with(User, note.author.id)
+ .and_return(batch_loader)
+ expect(batch_loader).to receive(:find)
+
+ note_author
+ end
+ end
end
diff --git a/spec/graphql/types/notes/noteable_interface_spec.rb b/spec/graphql/types/notes/noteable_interface_spec.rb
index c88cfe18b81..e11dece60b8 100644
--- a/spec/graphql/types/notes/noteable_interface_spec.rb
+++ b/spec/graphql/types/notes/noteable_interface_spec.rb
@@ -19,7 +19,6 @@ RSpec.describe Types::Notes::NoteableInterface do
expect(described_class.resolve_type(build(:merge_request), {})).to eq(Types::MergeRequestType)
expect(described_class.resolve_type(build(:design), {})).to eq(Types::DesignManagement::DesignType)
expect(described_class.resolve_type(build(:alert_management_alert), {})).to eq(Types::AlertManagement::AlertType)
- expect(described_class.resolve_type(build(:abuse_report), {})).to eq(Types::AbuseReportType)
end
end
end
diff --git a/spec/haml_lint/linter/documentation_links_spec.rb b/spec/haml_lint/linter/documentation_links_spec.rb
index 0b3c93306db..f1a7e4146be 100644
--- a/spec/haml_lint/linter/documentation_links_spec.rb
+++ b/spec/haml_lint/linter/documentation_links_spec.rb
@@ -11,49 +11,49 @@ RSpec.describe HamlLint::Linter::DocumentationLinks, feature_category: :tooling
shared_examples 'link validation rules' do |link_pattern|
context 'when link_to points to the existing file path' do
- let(:haml) { "= link_to 'Description', #{link_pattern}('index')" }
+ let(:haml) { "= link_to 'Description', #{link_pattern}('index.md')" }
it { is_expected.not_to report_lint }
end
context 'when link_to points to the existing file with valid anchor' do
- let(:haml) { "= link_to 'Description', #{link_pattern}('index', anchor: 'user-accounts'), target: '_blank'" }
+ let(:haml) { "= link_to 'Description', #{link_pattern}('index.md', anchor: 'user-accounts'), target: '_blank'" }
it { is_expected.not_to report_lint }
end
- context 'when link_to points to the existing file path with .md extension' do
- let(:haml) { "= link_to 'Description', #{link_pattern}('index.md')" }
+ context 'when link_to points to the existing file path without .md extension' do
+ let(:haml) { "= link_to 'Description', #{link_pattern}('index')" }
it { is_expected.to report_lint }
end
context 'when anchor is not correct' do
- let(:haml) { "= link_to 'Description', #{link_pattern}('index', anchor: 'wrong')" }
+ let(:haml) { "= link_to 'Description', #{link_pattern}('index.md', anchor: 'wrong')" }
it { is_expected.to report_lint }
context "when #{link_pattern} has multiple options" do
- let(:haml) { "= link_to 'Description', #{link_pattern}('index', key: :value, anchor: 'wrong')" }
+ let(:haml) { "= link_to 'Description', #{link_pattern}('index.md', key: :value, anchor: 'wrong')" }
it { is_expected.to report_lint }
end
end
context 'when file path is wrong' do
- let(:haml) { "= link_to 'Description', #{link_pattern}('wrong'), target: '_blank'" }
+ let(:haml) { "= link_to 'Description', #{link_pattern}('wrong.md'), target: '_blank'" }
it { is_expected.to report_lint }
context 'when haml ends with block definition' do
- let(:haml) { "= link_to 'Description', #{link_pattern}('wrong') do" }
+ let(:haml) { "= link_to 'Description', #{link_pattern}('wrong.md') do" }
it { is_expected.to report_lint }
end
end
context 'when link with wrong file path is assigned to a variable' do
- let(:haml) { "- my_link = link_to 'Description', #{link_pattern}('wrong')" }
+ let(:haml) { "- my_link = link_to 'Description', #{link_pattern}('wrong.md')" }
it { is_expected.to report_lint }
end
@@ -65,7 +65,7 @@ RSpec.describe HamlLint::Linter::DocumentationLinks, feature_category: :tooling
end
context 'when anchor belongs to a different element' do
- let(:haml) { "= link_to 'Description', #{link_pattern}('index'), target: (anchor: 'blank')" }
+ let(:haml) { "= link_to 'Description', #{link_pattern}('index.md'), target: (anchor: 'blank')" }
it { is_expected.not_to report_lint }
end
@@ -89,7 +89,7 @@ RSpec.describe HamlLint::Linter::DocumentationLinks, feature_category: :tooling
end
context 'when the second link is invalid' do
- let(:haml) { ".data-form{ data: { url: #{link_pattern}('index'), wrong_url: #{link_pattern}('wrong') } }" }
+ let(:haml) { ".data-form{ data: { url: #{link_pattern}('index.md'), wrong_url: #{link_pattern}('wrong') } }" }
it { is_expected.to report_lint }
end
diff --git a/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb b/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb
index 69afddf2406..d8a98d47356 100644
--- a/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/broadcast_message_sanitization_filter_spec.rb
@@ -8,9 +8,6 @@ RSpec.describe Banzai::Filter::BroadcastMessageSanitizationFilter, feature_categ
it_behaves_like 'default allowlist'
describe 'custom allowlist' do
- it_behaves_like 'XSS prevention'
- it_behaves_like 'sanitize link'
-
subject { filter(exp).to_html }
context 'allows `a` elements' do
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 0ba1dc9ed04..5e05a5cdabc 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -210,10 +210,11 @@ RSpec.describe Banzai::Filter::ExternalLinkFilter, feature_category: :team_plann
end
end
- it_behaves_like 'pipeline timing check'
+ it_behaves_like 'does not use pipeline timing check'
+
it_behaves_like 'a filter timeout' do
let(:text) { 'text' }
- let(:expected_result) { described_class::COMPLEX_MARKDOWN_MESSAGE }
+ let(:expected_result) { Banzai::Filter::SanitizeLinkFilter::TIMEOUT_MARKDOWN_MESSAGE }
let(:expected_timeout) { described_class::SANITIZATION_RENDER_TIMEOUT }
end
end
diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb
index bef26969517..4a6f22d3813 100644
--- a/spec/lib/banzai/filter/math_filter_spec.rb
+++ b/spec/lib/banzai/filter/math_filter_spec.rb
@@ -314,6 +314,7 @@ RSpec.describe Banzai::Filter::MathFilter, feature_category: :team_planning do
doc = Banzai::Pipeline::PlainMarkdownPipeline.call(doc[:output], context)
doc = Banzai::Filter::CodeLanguageFilter.call(doc[:output], context, nil)
doc = Banzai::Filter::SanitizationFilter.call(doc, context, nil)
+ doc = Banzai::Filter::SanitizeLinkFilter.call(doc, context, nil)
filter(doc, context)
end
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index 7228ff5a95c..878f24ddaf2 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -8,9 +8,6 @@ RSpec.describe Banzai::Filter::SanitizationFilter, feature_category: :team_plann
it_behaves_like 'default allowlist'
describe 'custom allowlist' do
- it_behaves_like 'XSS prevention'
- it_behaves_like 'sanitize link'
-
it 'customizes the allowlist only once' do
instance = described_class.new('Foo')
control_count = instance.allowlist[:transformers].size
@@ -224,6 +221,8 @@ RSpec.describe Banzai::Filter::SanitizationFilter, feature_category: :team_plann
end
end
+ it_behaves_like 'does not use pipeline timing check'
+
it_behaves_like 'a filter timeout' do
let(:text) { 'text' }
let(:expected_result) { described_class::COMPLEX_MARKDOWN_MESSAGE }
diff --git a/spec/lib/banzai/filter/sanitize_link_filter_spec.rb b/spec/lib/banzai/filter/sanitize_link_filter_spec.rb
new file mode 100644
index 00000000000..bc867b53958
--- /dev/null
+++ b/spec/lib/banzai/filter/sanitize_link_filter_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Banzai::Filter::SanitizeLinkFilter, feature_category: :team_planning do
+ include FilterSpecHelper
+
+ it_behaves_like 'XSS prevention'
+ it_behaves_like 'sanitize link'
+ it_behaves_like 'does not use pipeline timing check'
+
+ it_behaves_like 'a filter timeout' do
+ let(:text) { 'text' }
+ let(:expected_result) { described_class::TIMEOUT_MARKDOWN_MESSAGE }
+ let(:expected_timeout) { described_class::SANITIZATION_RENDER_TIMEOUT }
+ end
+end
diff --git a/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb b/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb
index 9f1af821d11..f186acd7062 100644
--- a/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/broadcast_message_pipeline_spec.rb
@@ -12,6 +12,8 @@ RSpec.describe Banzai::Pipeline::BroadcastMessagePipeline, feature_category: :te
subject { described_class.to_html(exp, project: project) }
+ it_behaves_like 'sanitize pipeline'
+
context "allows `a` elements" do
let(:exp) { "Link" }
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
index 2d831d7f7e0..67f5123fbbf 100644
--- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -21,6 +21,8 @@ RSpec.describe Banzai::Pipeline::DescriptionPipeline, feature_category: :team_pl
stub_commonmark_sourcepos_disabled
end
+ it_behaves_like 'sanitize pipeline'
+
it 'uses a limited allowlist' do
doc = parse('# Description')
diff --git a/spec/lib/banzai/pipeline/email_pipeline_spec.rb b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
index 4d2dca84b1b..8b4abbd2ba5 100644
--- a/spec/lib/banzai/pipeline/email_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
@@ -4,6 +4,8 @@ require 'spec_helper'
RSpec.describe Banzai::Pipeline::EmailPipeline, feature_category: :team_planning do
describe '.filters' do
+ it_behaves_like 'sanitize pipeline'
+
it 'returns the expected type' do
expect(described_class.filters).to be_kind_of(Banzai::FilterArray)
end
diff --git a/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb b/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb
index 6ecd7f56dec..4680a4e525d 100644
--- a/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/emoji_pipeline_spec.rb
@@ -9,6 +9,8 @@ RSpec.describe Banzai::Pipeline::EmojiPipeline, feature_category: :team_planning
described_class.to_html(text, {})
end
+ it_behaves_like 'sanitize pipeline'
+
it 'replaces emoji' do
expected_result = "Hello world #{Gitlab::Emoji.gl_emoji_tag(emoji)}"
diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
index 11d5f202cb7..ed4e9faa6dd 100644
--- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
@@ -5,6 +5,8 @@ require 'spec_helper'
RSpec.describe Banzai::Pipeline::FullPipeline, feature_category: :team_planning do
using RSpec::Parameterized::TableSyntax
+ it_behaves_like 'sanitize pipeline'
+
describe 'References' do
let(:project) { create(:project, :public) }
let(:issue) { create(:issue, project: project) }
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 908f29c3555..ad90c67e422 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Banzai::Pipeline::GfmPipeline, feature_category: :team_planning do
+ it_behaves_like 'sanitize pipeline'
+
describe 'integration between parsing regular and external issue references' do
let_it_be(:project) { create(:project, :with_redmine_integration, :public) }
diff --git a/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb b/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb
index 9e79be4333a..bd3fa6041bc 100644
--- a/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/incident_management/timeline_event_pipeline_spec.rb
@@ -5,12 +5,15 @@ require 'spec_helper'
RSpec.describe Banzai::Pipeline::IncidentManagement::TimelineEventPipeline do
let_it_be(:project) { create(:project) }
+ it_behaves_like 'sanitize pipeline'
+
describe '.filters' do
it 'contains required filters' do
expect(described_class.filters).to eq(
[
*Banzai::Pipeline::PlainMarkdownPipeline.filters,
Banzai::Filter::SanitizationFilter,
+ Banzai::Filter::SanitizeLinkFilter,
*Banzai::Pipeline::GfmPipeline.reference_filters,
Banzai::Filter::EmojiFilter,
Banzai::Filter::ExternalLinkFilter,
diff --git a/spec/lib/banzai/pipeline/service_desk_email_pipeline_spec.rb b/spec/lib/banzai/pipeline/service_desk_email_pipeline_spec.rb
index 83541494f68..886921d9a9e 100644
--- a/spec/lib/banzai/pipeline/service_desk_email_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/service_desk_email_pipeline_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Banzai::Pipeline::ServiceDeskEmailPipeline, feature_category: :service_desk do
+ it_behaves_like 'sanitize pipeline'
+
describe '.filters' do
it 'returns the expected type' do
expect(described_class.filters).to be_kind_of(Banzai::FilterArray)
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 423378bbd21..b9b386f244c 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -8,6 +8,8 @@ RSpec.describe Banzai::Pipeline::WikiPipeline, feature_category: :wiki do
let_it_be(:wiki) { ProjectWiki.new(project, nil) }
let_it_be(:page) { build(:wiki_page, wiki: wiki, title: 'nested/twice/start-page') }
+ it_behaves_like 'sanitize pipeline'
+
describe 'TableOfContents' do
it 'replaces the tag with the TableOfContentsTagFilter result' do
markdown = <<-MD.strip_heredoc
diff --git a/spec/lib/gitlab/experiment/rollout/feature_spec.rb b/spec/lib/gitlab/experiment_feature_rollout_spec.rb
similarity index 97%
rename from spec/lib/gitlab/experiment/rollout/feature_spec.rb
rename to spec/lib/gitlab/experiment_feature_rollout_spec.rb
index 9d602083ad6..62eddbfd5f9 100644
--- a/spec/lib/gitlab/experiment/rollout/feature_spec.rb
+++ b/spec/lib/gitlab/experiment_feature_rollout_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::Experiment::Rollout::Feature, :experiment, feature_category: :acquisition do
+RSpec.describe Gitlab::ExperimentFeatureRollout, :experiment, feature_category: :acquisition do
subject(:experiment_instance) { described_class.new(subject_experiment) }
let(:subject_experiment) { experiment('namespaced/stub') }
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index baa29e4e3ce..54ce7055be4 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -1140,7 +1140,7 @@ approval_rules:
- approval_project_rules_users
- approval_project_rules_protected_branches
- scan_result_policy_read
- - approval_policy_rules
+ - approval_policy_rule
approval_project_rules_users:
- user
- approval_project_rule
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index 420d4ad0197..f33826058ab 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -64,6 +64,7 @@ RSpec.describe Gitlab::UrlBuilder do
:package | ->(package) { "/#{package.project.full_path}/-/packages/#{package.id}" }
:user_namespace | ->(user_namespace) { "/#{user_namespace.owner.full_path}" }
:project_namespace | ->(project_namespace) { "/#{project_namespace.project.full_path}" }
+ :abuse_report_note | ->(note) { "/admin/abuse_reports/#{note.abuse_report_id}#anti_abuse_reports_note_#{note.id}" }
end
with_them do
diff --git a/spec/models/anti_abuse/reports/note_spec.rb b/spec/models/anti_abuse/reports/note_spec.rb
index d8a4192f235..c0da5bb4601 100644
--- a/spec/models/anti_abuse/reports/note_spec.rb
+++ b/spec/models/anti_abuse/reports/note_spec.rb
@@ -32,5 +32,23 @@ RSpec.describe AntiAbuse::Reports::Note, feature_category: :insider_threat do
expect(note1.note_html).to include('some note
')
end
end
+
+ describe 'Scopes' do
+ describe '.inc_relations_for_view' do
+ subject(:incl_relations) { described_class.all.inc_relations_for_view.first }
+
+ it 'loads associations' do
+ expect(incl_relations.association(:author).loaded?).to be(true)
+ expect(incl_relations.association(:updated_by).loaded?).to be(true)
+ expect(incl_relations.association(:award_emoji).loaded?).to be(true)
+ end
+ end
+ end
+
+ describe '#parent_object_field' do
+ it 'returns the correct value' do
+ expect(described_class.parent_object_field).to eq(:abuse_report)
+ end
+ end
end
end
diff --git a/spec/requests/api/graphql/abuse_report_spec.rb b/spec/requests/api/graphql/abuse_report_spec.rb
index 5ffa3588c67..2596a247f68 100644
--- a/spec/requests/api/graphql/abuse_report_spec.rb
+++ b/spec/requests/api/graphql/abuse_report_spec.rb
@@ -66,4 +66,33 @@ RSpec.describe 'Querying an Abuse Report', feature_category: :insider_threat do
)
end
end
+
+ describe 'notes' do
+ let_it_be(:abuse_report) { create(:abuse_report) }
+ let_it_be(:note_1) { create(:abuse_report_note, abuse_report: abuse_report) }
+ let_it_be(:note_2) { create(:abuse_report_note, abuse_report: abuse_report) }
+ let_it_be(:reply) { create(:abuse_report_note, in_reply_to: note_1) }
+
+ let(:notes_response) do
+ graphql_data_at(:abuse_report, :notes, :nodes)
+ end
+
+ let(:abuse_report_fields) do
+ <<~GRAPHQL
+ notes {
+ nodes {
+ id
+ body
+ bodyHtml
+ }
+ }
+ GRAPHQL
+ end
+
+ it 'returns notes' do
+ expect(notes_response).to match_array(
+ [a_graphql_entity_for(note_1), a_graphql_entity_for(note_2), a_graphql_entity_for(reply)]
+ )
+ end
+ end
end
diff --git a/spec/requests/api/project_attributes.yml b/spec/requests/api/project_attributes.yml
index f4599f03a3e..7a55b8e7416 100644
--- a/spec/requests/api/project_attributes.yml
+++ b/spec/requests/api/project_attributes.yml
@@ -190,6 +190,7 @@ project_setting:
- duo_features_enabled
- require_reauthentication_to_approve
- observability_alerts_enabled
+ - spp_repository_pipeline_access
build_service_desk_setting: # service_desk_setting
unexposed_attributes:
diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml
index 89054a15a79..bedb7dcd263 100644
--- a/spec/support/rspec_order_todo.yml
+++ b/spec/support/rspec_order_todo.yml
@@ -5176,7 +5176,7 @@
- './spec/lib/gitlab/exclusive_lease_helpers/sleeping_lock_spec.rb'
- './spec/lib/gitlab/exclusive_lease_helpers_spec.rb'
- './spec/lib/gitlab/exclusive_lease_spec.rb'
-- './spec/lib/gitlab/experiment/rollout/feature_spec.rb'
+- './spec/lib/gitlab/experiment_feature_rollout_spec.rb'
- './spec/lib/gitlab/external_authorization/access_spec.rb'
- './spec/lib/gitlab/external_authorization/cache_spec.rb'
- './spec/lib/gitlab/external_authorization/client_spec.rb'
diff --git a/spec/support/shared_examples/banzai/filters/filter_timeout_shared_examples.rb b/spec/support/shared_examples/banzai/filters/filter_timeout_shared_examples.rb
index 6927b2d7a6d..4074745cfbc 100644
--- a/spec/support/shared_examples/banzai/filters/filter_timeout_shared_examples.rb
+++ b/spec/support/shared_examples/banzai/filters/filter_timeout_shared_examples.rb
@@ -75,6 +75,15 @@ RSpec.shared_examples 'pipeline timing check' do |context: {}|
end
end
+# Usage:
+#
+# it_behaves_like 'does not use pipeline timing check'
+RSpec.shared_examples 'does not use pipeline timing check' do
+ it 'does not include Concerns::PipelineTimingCheck' do
+ expect(described_class).not_to include Banzai::Filter::Concerns::PipelineTimingCheck
+ end
+end
+
# Usage:
#
# it_behaves_like 'limits the number of filtered items' do
diff --git a/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb b/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb
index e335255e426..6a06472e386 100644
--- a/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb
+++ b/spec/support/shared_examples/lib/banzai/filters/sanitization_filter_shared_examples.rb
@@ -13,18 +13,6 @@ RSpec.shared_examples 'default allowlist' do
expect(filter(act).to_html).to eq exp
end
- it 'sanitizes javascript in attributes' do
- act = %q(Text)
- exp = 'Text'
- expect(filter(act).to_html).to eq exp
- end
-
- it 'sanitizes mixed-cased javascript in attributes' do
- act = %q(Text)
- exp = 'Text'
- expect(filter(act).to_html).to eq exp
- end
-
it 'allows allowlisted HTML tags from the user' do
exp = act = "\n- Term
\n- Definition
\n
"
expect(filter(act).to_html).to eq exp
@@ -39,6 +27,13 @@ RSpec.shared_examples 'default allowlist' do
act = %q(Emphasis)
expect(filter(act).to_html).to eq %q(Emphasis)
end
+
+ it 'removes `rel` attribute from `a` elements' do
+ act = %q(Link)
+ exp = %q(Link)
+
+ expect(filter(act).to_html).to eq exp
+ end
end
RSpec.shared_examples 'XSS prevention' do
@@ -133,6 +128,18 @@ RSpec.shared_examples 'XSS prevention' do
end
end
+ it 'sanitizes javascript in attributes' do
+ act = %q(Text)
+ exp = 'Text'
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'sanitizes mixed-cased javascript in attributes' do
+ act = %q(Text)
+ exp = 'Text'
+ expect(filter(act).to_html).to eq exp
+ end
+
it 'disallows data links' do
input = 'XSS'
output = filter(input)
@@ -149,13 +156,6 @@ RSpec.shared_examples 'XSS prevention' do
end
RSpec.shared_examples 'sanitize link' do
- it 'removes `rel` attribute from `a` elements' do
- act = %q(Link)
- exp = %q(Link)
-
- expect(filter(act).to_html).to eq exp
- end
-
it 'disallows invalid URIs' do
expect(Addressable::URI).to receive(:parse).with('foo://example.com')
.and_raise(Addressable::URI::InvalidURIError)
@@ -180,3 +180,18 @@ RSpec.shared_examples 'sanitize link' do
expect(act.to_html).to eq exp
end
end
+
+# not meant to be exhaustive, but verify that the pipeline is doing sanitization
+RSpec.shared_examples 'sanitize pipeline' do
+ subject { described_class.to_html(act, project: nil) }
+
+ it 'includes BaseSanitizationFilter' do
+ result = described_class.filters.filter { |filter| filter.ancestors.include? Banzai::Filter::BaseSanitizationFilter }
+
+ expect(result).not_to be_empty
+ end
+
+ it 'includes SanitizeLinkFilter' do
+ expect(described_class.filters).to include(Banzai::Filter::SanitizeLinkFilter)
+ end
+end
diff --git a/workhorse/go.mod b/workhorse/go.mod
index 577e1f81b3f..ac0c5670c51 100644
--- a/workhorse/go.mod
+++ b/workhorse/go.mod
@@ -29,7 +29,7 @@ require (
gitlab.com/gitlab-org/labkit v1.21.0
go.uber.org/goleak v1.3.0
gocloud.dev v0.39.0
- golang.org/x/image v0.19.0
+ golang.org/x/image v0.20.0
golang.org/x/net v0.28.0
golang.org/x/oauth2 v0.22.0
google.golang.org/grpc v1.65.0
@@ -122,7 +122,7 @@ require (
golang.org/x/mod v0.20.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
- golang.org/x/text v0.17.0 // indirect
+ golang.org/x/text v0.18.0 // indirect
golang.org/x/time v0.6.0 // indirect
golang.org/x/tools v0.24.0 // indirect
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
diff --git a/workhorse/go.sum b/workhorse/go.sum
index ca2b12ee9ff..a23262896b2 100644
--- a/workhorse/go.sum
+++ b/workhorse/go.sum
@@ -554,8 +554,8 @@ golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/i
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
-golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ=
-golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys=
+golang.org/x/image v0.20.0 h1:7cVCUjQwfL18gyBJOmYvptfSHS8Fb3YUDtfLIZ7Nbpw=
+golang.org/x/image v0.20.0/go.mod h1:0a88To4CYVBAHp5FXJm8o7QbUl37Vd85ply1vyD8auM=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -751,8 +751,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
-golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
-golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
diff --git a/yarn.lock b/yarn.lock
index 471bef114f2..b4401ebf43e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1378,10 +1378,10 @@
vue-functional-data-merge "^3.1.0"
vue-runtime-helpers "^1.1.2"
-"@gitlab/web-ide@^0.0.1-dev-20240816130114":
- version "0.0.1-dev-20240816130114"
- resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20240816130114.tgz#25a88d945095ea10bab9fbed5de1daea205b0bf1"
- integrity sha512-Uv3n+l3oS5ywBWxzXhriFvxYUYw4KBHxlQJEIN3w0gzEiFgV7sYwQmJjCjhukN0PNCIX0akHZYwMm+ow/vD9IA==
+"@gitlab/web-ide@^0.0.1-dev-20240909013227":
+ version "0.0.1-dev-20240909013227"
+ resolved "https://registry.yarnpkg.com/@gitlab/web-ide/-/web-ide-0.0.1-dev-20240909013227.tgz#6ba20cabe4b3dee8eacbb0e3aa4d71b49b30fecc"
+ integrity sha512-fWkkQ3Vm03NmDrJVmEO7nteRzXHj2J4GGfKifILpMeWjKp2X7nPjatHsbOWS8TqEVQUTrL5SB6yV+p6242fAtA==
"@graphql-eslint/eslint-plugin@3.20.1":
version "3.20.1"
@@ -13104,16 +13104,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
-"string-width-cjs@npm:string-width@^4.2.0":
- version "4.2.3"
- resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
- integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
- dependencies:
- emoji-regex "^8.0.0"
- is-fullwidth-code-point "^3.0.0"
- strip-ansi "^6.0.1"
-
-"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -13166,7 +13157,7 @@ string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -13180,13 +13171,6 @@ strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"
-strip-ansi@^6.0.0, strip-ansi@^6.0.1:
- version "6.0.1"
- resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
- integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
- dependencies:
- ansi-regex "^5.0.1"
-
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -14878,7 +14862,7 @@ worker-loader@^3.0.8:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -14896,15 +14880,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
-wrap-ansi@^7.0.0:
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
- integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
- dependencies:
- ansi-styles "^4.0.0"
- string-width "^4.1.0"
- strip-ansi "^6.0.0"
-
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"