mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-07-21 23:43:41 +00:00
Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
@ -313,9 +313,6 @@ export default {
|
||||
'ee/app/assets/javascripts/projects/components/move_personal_project_to_group_modal.vue',
|
||||
'ee/app/assets/javascripts/projects/merge_requests/blocking_mr_input_root.vue',
|
||||
'ee/app/assets/javascripts/projects/settings/branch_rules/components/view/index.vue',
|
||||
'ee/app/assets/javascripts/requirements/components/export_requirements_modal.vue',
|
||||
'ee/app/assets/javascripts/requirements/components/import_requirements_modal.vue',
|
||||
'ee/app/assets/javascripts/requirements/components/requirements_root.vue',
|
||||
'ee/app/assets/javascripts/roadmap/components/current_day_indicator.vue',
|
||||
'ee/app/assets/javascripts/roadmap/components/epic_item.vue',
|
||||
'ee/app/assets/javascripts/roadmap/components/epic_item_details.vue',
|
||||
|
@ -582,6 +582,7 @@ lib/gitlab/checks/**
|
||||
/doc/administration/application_settings_cache.md @jglassman1
|
||||
/doc/administration/auditor_users.md @idurham
|
||||
/doc/administration/auth/ @idurham
|
||||
/doc/administration/backup_restore/ @axil
|
||||
/doc/administration/broadcast_messages.md @sselhorn
|
||||
/doc/administration/cells.md @emily.sahlani
|
||||
/doc/administration/cicd/ @lyspin
|
||||
@ -658,7 +659,7 @@ lib/gitlab/checks/**
|
||||
/doc/administration/raketasks/tokens/ @idurham
|
||||
/doc/administration/raketasks/x509_signatures.md @brendan777
|
||||
/doc/administration/read_only_gitlab.md @axil @eread
|
||||
/doc/administration/redis/ @axil @eread
|
||||
/doc/administration/redis/ @axil
|
||||
/doc/administration/reference_architectures/ @axil @eread
|
||||
/doc/administration/reply_by_email.md @lciutacu
|
||||
/doc/administration/reply_by_email_postfix_setup.md @axil @eread
|
||||
@ -704,8 +705,7 @@ lib/gitlab/checks/**
|
||||
/doc/administration/settings/third_party_offers.md @phillipwells
|
||||
/doc/administration/settings/visibility_and_access_controls.md @brendan777
|
||||
/doc/administration/settings/vscode_extension_marketplace.md @brendan777
|
||||
/doc/administration/sidekiq/ @axil @eread
|
||||
/doc/administration/sidekiq/sidekiq_memory_killer.md @jglassman1
|
||||
/doc/administration/sidekiq/ @axil
|
||||
/doc/administration/silent_mode/ @axil
|
||||
/doc/administration/smime_signing_email.md @axil @eread
|
||||
/doc/administration/snippets/ @brendan777
|
||||
@ -953,6 +953,7 @@ lib/gitlab/checks/**
|
||||
/doc/development/cascading_settings.md @gitlab-org/foundations/engineering
|
||||
/doc/development/cells/ @OmarQunsulGitlab @bmarjanovic
|
||||
/doc/development/cicd/ @gitlab-org/maintainers/cicd-verify
|
||||
/doc/development/compromised_password_detection.md @gitlab-org/software-supply-chain-security/authentication/approvers
|
||||
/doc/development/contributing/verify/ @gitlab-org/maintainers/cicd-verify
|
||||
/doc/development/database/ @OmarQunsulGitlab @bmarjanovic
|
||||
/doc/development/distributed_tracing.md @gitlab-org/analytics-section/product-analytics/engineers/frontend
|
||||
|
@ -74,6 +74,8 @@ compile-production-assets as-if-foss:
|
||||
- .qa:rules:test-on-omnibus-ce:follow-up
|
||||
|
||||
compile-test-assets:
|
||||
variables:
|
||||
BABEL_ENV: "$BABEL_ENV"
|
||||
extends:
|
||||
- .compile-assets-base
|
||||
- .frontend:rules:compile-test-assets
|
||||
|
@ -2025,6 +2025,8 @@
|
||||
variables:
|
||||
<<: *qa-e2e-test-schedule-variables
|
||||
COVERBAND_ENABLED: "true"
|
||||
BABEL_ENV: "istanbul" # This sets the environmental variable enabling `istanbul` plugin in Babel
|
||||
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/babel.config.js
|
||||
QA_RUN_IN_PARALLEL: "false"
|
||||
|
||||
.qa:rules:e2e:test-on-cng:
|
||||
|
@ -56,6 +56,7 @@ workflow:
|
||||
job: clone-gitlab-repo
|
||||
variables:
|
||||
COVERBAND_ENABLED: "$COVERBAND_ENABLED" # explicitly define variable so it is passed in to gdk service container
|
||||
BABEL_ENV: "$BABEL_ENV"
|
||||
GITLAB_CRON_JOBS_POLL_INTERVAL: "0"
|
||||
GITLAB_DEVELOPMENT_USE_PRECOMPILED_ASSETS: "true"
|
||||
GITLAB_QA_ADMIN_ACCESS_TOKEN: $QA_ADMIN_ACCESS_TOKEN
|
||||
|
@ -1,6 +1,7 @@
|
||||
/* eslint-disable func-names, no-underscore-dangle, no-param-reassign, consistent-return */
|
||||
|
||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||
import { updateHash } from '~/blob/state';
|
||||
|
||||
// LineHighlighter
|
||||
//
|
||||
@ -174,6 +175,7 @@ LineHighlighter.prototype.setHash = function (firstLineNumber, lastLineNumber) {
|
||||
hash = `#L${firstLineNumber}`;
|
||||
}
|
||||
this._hash = hash;
|
||||
updateHash(hash);
|
||||
return this.__setLocationHash__(hash);
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,6 @@ export const hashState = Vue.observable({
|
||||
currentHash: window.location.hash,
|
||||
});
|
||||
|
||||
export const updateLineNumber = (lineNumber) => {
|
||||
hashState.currentHash = lineNumber;
|
||||
export const updateHash = (newHash) => {
|
||||
hashState.currentHash = newHash;
|
||||
};
|
||||
|
@ -6,9 +6,8 @@ import { InternalEvents } from '~/tracking';
|
||||
import { keysFor, PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
|
||||
import { Mousetrap } from '~/lib/mousetrap';
|
||||
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
|
||||
import { getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
|
||||
import { hashState } from '~/blob/state';
|
||||
import { getPageParamValue, getPageSearchString } from '~/blob/utils';
|
||||
import { hashState, updateHash } from '~/blob/state';
|
||||
import { getAbsolutePermalinkPath } from './utils';
|
||||
|
||||
Vue.use(GlToast);
|
||||
|
||||
@ -41,26 +40,22 @@ export default {
|
||||
return shouldDisableShortcuts();
|
||||
},
|
||||
absolutePermalinkPath() {
|
||||
const baseAbsolutePath = relativePathToAbsolute(this.permalinkPath, getBaseURL());
|
||||
if (hashState.currentHash) {
|
||||
const page = getPageParamValue(hashState.currentHash);
|
||||
const searchString = getPageSearchString(baseAbsolutePath, page);
|
||||
if (Number.isNaN(Number(hashState.currentHash))) {
|
||||
return `${baseAbsolutePath}${searchString}${hashState.currentHash}`;
|
||||
}
|
||||
return `${baseAbsolutePath}${searchString}#L${hashState.currentHash}`;
|
||||
}
|
||||
return baseAbsolutePath;
|
||||
return getAbsolutePermalinkPath(this.permalinkPath, hashState.currentHash);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.mousetrap = new Mousetrap();
|
||||
this.mousetrap.bind(keysFor(PROJECT_FILES_GO_TO_PERMALINK), this.triggerCopyPermalink);
|
||||
window.addEventListener('hashchange', this.onHashChange);
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.mousetrap.unbind(keysFor(PROJECT_FILES_GO_TO_PERMALINK));
|
||||
window.removeEventListener('hashchange', this.onHashChange);
|
||||
},
|
||||
methods: {
|
||||
onHashChange() {
|
||||
updateHash(window.location.hash || '');
|
||||
},
|
||||
triggerCopyPermalink() {
|
||||
const buttonElement = this.$refs.copyPermalinkButton.$el;
|
||||
buttonElement.click();
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
|
||||
import { getPageParamValue, getPageSearchString } from '~/blob/utils';
|
||||
|
||||
/**
|
||||
* Generates an absolute permalink path with proper handling of URL hash
|
||||
*
|
||||
* @param {String} permalinkPath - The relative permalink path
|
||||
* @param {String} hash - The URL hash (after #)
|
||||
* @returns {String} - The absolute permalink path with hash handling
|
||||
*/
|
||||
export const getAbsolutePermalinkPath = (permalinkPath, inputHash) => {
|
||||
const baseAbsolutePath = relativePathToAbsolute(permalinkPath, getBaseURL());
|
||||
|
||||
const hash = inputHash || '';
|
||||
|
||||
const page = getPageParamValue(hash);
|
||||
const searchString = getPageSearchString(baseAbsolutePath, page);
|
||||
|
||||
// Ensure hash starts with # if it doesn't already
|
||||
let normalizedHash = '';
|
||||
if (hash.trim()) {
|
||||
normalizedHash = hash.startsWith('#') ? hash : `#${hash}`;
|
||||
}
|
||||
return `${baseAbsolutePath}${searchString}${normalizedHash}`;
|
||||
};
|
@ -6,7 +6,6 @@ import { GlIntersectionObserver } from '@gitlab/ui';
|
||||
import SafeHtml from '~/vue_shared/directives/safe_html';
|
||||
import { getPageParamValue, getPageSearchString } from '~/blob/utils';
|
||||
import { addInteractionClass } from '~/code_navigation/utils';
|
||||
import { updateLineNumber } from '~/blob/state';
|
||||
|
||||
/*
|
||||
* We only highlight the chunk that is currently visible to the user.
|
||||
@ -105,10 +104,6 @@ export default {
|
||||
this.hasAppeared = true;
|
||||
this.$emit('appear');
|
||||
},
|
||||
handleOnClick(event) {
|
||||
const { lineNumber } = event.target.dataset;
|
||||
updateLineNumber(lineNumber);
|
||||
},
|
||||
calculateLineNumber(index) {
|
||||
return this.startingFrom + index + 1;
|
||||
},
|
||||
@ -148,7 +143,6 @@ export default {
|
||||
class="file-line-num gl-select-none !gl-shadow-none"
|
||||
:href="`#L${calculateLineNumber(index)}`"
|
||||
:data-line-number="calculateLineNumber(index)"
|
||||
@click="handleOnClick"
|
||||
>
|
||||
{{ calculateLineNumber(index) }}
|
||||
</a>
|
||||
|
@ -7,10 +7,9 @@
|
||||
= render 'shared/new_project_item_vue_select'
|
||||
|
||||
- if @milestone_states.any? { |name, count| count > 0 }
|
||||
.top-area
|
||||
.gl-flex.gl-flex-wrap.gl-items-center.gl-flex-wrap-reverse.gl-border-b
|
||||
= render 'shared/milestones_filter', counts: @milestone_states
|
||||
.nav-controls
|
||||
= render 'shared/milestones/search_form'
|
||||
= render 'shared/milestones/search_form'
|
||||
|
||||
- if @milestones.blank?
|
||||
= render 'shared/empty_states/milestones_tab', active_tab: params[:state] do
|
||||
|
@ -41,6 +41,7 @@
|
||||
= dispensable_render_if_exists "shared/new_user_signups_cap_reached_alert"
|
||||
= dispensable_render_if_exists "shared/silent_mode_banner"
|
||||
= dispensable_render_if_exists "shared/pipl_compliance_alert"
|
||||
= dispensable_render_if_exists "shared/compromised_password_detection_alert"
|
||||
= yield :page_level_alert
|
||||
|
||||
-# Top bar
|
||||
|
@ -1,5 +1,7 @@
|
||||
const coreJSVersion = require('./node_modules/core-js/package.json').version;
|
||||
|
||||
console.debug(`BABEL_ENV inside Babel config is: ${process.env.BABEL_ENV}`);
|
||||
|
||||
let presets = [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
|
@ -1,9 +0,0 @@
|
||||
---
|
||||
name: revalidate_gpg_fingerprints
|
||||
feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/349505
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/182738
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/534717
|
||||
milestone: '17.11'
|
||||
group: group::source code
|
||||
type: beta
|
||||
default_enabled: true
|
@ -89,6 +89,8 @@ if (WEBPACK_REPORT) {
|
||||
NO_HASHED_CHUNKS = true;
|
||||
}
|
||||
|
||||
console.debug(`BABEL_ENV inside Webpack is: ${process.env.BABEL_ENV}`);
|
||||
|
||||
const devtool = IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map';
|
||||
|
||||
const incrementalCompiler = createIncrementalWebpackCompiler(
|
||||
|
@ -2068,6 +2068,7 @@ Input type: `AiActionInput`
|
||||
| <a id="mutationaiactionplatformorigin"></a>`platformOrigin` | [`String`](#string) | Specifies the origin platform of the request. |
|
||||
| <a id="mutationaiactionprojectid"></a>`projectId` | [`ProjectID`](#projectid) | Global ID of the project the user is acting on. |
|
||||
| <a id="mutationaiactionresolvevulnerability"></a>`resolveVulnerability` | [`AiResolveVulnerabilityInput`](#airesolvevulnerabilityinput) | Input for resolve_vulnerability AI action. |
|
||||
| <a id="mutationaiactionrootnamespaceid"></a>`rootNamespaceId` | [`NamespaceID`](#namespaceid) | Global ID of the top-level namespace the user is acting on. |
|
||||
| <a id="mutationaiactionsummarizecomments"></a>`summarizeComments` | [`AiSummarizeCommentsInput`](#aisummarizecommentsinput) | Input for summarize_comments AI action. |
|
||||
| <a id="mutationaiactionsummarizenewmergerequest"></a>`summarizeNewMergeRequest` | [`AiSummarizeNewMergeRequestInput`](#aisummarizenewmergerequestinput) | Input for summarize_new_merge_request AI action. |
|
||||
| <a id="mutationaiactionsummarizereview"></a>`summarizeReview` | [`AiSummarizeReviewInput`](#aisummarizereviewinput) | Input for summarize_review AI action. |
|
||||
|
@ -37,3 +37,39 @@ There is no need to set up the backend components of Duo Workflow to test change
|
||||
A local build of the UI is required if you are making Duo Workflow UI changes that you need to view locally. A local build is also required if you want to use a version of the UI that has not been released yet.
|
||||
|
||||
Refer to the [GitLab Duo Workflow README](https://gitlab.com/gitlab-org/editor-extensions/gitlab-lsp/-/blob/main/packages/webview_duo_workflow/README.md) file in the Language Server project to get started with local development of GitLab Duo Workflow UI.
|
||||
|
||||
## Development settings
|
||||
|
||||
Each of these settings can be turned on in your user settings in VS Code.
|
||||
|
||||
### Change view type
|
||||
|
||||
Enable the Duo Workflow as a sidepanel instead of fullview. This is going to be the default for public beta.
|
||||
|
||||
`"gitlab.featureFlags.duoWorkflowPanel": true,`
|
||||
|
||||
### Executor type
|
||||
|
||||
Allow to define which Duo Workflow executor is selected. Accepts:
|
||||
|
||||
- `shell` - Current default, runs the go binary directly on the user's machine
|
||||
- `docker` - Runs the go binary inside a Docker container (deprecated)
|
||||
- `node` - Runs a nodeJs/TypeScript executor directly inside the languge server. Expected to become the default.
|
||||
|
||||
`"gitlab.duo.workflow.executor": "node",`
|
||||
|
||||
### Workflow graph
|
||||
|
||||
Experimental settings that allow Duo Workflow graph to be swapped. Includes:
|
||||
|
||||
- `software_development` - default
|
||||
- `chat` - used by agentic chat
|
||||
- `search_and_replace` - Used to scan large number of files and replace results with specific instructions
|
||||
|
||||
`"gitlab.duo.workflow.graph": "software_development",`
|
||||
|
||||
### Tool approval
|
||||
|
||||
Allow users to get access to tools that require approval such as running terminal commands.
|
||||
|
||||
`"gitlab.duo.workflow.toolApproval": true`
|
||||
|
@ -66,3 +66,23 @@ For example, if your IP is `192.168.0.12`:
|
||||
```shell
|
||||
bundle exec bin/qa Test::Instance::All http://192.168.0.12:3000
|
||||
```
|
||||
|
||||
## Tests sign out when visiting a page
|
||||
|
||||
If the tests sign in successfully as a test user, but then unexpectedly sign out, you might be using an
|
||||
incorrect URL to execute the test. By default, tests use the URL `http://127.0.0.1:3000`, but if a hostname
|
||||
has been configured for the instance, you must explicitly pass that hostname to the tests. The tests use
|
||||
the `web_url` returned by the API to go to different pages. They go to the configured hostname, rather than
|
||||
`http://127.0.0.1:3000`, so the test user appears signed out.
|
||||
|
||||
This example runs the tests against `http://127.0.0.1:3000`, and signs out if a hostname has been configured:
|
||||
|
||||
```shell
|
||||
bundle exec rspec qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb
|
||||
```
|
||||
|
||||
To avoid this, explicitly set `QA_GITLAB_URL` to the configured hostname, for example:
|
||||
|
||||
```shell
|
||||
QA_GITLAB_URL=http://gdk.test:3000 bundle exec rspec qa/specs/features/ee/browser_ui/3_create/repository/code_owners_spec.rb
|
||||
```
|
||||
|
@ -9,16 +9,6 @@ module Gitlab
|
||||
|
||||
# rubocop: disable CodeReuse/ActiveRecord
|
||||
def run
|
||||
unless Feature.enabled?(:revalidate_gpg_fingerprints, @gpg_key.user)
|
||||
return CommitSignatures::GpgSignature
|
||||
.select(:id, :commit_sha, :project_id)
|
||||
.where('gpg_key_id IS NULL OR verification_status <> ?', CommitSignatures::GpgSignature.verification_statuses[:verified])
|
||||
.where(gpg_key_primary_keyid: @gpg_key.keyids)
|
||||
.find_each do |sig|
|
||||
sig.gpg_commit&.update_signature!(sig)
|
||||
end
|
||||
end
|
||||
|
||||
[@gpg_key].concat(@gpg_key.subkeys).each do |key|
|
||||
Gitlab::Gpg.using_tmp_keychain do
|
||||
Gitlab::Gpg::CurrentKeyChain.add(key.key)
|
||||
|
@ -42,7 +42,7 @@ namespace :tw do
|
||||
# CodeOwnerRule.new('Database Operations', ''),
|
||||
# CodeOwnerRule.new('DataOps', ''),
|
||||
# CodeOwnerRule.new('Delivery', ''),
|
||||
# CodeOwnerRule.new('Durability', ''),
|
||||
CodeOwnerRule.new('Durability', '@axil'),
|
||||
CodeOwnerRule.new('Duo Chat', '@jglassman1'),
|
||||
CodeOwnerRule.new('Duo Workflow', '@sselhorn'),
|
||||
CodeOwnerRule.new('Dynamic Analysis', '@phillipwells'),
|
||||
|
@ -16748,6 +16748,42 @@ msgstr ""
|
||||
msgid "Components must have a 'name'"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|Change GitLab Password"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|Failure to change your password may lead to temporary account access restrictions to prevent potential unauthorized access."
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|For your security, we also recommend enabling %{mfa_link} if you have not done so already."
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|GitLab documentation"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|Instructions for changing your password can be found in the %{doc_link}."
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|Please change your password immediately."
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|Security Alert: Change Your GitLab.com Password"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|Security Alert: Change your GitLab password"
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|The password used for your GitLab.com account %{code_start}%{username}%{code_end} may have been compromised due to a data breach on another service or platform. This does not necessarily mean that your GitLab account was accessed. However, leaving your password unchanged presents a significant security risk to your account."
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|The password used for your GitLab.com account %{username} may have been compromised due to a data breach on another service or platform. This does not necessarily mean that your GitLab account was accessed. However, leaving your password unchanged presents a significant security risk to your account."
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|Your GitLab.com account password may be compromised due to a data breach on another service or platform. Please change your password immediately."
|
||||
msgstr ""
|
||||
|
||||
msgid "CompromisedPasswordDetection|two-factor authentication"
|
||||
msgstr ""
|
||||
|
||||
msgid "Compute minutes"
|
||||
msgstr ""
|
||||
|
||||
@ -27745,9 +27781,6 @@ msgstr ""
|
||||
msgid "Geo|Resync all"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Retry count"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Reverify"
|
||||
msgstr ""
|
||||
|
||||
@ -27793,9 +27826,6 @@ msgstr ""
|
||||
msgid "Geo|Shards to synchronize"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Show more"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Site name can't be blank"
|
||||
msgstr ""
|
||||
|
||||
@ -27829,9 +27859,6 @@ msgstr ""
|
||||
msgid "Geo|Synchronization"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Synchronization failed - %{error}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Synchronization settings"
|
||||
msgstr ""
|
||||
|
||||
@ -27928,9 +27955,6 @@ msgstr ""
|
||||
msgid "Geo|Verification concurrency limit"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Verification failed - %{error}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Geo|Verification information"
|
||||
msgstr ""
|
||||
|
||||
@ -28195,6 +28219,9 @@ msgstr ""
|
||||
msgid "GitLab Premium"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab Security"
|
||||
msgstr ""
|
||||
|
||||
msgid "GitLab Shell"
|
||||
msgstr ""
|
||||
|
||||
@ -61139,6 +61166,9 @@ msgstr ""
|
||||
msgid "Thank you for your support request! We are tracking your request as ticket #%{issue_iid}, and will respond as soon as we can."
|
||||
msgstr ""
|
||||
|
||||
msgid "Thank you,"
|
||||
msgstr ""
|
||||
|
||||
msgid "Thanks for signing up to GitLab!"
|
||||
msgstr ""
|
||||
|
||||
|
@ -72,7 +72,7 @@ RSpec.configure do |config|
|
||||
begin
|
||||
Capybara.current_session.execute_script("window.__coveragePathsPersistence.reset()")
|
||||
rescue StandardError => e
|
||||
QA::Runtime::Logger.warn("Failed to reset coverage paths: #{e.message}")
|
||||
QA::Runtime::Logger.warn("Failed to reset coverage paths, check if it is an api spec: #{e.message}")
|
||||
end
|
||||
end
|
||||
|
||||
@ -111,7 +111,7 @@ RSpec.configure do |config|
|
||||
example.metadata[:coverage_paths] = coverage_paths
|
||||
front_end_coverage_by_example[example.metadata[:location]] = coverage_paths
|
||||
rescue StandardError => e
|
||||
QA::Runtime::Logger.warn("Failed to collect coverage paths: #{e.message}")
|
||||
QA::Runtime::Logger.warn("Failed to collect coverage paths, check if it is an api spec: #{e.message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -9,17 +9,24 @@ module QA
|
||||
include Helpers
|
||||
|
||||
PROJECT = "gitlab-qa-resources"
|
||||
BUCKET = "code-path-mappings"
|
||||
DEFAULT_BUCKET = "code-path-mappings"
|
||||
DEFAULT_FILE_NAME = "test-code-paths-mapping-merged-pipeline"
|
||||
|
||||
def self.export(mapping_files_glob)
|
||||
new.export(mapping_files_glob)
|
||||
def self.export(mapping_files_glob, **kwargs)
|
||||
if kwargs.key?(:bucket) || kwargs.key?(:file_name)
|
||||
new.export(mapping_files_glob, **kwargs)
|
||||
else
|
||||
new.export(mapping_files_glob)
|
||||
end
|
||||
end
|
||||
|
||||
# Export code path mappings to GCP
|
||||
#
|
||||
# @param [String] mapping_files_glob - glob pattern for mapping files
|
||||
# @param [String] bucket - custom bucket name (optional)
|
||||
# @param [String] file_name - custom file name (optional)
|
||||
# @return [void]
|
||||
def export(mapping_files_glob)
|
||||
def export(mapping_files_glob, bucket: DEFAULT_BUCKET, file_name: DEFAULT_FILE_NAME)
|
||||
mapping_files = Dir.glob(mapping_files_glob)
|
||||
return logger.warn("No files matched pattern, skipping coverage mapping upload") if mapping_files.empty?
|
||||
|
||||
@ -30,21 +37,25 @@ module QA
|
||||
logger.info("Number of mapping files found: #{mapping_files.size}")
|
||||
|
||||
mapping_data = mapping_files.flat_map { |file| JSON.parse(File.read(file)) }.reduce(:merge!)
|
||||
file = "#{ENV['CI_COMMIT_REF_SLUG']}/#{ENV['QA_RUN_TYPE']}/test-code-paths-mapping-merged-pipeline-#{\
|
||||
file = "#{ENV['CI_COMMIT_REF_SLUG']}/#{ENV['QA_RUN_TYPE']}/#{file_name}-#{\
|
||||
ENV['CI_PIPELINE_ID'] || 'local'}.json"
|
||||
upload_to_gcs(file, mapping_data)
|
||||
upload_to_gcs(file, mapping_data, bucket)
|
||||
end
|
||||
|
||||
# Import code path mappings from GCP
|
||||
#
|
||||
# @param [String] branch - branch name
|
||||
# @param [String] run_type - run type
|
||||
# @param [String] bucket - custom bucket name (optional)
|
||||
# @param [String] file_name - custom file name base (optional)
|
||||
# @return [Hash]
|
||||
def import(branch, run_type)
|
||||
filename = code_paths_mapping_file("#{branch}/#{run_type}")
|
||||
def import(branch, run_type, bucket: DEFAULT_BUCKET, file_name: DEFAULT_FILE_NAME)
|
||||
prefix = "#{branch}/#{run_type}/#{file_name}"
|
||||
|
||||
filename = code_paths_mapping_file(prefix, bucket)
|
||||
|
||||
logger.info("The mapping file fetched in import: #{filename}")
|
||||
file = client.get_object(BUCKET, filename)
|
||||
file = client.get_object(bucket, filename)
|
||||
JSON.parse(file[:body])
|
||||
rescue StandardError => e
|
||||
logger.error("Failed to download code paths mapping from GCS. Error: #{e}")
|
||||
@ -54,8 +65,9 @@ module QA
|
||||
|
||||
private
|
||||
|
||||
def upload_to_gcs(file_name, mapping_data)
|
||||
client.put_object(BUCKET, file_name, JSON.pretty_generate(mapping_data))
|
||||
def upload_to_gcs(file_name, mapping_data, bucket)
|
||||
client.put_object(bucket, file_name, JSON.pretty_generate(mapping_data))
|
||||
logger.info("Successfully uploaded to bucket: #{bucket}, file: #{file_name}")
|
||||
rescue StandardError => e
|
||||
logger.error("Failed to upload code paths mapping to GCS. Error: #{e}")
|
||||
logger.error("Backtrace: #{e.backtrace}")
|
||||
@ -85,8 +97,8 @@ module QA
|
||||
#
|
||||
# Get most up to date mapping file based on pipeline type
|
||||
# @return [String]
|
||||
def code_paths_mapping_file(prefix)
|
||||
paginated_list(client.list_objects(BUCKET, prefix: prefix)).last&.name
|
||||
def code_paths_mapping_file(prefix, bucket = DEFAULT_BUCKET)
|
||||
paginated_list(client.list_objects(bucket, prefix: prefix)).last&.name
|
||||
end
|
||||
|
||||
# Paginated list of items
|
||||
@ -98,7 +110,7 @@ module QA
|
||||
return list.items if list.next_page_token.nil?
|
||||
|
||||
paginated_list(
|
||||
client.list_objects(BUCKET, prefix: list.prefixes.first, page_token: list.next_page_token)
|
||||
client.list_objects(list.bucket, prefix: list.prefixes.first, page_token: list.next_page_token)
|
||||
) + list.items
|
||||
end
|
||||
end
|
||||
|
@ -51,6 +51,29 @@ RSpec.describe QA::Tools::Ci::CodePathsMapping do
|
||||
end
|
||||
end
|
||||
|
||||
context "with bucket and file name prefix passed as arguments" do
|
||||
let(:new_bucket) { "new_bucket" }
|
||||
let(:upload_file_name) { "new_name_prefix" }
|
||||
let(:expected_file_path) { "#{commit_ref}/#{run_type}/#{upload_file_name}-#{ENV['CI_PIPELINE_ID']}.json" }
|
||||
|
||||
it "exports mapping json with correct file name prefix" do
|
||||
expect(logger).to receive(:info).with("Number of mapping files found: #{file_paths.size}")
|
||||
expect(gcs_client).to receive(:put_object).with(new_bucket, expected_file_path, pretty_generated_mapping_json)
|
||||
described_class.export(glob, bucket: new_bucket, file_name: upload_file_name)
|
||||
end
|
||||
end
|
||||
|
||||
context "with bucket and file name prefix not passed as arguments" do
|
||||
let(:default_bucket) { QA::Tools::Ci::CodePathsMapping::DEFAULT_BUCKET }
|
||||
let(:default_filename) { QA::Tools::Ci::CodePathsMapping::DEFAULT_FILE_NAME }
|
||||
let(:expected_file_path) { "#{commit_ref}/#{run_type}/#{default_filename}-#{ENV['CI_PIPELINE_ID']}.json" }
|
||||
|
||||
it "exports mapping json with default file name prefix to default bucket" do
|
||||
expect(gcs_client).to receive(:put_object).with(default_bucket, expected_file_path, pretty_generated_mapping_json)
|
||||
described_class.export(glob)
|
||||
end
|
||||
end
|
||||
|
||||
context "with no mapping files present" do
|
||||
let(:file_paths) { [] }
|
||||
|
||||
|
@ -103,10 +103,18 @@ namespace :ci do
|
||||
QA::Tools::Ci::TestMetrics.export(args[:glob])
|
||||
end
|
||||
|
||||
desc "Export code paths mapping to GCP"
|
||||
desc "Export backend code paths mapping to GCP"
|
||||
task :export_code_paths_mapping, [:glob] do |_, args|
|
||||
raise("Code paths mapping JSON glob pattern is required") unless args[:glob]
|
||||
|
||||
QA::Tools::Ci::CodePathsMapping.export(args[:glob])
|
||||
end
|
||||
|
||||
desc "Export frontend code paths mapping to GCP"
|
||||
task :export_code_paths_mapping, [:glob] do |_, args|
|
||||
raise("Code paths mapping JSON glob pattern is required") unless args[:glob]
|
||||
|
||||
QA::Tools::Ci::CodePathsMapping.export(args[:glob], bucket: "code-path-mappings",
|
||||
file_name: "js-coverage-by-example-merged-pipeline")
|
||||
end
|
||||
end
|
||||
|
@ -2,8 +2,11 @@
|
||||
import htmlStaticLineHighlighter from 'test_fixtures_static/line_highlighter.html';
|
||||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
import LineHighlighter from '~/blob/line_highlighter';
|
||||
import { updateHash } from '~/blob/state';
|
||||
import * as utils from '~/lib/utils/common_utils';
|
||||
|
||||
jest.mock('~/blob/state');
|
||||
|
||||
describe('LineHighlighter', () => {
|
||||
const testContext = {};
|
||||
|
||||
@ -283,5 +286,17 @@ describe('LineHighlighter', () => {
|
||||
|
||||
expect(testContext.spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15');
|
||||
});
|
||||
|
||||
it('calls updateHash with the correct hash for a single line', () => {
|
||||
testContext.subject(5);
|
||||
|
||||
expect(updateHash).toHaveBeenCalledWith('#L5');
|
||||
});
|
||||
|
||||
it('calls updateHash with the correct hash for a range', () => {
|
||||
testContext.subject(5, 15);
|
||||
|
||||
expect(updateHash).toHaveBeenCalledWith('#L5-15');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -9,7 +9,6 @@ import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { blobControlsDataMock } from 'ee_else_ce_jest/repository/mock_data';
|
||||
|
||||
jest.mock('~/behaviors/shortcuts/shortcuts_toggle');
|
||||
jest.mock('~/blob/state');
|
||||
|
||||
const relativePermalinkPath =
|
||||
'flightjs/Flight/-/blob/46ca9ebd5a43ec240ee8d64e2bb829169dff744e/bower.json';
|
||||
|
@ -4,7 +4,7 @@ import PermalinkDropdownItem from '~/repository/components/header_area/permalink
|
||||
import { keysFor, PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
|
||||
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
|
||||
import { Mousetrap } from '~/lib/mousetrap';
|
||||
import { hashState } from '~/blob/state';
|
||||
import { hashState, updateHash } from '~/blob/state';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
|
||||
|
||||
@ -46,6 +46,24 @@ describe('PermalinkDropdownItem', () => {
|
||||
expect(findPermalinkLinkDropdown().exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('hash change handling', () => {
|
||||
it('calls updateHash when hash changes', () => {
|
||||
window.location.hash = 'L42';
|
||||
createComponent();
|
||||
window.dispatchEvent(new Event('hashchange'));
|
||||
|
||||
expect(updateHash).toHaveBeenCalledWith('#L42');
|
||||
});
|
||||
|
||||
it('handles empty hash correctly', () => {
|
||||
window.location.hash = '';
|
||||
createComponent();
|
||||
window.dispatchEvent(new Event('hashchange'));
|
||||
|
||||
expect(updateHash).toHaveBeenCalledWith('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('updatedPermalinkPath', () => {
|
||||
it('returns absolutePermalinkPath when no line number is set', () => {
|
||||
expect(findPermalinkLinkDropdown().attributes('data-clipboard-text')).toBe(
|
||||
@ -54,7 +72,7 @@ describe('PermalinkDropdownItem', () => {
|
||||
});
|
||||
|
||||
it('returns updated path with line number when set', () => {
|
||||
hashState.currentHash = 10;
|
||||
hashState.currentHash = '#L10';
|
||||
createComponent();
|
||||
|
||||
expect(findPermalinkLinkDropdown().attributes('data-clipboard-text')).toBe(
|
||||
@ -158,6 +176,17 @@ describe('PermalinkDropdownItem', () => {
|
||||
wrapper.destroy();
|
||||
expect(unbindSpy).toHaveBeenCalledWith(keysFor(PROJECT_FILES_GO_TO_PERMALINK));
|
||||
});
|
||||
|
||||
it('add and remove event listener for hashChange event', () => {
|
||||
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
|
||||
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
|
||||
|
||||
createComponent();
|
||||
expect(addEventListenerSpy).toHaveBeenCalledWith('hashchange', expect.any(Function));
|
||||
|
||||
wrapper.destroy();
|
||||
expect(removeEventListenerSpy).toHaveBeenCalledWith('hashchange', expect.any(Function));
|
||||
});
|
||||
});
|
||||
|
||||
it('displays the shortcut key when shortcuts are not disabled', () => {
|
||||
|
@ -0,0 +1,75 @@
|
||||
import { getAbsolutePermalinkPath } from '~/repository/components/header_area/utils';
|
||||
import * as urlUtility from '~/lib/utils/url_utility';
|
||||
import * as blobUtils from '~/blob/utils';
|
||||
|
||||
describe('getAbsolutePermalinkPath', () => {
|
||||
const permalinkPath = '/project/repo/-/blob/main/file.js';
|
||||
const baseUrl = 'https://gitlab.example.com';
|
||||
const absolutePath = 'https://gitlab.example.com/project/repo/-/blob/main/file.js';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.spyOn(urlUtility, 'getBaseURL').mockReturnValue(baseUrl);
|
||||
jest.spyOn(urlUtility, 'relativePathToAbsolute').mockReturnValue(absolutePath);
|
||||
jest.spyOn(blobUtils, 'getPageParamValue').mockReturnValue(null);
|
||||
jest.spyOn(blobUtils, 'getPageSearchString').mockReturnValue('');
|
||||
});
|
||||
|
||||
describe('when hash is not provided', () => {
|
||||
it.each([
|
||||
['null', null],
|
||||
['empty string', ''],
|
||||
['undefined', undefined],
|
||||
])('returns absolute path when hash is %s', (_, hash) => {
|
||||
expect(getAbsolutePermalinkPath(permalinkPath, hash)).toBe(absolutePath);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when handling different hash formats', () => {
|
||||
it.each([
|
||||
['line number format', '#L6', '#L6'],
|
||||
['line number range format', '#L10-19', '#L10-19'],
|
||||
[
|
||||
'anchor hash',
|
||||
'#developer-certificate-of-origin--license',
|
||||
'#developer-certificate-of-origin--license',
|
||||
],
|
||||
])('handles %s (%s)', (_, hash, expectedHash) => {
|
||||
expect(getAbsolutePermalinkPath(permalinkPath, hash)).toBe(`${absolutePath}${expectedHash}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when hash normalization is needed', () => {
|
||||
it.each([
|
||||
['line number', 'L6', '#L6'],
|
||||
['line number range', 'L10-19', '#L10-19'],
|
||||
[
|
||||
'complex anchor',
|
||||
'developer-certificate-of-origin--license',
|
||||
'#developer-certificate-of-origin--license',
|
||||
],
|
||||
])('normalizes %s hash by adding # prefix when missing', (_, hash, expectedHash) => {
|
||||
expect(getAbsolutePermalinkPath(permalinkPath, hash)).toBe(`${absolutePath}${expectedHash}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when page parameters are present', () => {
|
||||
const searchString = '?plain=1';
|
||||
|
||||
beforeEach(() => {
|
||||
blobUtils.getPageParamValue.mockReturnValue('1');
|
||||
blobUtils.getPageSearchString.mockReturnValue(searchString);
|
||||
});
|
||||
|
||||
it.each([
|
||||
['with # prefix', '#L6', '#L6'],
|
||||
['without # prefix', 'L20', '#L20'],
|
||||
['with empty hash', '', ''],
|
||||
['with null hash', null, ''],
|
||||
['with undefined hash', undefined, ''],
|
||||
])('includes search string when hash is %s', (_, hash, expectedHash) => {
|
||||
expect(getAbsolutePermalinkPath(permalinkPath, hash)).toBe(
|
||||
`${absolutePath}${searchString}${expectedHash}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
@ -5,11 +5,9 @@ import { GlIntersectionObserver } from '@gitlab/ui';
|
||||
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||
import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
|
||||
import { addInteractionClass } from '~/code_navigation/utils';
|
||||
import { updateLineNumber } from '~/blob/state';
|
||||
import { CHUNK_1, CHUNK_2 } from '../mock_data';
|
||||
|
||||
jest.mock('~/code_navigation/utils');
|
||||
jest.mock('~/blob/state');
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
@ -103,18 +101,5 @@ describe('Chunk component', () => {
|
||||
|
||||
expect(addInteractionClass).toHaveBeenCalledWith({ d: 'test', path: 'index.js' });
|
||||
});
|
||||
|
||||
it('calls updateLineNumber with the correct line number when a line is clicked', async () => {
|
||||
createComponent({ ...CHUNK_2, isHighlighted: true });
|
||||
|
||||
const lineNumber = '71';
|
||||
const lineNumberElement = wrapper.find(`[data-line-number="${lineNumber}"]`);
|
||||
|
||||
expect(lineNumberElement.exists()).toBe(true);
|
||||
|
||||
await lineNumberElement.trigger('click');
|
||||
|
||||
expect(updateLineNumber).toHaveBeenCalledWith(lineNumber);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -67,23 +67,6 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater, :sidekiq_inline do
|
||||
)
|
||||
end
|
||||
|
||||
it 'assigns the gpg key even with revalidate_gpg_fingerprints feature flag disabled' do
|
||||
stub_feature_flags(revalidate_gpg_fingerprints: false)
|
||||
|
||||
# InvalidGpgSignatureUpdater is called by the after_create hook
|
||||
gpg_key = create :gpg_key,
|
||||
key: GpgHelpers::User1.public_key,
|
||||
user: user
|
||||
|
||||
expect(valid_gpg_signature.reload).to have_attributes(
|
||||
project: project,
|
||||
commit_sha: commit_sha,
|
||||
gpg_key: gpg_key,
|
||||
gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid,
|
||||
verification_status: 'verified'
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not assign the gpg key when an unrelated gpg key is added' do
|
||||
# InvalidGpgSignatureUpdater is called by the after_create hook
|
||||
create :gpg_key,
|
||||
@ -127,23 +110,6 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater, :sidekiq_inline do
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not update the signature when revalidate_gpg_fingerprints feature flag is disabled' do
|
||||
stub_feature_flags(revalidate_gpg_fingerprints: false)
|
||||
|
||||
# InvalidGpgSignatureUpdater is called by the after_create hook
|
||||
create :gpg_key,
|
||||
key: GpgHelpers::User1.public_key,
|
||||
user: user
|
||||
|
||||
expect(invalid_gpg_signature.reload).to have_attributes(
|
||||
project: project,
|
||||
commit_sha: commit_sha,
|
||||
gpg_key: nil,
|
||||
gpg_key_primary_keyid: GpgHelpers::User1.fingerprint,
|
||||
verification_status: 'unknown_key'
|
||||
)
|
||||
end
|
||||
|
||||
it 'keeps the signature at being invalid when an unrelated gpg key is added' do
|
||||
# InvalidGpgSignatureUpdater is called by the after_create hook
|
||||
create :gpg_key,
|
||||
|
@ -8,7 +8,8 @@
|
||||
- Groups::EnvironmentScopesFinder # Reason: There is no need to have anything else besides the simple strucutre with the scope name
|
||||
- Security::RelatedPipelinesFinder # Reason: There is no need to have anything else besides the IDs of pipelines
|
||||
- Llm::ExtraResourceFinder # Reason: The finder does not deal with DB-backend resource for now.
|
||||
- Security::VulnerabilityReadsElasticFinder # Reason: The finder deals with Elasticsearch records and not DB records
|
||||
- Security::VulnerabilityElasticFinder # Reason: The finder deals with Elasticsearch records and not DB records
|
||||
- Security::VulnerabilityElasticAggregationFinder # Reason: The finder deals with Elasticsearch records and not DB records
|
||||
|
||||
# Temporary excludes (aka TODOs)
|
||||
# For example:
|
||||
|
Reference in New Issue
Block a user