Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-29 18:08:58 +00:00
parent 8c2d06cba7
commit a16398e10f
108 changed files with 1234 additions and 813 deletions

View File

@ -1 +1 @@
0fa08d953e0d5497fe5366836d0ed54b9ff557d8
31ea786fbf0a7874b44efcb1f8b43bc4536d9b7e

View File

@ -21,7 +21,7 @@
import { Mark } from 'prosemirror-model';
import { visitParents, SKIP } from 'unist-util-visit-parents';
import { isFunction, isString, noop } from 'lodash';
import { isFunction, isString, noop, mapValues } from 'lodash';
const NO_ATTRIBUTES = {};
@ -72,6 +72,21 @@ function createSourceMapAttributes(hastNode, markdown) {
: {};
}
/**
* Creates a function that resolves the attributes
* of a ProseMirror node based on a hast node.
*
* @param {Object} params Parameters
* @param {String} params.markdown Markdown source from which the AST was generated
* @param {Object} params.attributeTransformer An object that allows applying a transformation
* function to all the attributes listed in the attributes property.
* @param {Array} params.attributeTransformer.attributes A list of attributes names
* that the getAttrs function should apply the transformation
* @param {Function} params.attributeTransformer.transform A function that applies
* a transform operation on an attribute value.
* @returns A `getAttrs` function
*/
const getAttrsFactory = ({ attributeTransformer, markdown }) =>
/**
* Compute ProseMirror nodes attributes from a Hast node.
* By default, this function includes sourcemap position
@ -80,21 +95,25 @@ function createSourceMapAttributes(hastNode, markdown) {
* Other attributes are retrieved by invoking a getAttrs
* function provided by the ProseMirror node factory spec.
*
* @param {*} proseMirrorNodeSpec ProseMirror node spec object
* @param {HastNode} hastNode A hast node
* @param {Array<HastNode>} hastParents All the ancestors of the hastNode
* @param {Object} proseMirrorNodeSpec ProseMirror node spec object
* @param {Object} hastNode A hast node
* @param {Array} hastParents All the ancestors of the hastNode
* @param {String} markdown Markdown source files content
*
* @returns An object that contains a ProseMirror nodes attributes
*/
function getAttrs(proseMirrorNodeSpec, hastNode, hastParents, markdown) {
function getAttrs(proseMirrorNodeSpec, hastNode, hastParents) {
const { getAttrs: specGetAttrs } = proseMirrorNodeSpec;
return {
const attributes = {
...createSourceMapAttributes(hastNode, markdown),
...(isFunction(specGetAttrs) ? specGetAttrs(hastNode, hastParents, markdown) : {}),
};
}
return mapValues(attributes, (value, key) =>
attributeTransformer.attributes.includes(key)
? attributeTransformer.transform(value, key)
: value,
);
};
/**
* Keeps track of the Hast -> ProseMirror conversion process.
@ -322,7 +341,13 @@ class HastToProseMirrorConverterState {
*
* @returns An object that contains ProseMirror node factories
*/
const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, markdown) => {
const createProseMirrorNodeFactories = (
schema,
proseMirrorFactorySpecs,
attributeTransformer,
markdown,
) => {
const getAttrs = getAttrsFactory({ attributeTransformer, markdown });
const factories = {
root: {
selector: 'root',
@ -355,20 +380,20 @@ const createProseMirrorNodeFactories = (schema, proseMirrorFactorySpecs, markdow
const nodeType = schema.nodeType(proseMirrorName);
state.closeUntil(parent);
state.openNode(nodeType, hastNode, getAttrs(factory, hastNode, parent, markdown), factory);
state.openNode(nodeType, hastNode, getAttrs(factory, hastNode, parent), factory);
};
} else if (factory.type === 'inline') {
const nodeType = schema.nodeType(proseMirrorName);
factory.handle = (state, hastNode, parent) => {
state.closeUntil(parent);
state.openNode(nodeType, hastNode, getAttrs(factory, hastNode, parent, markdown), factory);
state.openNode(nodeType, hastNode, getAttrs(factory, hastNode, parent), factory);
// Inline nodes do not have children therefore they are immediately closed
state.closeNode();
};
} else if (factory.type === 'mark') {
const markType = schema.marks[proseMirrorName];
factory.handle = (state, hastNode, parent) => {
state.openMark(markType, hastNode, getAttrs(factory, hastNode, parent, markdown), factory);
state.openMark(markType, hastNode, getAttrs(factory, hastNode, parent), factory);
};
} else if (factory.type === 'ignore') {
factory.handle = noop;
@ -581,9 +606,15 @@ export const createProseMirrorDocFromMdastTree = ({
factorySpecs,
wrappableTags,
tree,
attributeTransformer,
markdown,
}) => {
const proseMirrorNodeFactories = createProseMirrorNodeFactories(schema, factorySpecs, markdown);
const proseMirrorNodeFactories = createProseMirrorNodeFactories(
schema,
factorySpecs,
attributeTransformer,
markdown,
);
const state = new HastToProseMirrorConverterState();
visitParents(tree, (hastNode, ancestors) => {

View File

@ -125,6 +125,7 @@ const factorySpecs = {
selector: 'img',
getAttrs: (hastNode) => ({
src: hastNode.properties.src,
canonicalSrc: hastNode.properties.src,
title: hastNode.properties.title,
alt: hastNode.properties.alt,
}),
@ -154,6 +155,7 @@ const factorySpecs = {
type: 'mark',
selector: 'a',
getAttrs: (hastNode) => ({
canonicalSrc: hastNode.properties.href,
href: hastNode.properties.href,
title: hastNode.properties.title,
}),
@ -182,6 +184,31 @@ const factorySpecs = {
},
};
const resolveUrl = (url) => {
try {
return new URL(url, window.location.origin).toString();
} catch {
return null;
}
};
const attributeTransformer = {
attributes: ['href', 'src'],
transform: (url) => {
if (!url) {
return url;
}
/**
* Resolves a URL if provided. The URL is not resolved against
* the client origin initially to protect the URL protocol
* when it is available, for example, we want to preserve
* mailto and application-specific protocols
*/
return resolveUrl(url);
},
};
export default () => {
return {
deserialize: async ({ schema, markdown }) => {
@ -193,6 +220,7 @@ export default () => {
factorySpecs,
tree,
wrappableTags,
attributeTransformer,
markdown,
}),
skipRendering: ['footnoteReference', 'footnoteDefinition', 'code', 'definition'],

View File

@ -1,4 +1,5 @@
import { uniq, isString, omit, isFunction } from 'lodash';
import { removeLastSlashInUrlPath, removeUrlProtocol } from '../../lib/utils/url_utility';
const defaultAttrs = {
td: { colspan: 1, rowspan: 1, colwidth: null },
@ -497,9 +498,7 @@ const linkType = (sourceMarkdown) => {
return LINK_HTML;
};
const removeUrlProtocol = (url) => url.replace(/^\w+:\/?\/?/, '');
const normalizeUrl = (url) => decodeURIComponent(removeUrlProtocol(url));
const normalizeUrl = (url) => decodeURIComponent(removeLastSlashInUrlPath(removeUrlProtocol(url)));
/**
* Validates that the provided URL is well-formed

View File

@ -669,3 +669,27 @@ export function constructWebIDEPath({
webIDEUrl(`/${sourceProjectFullPath}/merge_requests/${iid}`),
);
}
/**
* Examples
*
* http://gitlab.com => gitlab.com
* https://gitlab.com => gitlab.com
*
* @param {String} url
* @returns A url without a protocol / scheme
*/
export const removeUrlProtocol = (url) => url.replace(/^\w+:\/?\/?/, '');
/**
* Examples
*
* https://www.gitlab.com/path/ => https://www.gitlab.com/path
* https://www.gitlab.com/?query=search => https://www.gitlab.com?query=search
* https://www.gitlab.com/#fragment => https://www.gitlab.com#fragment
*
* @param {String} url
* @returns A URL that does not have a path that ends with slash
*/
export const removeLastSlashInUrlPath = (url) =>
url.replace(/\/$/, '').replace(/\/(\?|#){1}([^/]*)$/, '$1$2');

View File

@ -1,54 +0,0 @@
/**
* Overrides styles from ToastUI editor
*/
.tui-editor-defaultUI {
// Toolbar buttons
.tui-editor-defaultUI-toolbar .toolbar-button {
color: $gray-500;
border: 0;
&:hover,
&:active {
color: $blue-500;
border: 0;
}
}
// Contextual menu's & popups
.tui-popup-wrapper {
@include gl-overflow-hidden;
@include gl-rounded-base;
@include gl-border-gray-200;
hr {
@include gl-m-0;
@include gl-bg-gray-200;
}
button {
@include gl-text-gray-700;
}
}
/**
* Overrides styles from ToastUI's Code Mirror (markdown mode) editor.
* Toast UI internally overrides some of these using the `.tui-md-` prefix.
* https://codemirror.net/doc/manual.html#styling
*/
.te-md-container .CodeMirror * {
@include gl-font-monospace;
@include gl-font-size-monospace;
@include gl-line-height-20;
}
}
/**
* Styling below ensures that YouTube videos are displayed in the editor the same as they would in about.gitlab.com
* https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/main/source/stylesheets/_base.scss#L977
*/
.video_container {
padding-bottom: 56.25%;
}

View File

@ -66,4 +66,9 @@ class DeployKey < Key
query
end
# This is used for the internal logic of AuditEvents::BuildService.
def impersonated?
false
end
end

View File

@ -40,6 +40,7 @@ class Key < ApplicationRecord
after_destroy :refresh_user_cache
alias_attribute :fingerprint_md5, :fingerprint
alias_attribute :name, :title
scope :preload_users, -> { preload(:user) }
scope :for_user, -> (user) { where(user: user) }

View File

@ -1809,14 +1809,10 @@ class User < ApplicationRecord
end
def attention_requested_open_merge_requests_count(force: false)
if Feature.enabled?(:uncached_mr_attention_requests_count, self)
MergeRequestsFinder.new(self, attention: self.username, state: 'opened', non_archived: true).execute.count
else
Rails.cache.fetch(attention_request_cache_key, force: force, expires_in: COUNT_CACHE_VALIDITY_PERIOD) do
MergeRequestsFinder.new(self, attention: self.username, state: 'opened', non_archived: true).execute.count
end
end
end
def assigned_open_issues_count(force: false)
Rails.cache.fetch(['users', id, 'assigned_open_issues_count'], force: force, expires_in: COUNT_CACHE_VALIDITY_PERIOD) do

View File

@ -1,8 +1,8 @@
---
name: uncached_mr_attention_requests_count
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84145
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/357480
milestone: '14.10'
name: audit_event_streaming_git_operations_deploy_key_event
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/93160
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368765
milestone: '15.3'
type: development
group: group::code review
group: group::release
default_enabled: false

View File

@ -336,6 +336,7 @@ Users with at least the Owner role for a group can list event streaming destinat
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/332747) in GitLab 14.9 [with a flag](../administration/feature_flags.md) named `audit_event_streaming_git_operations`. Disabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/357211) in GitLab 15.0.
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/357211) in GitLab 15.1 by default.
> - [Added `details.author_class` field](https://gitlab.com/gitlab-org/gitlab/-/issues/363876) in GitLab 15.3.
FLAG:
On self-managed GitLab, by default this feature is available. To hide the
@ -377,6 +378,7 @@ Fetch:
"entity_type": "Project",
"details": {
"author_name": "Administrator",
"author_class": "User",
"target_id": 29,
"target_type": "Project",
"target_details": "example-project",
@ -408,6 +410,7 @@ Push:
"entity_type": "Project",
"details": {
"author_name": "Administrator",
"author_class": "User",
"target_id": 29,
"target_type": "Project",
"target_details": "example-project",
@ -429,6 +432,42 @@ Push:
}
```
#### Example payloads for SSH events with Deploy Key
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/363876) in GitLab 15.3 [with a flag](../administration/feature_flags.md) named `audit_event_streaming_git_operations_deploy_key_event`. Disabled by default.
Fetch:
```json
{
"id": 1,
"author_id": -3,
"entity_id": 29,
"entity_type": "Project",
"details": {
"author_name": "deploy-key-name",
"author_class": "DeployKey",
"target_id": 29,
"target_type": "Project",
"target_details": "example-project",
"custom_message": {
"protocol": "ssh",
"action": "git-upload-pack"
},
"ip_address": "127.0.0.1",
"entity_path": "example-group/example-project"
},
"ip_address": "127.0.0.1",
"author_name": "deploy-key-name",
"entity_path": "example-group/example-project",
"target_details": "example-project",
"created_at": "2022-07-26T05:43:53.662Z",
"target_type": "Project",
"target_id": 29,
"event_type": "repository_git_operation"
}
```
### Example payloads for HTTP and HTTPS events
Fetch:
@ -441,6 +480,7 @@ Fetch:
"entity_type": "Project",
"details": {
"author_name": "Administrator",
"author_class": "User",
"target_id": 29,
"target_type": "Project",
"target_details": "example-project",
@ -472,6 +512,7 @@ Push:
"entity_type": "Project",
"details": {
"author_name": "Administrator",
"author_class": "User",
"target_id": 29,
"target_type": "Project",
"target_details": "example-project",
@ -493,6 +534,40 @@ Push:
}
```
#### Example payloads for HTTP and HTTPS events with Deploy Token
Fetch:
```json
{
"id": 1,
"author_id": -2,
"entity_id": 22,
"entity_type": "Project",
"details": {
"author_name": "deploy-token-name",
"author_class": "DeployToken",
"target_id": 22,
"target_type": "Project",
"target_details": "example-project",
"custom_message": {
"protocol": "http",
"action": "git-upload-pack"
},
"ip_address": "127.0.0.1",
"entity_path": "example-group/example-project"
},
"ip_address": "127.0.0.1",
"author_name": "deploy-token-name",
"entity_path": "example-group/example-project",
"target_details": "example-project",
"created_at": "2022-07-26T05:46:25.850Z",
"target_type": "Project",
"target_id": 22,
"event_type": "repository_git_operation"
}
```
### Example payloads for events from GitLab UI download button
Fetch:
@ -506,6 +581,7 @@ Fetch:
"details": {
"custom_message": "Repository Download Started",
"author_name": "example_username",
"author_class": "User",
"target_id": 29,
"target_type": "Project",
"target_details": "example-group/example-project",

View File

@ -49,6 +49,18 @@ to all the members of that project's group. This might be interpreted as spam.
Notifications and mentions can be disabled in
[a group's settings](../group/manage.md#disable-email-notifications).
### Mention a group in an issue or merge request
When you mention a group in a comment, every member of the group gets a to-do item
added to their To-do list.
1. Open the MR or issue.
1. In a comment, type `@` followed by the user, group, or subgroup namespace.
For example, `@alex`, `@alex-team`, or `@alex-team/marketing`.
1. Select **Comment**.
A to-do item is created for all the group and subgroup members.
## Add a comment to a merge request diff
You can add comments to a merge request diff. These comments

View File

@ -48,29 +48,6 @@ For example, consider a user named Alex:
| Alex creates a group for their team with the group name `alex-team`. The group and its projects are available at: `https://gitlab.example.com/alex-team`. | The namespace in this case is `alex-team`. |
| Alex creates a subgroup of `alex-team` with the subgroup name `marketing`. The subgroup and its projects are available at: `https://gitlab.example.com/alex-team/marketing`. | The namespace in this case is `alex-team/marketing`. |
## Mention a group in an issue or merge request
When you mention a group in a comment, every member of the group gets a to-do item
added to their To-do list.
1. Open the MR or issue.
1. In a comment, type `@` followed by the user, group, or subgroup namespace.
For example, `@alex`, `@alex-team`, or `@alex-team/marketing`.
1. Select **Comment**.
A to-do item is created for all the group and subgroup members.
## Export members as CSV **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/287940) in GitLab 14.2.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/336520) in GitLab 14.5.
You can export a list of members in a group or subgroup as a CSV.
1. Go to your group or subgroup and select either **Group information > Members** or **Subgroup information > Members**.
1. Select **Export as CSV**.
1. After the CSV file has been generated, it is emailed as an attachment to the user that requested it.
## Related topics
- [Group wikis](../project/wiki/index.md)

View File

@ -380,6 +380,17 @@ To disable group mentions:
1. Select **Disable group mentions**.
1. Select **Save changes**.
## Export members as CSV **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/287940) in GitLab 14.2.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/336520) in GitLab 14.5.
You can export a list of members in a group or subgroup as a CSV.
1. Go to your group or subgroup and select either **Group information > Members** or **Subgroup information > Members**.
1. Select **Export as CSV**.
1. After the CSV file has been generated, it is emailed as an attachment to the user that requested it.
## User cap for groups
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330027) in GitLab 14.7.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -32,6 +32,10 @@ module API
key || user
end
def deploy_key_or_user
key.instance_of?(DeployKey) ? key : user
end
def username
user&.username
end

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Gitlab
module Audit
class DeployKeyAuthor < Gitlab::Audit::NullAuthor
def initialize(name: nil)
super(id: -3, name: name)
end
def name
@name || _('Deploy Key')
end
end
end
end

View File

@ -24,6 +24,8 @@ module Gitlab
Gitlab::Audit::UnauthenticatedAuthor.new(name: name)
elsif id == -2
Gitlab::Audit::DeployTokenAuthor.new(name: name)
elsif id == -3
Gitlab::Audit::DeployKeyAuthor.new(name: name)
else
Gitlab::Audit::DeletedAuthor.new(id: id, name: name)
end

View File

@ -20,13 +20,24 @@ namespace :contracts do
Pact::VerificationTask.new(:get_pipeline_header_data) do |pact|
pact.uri(
"#{contracts}/contracts/project/pipeline/show/pipelines#show-get_pipeline_header_data.json",
pact_helper: "#{provider}/pact_helpers/project/pipeline/get_pipeline_header_data_helper.rb"
pact_helper: "#{provider}/pact_helpers/project/pipeline/show/get_pipeline_header_data_helper.rb"
)
end
Pact::VerificationTask.new(:delete_pipeline) do |pact|
pact.uri(
"#{contracts}/contracts/project/pipeline/show/pipelines#show-delete_pipeline.json",
pact_helper: "#{provider}/pact_helpers/project/pipeline/show/delete_pipeline_helper.rb"
)
end
desc 'Run all pipeline contract tests'
task 'test:pipelines', :contract_mr do |_t, arg|
errors = %w[get_list_project_pipelines get_pipeline_header_data].each_with_object([]) do |task, err|
errors = %w[
get_list_project_pipelines
get_pipeline_header_data
delete_pipeline
].each_with_object([]) do |task, err|
Rake::Task["contracts:pipelines:pact:verify:#{task}"].execute
rescue StandardError, SystemExit
err << "contracts:pipelines:pact:verify:#{task}"

View File

@ -34,7 +34,7 @@ Usage: rake "gitlab:gitaly:install[/installation/dir,/storage/path]")
env["BUNDLE_DEPLOYMENT"] = 'false'
end
output, status = Gitlab::Popen.popen([make_cmd, 'all', 'git'], nil, env)
output, status = Gitlab::Popen.popen([make_cmd, 'clean-build', 'all', 'git'], nil, env)
raise "Gitaly failed to compile: #{output}" unless status&.zero?
end
end

View File

@ -12731,6 +12731,9 @@ msgid_plural "Deploys"
msgstr[0] ""
msgstr[1] ""
msgid "Deploy Key"
msgstr ""
msgid "Deploy Keys"
msgstr ""

View File

@ -98,7 +98,6 @@
"cache-loader": "^4.1.0",
"canvas-confetti": "^1.4.0",
"clipboard": "^2.0.8",
"codemirror": "^5.48.4",
"codesandbox-api": "0.0.23",
"compression-webpack-plugin": "^5.0.2",
"copy-webpack-plugin": "^6.4.1",

View File

@ -0,0 +1,19 @@
const DeletePipeline = {
success: {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
},
request: {
method: 'POST',
path: '/api/graphql',
},
variables: {
id: 'gid://gitlab/Ci::Pipeline/316112',
},
};
export { DeletePipeline };

View File

@ -23,3 +23,24 @@ export async function getPipelineHeaderDataRequest(endpoint) {
data: graphqlQuery,
});
}
export async function deletePipeline(endpoint) {
const { url } = endpoint;
const query = await extractGraphQLQuery(
'app/assets/javascripts/pipelines/graphql/mutations/delete_pipeline.mutation.graphql',
);
const graphqlQuery = {
query,
variables: {
id: 'gid://gitlab/Ci::Pipeline/316112',
},
};
return axios({
baseURL: url,
url: '/api/graphql',
method: 'POST',
headers: { Accept: '*/*' },
data: graphqlQuery,
});
}

View File

@ -6,24 +6,27 @@ import { GraphQLInteraction } from '@pact-foundation/pact';
import { extractGraphQLQuery } from '../../../helpers/graphql_query_extractor';
import { PipelineHeaderData } from '../../../fixtures/project/pipeline/get_pipeline_header_data.fixture';
import { getPipelineHeaderDataRequest } from '../../../resources/graphql/pipelines';
import { DeletePipeline } from '../../../fixtures/project/pipeline/delete_pipeline.fixture';
import { getPipelineHeaderDataRequest, deletePipeline } from '../../../resources/graphql/pipelines';
const CONSUMER_NAME = 'Pipelines#show';
const CONSUMER_LOG = '../logs/consumer.log';
const CONTRACT_DIR = '../contracts/project/pipeline/show';
const PROVIDER_NAME = 'GET pipeline header data';
const GET_PIPELINE_HEADER_DATA_PROVIDER_NAME = 'GET pipeline header data';
const DELETE_PIPELINE_PROVIDER_NAME = 'DELETE pipeline';
// GraphQL query: getPipelineHeaderData
pactWith(
{
consumer: CONSUMER_NAME,
provider: PROVIDER_NAME,
provider: GET_PIPELINE_HEADER_DATA_PROVIDER_NAME,
log: CONSUMER_LOG,
dir: CONTRACT_DIR,
},
(provider) => {
describe(PROVIDER_NAME, () => {
describe(GET_PIPELINE_HEADER_DATA_PROVIDER_NAME, () => {
beforeEach(async () => {
const query = await extractGraphQLQuery(
'app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql',
@ -50,4 +53,41 @@ pactWith(
},
);
// GraphQL query: deletePipeline
pactWith(
{
consumer: CONSUMER_NAME,
provider: DELETE_PIPELINE_PROVIDER_NAME,
log: CONSUMER_LOG,
dir: CONTRACT_DIR,
},
(provider) => {
describe(DELETE_PIPELINE_PROVIDER_NAME, () => {
beforeEach(async () => {
const query = await extractGraphQLQuery(
'app/assets/javascripts/pipelines/graphql/mutations/delete_pipeline.mutation.graphql',
);
const graphqlQuery = new GraphQLInteraction()
.given('a pipeline for a project exists')
.uponReceiving('a request to delete the pipeline')
.withQuery(query)
.withRequest(DeletePipeline.request)
.withVariables(DeletePipeline.variables)
.willRespondWith(DeletePipeline.success);
provider.addInteraction(graphqlQuery);
});
it('returns a successful body', async () => {
const deletePipelineResponse = await deletePipeline({
url: provider.mockService.baseUrl,
});
expect(deletePipelineResponse.status).toEqual(DeletePipeline.success.status);
});
});
},
);
/* eslint-enable @gitlab/require-i18n-strings */

View File

@ -0,0 +1,44 @@
{
"consumer": {
"name": "Pipelines#show"
},
"provider": {
"name": "DELETE pipeline"
},
"interactions": [
{
"description": "a request to delete the pipeline",
"providerState": "a pipeline for a project exists",
"request": {
"method": "POST",
"path": "/api/graphql",
"headers": {
"content-type": "application/json"
},
"body": {
"query": "mutation deletePipeline($id: CiPipelineID!) {\n pipelineDestroy(input: { id: $id }) {\n errors\n }\n}\n",
"variables": {
"id": "gid://gitlab/Ci::Pipeline/316112"
}
},
"matchingRules": {
"$.body.query": {
"match": "regex",
"regex": "mutation\\s*deletePipeline\\(\\$id:\\s*CiPipelineID!\\)\\s*\\{\\s*pipelineDestroy\\(input:\\s*\\{\\s*id:\\s*\\$id\\s*\\}\\)\\s*\\{\\s*errors\\s*\\}\\s*\\}\\s*"
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json; charset=utf-8"
}
}
}
],
"metadata": {
"pactSpecification": {
"version": "2.0.0"
}
}
}

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
require_relative '../../../../spec_helper'
require_relative '../../../../states/project/pipeline/show_state'
module Provider
module DeletePipelineHelper
Pact.service_provider "DELETE pipeline" do
app { Environments::Test.app }
honours_pact_with 'Pipelines#show' do
pact_uri '../contracts/project/pipeline/show/pipelines#show-delete_pipeline.json'
end
end
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
require_relative '../../../spec_helper'
require_relative '../../../states/project/pipeline/pipeline_state'
require_relative '../../../../spec_helper'
require_relative '../../../../states/project/pipeline/pipeline_state'
module Provider
module GetPipelinesHeaderDataHelper

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
Pact.provider_states_for "Pipelines#show" do
provider_state "a pipeline for a project exists" do
set_up do
user = User.find_by(name: Provider::UsersHelper::CONTRACT_USER_NAME)
namespace = create(:namespace, name: 'gitlab-org')
project = create(:project, :repository, name: 'gitlab-qa', namespace: namespace, creator: user)
scheduled_job = create(:ci_build, :scheduled)
manual_job = create(:ci_build, :manual)
project.add_maintainer(user)
create(
:ci_pipeline,
:with_job,
:success,
id: 316112,
iid: 1,
project: project,
user: user,
duration: 10,
finished_at: '2022-06-01T02:47:31.432Z',
builds: [scheduled_job, manual_job]
)
end
end
end

View File

@ -257,7 +257,12 @@ describe('Client side Markdown processing', () => {
expectedDoc: doc(
paragraph(
source('<img src="bar" alt="foo" />'),
image({ ...source('<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
image({
...source('<img src="bar" alt="foo" />'),
alt: 'foo',
canonicalSrc: 'bar',
src: 'http://test.host/bar',
}),
),
),
},
@ -275,7 +280,12 @@ describe('Client side Markdown processing', () => {
),
paragraph(
source('<img src="bar" alt="foo" />'),
image({ ...source('<img src="bar" alt="foo" />'), alt: 'foo', src: 'bar' }),
image({
...source('<img src="bar" alt="foo" />'),
alt: 'foo',
src: 'http://test.host/bar',
canonicalSrc: 'bar',
}),
),
),
},
@ -287,7 +297,8 @@ describe('Client side Markdown processing', () => {
link(
{
...source('[GitLab](https://gitlab.com "Go to GitLab")'),
href: 'https://gitlab.com',
href: 'https://gitlab.com/',
canonicalSrc: 'https://gitlab.com',
title: 'Go to GitLab',
},
'GitLab',
@ -305,7 +316,8 @@ describe('Client side Markdown processing', () => {
link(
{
...source('[GitLab](https://gitlab.com "Go to GitLab")'),
href: 'https://gitlab.com',
href: 'https://gitlab.com/',
canonicalSrc: 'https://gitlab.com',
title: 'Go to GitLab',
},
'GitLab',
@ -322,7 +334,8 @@ describe('Client side Markdown processing', () => {
link(
{
...source('www.commonmark.org'),
href: 'http://www.commonmark.org',
canonicalSrc: 'http://www.commonmark.org',
href: 'http://www.commonmark.org/',
},
'www.commonmark.org',
),
@ -338,6 +351,7 @@ describe('Client side Markdown processing', () => {
link(
{
...source('www.commonmark.org/help'),
canonicalSrc: 'http://www.commonmark.org/help',
href: 'http://www.commonmark.org/help',
},
'www.commonmark.org/help',
@ -355,6 +369,7 @@ describe('Client side Markdown processing', () => {
link(
{
...source('hello+xyz@mail.example'),
canonicalSrc: 'mailto:hello+xyz@mail.example',
href: 'mailto:hello+xyz@mail.example',
},
'hello+xyz@mail.example',
@ -373,7 +388,8 @@ describe('Client side Markdown processing', () => {
{
sourceMapKey: null,
sourceMarkdown: null,
href: 'https://gitlab.com',
canonicalSrc: 'https://gitlab.com',
href: 'https://gitlab.com/',
},
'https://gitlab.com',
),
@ -402,6 +418,7 @@ hard line break`,
image({
...source('![GitLab Logo](https://gitlab.com/logo.png "GitLab Logo")'),
alt: 'GitLab Logo',
canonicalSrc: 'https://gitlab.com/logo.png',
src: 'https://gitlab.com/logo.png',
title: 'GitLab Logo',
}),
@ -595,7 +612,12 @@ two
paragraph(
source('List item with an image ![bar](foo.png)'),
'List item with an image',
image({ ...source('![bar](foo.png)'), alt: 'bar', src: 'foo.png' }),
image({
...source('![bar](foo.png)'),
alt: 'bar',
canonicalSrc: 'foo.png',
src: 'http://test.host/foo.png',
}),
),
),
),
@ -944,8 +966,17 @@ Paragraph
paragraph(
source('[![moon](moon.jpg)](/uri)'),
link(
{ ...source('[![moon](moon.jpg)](/uri)'), href: '/uri' },
image({ ...source('![moon](moon.jpg)'), src: 'moon.jpg', alt: 'moon' }),
{
...source('[![moon](moon.jpg)](/uri)'),
canonicalSrc: '/uri',
href: 'http://test.host/uri',
},
image({
...source('![moon](moon.jpg)'),
canonicalSrc: 'moon.jpg',
src: 'http://test.host/moon.jpg',
alt: 'moon',
}),
),
),
),
@ -975,12 +1006,26 @@ Paragraph
source('~[moon](moon.jpg) and [sun](sun.jpg)~'),
strike(
source('~[moon](moon.jpg) and [sun](sun.jpg)~'),
link({ ...source('[moon](moon.jpg)'), href: 'moon.jpg' }, 'moon'),
link(
{
...source('[moon](moon.jpg)'),
canonicalSrc: 'moon.jpg',
href: 'http://test.host/moon.jpg',
},
'moon',
),
),
strike(source('~[moon](moon.jpg) and [sun](sun.jpg)~'), ' and '),
strike(
source('~[moon](moon.jpg) and [sun](sun.jpg)~'),
link({ ...source('[sun](sun.jpg)'), href: 'sun.jpg' }, 'sun'),
link(
{
...source('[sun](sun.jpg)'),
href: 'http://test.host/sun.jpg',
canonicalSrc: 'sun.jpg',
},
'sun',
),
),
),
),
@ -1094,7 +1139,12 @@ _world_.
paragraph(
source('[GitLab][gitlab-url]'),
link(
{ ...source('[GitLab][gitlab-url]'), href: 'https://gitlab.com', title: 'GitLab' },
{
...source('[GitLab][gitlab-url]'),
href: 'https://gitlab.com/',
canonicalSrc: 'https://gitlab.com',
title: 'GitLab',
},
'GitLab',
),
),

View File

@ -1223,7 +1223,7 @@ paragraph
${'italic'} | ${'<em>italic</em>'} | ${'<em>italic modified</em>'} | ${defaultEditAction}
${'italic'} | ${'<i>italic</i>'} | ${'<i>italic modified</i>'} | ${defaultEditAction}
${'link'} | ${'[gitlab](https://gitlab.com)'} | ${'[gitlab modified](https://gitlab.com)'} | ${defaultEditAction}
${'link'} | ${'<a href="https://gitlab.com">link</a>'} | ${'<a href="https://gitlab.com">link modified</a>'} | ${defaultEditAction}
${'link'} | ${'<a href="https://gitlab.com">link</a>'} | ${'<a href="https://gitlab.com/">link modified</a>'} | ${defaultEditAction}
${'link'} | ${'link www.gitlab.com'} | ${'modified link www.gitlab.com'} | ${prependContentEditAction}
${'link'} | ${'link https://www.gitlab.com'} | ${'modified link https://www.gitlab.com'} | ${prependContentEditAction}
${'link'} | ${'link(https://www.gitlab.com)'} | ${'modified link(https://www.gitlab.com)'} | ${prependContentEditAction}
@ -1232,6 +1232,11 @@ paragraph
${'link'} | ${'link [https://www.gitlab.com>'} | ${'modified link \\[https://www.gitlab.com>'} | ${prependContentEditAction}
${'link'} | ${'link <https://www.gitlab.com'} | ${'modified link <https://www.gitlab.com'} | ${prependContentEditAction}
${'link'} | ${'link https://www.gitlab.com>'} | ${'modified link [https://www.gitlab.com>](https://www.gitlab.com%3E)'} | ${prependContentEditAction}
${'link'} | ${'link https://www.gitlab.com/path'} | ${'modified link https://www.gitlab.com/path'} | ${prependContentEditAction}
${'link'} | ${'link https://www.gitlab.com?query=search'} | ${'modified link https://www.gitlab.com?query=search'} | ${prependContentEditAction}
${'link'} | ${'link https://www.gitlab.com/#fragment'} | ${'modified link https://www.gitlab.com/#fragment'} | ${prependContentEditAction}
${'link'} | ${'link https://www.gitlab.com/?query=search'} | ${'modified link https://www.gitlab.com/?query=search'} | ${prependContentEditAction}
${'link'} | ${'link https://www.gitlab.com#fragment'} | ${'modified link https://www.gitlab.com#fragment'} | ${prependContentEditAction}
${'link'} | ${'link **https://www.gitlab.com]**'} | ${'modified link [**https://www.gitlab.com\\]**](https://www.gitlab.com%5D)'} | ${prependContentEditAction}
${'code'} | ${'`code`'} | ${'`code modified`'} | ${defaultEditAction}
${'code'} | ${'<code>code</code>'} | ${'<code>code modified</code>'} | ${defaultEditAction}
@ -1248,7 +1253,7 @@ paragraph
${'taskList'} | ${'2) [ ] task list item'} | ${'2) [ ] task list item modified'} | ${defaultEditAction}
${'taskList'} | ${'2) [x] task list item'} | ${'2) [x] task list item modified'} | ${defaultEditAction}
`(
'preserves original $mark syntax when sourceMarkdown is available for $content',
'preserves original $mark syntax when sourceMarkdown is available for $markdown',
async ({ markdown, modifiedMarkdown, editAction }) => {
const { document } = await remarkMarkdownDeserializer().deserialize({
schema: tiptapEditor.schema,

View File

@ -1062,4 +1062,28 @@ describe('URL utility', () => {
expect(urlUtils.PROMO_URL).toBe(url);
});
});
describe('removeUrlProtocol', () => {
it.each`
input | output
${'http://gitlab.com'} | ${'gitlab.com'}
${'https://gitlab.com'} | ${'gitlab.com'}
${'foo:bar.com'} | ${'bar.com'}
${'gitlab.com'} | ${'gitlab.com'}
`('transforms $input to $output', ({ input, output }) => {
expect(urlUtils.removeUrlProtocol(input)).toBe(output);
});
});
describe('removeLastSlashInUrlPath', () => {
it.each`
input | output
${'https://www.gitlab.com/path/'} | ${'https://www.gitlab.com/path'}
${'https://www.gitlab.com/?query=search'} | ${'https://www.gitlab.com?query=search'}
${'https://www.gitlab.com/#fragment'} | ${'https://www.gitlab.com#fragment'}
${'https://www.gitlab.com/hello'} | ${'https://www.gitlab.com/hello'}
`('transforms $input to $output', ({ input, output }) => {
expect(urlUtils.removeLastSlashInUrlPath(input)).toBe(output);
});
});
});

View File

@ -83,6 +83,36 @@ RSpec.describe API::Support::GitAccessActor do
end
end
describe '#deploy_key_or_user' do
it 'returns a deploy key when the params contains deploy key' do
key = create(:deploy_key)
params = { key_id: key.id }
expect(described_class.from_params(params).deploy_key_or_user).to eq(key)
end
it 'returns a user when the params contains personal key' do
key = create(:key)
params = { key_id: key.id }
expect(described_class.from_params(params).deploy_key_or_user).to eq(key.user)
end
it 'returns a user when the params contains user id' do
user = create(:user)
params = { user_id: user.id }
expect(described_class.from_params(params).deploy_key_or_user).to eq(user)
end
it 'returns a user when the params contains user name' do
user = create(:user)
params = { username: user.username }
expect(described_class.from_params(params).deploy_key_or_user).to eq(user)
end
end
describe '#username' do
context 'when initialized with a User' do
let(:user) { build(:user) }

View File

@ -0,0 +1,17 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Audit::DeployKeyAuthor do
describe '#initialize' do
it 'sets correct attributes' do
expect(described_class.new(name: 'Lorem deploy key'))
.to have_attributes(id: -3, name: 'Lorem deploy key')
end
it 'sets default name when it is not provided' do
expect(described_class.new)
.to have_attributes(id: -3, name: 'Deploy Key')
end
end
end

View File

@ -57,6 +57,15 @@ RSpec.describe Gitlab::Audit::NullAuthor do
expect(subject.for(-2, audit_event)).to be_a(Gitlab::Audit::DeployTokenAuthor)
expect(subject.for(-2, audit_event)).to have_attributes(id: -2, name: 'Test deploy token')
end
it 'returns DeployKeyAuthor when id equals -3', :aggregate_failures do
allow(audit_event).to receive(:[]).with(:author_name).and_return('Test deploy key')
allow(audit_event).to receive(:details).and_return({})
allow(audit_event).to receive(:target_type)
expect(subject.for(-3, audit_event)).to be_a(Gitlab::Audit::DeployKeyAuthor)
expect(subject.for(-3, audit_event)).to have_attributes(id: -3, name: 'Test deploy key')
end
end
describe '#current_sign_in_ip' do

View File

@ -5245,25 +5245,8 @@ RSpec.describe User do
end
it 'returns number of open merge requests from non-archived projects' do
expect(Rails.cache).not_to receive(:fetch)
expect(user.attention_requested_open_merge_requests_count(force: true)).to eq 1
end
context 'when uncached_mr_attention_requests_count is disabled' do
before do
stub_feature_flags(uncached_mr_attention_requests_count: false)
end
it 'fetches from cache' do
expect(Rails.cache).to receive(:fetch).with(
user.attention_request_cache_key,
force: false,
expires_in: described_class::COUNT_CACHE_VALIDITY_PERIOD
).and_call_original
expect(user.attention_requested_open_merge_requests_count).to eq 1
end
end
end
describe '#assigned_open_issues_count' do

View File

@ -66,7 +66,7 @@ RSpec.describe 'gitlab:gitaly namespace rake task', :silence_stdout do
.with(%w[which gmake])
.and_return(['/usr/bin/gmake', 0])
expect(Gitlab::Popen).to receive(:popen)
.with(%w[gmake all git], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.with(%w[gmake clean-build all git], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.and_return(['ok', 0])
subject
@ -78,7 +78,7 @@ RSpec.describe 'gitlab:gitaly namespace rake task', :silence_stdout do
.with(%w[which gmake])
.and_return(['/usr/bin/gmake', 0])
expect(Gitlab::Popen).to receive(:popen)
.with(%w[gmake all git], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.with(%w[gmake clean-build all git], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.and_return(['output', 1])
expect { subject }.to raise_error /Gitaly failed to compile: output/
@ -95,14 +95,14 @@ RSpec.describe 'gitlab:gitaly namespace rake task', :silence_stdout do
it 'calls make in the gitaly directory' do
expect(Gitlab::Popen).to receive(:popen)
.with(%w[make all git], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.with(%w[make clean-build all git], nil, { "BUNDLE_GEMFILE" => nil, "RUBYOPT" => nil })
.and_return(['output', 0])
subject
end
context 'when Rails.env is test' do
let(:command) { %w[make all git] }
let(:command) { %w[make clean-build all git] }
before do
stub_rails_env('test')

View File

@ -3458,11 +3458,6 @@ co@^4.6.0:
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
codemirror@^5.48.4:
version "5.53.2"
resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.53.2.tgz#9799121cf8c50809cca487304e9de3a74d33f428"
integrity sha512-wvSQKS4E+P8Fxn/AQ+tQtJnF1qH5UOlxtugFLpubEZ5jcdH2iXTVinb+Xc/4QjshuOxRm4fUsU2QPF1JJKiyXA==
codesandbox-api@0.0.23:
version "0.0.23"
resolved "https://registry.yarnpkg.com/codesandbox-api/-/codesandbox-api-0.0.23.tgz#bf650a21b5f3c2369e03f0c19d10b4e2ba255b4f"