mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-07-29 12:00:32 +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/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/merge_requests/blocking_mr_input_root.vue',
|
||||||
'ee/app/assets/javascripts/projects/settings/branch_rules/components/view/index.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/current_day_indicator.vue',
|
||||||
'ee/app/assets/javascripts/roadmap/components/epic_item.vue',
|
'ee/app/assets/javascripts/roadmap/components/epic_item.vue',
|
||||||
'ee/app/assets/javascripts/roadmap/components/epic_item_details.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/application_settings_cache.md @jglassman1
|
||||||
/doc/administration/auditor_users.md @idurham
|
/doc/administration/auditor_users.md @idurham
|
||||||
/doc/administration/auth/ @idurham
|
/doc/administration/auth/ @idurham
|
||||||
|
/doc/administration/backup_restore/ @axil
|
||||||
/doc/administration/broadcast_messages.md @sselhorn
|
/doc/administration/broadcast_messages.md @sselhorn
|
||||||
/doc/administration/cells.md @emily.sahlani
|
/doc/administration/cells.md @emily.sahlani
|
||||||
/doc/administration/cicd/ @lyspin
|
/doc/administration/cicd/ @lyspin
|
||||||
@ -658,7 +659,7 @@ lib/gitlab/checks/**
|
|||||||
/doc/administration/raketasks/tokens/ @idurham
|
/doc/administration/raketasks/tokens/ @idurham
|
||||||
/doc/administration/raketasks/x509_signatures.md @brendan777
|
/doc/administration/raketasks/x509_signatures.md @brendan777
|
||||||
/doc/administration/read_only_gitlab.md @axil @eread
|
/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/reference_architectures/ @axil @eread
|
||||||
/doc/administration/reply_by_email.md @lciutacu
|
/doc/administration/reply_by_email.md @lciutacu
|
||||||
/doc/administration/reply_by_email_postfix_setup.md @axil @eread
|
/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/third_party_offers.md @phillipwells
|
||||||
/doc/administration/settings/visibility_and_access_controls.md @brendan777
|
/doc/administration/settings/visibility_and_access_controls.md @brendan777
|
||||||
/doc/administration/settings/vscode_extension_marketplace.md @brendan777
|
/doc/administration/settings/vscode_extension_marketplace.md @brendan777
|
||||||
/doc/administration/sidekiq/ @axil @eread
|
/doc/administration/sidekiq/ @axil
|
||||||
/doc/administration/sidekiq/sidekiq_memory_killer.md @jglassman1
|
|
||||||
/doc/administration/silent_mode/ @axil
|
/doc/administration/silent_mode/ @axil
|
||||||
/doc/administration/smime_signing_email.md @axil @eread
|
/doc/administration/smime_signing_email.md @axil @eread
|
||||||
/doc/administration/snippets/ @brendan777
|
/doc/administration/snippets/ @brendan777
|
||||||
@ -953,6 +953,7 @@ lib/gitlab/checks/**
|
|||||||
/doc/development/cascading_settings.md @gitlab-org/foundations/engineering
|
/doc/development/cascading_settings.md @gitlab-org/foundations/engineering
|
||||||
/doc/development/cells/ @OmarQunsulGitlab @bmarjanovic
|
/doc/development/cells/ @OmarQunsulGitlab @bmarjanovic
|
||||||
/doc/development/cicd/ @gitlab-org/maintainers/cicd-verify
|
/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/contributing/verify/ @gitlab-org/maintainers/cicd-verify
|
||||||
/doc/development/database/ @OmarQunsulGitlab @bmarjanovic
|
/doc/development/database/ @OmarQunsulGitlab @bmarjanovic
|
||||||
/doc/development/distributed_tracing.md @gitlab-org/analytics-section/product-analytics/engineers/frontend
|
/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
|
- .qa:rules:test-on-omnibus-ce:follow-up
|
||||||
|
|
||||||
compile-test-assets:
|
compile-test-assets:
|
||||||
|
variables:
|
||||||
|
BABEL_ENV: "$BABEL_ENV"
|
||||||
extends:
|
extends:
|
||||||
- .compile-assets-base
|
- .compile-assets-base
|
||||||
- .frontend:rules:compile-test-assets
|
- .frontend:rules:compile-test-assets
|
||||||
|
@ -2025,6 +2025,8 @@
|
|||||||
variables:
|
variables:
|
||||||
<<: *qa-e2e-test-schedule-variables
|
<<: *qa-e2e-test-schedule-variables
|
||||||
COVERBAND_ENABLED: "true"
|
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_RUN_IN_PARALLEL: "false"
|
||||||
|
|
||||||
.qa:rules:e2e:test-on-cng:
|
.qa:rules:e2e:test-on-cng:
|
||||||
|
@ -56,6 +56,7 @@ workflow:
|
|||||||
job: clone-gitlab-repo
|
job: clone-gitlab-repo
|
||||||
variables:
|
variables:
|
||||||
COVERBAND_ENABLED: "$COVERBAND_ENABLED" # explicitly define variable so it is passed in to gdk service container
|
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_CRON_JOBS_POLL_INTERVAL: "0"
|
||||||
GITLAB_DEVELOPMENT_USE_PRECOMPILED_ASSETS: "true"
|
GITLAB_DEVELOPMENT_USE_PRECOMPILED_ASSETS: "true"
|
||||||
GITLAB_QA_ADMIN_ACCESS_TOKEN: $QA_ADMIN_ACCESS_TOKEN
|
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 */
|
/* eslint-disable func-names, no-underscore-dangle, no-param-reassign, consistent-return */
|
||||||
|
|
||||||
import { scrollToElement } from '~/lib/utils/common_utils';
|
import { scrollToElement } from '~/lib/utils/common_utils';
|
||||||
|
import { updateHash } from '~/blob/state';
|
||||||
|
|
||||||
// LineHighlighter
|
// LineHighlighter
|
||||||
//
|
//
|
||||||
@ -174,6 +175,7 @@ LineHighlighter.prototype.setHash = function (firstLineNumber, lastLineNumber) {
|
|||||||
hash = `#L${firstLineNumber}`;
|
hash = `#L${firstLineNumber}`;
|
||||||
}
|
}
|
||||||
this._hash = hash;
|
this._hash = hash;
|
||||||
|
updateHash(hash);
|
||||||
return this.__setLocationHash__(hash);
|
return this.__setLocationHash__(hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,6 +4,6 @@ export const hashState = Vue.observable({
|
|||||||
currentHash: window.location.hash,
|
currentHash: window.location.hash,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateLineNumber = (lineNumber) => {
|
export const updateHash = (newHash) => {
|
||||||
hashState.currentHash = lineNumber;
|
hashState.currentHash = newHash;
|
||||||
};
|
};
|
||||||
|
@ -6,9 +6,8 @@ import { InternalEvents } from '~/tracking';
|
|||||||
import { keysFor, PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
|
import { keysFor, PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
|
||||||
import { Mousetrap } from '~/lib/mousetrap';
|
import { Mousetrap } from '~/lib/mousetrap';
|
||||||
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
|
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
|
||||||
import { getBaseURL, relativePathToAbsolute } from '~/lib/utils/url_utility';
|
import { hashState, updateHash } from '~/blob/state';
|
||||||
import { hashState } from '~/blob/state';
|
import { getAbsolutePermalinkPath } from './utils';
|
||||||
import { getPageParamValue, getPageSearchString } from '~/blob/utils';
|
|
||||||
|
|
||||||
Vue.use(GlToast);
|
Vue.use(GlToast);
|
||||||
|
|
||||||
@ -41,26 +40,22 @@ export default {
|
|||||||
return shouldDisableShortcuts();
|
return shouldDisableShortcuts();
|
||||||
},
|
},
|
||||||
absolutePermalinkPath() {
|
absolutePermalinkPath() {
|
||||||
const baseAbsolutePath = relativePathToAbsolute(this.permalinkPath, getBaseURL());
|
return getAbsolutePermalinkPath(this.permalinkPath, hashState.currentHash);
|
||||||
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;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.mousetrap = new Mousetrap();
|
this.mousetrap = new Mousetrap();
|
||||||
this.mousetrap.bind(keysFor(PROJECT_FILES_GO_TO_PERMALINK), this.triggerCopyPermalink);
|
this.mousetrap.bind(keysFor(PROJECT_FILES_GO_TO_PERMALINK), this.triggerCopyPermalink);
|
||||||
|
window.addEventListener('hashchange', this.onHashChange);
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.mousetrap.unbind(keysFor(PROJECT_FILES_GO_TO_PERMALINK));
|
this.mousetrap.unbind(keysFor(PROJECT_FILES_GO_TO_PERMALINK));
|
||||||
|
window.removeEventListener('hashchange', this.onHashChange);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
onHashChange() {
|
||||||
|
updateHash(window.location.hash || '');
|
||||||
|
},
|
||||||
triggerCopyPermalink() {
|
triggerCopyPermalink() {
|
||||||
const buttonElement = this.$refs.copyPermalinkButton.$el;
|
const buttonElement = this.$refs.copyPermalinkButton.$el;
|
||||||
buttonElement.click();
|
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 SafeHtml from '~/vue_shared/directives/safe_html';
|
||||||
import { getPageParamValue, getPageSearchString } from '~/blob/utils';
|
import { getPageParamValue, getPageSearchString } from '~/blob/utils';
|
||||||
import { addInteractionClass } from '~/code_navigation/utils';
|
import { addInteractionClass } from '~/code_navigation/utils';
|
||||||
import { updateLineNumber } from '~/blob/state';
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We only highlight the chunk that is currently visible to the user.
|
* We only highlight the chunk that is currently visible to the user.
|
||||||
@ -105,10 +104,6 @@ export default {
|
|||||||
this.hasAppeared = true;
|
this.hasAppeared = true;
|
||||||
this.$emit('appear');
|
this.$emit('appear');
|
||||||
},
|
},
|
||||||
handleOnClick(event) {
|
|
||||||
const { lineNumber } = event.target.dataset;
|
|
||||||
updateLineNumber(lineNumber);
|
|
||||||
},
|
|
||||||
calculateLineNumber(index) {
|
calculateLineNumber(index) {
|
||||||
return this.startingFrom + index + 1;
|
return this.startingFrom + index + 1;
|
||||||
},
|
},
|
||||||
@ -148,7 +143,6 @@ export default {
|
|||||||
class="file-line-num gl-select-none !gl-shadow-none"
|
class="file-line-num gl-select-none !gl-shadow-none"
|
||||||
:href="`#L${calculateLineNumber(index)}`"
|
:href="`#L${calculateLineNumber(index)}`"
|
||||||
:data-line-number="calculateLineNumber(index)"
|
:data-line-number="calculateLineNumber(index)"
|
||||||
@click="handleOnClick"
|
|
||||||
>
|
>
|
||||||
{{ calculateLineNumber(index) }}
|
{{ calculateLineNumber(index) }}
|
||||||
</a>
|
</a>
|
||||||
|
@ -7,10 +7,9 @@
|
|||||||
= render 'shared/new_project_item_vue_select'
|
= render 'shared/new_project_item_vue_select'
|
||||||
|
|
||||||
- if @milestone_states.any? { |name, count| count > 0 }
|
- 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
|
= render 'shared/milestones_filter', counts: @milestone_states
|
||||||
.nav-controls
|
= render 'shared/milestones/search_form'
|
||||||
= render 'shared/milestones/search_form'
|
|
||||||
|
|
||||||
- if @milestones.blank?
|
- if @milestones.blank?
|
||||||
= render 'shared/empty_states/milestones_tab', active_tab: params[:state] do
|
= 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/new_user_signups_cap_reached_alert"
|
||||||
= dispensable_render_if_exists "shared/silent_mode_banner"
|
= dispensable_render_if_exists "shared/silent_mode_banner"
|
||||||
= dispensable_render_if_exists "shared/pipl_compliance_alert"
|
= dispensable_render_if_exists "shared/pipl_compliance_alert"
|
||||||
|
= dispensable_render_if_exists "shared/compromised_password_detection_alert"
|
||||||
= yield :page_level_alert
|
= yield :page_level_alert
|
||||||
|
|
||||||
-# Top bar
|
-# Top bar
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
const coreJSVersion = require('./node_modules/core-js/package.json').version;
|
const coreJSVersion = require('./node_modules/core-js/package.json').version;
|
||||||
|
|
||||||
|
console.debug(`BABEL_ENV inside Babel config is: ${process.env.BABEL_ENV}`);
|
||||||
|
|
||||||
let presets = [
|
let presets = [
|
||||||
[
|
[
|
||||||
'@babel/preset-env',
|
'@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;
|
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 devtool = IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map';
|
||||||
|
|
||||||
const incrementalCompiler = createIncrementalWebpackCompiler(
|
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="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="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="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="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="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. |
|
| <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.
|
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.
|
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
|
```shell
|
||||||
bundle exec bin/qa Test::Instance::All http://192.168.0.12:3000
|
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
|
# rubocop: disable CodeReuse/ActiveRecord
|
||||||
def run
|
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|
|
[@gpg_key].concat(@gpg_key.subkeys).each do |key|
|
||||||
Gitlab::Gpg.using_tmp_keychain do
|
Gitlab::Gpg.using_tmp_keychain do
|
||||||
Gitlab::Gpg::CurrentKeyChain.add(key.key)
|
Gitlab::Gpg::CurrentKeyChain.add(key.key)
|
||||||
|
@ -42,7 +42,7 @@ namespace :tw do
|
|||||||
# CodeOwnerRule.new('Database Operations', ''),
|
# CodeOwnerRule.new('Database Operations', ''),
|
||||||
# CodeOwnerRule.new('DataOps', ''),
|
# CodeOwnerRule.new('DataOps', ''),
|
||||||
# CodeOwnerRule.new('Delivery', ''),
|
# CodeOwnerRule.new('Delivery', ''),
|
||||||
# CodeOwnerRule.new('Durability', ''),
|
CodeOwnerRule.new('Durability', '@axil'),
|
||||||
CodeOwnerRule.new('Duo Chat', '@jglassman1'),
|
CodeOwnerRule.new('Duo Chat', '@jglassman1'),
|
||||||
CodeOwnerRule.new('Duo Workflow', '@sselhorn'),
|
CodeOwnerRule.new('Duo Workflow', '@sselhorn'),
|
||||||
CodeOwnerRule.new('Dynamic Analysis', '@phillipwells'),
|
CodeOwnerRule.new('Dynamic Analysis', '@phillipwells'),
|
||||||
|
@ -16748,6 +16748,42 @@ msgstr ""
|
|||||||
msgid "Components must have a 'name'"
|
msgid "Components must have a 'name'"
|
||||||
msgstr ""
|
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"
|
msgid "Compute minutes"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -27745,9 +27781,6 @@ msgstr ""
|
|||||||
msgid "Geo|Resync all"
|
msgid "Geo|Resync all"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Geo|Retry count"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Geo|Reverify"
|
msgid "Geo|Reverify"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -27793,9 +27826,6 @@ msgstr ""
|
|||||||
msgid "Geo|Shards to synchronize"
|
msgid "Geo|Shards to synchronize"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Geo|Show more"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Geo|Site name can't be blank"
|
msgid "Geo|Site name can't be blank"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -27829,9 +27859,6 @@ msgstr ""
|
|||||||
msgid "Geo|Synchronization"
|
msgid "Geo|Synchronization"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Geo|Synchronization failed - %{error}"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Geo|Synchronization settings"
|
msgid "Geo|Synchronization settings"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -27928,9 +27955,6 @@ msgstr ""
|
|||||||
msgid "Geo|Verification concurrency limit"
|
msgid "Geo|Verification concurrency limit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "Geo|Verification failed - %{error}"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
msgid "Geo|Verification information"
|
msgid "Geo|Verification information"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
@ -28195,6 +28219,9 @@ msgstr ""
|
|||||||
msgid "GitLab Premium"
|
msgid "GitLab Premium"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "GitLab Security"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "GitLab Shell"
|
msgid "GitLab Shell"
|
||||||
msgstr ""
|
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."
|
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 ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Thank you,"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "Thanks for signing up to GitLab!"
|
msgid "Thanks for signing up to GitLab!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ RSpec.configure do |config|
|
|||||||
begin
|
begin
|
||||||
Capybara.current_session.execute_script("window.__coveragePathsPersistence.reset()")
|
Capybara.current_session.execute_script("window.__coveragePathsPersistence.reset()")
|
||||||
rescue StandardError => e
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -111,7 +111,7 @@ RSpec.configure do |config|
|
|||||||
example.metadata[:coverage_paths] = coverage_paths
|
example.metadata[:coverage_paths] = coverage_paths
|
||||||
front_end_coverage_by_example[example.metadata[:location]] = coverage_paths
|
front_end_coverage_by_example[example.metadata[:location]] = coverage_paths
|
||||||
rescue StandardError => e
|
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
|
end
|
||||||
end
|
end
|
||||||
|
@ -9,17 +9,24 @@ module QA
|
|||||||
include Helpers
|
include Helpers
|
||||||
|
|
||||||
PROJECT = "gitlab-qa-resources"
|
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)
|
def self.export(mapping_files_glob, **kwargs)
|
||||||
new.export(mapping_files_glob)
|
if kwargs.key?(:bucket) || kwargs.key?(:file_name)
|
||||||
|
new.export(mapping_files_glob, **kwargs)
|
||||||
|
else
|
||||||
|
new.export(mapping_files_glob)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Export code path mappings to GCP
|
# Export code path mappings to GCP
|
||||||
#
|
#
|
||||||
# @param [String] mapping_files_glob - glob pattern for mapping files
|
# @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]
|
# @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)
|
mapping_files = Dir.glob(mapping_files_glob)
|
||||||
return logger.warn("No files matched pattern, skipping coverage mapping upload") if mapping_files.empty?
|
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}")
|
logger.info("Number of mapping files found: #{mapping_files.size}")
|
||||||
|
|
||||||
mapping_data = mapping_files.flat_map { |file| JSON.parse(File.read(file)) }.reduce(:merge!)
|
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"
|
ENV['CI_PIPELINE_ID'] || 'local'}.json"
|
||||||
upload_to_gcs(file, mapping_data)
|
upload_to_gcs(file, mapping_data, bucket)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Import code path mappings from GCP
|
# Import code path mappings from GCP
|
||||||
#
|
#
|
||||||
# @param [String] branch - branch name
|
# @param [String] branch - branch name
|
||||||
# @param [String] run_type - run type
|
# @param [String] run_type - run type
|
||||||
|
# @param [String] bucket - custom bucket name (optional)
|
||||||
|
# @param [String] file_name - custom file name base (optional)
|
||||||
# @return [Hash]
|
# @return [Hash]
|
||||||
def import(branch, run_type)
|
def import(branch, run_type, bucket: DEFAULT_BUCKET, file_name: DEFAULT_FILE_NAME)
|
||||||
filename = code_paths_mapping_file("#{branch}/#{run_type}")
|
prefix = "#{branch}/#{run_type}/#{file_name}"
|
||||||
|
|
||||||
|
filename = code_paths_mapping_file(prefix, bucket)
|
||||||
|
|
||||||
logger.info("The mapping file fetched in import: #{filename}")
|
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])
|
JSON.parse(file[:body])
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
logger.error("Failed to download code paths mapping from GCS. Error: #{e}")
|
logger.error("Failed to download code paths mapping from GCS. Error: #{e}")
|
||||||
@ -54,8 +65,9 @@ module QA
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def upload_to_gcs(file_name, mapping_data)
|
def upload_to_gcs(file_name, mapping_data, bucket)
|
||||||
client.put_object(BUCKET, file_name, JSON.pretty_generate(mapping_data))
|
client.put_object(bucket, file_name, JSON.pretty_generate(mapping_data))
|
||||||
|
logger.info("Successfully uploaded to bucket: #{bucket}, file: #{file_name}")
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
logger.error("Failed to upload code paths mapping to GCS. Error: #{e}")
|
logger.error("Failed to upload code paths mapping to GCS. Error: #{e}")
|
||||||
logger.error("Backtrace: #{e.backtrace}")
|
logger.error("Backtrace: #{e.backtrace}")
|
||||||
@ -85,8 +97,8 @@ module QA
|
|||||||
#
|
#
|
||||||
# Get most up to date mapping file based on pipeline type
|
# Get most up to date mapping file based on pipeline type
|
||||||
# @return [String]
|
# @return [String]
|
||||||
def code_paths_mapping_file(prefix)
|
def code_paths_mapping_file(prefix, bucket = DEFAULT_BUCKET)
|
||||||
paginated_list(client.list_objects(BUCKET, prefix: prefix)).last&.name
|
paginated_list(client.list_objects(bucket, prefix: prefix)).last&.name
|
||||||
end
|
end
|
||||||
|
|
||||||
# Paginated list of items
|
# Paginated list of items
|
||||||
@ -98,7 +110,7 @@ module QA
|
|||||||
return list.items if list.next_page_token.nil?
|
return list.items if list.next_page_token.nil?
|
||||||
|
|
||||||
paginated_list(
|
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
|
) + list.items
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -51,6 +51,29 @@ RSpec.describe QA::Tools::Ci::CodePathsMapping do
|
|||||||
end
|
end
|
||||||
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
|
context "with no mapping files present" do
|
||||||
let(:file_paths) { [] }
|
let(:file_paths) { [] }
|
||||||
|
|
||||||
|
@ -103,10 +103,18 @@ namespace :ci do
|
|||||||
QA::Tools::Ci::TestMetrics.export(args[:glob])
|
QA::Tools::Ci::TestMetrics.export(args[:glob])
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "Export code paths mapping to GCP"
|
desc "Export backend code paths mapping to GCP"
|
||||||
task :export_code_paths_mapping, [:glob] do |_, args|
|
task :export_code_paths_mapping, [:glob] do |_, args|
|
||||||
raise("Code paths mapping JSON glob pattern is required") unless args[:glob]
|
raise("Code paths mapping JSON glob pattern is required") unless args[:glob]
|
||||||
|
|
||||||
QA::Tools::Ci::CodePathsMapping.export(args[:glob])
|
QA::Tools::Ci::CodePathsMapping.export(args[:glob])
|
||||||
end
|
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
|
end
|
||||||
|
@ -2,8 +2,11 @@
|
|||||||
import htmlStaticLineHighlighter from 'test_fixtures_static/line_highlighter.html';
|
import htmlStaticLineHighlighter from 'test_fixtures_static/line_highlighter.html';
|
||||||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||||
import LineHighlighter from '~/blob/line_highlighter';
|
import LineHighlighter from '~/blob/line_highlighter';
|
||||||
|
import { updateHash } from '~/blob/state';
|
||||||
import * as utils from '~/lib/utils/common_utils';
|
import * as utils from '~/lib/utils/common_utils';
|
||||||
|
|
||||||
|
jest.mock('~/blob/state');
|
||||||
|
|
||||||
describe('LineHighlighter', () => {
|
describe('LineHighlighter', () => {
|
||||||
const testContext = {};
|
const testContext = {};
|
||||||
|
|
||||||
@ -283,5 +286,17 @@ describe('LineHighlighter', () => {
|
|||||||
|
|
||||||
expect(testContext.spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15');
|
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';
|
import { blobControlsDataMock } from 'ee_else_ce_jest/repository/mock_data';
|
||||||
|
|
||||||
jest.mock('~/behaviors/shortcuts/shortcuts_toggle');
|
jest.mock('~/behaviors/shortcuts/shortcuts_toggle');
|
||||||
jest.mock('~/blob/state');
|
|
||||||
|
|
||||||
const relativePermalinkPath =
|
const relativePermalinkPath =
|
||||||
'flightjs/Flight/-/blob/46ca9ebd5a43ec240ee8d64e2bb829169dff744e/bower.json';
|
'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 { keysFor, PROJECT_FILES_GO_TO_PERMALINK } from '~/behaviors/shortcuts/keybindings';
|
||||||
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
|
import { shouldDisableShortcuts } from '~/behaviors/shortcuts/shortcuts_toggle';
|
||||||
import { Mousetrap } from '~/lib/mousetrap';
|
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 { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
|
import { useMockInternalEventsTracking } from 'helpers/tracking_internal_events_helper';
|
||||||
|
|
||||||
@ -46,6 +46,24 @@ describe('PermalinkDropdownItem', () => {
|
|||||||
expect(findPermalinkLinkDropdown().exists()).toBe(true);
|
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', () => {
|
describe('updatedPermalinkPath', () => {
|
||||||
it('returns absolutePermalinkPath when no line number is set', () => {
|
it('returns absolutePermalinkPath when no line number is set', () => {
|
||||||
expect(findPermalinkLinkDropdown().attributes('data-clipboard-text')).toBe(
|
expect(findPermalinkLinkDropdown().attributes('data-clipboard-text')).toBe(
|
||||||
@ -54,7 +72,7 @@ describe('PermalinkDropdownItem', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns updated path with line number when set', () => {
|
it('returns updated path with line number when set', () => {
|
||||||
hashState.currentHash = 10;
|
hashState.currentHash = '#L10';
|
||||||
createComponent();
|
createComponent();
|
||||||
|
|
||||||
expect(findPermalinkLinkDropdown().attributes('data-clipboard-text')).toBe(
|
expect(findPermalinkLinkDropdown().attributes('data-clipboard-text')).toBe(
|
||||||
@ -158,6 +176,17 @@ describe('PermalinkDropdownItem', () => {
|
|||||||
wrapper.destroy();
|
wrapper.destroy();
|
||||||
expect(unbindSpy).toHaveBeenCalledWith(keysFor(PROJECT_FILES_GO_TO_PERMALINK));
|
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', () => {
|
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 { shallowMountExtended } from 'helpers/vue_test_utils_helper';
|
||||||
import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
|
import Chunk from '~/vue_shared/components/source_viewer/components/chunk.vue';
|
||||||
import { addInteractionClass } from '~/code_navigation/utils';
|
import { addInteractionClass } from '~/code_navigation/utils';
|
||||||
import { updateLineNumber } from '~/blob/state';
|
|
||||||
import { CHUNK_1, CHUNK_2 } from '../mock_data';
|
import { CHUNK_1, CHUNK_2 } from '../mock_data';
|
||||||
|
|
||||||
jest.mock('~/code_navigation/utils');
|
jest.mock('~/code_navigation/utils');
|
||||||
jest.mock('~/blob/state');
|
|
||||||
|
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
|
||||||
@ -103,18 +101,5 @@ describe('Chunk component', () => {
|
|||||||
|
|
||||||
expect(addInteractionClass).toHaveBeenCalledWith({ d: 'test', path: 'index.js' });
|
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
|
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
|
it 'does not assign the gpg key when an unrelated gpg key is added' do
|
||||||
# InvalidGpgSignatureUpdater is called by the after_create hook
|
# InvalidGpgSignatureUpdater is called by the after_create hook
|
||||||
create :gpg_key,
|
create :gpg_key,
|
||||||
@ -127,23 +110,6 @@ RSpec.describe Gitlab::Gpg::InvalidGpgSignatureUpdater, :sidekiq_inline do
|
|||||||
)
|
)
|
||||||
end
|
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
|
it 'keeps the signature at being invalid when an unrelated gpg key is added' do
|
||||||
# InvalidGpgSignatureUpdater is called by the after_create hook
|
# InvalidGpgSignatureUpdater is called by the after_create hook
|
||||||
create :gpg_key,
|
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
|
- 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
|
- 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.
|
- 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)
|
# Temporary excludes (aka TODOs)
|
||||||
# For example:
|
# For example:
|
||||||
|
Reference in New Issue
Block a user