Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-15 00:08:49 +00:00
parent cde60c9291
commit 9de5cc2d1f
59 changed files with 626 additions and 310 deletions

View file

@ -16,60 +16,32 @@ export const dateFormats = {
// Some content is duplicated due to backward compatibility.
// It will be removed with https://gitlab.com/gitlab-org/gitlab/-/issues/350614 in 14.9
export const METRICS_POPOVER_CONTENT = {
'lead-time': {
description: s__('ValueStreamAnalytics|Median time from issue created to issue closed.'),
},
lead_time: {
description: s__('ValueStreamAnalytics|Median time from issue created to issue closed.'),
},
'cycle-time': {
description: s__(
"ValueStreamAnalytics|Median time from the earliest commit of a linked issue's merge request to when that issue is closed.",
),
},
cycle_time: {
description: s__(
"ValueStreamAnalytics|Median time from the earliest commit of a linked issue's merge request to when that issue is closed.",
),
},
'lead-time-for-changes': {
description: s__(
'ValueStreamAnalytics|Median time between merge request merge and deployment to a production environment for all MRs deployed in the given time period.',
),
},
lead_time_for_changes: {
description: s__(
'ValueStreamAnalytics|Median time between merge request merge and deployment to a production environment for all MRs deployed in the given time period.',
),
},
issues: { description: s__('ValueStreamAnalytics|Number of new issues created.') },
'new-issue': { description: s__('ValueStreamAnalytics|Number of new issues created.') },
'new-issues': { description: s__('ValueStreamAnalytics|Number of new issues created.') },
deploys: { description: s__('ValueStreamAnalytics|Total number of deploys to production.') },
'deployment-frequency': {
description: s__('ValueStreamAnalytics|Average number of deployments to production per day.'),
},
deployment_frequency: {
description: s__('ValueStreamAnalytics|Average number of deployments to production per day.'),
},
commits: {
description: s__('ValueStreamAnalytics|Number of commits pushed to the default branch'),
},
'time-to-restore-service': {
description: s__(
'ValueStreamAnalytics|Median time an incident was open on a production environment in the given time period.',
),
},
time_to_restore_service: {
description: s__(
'ValueStreamAnalytics|Median time an incident was open on a production environment in the given time period.',
),
},
'change-failure-rate': {
description: s__(
'ValueStreamAnalytics|Percentage of deployments that cause an incident in production.',
),
},
change_failure_rate: {
description: s__(
'ValueStreamAnalytics|Percentage of deployments that cause an incident in production.',

View file

@ -209,6 +209,7 @@ export default {
:textarea-value="timelineText"
:restricted-tool-bar-items="$options.restrictedToolBarItems"
markdown-docs-path=""
:enable-preview="false"
class="bordered-box gl-mt-0"
>
<template #textarea>

View file

@ -34,6 +34,7 @@ export default {
},
computed: {
...mapState('editNew', [
'isExistingRelease',
'isFetchingRelease',
'isUpdatingRelease',
'fetchError',
@ -50,7 +51,7 @@ export default {
'groupMilestonesAvailable',
'tagNotes',
]),
...mapGetters('editNew', ['isValid', 'isExistingRelease', 'formattedReleaseNotes']),
...mapGetters('editNew', ['isValid', 'formattedReleaseNotes']),
showForm() {
return Boolean(!this.isFetchingRelease && !this.fetchError && this.release);
},

View file

@ -1,5 +1,5 @@
<script>
import { mapGetters } from 'vuex';
import { mapState } from 'vuex';
import TagFieldExisting from './tag_field_existing.vue';
import TagFieldNew from './tag_field_new.vue';
@ -9,7 +9,7 @@ export default {
TagFieldNew,
},
computed: {
...mapGetters('editNew', ['isExistingRelease']),
...mapState('editNew', ['isExistingRelease']),
},
};
</script>

View file

@ -22,12 +22,10 @@ export default {
// the input field. This is used to avoid showing validation
// errors immediately when the page loads.
isInputDirty: false,
showCreateFrom: true,
};
},
computed: {
...mapState('editNew', ['projectId', 'release', 'createFrom']),
...mapState('editNew', ['projectId', 'release', 'createFrom', 'showCreateFrom']),
...mapGetters('editNew', ['validationErrors']),
tagName: {
get() {
@ -40,7 +38,7 @@ export default {
// When this is called, the selection originated from the
// dropdown list of existing tag names, so we know the tag
// already exists and don't need to show the "create from" input
this.showCreateFrom = false;
this.updateShowCreateFrom(false);
},
},
createFromModel: {
@ -70,7 +68,12 @@ export default {
},
},
methods: {
...mapActions('editNew', ['updateReleaseTagName', 'updateCreateFrom', 'fetchTagNotes']),
...mapActions('editNew', [
'updateReleaseTagName',
'updateCreateFrom',
'fetchTagNotes',
'updateShowCreateFrom',
]),
markInputAsDirty() {
this.isInputDirty = true;
},
@ -80,7 +83,7 @@ export default {
// This method is called when the user selects the "create tag"
// option, so the tag does not already exist. Because of this,
// we need to show the "create from" input.
this.showCreateFrom = true;
this.updateShowCreateFrom(true);
},
shouldShowCreateTagOption(isLoading, matches, query) {
// Show the "create tag" option if:

View file

@ -11,7 +11,7 @@ export default () => {
const store = createStore({
modules: {
editNew: createEditNewModule(el.dataset),
editNew: createEditNewModule({ ...el.dataset, isExistingRelease: true }),
},
});

View file

@ -11,7 +11,7 @@ export default () => {
const store = createStore({
modules: {
editNew: createEditNewModule(el.dataset),
editNew: createEditNewModule({ ...el.dataset, isExistingRelease: false }),
},
});

View file

@ -11,8 +11,8 @@ import { gqClient, convertOneReleaseGraphQLResponse } from '~/releases/util';
import * as types from './mutation_types';
export const initializeRelease = ({ commit, dispatch, getters }) => {
if (getters.isExistingRelease) {
export const initializeRelease = ({ commit, dispatch, state }) => {
if (state.isExistingRelease) {
// When editing an existing release,
// fetch the release object from the API
return dispatch('fetchRelease');
@ -53,6 +53,9 @@ export const updateReleaseTagName = ({ commit }, tagName) =>
export const updateCreateFrom = ({ commit }, createFrom) =>
commit(types.UPDATE_CREATE_FROM, createFrom);
export const updateShowCreateFrom = ({ commit }, showCreateFrom) =>
commit(types.UPDATE_SHOW_CREATE_FROM, showCreateFrom);
export const updateReleaseTitle = ({ commit }, title) => commit(types.UPDATE_RELEASE_TITLE, title);
export const updateReleaseNotes = ({ commit }, notes) => commit(types.UPDATE_RELEASE_NOTES, notes);
@ -88,10 +91,10 @@ export const receiveSaveReleaseSuccess = ({ commit }, urlToRedirectTo) => {
redirectTo(urlToRedirectTo);
};
export const saveRelease = ({ commit, dispatch, getters }) => {
export const saveRelease = ({ commit, dispatch, state }) => {
commit(types.REQUEST_SAVE_RELEASE);
dispatch(getters.isExistingRelease ? 'updateRelease' : 'createRelease');
dispatch(state.isExistingRelease ? 'updateRelease' : 'createRelease');
};
/**

View file

@ -3,14 +3,6 @@ import { s__ } from '~/locale';
import { hasContent } from '~/lib/utils/text_utility';
import { getDuplicateItemsFromArray } from '~/lib/utils/array_utility';
/**
* @returns {Boolean} `true` if the app is editing an existing release.
* `false` if the app is creating a new release.
*/
export const isExistingRelease = (state) => {
return Boolean(state.tagName);
};
/**
* @param {Object} link The link to test
* @returns {Boolean} `true` if the release link is empty, i.e. it has

View file

@ -6,6 +6,7 @@ export const RECEIVE_RELEASE_ERROR = 'RECEIVE_RELEASE_ERROR';
export const UPDATE_RELEASE_TAG_NAME = 'UPDATE_RELEASE_TAG_NAME';
export const UPDATE_CREATE_FROM = 'UPDATE_CREATE_FROM';
export const UPDATE_SHOW_CREATE_FROM = 'UPDATE_SHOW_CREATE_FROM';
export const UPDATE_RELEASE_TITLE = 'UPDATE_RELEASE_TITLE';
export const UPDATE_RELEASE_NOTES = 'UPDATE_RELEASE_NOTES';
export const UPDATE_RELEASE_MILESTONES = 'UPDATE_RELEASE_MILESTONES';

View file

@ -9,7 +9,7 @@ const findReleaseLink = (release, id) => {
export default {
[types.INITIALIZE_EMPTY_RELEASE](state) {
state.release = {
tagName: null,
tagName: state.tagName,
name: '',
description: '',
milestones: [],
@ -42,6 +42,9 @@ export default {
[types.UPDATE_CREATE_FROM](state, createFrom) {
state.createFrom = createFrom;
},
[types.UPDATE_SHOW_CREATE_FROM](state, showCreateFrom) {
state.showCreateFrom = showCreateFrom;
},
[types.UPDATE_RELEASE_TITLE](state, title) {
state.release.name = title;
},

View file

@ -1,4 +1,5 @@
export default ({
isExistingRelease,
projectId,
groupId,
groupMilestonesAvailable = false,
@ -15,6 +16,7 @@ export default ({
tagName = null,
defaultBranch = null,
}) => ({
isExistingRelease,
projectId,
groupId,
groupMilestonesAvailable: Boolean(groupMilestonesAvailable),
@ -30,9 +32,10 @@ export default ({
/**
* The name of the tag associated with the release, provided by the backend.
* When creating a new release, this value is null.
* When creating a new release, this is the default from the URL
*/
tagName,
showCreateFrom: !tagName,
defaultBranch,
createFrom: defaultBranch,

View file

@ -37,6 +37,10 @@
&.class_ {
color: var(--color-hljs-class);
&.inherited__ {
color: var(--color-hljs-variable);
}
}
&.function_ {

View file

@ -116,6 +116,7 @@ $monokai-gh: #75715e;
@include hljs-override('bullet', $monokai-n);
@include hljs-override('subst', $monokai-p);
@include hljs-override('symbol', $monokai-ss);
@include hljs-override('title.class_.inherited__', $monokai-no);
// Line numbers
.file-line-num {

View file

@ -119,6 +119,7 @@ $solarized-dark-il: #2aa198;
@include hljs-override('bullet', $solarized-dark-n);
@include hljs-override('subst', $solarized-dark-p);
@include hljs-override('symbol', $solarized-dark-ni);
@include hljs-override('title.class_.inherited__', $solarized-dark-no);
// Line numbers
.file-line-num {

View file

@ -106,6 +106,7 @@ $solarized-light-il: #2aa198;
}
.code.solarized-light {
@include hljs-override('title.class_.inherited__', $solarized-light-no);
// Line numbers
.file-line-num {
@include line-link($black, 'link');

View file

@ -2,7 +2,9 @@
@import '../white_base';
@include conflict-colors('white');
@include hljs-override('variable', $white-nv);
@include hljs-override('symbol', $white-ss);
@include hljs-override('title.class_.inherited__', $white-no);
}
:root {

View file

@ -32,7 +32,8 @@ module Mutations
def create_note_params(noteable, args)
super(noteable, args).merge({
type: 'DiffNote',
position: position(noteable, args)
position: position(noteable, args),
merge_request_diff_head_sha: args[:position][:head_sha]
})
end

View file

@ -59,6 +59,7 @@ module ReleasesHelper
def data_for_new_release_page
new_edit_pages_shared_data.merge(
tag_name: params[:tag_name],
default_branch: @project.default_branch,
releases_page_path: project_releases_path(@project)
)

View file

@ -408,14 +408,6 @@ class Issue < ApplicationRecord
end
end
# Returns boolean if a related branch exists for the current issue
# ignores merge requests branchs
def has_related_branch?
project.repository.branch_names.any? do |branch|
/\A#{iid}-(?!\d+-stable)/i =~ branch
end
end
# To allow polymorphism with MergeRequest.
def source_project
project

View file

@ -52,8 +52,6 @@ module ServicePing
ServicePing::DevopsReport.new(response).execute
end
return unless Feature.enabled?(:measure_service_ping_metric_collection)
submit_payload({ metadata: { metrics: metrics_collection_time(usage_data) } }, path: METADATA_PATH)
end

View file

@ -1,8 +0,0 @@
---
name: measure_service_ping_metric_collection
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82607
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/358128
milestone: '15.0'
type: development
group: group::product intelligence
default_enabled: true

View file

@ -1,12 +1,11 @@
# frozen_string_literal: true
class CleanupBackfillDraftStatusOnMergeRequests < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
MIGRATION = 'BackfillDraftStatusOnMergeRequests'
def up
finalize_background_migration(MIGRATION)
# no-op
#
# moved to post-deployment migration:
# db/post_migrate/20220713133515_cleanup_backfill_draft_statuses_on_merge_requests.rb
end
def down

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
class CleanupBackfillDraftStatusesOnMergeRequests < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
MIGRATION = 'BackfillDraftStatusOnMergeRequests'
def up
finalize_background_migration(MIGRATION)
end
def down
# no-op
end
end

View file

@ -0,0 +1 @@
4c0f48149987c821c8666df7a1d9e9780146d356ffb9539572d5a3c77038e237

View file

@ -113,11 +113,8 @@ sequenceDiagram
1. Finally, the timing metadata information that is used for diagnostic purposes is submitted to the Versions application. It consists of a list of metric identifiers and the time it took to calculate the metrics:
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37911) in GitLab 15.0 [with a flag(../../user/feature_flags.md), enabled by default.
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `measure_service_ping_metric_collection`.
On GitLab.com, this feature is available.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/37911) in GitLab 15.0 [with a flag(../../user/feature_flags.md), enabled by default.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/295289) in GitLab 15.2. [Feature flag `measure_service_ping_metric_collection`](https://gitlab.com/gitlab-org/gitlab/-/issues/358128) removed.
```ruby
{"metadata"=>
@ -161,25 +158,6 @@ We also collect metrics specific to [Geo](../../administration/geo/index.md) sec
]
```
### Enable or disable service ping metadata reporting
Service Ping timing metadata reporting is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can opt to disable it.
To enable it:
```ruby
Feature.enable(:measure_service_ping_metric_collection)
```
To disable it:
```ruby
Feature.disable(:measure_service_ping_metric_collection)
```
## Implementing Service Ping
See the [implement Service Ping](implement.md) guide.

View file

@ -28,7 +28,7 @@ module ContainerRegistry
end
def self.deduplicated_size(path)
with_dummy_client(token_config: { type: :nested_repositories_token, path: path }) do |client|
with_dummy_client(token_config: { type: :nested_repositories_token, path: path&.downcase }) do |client|
client.repository_details(path, sizing: :self_with_descendants)['size_bytes']
end
end

View file

@ -86,13 +86,15 @@ module Gitlab
create_labels
issue_type_id = WorkItems::Type.default_issue_type.id
client.issues(repo).each do |issue|
import_issue(issue)
import_issue(issue, issue_type_id)
end
end
# rubocop: disable CodeReuse/ActiveRecord
def import_issue(issue)
def import_issue(issue, issue_type_id)
description = ''
description += @formatter.author_line(issue.author) unless find_user_id(issue.author)
description += issue.description
@ -108,6 +110,7 @@ module Gitlab
author_id: gitlab_user_id(project, issue.author),
namespace_id: project.project_namespace_id,
milestone: milestone,
work_item_type_id: issue_type_id,
created_at: issue.created_at,
updated_at: issue.updated_at
)

View file

@ -651,8 +651,6 @@ module Gitlab
end
def with_duration
return yield unless Feature.enabled?(:measure_service_ping_metric_collection)
result = nil
duration = Benchmark.realtime do
result = yield

View file

@ -17,9 +17,16 @@ namespace :contracts do
)
end
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"
)
end
desc 'Run all pipeline contract tests'
task 'test:pipelines', :contract_mr do |_t, arg|
errors = %w[get_list_project_pipelines].each_with_object([]) do |task, err|
errors = %w[get_list_project_pipelines get_pipeline_header_data].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

@ -0,0 +1,99 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { Matchers } from '@pact-foundation/pact';
import {
JOB_STATUSES,
PIPELINE_GROUPS,
PIPELINE_STATUSES,
PIPELINE_TEXTS,
URL,
URL_PATH,
} from '../../../helpers/common_regex_patterns';
const body = {
data: {
project: {
id: Matchers.string('gid://gitlab/Project/278964'),
pipeline: {
id: Matchers.string('gid://gitlab/Ci::Pipeline/577266584'),
iid: Matchers.string('1175084'),
status: Matchers.term({
matcher: JOB_STATUSES,
generate: 'RUNNING',
}),
retryable: Matchers.boolean(false),
cancelable: Matchers.boolean(true),
userPermissions: {
destroyPipeline: Matchers.boolean(false),
updatePipeline: Matchers.boolean(true),
},
detailedStatus: {
id: Matchers.string('running-577266584-577266584'),
detailsPath: Matchers.term({
matcher: URL_PATH,
generate: '/gitlab-org/gitlab/-/pipelines/577266584',
}),
icon: Matchers.term({
matcher: PIPELINE_STATUSES,
generate: 'status_running',
}),
group: Matchers.term({
matcher: PIPELINE_GROUPS,
generate: 'running',
}),
text: Matchers.term({
matcher: PIPELINE_TEXTS,
generate: 'running',
}),
},
createdAt: Matchers.iso8601DateTime('2022-06-30T16:58:59Z'),
user: {
id: Matchers.string('gid://gitlab/User/194645'),
name: Matchers.string('John Doe'),
username: Matchers.string('jdoe'),
webPath: Matchers.term({
matcher: URL_PATH,
generate: '/gitlab-bot',
}),
webUrl: Matchers.term({
matcher: URL,
generate: 'https://gitlab.com/gitlab-bot',
}),
email: null,
avatarUrl: Matchers.term({
matcher: URL,
generate:
'https://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=80&d=identicon',
}),
status: null,
},
},
},
},
};
const PipelineHeaderData = {
body: Matchers.extractPayload(body),
success: {
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
body,
},
request: {
method: 'POST',
path: '/api/graphql',
},
variables: {
fullPath: 'gitlab-org/gitlab-qa',
iid: 1,
},
};
export { PipelineHeaderData };
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -16,5 +16,9 @@ export const PIPELINE_STATUSES =
export const PIPELINE_TEXTS =
'^(canceled|created|delayed|failed|manual|passed|pending|preparing|running|skipped|waiting)$';
// Jobs
export const JOB_STATUSES =
'^(CANCELED|CREATED|FAILED|MANUAL|PENDING|PREPARING|RUNNING|SCHEDULED|SKIPPED|SUCCESS|WAITING_FOR_RESOURCE)$';
// Users
export const USER_STATES = '^(active|blocked)$';

View file

@ -0,0 +1,8 @@
import { readFile } from 'fs/promises';
import path from 'path';
export async function extractGraphQLQuery(fileLocation) {
const file = path.resolve(__dirname, '..', '..', '..', '..', fileLocation);
return readFile(file, { encoding: 'UTF-8' });
}

View file

@ -0,0 +1,25 @@
import axios from 'axios';
import { extractGraphQLQuery } from '../../helpers/graphql_query_extractor';
export async function getPipelineHeaderDataRequest(endpoint) {
const { url } = endpoint;
const query = await extractGraphQLQuery(
'app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql',
);
const graphqlQuery = {
query,
variables: {
fullPath: 'gitlab-org/gitlab-qa',
iid: 1,
},
};
return axios({
baseURL: url,
url: '/api/graphql',
method: 'POST',
headers: { Accept: '*/*' },
data: graphqlQuery,
});
}

View file

@ -0,0 +1,53 @@
/* eslint-disable @gitlab/require-i18n-strings */
import { pactWith } from 'jest-pact';
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';
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';
// GraphQL query: getPipelineHeaderData
pactWith(
{
consumer: CONSUMER_NAME,
provider: PROVIDER_NAME,
log: CONSUMER_LOG,
dir: CONTRACT_DIR,
},
(provider) => {
describe(PROVIDER_NAME, () => {
beforeEach(async () => {
const query = await extractGraphQLQuery(
'app/assets/javascripts/pipelines/graphql/queries/get_pipeline_header_data.query.graphql',
);
const graphqlQuery = new GraphQLInteraction()
.given('a pipeline for a project exists')
.uponReceiving('a request for the pipeline header data')
.withQuery(query)
.withRequest(PipelineHeaderData.request)
.withVariables(PipelineHeaderData.variables)
.willRespondWith(PipelineHeaderData.success);
provider.addInteraction(graphqlQuery);
});
it('returns a successful body', async () => {
const pipelineHeaderData = await getPipelineHeaderDataRequest({
url: provider.mockService.baseUrl,
});
expect(pipelineHeaderData.data).toEqual(PipelineHeaderData.body);
});
});
},
);
/* eslint-enable @gitlab/require-i18n-strings */

View file

@ -0,0 +1,152 @@
{
"consumer": {
"name": "Pipelines#show"
},
"provider": {
"name": "GET pipeline header data"
},
"interactions": [
{
"description": "a request for the pipeline header data",
"providerState": "a pipeline for a project exists",
"request": {
"method": "POST",
"path": "/api/graphql",
"headers": {
"content-type": "application/json"
},
"body": {
"query": "query getPipelineHeaderData($fullPath: ID!, $iid: ID!) {\n project(fullPath: $fullPath) {\n id\n pipeline(iid: $iid) {\n id\n iid\n status\n retryable\n cancelable\n userPermissions {\n destroyPipeline\n updatePipeline\n }\n detailedStatus {\n id\n detailsPath\n icon\n group\n text\n }\n createdAt\n user {\n id\n name\n username\n webPath\n webUrl\n email\n avatarUrl\n status {\n message\n emoji\n }\n }\n }\n }\n}\n",
"variables": {
"fullPath": "gitlab-org/gitlab-qa",
"iid": 1
}
},
"matchingRules": {
"$.body.query": {
"match": "regex",
"regex": "query\\s*getPipelineHeaderData\\(\\$fullPath:\\s*ID!,\\s*\\$iid:\\s*ID!\\)\\s*\\{\\s*project\\(fullPath:\\s*\\$fullPath\\)\\s*\\{\\s*id\\s*pipeline\\(iid:\\s*\\$iid\\)\\s*\\{\\s*id\\s*iid\\s*status\\s*retryable\\s*cancelable\\s*userPermissions\\s*\\{\\s*destroyPipeline\\s*updatePipeline\\s*\\}\\s*detailedStatus\\s*\\{\\s*id\\s*detailsPath\\s*icon\\s*group\\s*text\\s*\\}\\s*createdAt\\s*user\\s*\\{\\s*id\\s*name\\s*username\\s*webPath\\s*webUrl\\s*email\\s*avatarUrl\\s*status\\s*\\{\\s*message\\s*emoji\\s*\\}\\s*\\}\\s*\\}\\s*\\}\\s*\\}\\s*"
}
}
},
"response": {
"status": 200,
"headers": {
"Content-Type": "application/json; charset=utf-8"
},
"body": {
"data": {
"project": {
"id": "gid://gitlab/Project/278964",
"pipeline": {
"id": "gid://gitlab/Ci::Pipeline/577266584",
"iid": "1175084",
"status": "RUNNING",
"retryable": false,
"cancelable": true,
"userPermissions": {
"destroyPipeline": false,
"updatePipeline": true
},
"detailedStatus": {
"id": "running-577266584-577266584",
"detailsPath": "/gitlab-org/gitlab/-/pipelines/577266584",
"icon": "status_running",
"group": "running",
"text": "running"
},
"createdAt": "2022-06-30T16:58:59Z",
"user": {
"id": "gid://gitlab/User/194645",
"name": "John Doe",
"username": "jdoe",
"webPath": "/gitlab-bot",
"webUrl": "https://gitlab.com/gitlab-bot",
"email": null,
"avatarUrl": "https://www.gravatar.com/avatar/10fc7f102be8de7657fb4d80898bbfe3?s=80&d=identicon",
"status": null
}
}
}
}
},
"matchingRules": {
"$.body.data.project.id": {
"match": "type"
},
"$.body.data.project.pipeline.id": {
"match": "type"
},
"$.body.data.project.pipeline.iid": {
"match": "type"
},
"$.body.data.project.pipeline.status": {
"match": "regex",
"regex": "^(CANCELED|CREATED|FAILED|MANUAL|PENDING|PREPARING|RUNNING|SCHEDULED|SKIPPED|SUCCESS|WAITING_FOR_RESOURCE)$"
},
"$.body.data.project.pipeline.retryable": {
"match": "type"
},
"$.body.data.project.pipeline.cancelable": {
"match": "type"
},
"$.body.data.project.pipeline.userPermissions.destroyPipeline": {
"match": "type"
},
"$.body.data.project.pipeline.userPermissions.updatePipeline": {
"match": "type"
},
"$.body.data.project.pipeline.detailedStatus.id": {
"match": "type"
},
"$.body.data.project.pipeline.detailedStatus.detailsPath": {
"match": "regex",
"regex": "^\\/[a-zA-Z0-9#-=?_]+$"
},
"$.body.data.project.pipeline.detailedStatus.icon": {
"match": "regex",
"regex": "^status_(canceled|created|failed|manual|pending|preparing|running|scheduled|skipped|success|warning)$"
},
"$.body.data.project.pipeline.detailedStatus.group": {
"match": "regex",
"regex": "^(canceled|created|failed|manual|pending|preparing|running|scheduled|skipped|success|success_warning|waiting-for-resource)$"
},
"$.body.data.project.pipeline.detailedStatus.text": {
"match": "regex",
"regex": "^(canceled|created|delayed|failed|manual|passed|pending|preparing|running|skipped|waiting)$"
},
"$.body.data.project.pipeline.createdAt": {
"match": "regex",
"regex": "^\\d{4}-[01]\\d-[0-3]\\dT[0-2]\\d:[0-5]\\d:[0-5]\\d([+-][0-2]\\d:[0-5]\\d|Z)$"
},
"$.body.data.project.pipeline.user.id": {
"match": "type"
},
"$.body.data.project.pipeline.user.name": {
"match": "type"
},
"$.body.data.project.pipeline.user.username": {
"match": "type"
},
"$.body.data.project.pipeline.user.webPath": {
"match": "regex",
"regex": "^\\/[a-zA-Z0-9#-=?_]+$"
},
"$.body.data.project.pipeline.user.webUrl": {
"match": "regex",
"regex": "^(http|https):\\/\\/[a-z0-9]+([-.]{1}[a-z0-9]+)*.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$"
},
"$.body.data.project.pipeline.user.avatarUrl": {
"match": "regex",
"regex": "^(http|https):\\/\\/[a-z0-9]+([-.]{1}[a-z0-9]+)*.[a-z]{2,5}(:[0-9]{1,5})?(\\/.*)?$"
}
}
}
}
],
"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/pipeline_state'
module Provider
module GetPipelinesHeaderDataHelper
Pact.service_provider "GET pipeline header data" do
app { Environments::Test.app }
honours_pact_with 'Pipelines#show' do
pact_uri '../contracts/project/pipeline/show/pipelines#show-get_project_pipeline_header_data.json'
end
end
end
end

View file

@ -0,0 +1,27 @@
# 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,
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

@ -111,6 +111,27 @@ RSpec.describe 'User creates release', :js do
end
end
context 'when tag name supplied in the parameters' do
let(:new_page_url) { new_project_release_path(project, tag_name: 'v1.1.0') }
it 'creates release with preselected tag' do
page.within '[data-testid="tag-name-field"]' do
expect(page).to have_text('v1.1.0')
end
expect(page).not_to have_selector('[data-testid="create-from-field"]')
fill_release_title("test release")
click_button('Create release')
wait_for_all_requests
release = project.releases.last
expect(release.tag).to eq('v1.1.0')
end
end
def fill_out_form_and_submit
select_new_tag_name(tag_name)

16
spec/fixtures/gitlab/git/gitattributes vendored Normal file
View file

@ -0,0 +1,16 @@
# This is a comment, it should be ignored.
*.txt text
*.jpg -text
*.sh eol=lf gitlab-language=shell
*.haml.* gitlab-language=haml
foo/bar.* foo
*.cgi key=value?p1=v1&p2=v2
/*.png gitlab-language=png
*.binary binary
/custom-highlighting/*.gitlab-custom gitlab-language=ruby
/custom-highlighting/*.gitlab-cgi gitlab-language=erb?parent=json
# This uses a tab instead of spaces to ensure the parser also supports this.
*.md gitlab-language=markdown
bla/bla.txt

Binary file not shown.

View file

@ -30,6 +30,7 @@ describe('Release edit/new component', () => {
const factory = async ({ featureFlags = {}, store: storeUpdates = {} } = {}) => {
state = {
release,
isExistingRelease: true,
markdownDocsPath: 'path/to/markdown/docs',
releasesPagePath,
projectId: '8',
@ -46,7 +47,6 @@ describe('Release edit/new component', () => {
getters = {
isValid: () => true,
isExistingRelease: () => true,
validationErrors: () => ({
assets: {
links: [],
@ -206,9 +206,7 @@ describe('Release edit/new component', () => {
store: {
modules: {
editNew: {
getters: {
isExistingRelease: () => false,
},
state: { isExistingRelease: false },
},
},
},

View file

@ -9,14 +9,14 @@ describe('releases/components/tag_field', () => {
let store;
let wrapper;
const createComponent = ({ tagName }) => {
const createComponent = ({ isExistingRelease }) => {
store = createStore({
modules: {
editNew: createEditNewModule({}),
},
});
store.state.editNew.tagName = tagName;
store.state.editNew.isExistingRelease = isExistingRelease;
wrapper = shallowMount(TagField, { store });
};
@ -31,7 +31,7 @@ describe('releases/components/tag_field', () => {
describe('when an existing release is being edited', () => {
beforeEach(() => {
createComponent({ tagName: 'v1.0' });
createComponent({ isExistingRelease: true });
});
it('renders the TagFieldExisting component', () => {
@ -45,7 +45,7 @@ describe('releases/components/tag_field', () => {
describe('when a new release is being created', () => {
beforeEach(() => {
createComponent({ tagName: null });
createComponent({ isExistingRelease: false });
});
it('renders the TagFieldNew component', () => {

View file

@ -37,19 +37,15 @@ describe('Release edit/new actions', () => {
let error;
const setupState = (updates = {}) => {
const getters = {
isExistingRelease: true,
};
state = {
...createState({
projectId: '18',
isExistingRelease: true,
tagName: releaseResponse.tag_name,
releasesPagePath: 'path/to/releases/page',
markdownDocsPath: 'path/to/markdown/docs',
markdownPreviewPath: 'path/to/markdown/preview',
}),
...getters,
...updates,
};
};
@ -186,6 +182,15 @@ describe('Release edit/new actions', () => {
});
});
describe('updateShowCreateFrom', () => {
it(`commits ${types.UPDATE_SHOW_CREATE_FROM} with the updated ref`, () => {
const newRef = 'my-feature-branch';
return testAction(actions.updateShowCreateFrom, newRef, state, [
{ type: types.UPDATE_SHOW_CREATE_FROM, payload: newRef },
]);
});
});
describe('updateReleaseTitle', () => {
it(`commits ${types.UPDATE_RELEASE_TITLE} with the updated release title`, () => {
const newTitle = 'The new release title';

View file

@ -2,20 +2,6 @@ import { s__ } from '~/locale';
import * as getters from '~/releases/stores/modules/edit_new/getters';
describe('Release edit/new getters', () => {
describe('isExistingRelease', () => {
it('returns true if the release is an existing release that already exists in the database', () => {
const state = { tagName: 'test-tag-name' };
expect(getters.isExistingRelease(state)).toBe(true);
});
it('returns false if the release is a new release that has not yet been saved to the database', () => {
const state = { tagName: null };
expect(getters.isExistingRelease(state)).toBe(false);
});
});
describe('releaseLinksToCreate', () => {
it("returns an empty array if state.release doesn't exist", () => {
const state = {};

View file

@ -25,7 +25,7 @@ describe('Release edit/new mutations', () => {
mutations[types.INITIALIZE_EMPTY_RELEASE](state);
expect(state.release).toEqual({
tagName: null,
tagName: 'v1.3',
name: '',
description: '',
milestones: [],
@ -103,6 +103,16 @@ describe('Release edit/new mutations', () => {
});
});
describe(`${types.UPDATE_SHOW_CREATE_FROM}`, () => {
it('updates the ref that the ref will be created from', () => {
state.showCreateFrom = true;
const newValue = false;
mutations[types.UPDATE_SHOW_CREATE_FROM](state, newValue);
expect(state.showCreateFrom).toBe(newValue);
});
});
describe(`${types.UPDATE_RELEASE_TITLE}`, () => {
it("updates the release's title", () => {
state.release = release;

View file

@ -77,6 +77,7 @@ RSpec.describe ReleasesHelper do
group_id
group_milestones_available
project_path
tag_name
releases_page_path
markdown_preview_path
markdown_docs_path

View file

@ -316,6 +316,17 @@ RSpec.describe ContainerRegistry::GitlabApiClient do
it { is_expected.to eq(nil) }
end
context 'with uppercase path' do
let(:path) { 'foo/Bar' }
before do
expect(Auth::ContainerRegistryAuthenticationService).to receive(:pull_nested_repositories_access_token).with(path.downcase).and_return(token)
stub_repository_details(path, sizing: :self_with_descendants, status_code: 200, respond_with: response)
end
it { is_expected.to eq(555) }
end
end
def stub_pre_import(path, status_code, pre:)

View file

@ -363,6 +363,14 @@ RSpec.describe Gitlab::BitbucketImport::Importer do
expect(project.issues.where("description LIKE ?", '%reporter3%').size).to eq(1)
expect(importer.errors).to be_empty
end
it 'sets work item type on new issues' do
allow(importer).to receive(:import_wiki)
importer.execute
expect(project.issues.map(&:work_item_type_id).uniq).to contain_exactly(WorkItems::Type.default_issue_type.id)
end
end
context 'metrics' do

View file

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Git::AttributesAtRefParser, :seed_helper do
RSpec.describe Gitlab::Git::AttributesAtRefParser do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }

View file

@ -2,9 +2,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Git::AttributesParser, :seed_helper do
let(:attributes_path) { File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info', 'attributes') }
let(:data) { File.read(attributes_path) }
RSpec.describe Gitlab::Git::AttributesParser do
let(:data) { fixture_file('gitlab/git/gitattributes') }
subject { described_class.new(data) }
@ -141,11 +140,12 @@ RSpec.describe Gitlab::Git::AttributesParser, :seed_helper do
expect { |b| subject.each_line(&b) }.to yield_successive_args(*args)
end
it 'does not yield when the attributes file has an unsupported encoding' do
path = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git', 'info', 'attributes')
attrs = described_class.new(File.read(path))
context 'unsupported encoding' do
let(:data) { fixture_file('gitlab/git/gitattributes_invalid') }
expect { |b| attrs.each_line(&b) }.not_to yield_control
it 'does not yield' do
expect { |b| subject.each_line(&b) }.not_to yield_control
end
end
end
end

View file

@ -1728,27 +1728,28 @@ RSpec.describe Gitlab::Git::Repository, :seed_helper do
end
describe '#gitattribute' do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_GITATTRIBUTES_REPO_PATH, '', 'group/project') }
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
after do
ensure_seeds
context 'with gitattributes' do
before do
repository.copy_gitattributes('gitattributes')
end
it 'returns matching language attribute' do
expect(repository.gitattribute("custom-highlighting/test.gitlab-custom", 'gitlab-language')).to eq('ruby')
end
it 'returns matching language attribute with additional options' do
expect(repository.gitattribute("custom-highlighting/test.gitlab-cgi", 'gitlab-language')).to eq('erb?parent=json')
end
it 'returns nil if nothing matches' do
expect(repository.gitattribute("report.xslt", 'gitlab-language')).to eq(nil)
end
end
it 'returns matching language attribute' do
expect(repository.gitattribute("custom-highlighting/test.gitlab-custom", 'gitlab-language')).to eq('ruby')
end
it 'returns matching language attribute with additional options' do
expect(repository.gitattribute("custom-highlighting/test.gitlab-cgi", 'gitlab-language')).to eq('erb?parent=json')
end
it 'returns nil if nothing matches' do
expect(repository.gitattribute("report.xslt", 'gitlab-language')).to eq(nil)
end
context 'without gitattributes file' do
let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
context 'without gitattributes' do
it 'returns nil' do
expect(repository.gitattribute("README.md", 'gitlab-language')).to eq(nil)
end

View file

@ -1365,29 +1365,11 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
end
describe ".with_duration" do
context 'with feature flag measure_service_ping_metric_collection turned off' do
before do
stub_feature_flags(measure_service_ping_metric_collection: false)
end
it 'records duration' do
expect(::Gitlab::Usage::ServicePing::LegacyMetricTimingDecorator)
.to receive(:new).with(2, kind_of(Float))
it 'does NOT record duration and return block response' do
expect(::Gitlab::Usage::ServicePing::LegacyMetricTimingDecorator).not_to receive(:new)
expect(described_class.with_duration { 1 + 1 }).to be 2
end
end
context 'with feature flag measure_service_ping_metric_collection turned off' do
before do
stub_feature_flags(measure_service_ping_metric_collection: true)
end
it 'records duration' do
expect(::Gitlab::Usage::ServicePing::LegacyMetricTimingDecorator)
.to receive(:new).with(2, kind_of(Float))
described_class.with_duration { 1 + 1 }
end
described_class.with_duration { 1 + 1 }
end
end

View file

@ -681,28 +681,6 @@ RSpec.describe Issue do
end
end
describe '#has_related_branch?' do
let(:issue) { create(:issue, project: reusable_project, title: "Blue Bell Knoll") }
subject { issue.has_related_branch? }
context 'branch found' do
before do
allow(issue.project.repository).to receive(:branch_names).and_return(["iceblink-luck", issue.to_branch_name])
end
it { is_expected.to eq true }
end
context 'branch not found' do
before do
allow(issue.project.repository).to receive(:branch_names).and_return(["lazy-calm"])
end
it { is_expected.to eq false }
end
end
it_behaves_like 'an editable mentionable' do
subject { create(:issue, project: create(:project, :repository)) }

View file

@ -10,11 +10,12 @@ RSpec.describe 'Adding a DiffNote' do
let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
let(:project) { create(:project, :repository) }
let(:diff_refs) { noteable.diff_refs }
let(:body) { 'Body text' }
let(:base_variables) do
{
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
body: 'Body text',
body: body,
position: {
paths: {
old_path: 'files/ruby/popen.rb',
@ -65,6 +66,17 @@ RSpec.describe 'Adding a DiffNote' do
it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
end
context 'with /merge quick action' do
let(:body) { "Body text \n/merge" }
it 'merges the merge request', :sidekiq_inline do
post_graphql_mutation(mutation, current_user: current_user)
expect(noteable.reload.state).to eq('merged')
expect(mutation_response['note']['body']).to eq('Body text')
end
end
it 'returns the note with the correct position' do
post_graphql_mutation(mutation, current_user: current_user)

View file

@ -377,62 +377,41 @@ RSpec.describe ServicePing::SubmitService do
stub_database_flavor_check
stub_application_setting(usage_ping_enabled: true)
stub_response(body: with_conv_index_params)
end
context 'with feature flag measure_service_ping_metric_collection turned on' do
let(:metric_double) { instance_double(Gitlab::Usage::ServicePing::LegacyMetricTimingDecorator, duration: 123) }
let(:payload) do
{
metric_a: metric_double,
metric_group: {
metric_b: metric_double
},
metric_without_timing: "value",
recorded_at: Time.current
}
end
let(:metadata_payload) do
{
metadata: {
metrics: [
{ name: 'metric_a', time_elapsed: 123 },
{ name: 'metric_group.metric_b', time_elapsed: 123 }
]
}
}
end
before do
stub_feature_flags(measure_service_ping_metric_collection: true)
allow_next_instance_of(ServicePing::BuildPayload) do |service|
allow(service).to receive(:execute).and_return(payload)
end
end
it 'submits metadata' do
response = stub_full_request(service_ping_metadata_url, method: :post)
.with(body: metadata_payload)
subject.execute
expect(response).to have_been_requested
allow_next_instance_of(ServicePing::BuildPayload) do |service|
allow(service).to receive(:execute).and_return(payload)
end
end
context 'with feature flag measure_service_ping_metric_collection turned off' do
before do
stub_feature_flags(measure_service_ping_metric_collection: false)
end
let(:metric_double) { instance_double(Gitlab::Usage::ServicePing::LegacyMetricTimingDecorator, duration: 123) }
let(:payload) do
{
metric_a: metric_double,
metric_group: {
metric_b: metric_double
},
metric_without_timing: "value",
recorded_at: Time.current
}
end
it 'does NOT submit metadata' do
response = stub_full_request(service_ping_metadata_url, method: :post)
let(:metadata_payload) do
{
metadata: {
metrics: [
{ name: 'metric_a', time_elapsed: 123 },
{ name: 'metric_group.metric_b', time_elapsed: 123 }
]
}
}
end
subject.execute
it 'submits metadata' do
response = stub_full_request(service_ping_metadata_url, method: :post)
.with(body: metadata_payload)
expect(response).not_to have_been_requested
end
subject.execute
expect(response).to have_been_requested
end
end

View file

@ -9,7 +9,6 @@ TEST_REPO_PATH = 'gitlab-git-test.git'
TEST_NORMAL_REPO_PATH = 'not-bare-repo.git'
TEST_MUTABLE_REPO_PATH = 'mutable-repo.git'
TEST_BROKEN_REPO_PATH = 'broken-repo.git'
TEST_GITATTRIBUTES_REPO_PATH = 'with-git-attributes.git'
module SeedHelper
GITLAB_GIT_TEST_REPO_URL = File.expand_path('../gitlab-git-test.git', __dir__)
@ -25,8 +24,6 @@ module SeedHelper
create_normal_seeds
create_mutable_seeds
create_broken_seeds
create_git_attributes
create_invalid_git_attributes
end
def create_bare_seeds
@ -67,48 +64,4 @@ module SeedHelper
FileUtils.rm_r(refs_path)
end
def create_git_attributes
system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} #{TEST_GITATTRIBUTES_REPO_PATH}),
chdir: SEED_STORAGE_PATH,
out: '/dev/null',
err: '/dev/null')
dir = File.join(SEED_STORAGE_PATH, 'with-git-attributes.git', 'info')
FileUtils.mkdir_p(dir)
File.open(File.join(dir, 'attributes'), 'w') do |handle|
handle.write <<-EOF.strip
# This is a comment, it should be ignored.
*.txt text
*.jpg -text
*.sh eol=lf gitlab-language=shell
*.haml.* gitlab-language=haml
foo/bar.* foo
*.cgi key=value?p1=v1&p2=v2
/*.png gitlab-language=png
*.binary binary
/custom-highlighting/*.gitlab-custom gitlab-language=ruby
/custom-highlighting/*.gitlab-cgi gitlab-language=erb?parent=json
# This uses a tab instead of spaces to ensure the parser also supports this.
*.md\tgitlab-language=markdown
bla/bla.txt
EOF
end
end
def create_invalid_git_attributes
dir = File.join(SEED_STORAGE_PATH, 'with-invalid-git-attributes.git', 'info')
FileUtils.mkdir_p(dir)
enc = Encoding::UTF_16
File.open(File.join(dir, 'attributes'), 'w', encoding: enc) do |handle|
handle.write('# hello'.encode(enc))
end
end
end

View file

@ -46,10 +46,10 @@ GEM
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
method_source (1.0.0)
mini_portile2 (2.8.0)
minitest (5.16.0)
nokogiri (1.13.6-arm64-darwin)
racc (~> 1.4)
nokogiri (1.13.6-x86_64-linux)
nokogiri (1.13.6)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
orm_adapter (0.5.0)
racc (1.6.0)
@ -92,8 +92,7 @@ GEM
zeitwerk (2.6.0)
PLATFORMS
arm64-darwin-21
x86_64-linux
ruby
DEPENDENCIES
activemodel (~> 6.1, < 8)

View file

@ -52,8 +52,7 @@ GEM
parser (>= 3.1.0)
PLATFORMS
x86_64-darwin-20
x86_64-linux
ruby
DEPENDENCIES
benchmark-memory (~> 0.2.0)