提交 67370c8b 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 185d9acf
......@@ -645,7 +645,7 @@
rules:
- if: '$DAST_DISABLED || $GITLAB_FEATURES !~ /\bdast\b/'
when: never
- <<: *if-dot-com-gitlab-org-schedule
- <<: *if-master-schedule-nightly
allow_failure: true
################
......
......@@ -18,19 +18,6 @@ Capybara/CurrentPathExpectation:
Layout/ArgumentAlignment:
Enabled: false
# Offense count: 13
# Cop supports --auto-correct.
Layout/ClosingHeredocIndentation:
Exclude:
- 'app/graphql/mutations/merge_requests/set_wip.rb'
- 'ee/db/geo/migrate/20180322062741_migrate_ci_job_artifacts_to_separate_registry.rb'
- 'ee/lib/gitlab/geo/health_check.rb'
- 'spec/features/merge_request/user_sees_diff_spec.rb'
- 'spec/lib/gitlab/asciidoc_spec.rb'
- 'spec/lib/gitlab/checks/project_moved_spec.rb'
- 'spec/rubocop/cop/active_record_association_reload_spec.rb'
- 'spec/services/task_list_toggle_service_spec.rb'
# Offense count: 13
# Cop supports --auto-correct.
Layout/ClosingParenthesisIndentation:
......
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { uniqueId } from 'lodash';
import produce from 'immer';
import { defaultDataIdFromObject } from 'apollo-cache-inmemory';
import createDefaultClient from '~/lib/graphql';
import activeDiscussionQuery from './graphql/queries/active_discussion.query.graphql';
......@@ -11,12 +12,17 @@ Vue.use(VueApollo);
const resolvers = {
Mutation: {
updateActiveDiscussion: (_, { id = null, source }, { cache }) => {
const data = cache.readQuery({ query: activeDiscussionQuery });
data.activeDiscussion = {
__typename: 'ActiveDiscussion',
id,
source,
};
const sourceData = cache.readQuery({ query: activeDiscussionQuery });
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.activeDiscussion = {
__typename: 'ActiveDiscussion',
id,
source,
};
});
cache.writeQuery({ query: activeDiscussionQuery, data });
},
},
......@@ -37,6 +43,7 @@ const defaultClient = createDefaultClient(
},
},
typeDefs,
assumeImmutableResults: true,
},
);
......
......@@ -281,13 +281,8 @@ export default {
.mutate({
mutation: moveDesignMutation,
variables: this.designMoveVariables(newIndex, element),
update: (store, { data: { designManagementMove } }) => {
return updateDesignsOnStoreAfterReorder(
store,
designManagementMove,
this.projectQueryBody,
);
},
update: (store, { data: { designManagementMove } }) =>
updateDesignsOnStoreAfterReorder(store, designManagementMove, this.projectQueryBody),
optimisticResponse: moveDesignOptimisticResponse(this.reorderedDesigns),
})
.catch(() => {
......@@ -327,7 +322,7 @@ export default {
v-if="isLatestVersion"
variant="link"
size="small"
class="gl-mr-3 js-select-all"
class="gl-mr-4 js-select-all"
@click="toggleDesignsSelection"
>{{ selectAllButtonText }}
</gl-button>
......
/* eslint-disable @gitlab/require-i18n-strings */
import { groupBy } from 'lodash';
import produce from 'immer';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import { extractCurrentDiscussion, extractDesign } from './design_management_utils';
import { extractCurrentDiscussion, extractDesign, extractDesigns } from './design_management_utils';
import {
ADD_IMAGE_DIFF_NOTE_ERROR,
UPDATE_IMAGE_DIFF_NOTE_ERROR,
......@@ -10,13 +11,20 @@ import {
designDeletionError,
} from './error_messages';
const designsOf = data => data.project.issue.designCollection.designs;
const isParticipating = (design, username) =>
design.issue.participants.nodes.some(participant => participant.username === username);
const deleteDesignsFromStore = (store, query, selectedDesigns) => {
const data = store.readQuery(query);
const sourceData = store.readQuery(query);
const changedDesigns = data.project.issue.designCollection.designs.nodes.filter(
node => !selectedDesigns.includes(node.filename),
);
data.project.issue.designCollection.designs.nodes = [...changedDesigns];
const data = produce(sourceData, draftData => {
const changedDesigns = designsOf(sourceData).nodes.filter(
design => !selectedDesigns.includes(design.filename),
);
designsOf(draftData).nodes = [...changedDesigns];
});
store.writeQuery({
...query,
......@@ -33,13 +41,15 @@ const deleteDesignsFromStore = (store, query, selectedDesigns) => {
*/
const addNewVersionToStore = (store, query, version) => {
if (!version) return;
const sourceData = store.readQuery(query);
const data = store.readQuery(query);
data.project.issue.designCollection.versions.nodes = [
version,
...data.project.issue.designCollection.versions.nodes,
];
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.project.issue.designCollection.versions.nodes = [
version,
...draftData.project.issue.designCollection.versions.nodes,
];
});
store.writeQuery({
...query,
......@@ -48,46 +58,41 @@ const addNewVersionToStore = (store, query, version) => {
};
const addDiscussionCommentToStore = (store, createNote, query, queryVariables, discussionId) => {
const data = store.readQuery({
const sourceData = store.readQuery({
query,
variables: queryVariables,
});
const design = extractDesign(data);
const currentDiscussion = extractCurrentDiscussion(design.discussions, discussionId);
currentDiscussion.notes.nodes = [...currentDiscussion.notes.nodes, createNote.note];
design.notesCount += 1;
if (
!design.issue.participants.nodes.some(
participant => participant.username === createNote.note.author.username,
)
) {
design.issue.participants.nodes = [
...design.issue.participants.nodes,
{
__typename: 'User',
...createNote.note.author,
},
];
}
const newParticipant = {
__typename: 'User',
...createNote.note.author,
};
const data = produce(sourceData, draftData => {
const design = extractDesign(draftData);
const currentDiscussion = extractCurrentDiscussion(design.discussions, discussionId);
currentDiscussion.notes.nodes = [...currentDiscussion.notes.nodes, createNote.note];
if (!isParticipating(design, createNote.note.author.username)) {
design.issue.participants.nodes = [...design.issue.participants.nodes, newParticipant];
}
design.notesCount += 1;
});
store.writeQuery({
query,
variables: queryVariables,
data: {
...data,
design: {
...design,
},
},
data,
});
};
const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) => {
const data = store.readQuery({
const sourceData = store.readQuery({
query,
variables,
});
const newDiscussion = {
__typename: 'Discussion',
id: createImageDiffNote.note.discussion.id,
......@@ -101,100 +106,100 @@ const addImageDiffNoteToStore = (store, createImageDiffNote, query, variables) =
nodes: [createImageDiffNote.note],
},
};
const design = extractDesign(data);
const notesCount = design.notesCount + 1;
design.discussions.nodes = [...design.discussions.nodes, newDiscussion];
if (
!design.issue.participants.nodes.some(
participant => participant.username === createImageDiffNote.note.author.username,
)
) {
design.issue.participants.nodes = [
...design.issue.participants.nodes,
{
__typename: 'User',
...createImageDiffNote.note.author,
},
];
}
const data = produce(sourceData, draftData => {
const design = extractDesign(draftData);
design.notesCount += 1;
design.discussions.nodes = [...design.discussions.nodes, newDiscussion];
if (
!design.issue.participants.nodes.some(
participant => participant.username === createImageDiffNote.note.author.username,
)
) {
design.issue.participants.nodes = [
...design.issue.participants.nodes,
{
__typename: 'User',
...createImageDiffNote.note.author,
},
];
}
});
store.writeQuery({
query,
variables,
data: {
...data,
design: {
...design,
notesCount,
},
},
data,
});
};
const updateImageDiffNoteInStore = (store, updateImageDiffNote, query, variables) => {
const data = store.readQuery({
const sourceData = store.readQuery({
query,
variables,
});
const design = extractDesign(data);
const discussion = extractCurrentDiscussion(
design.discussions,
updateImageDiffNote.note.discussion.id,
);
discussion.notes = {
...discussion.notes,
nodes: [updateImageDiffNote.note, ...discussion.notes.nodes.slice(1)],
};
const data = produce(sourceData, draftData => {
const design = extractDesign(draftData);
const discussion = extractCurrentDiscussion(
design.discussions,
updateImageDiffNote.note.discussion.id,
);
discussion.notes = {
...discussion.notes,
nodes: [updateImageDiffNote.note, ...discussion.notes.nodes.slice(1)],
};
});
store.writeQuery({
query,
variables,
data: {
...data,
design,
},
data,
});
};
const addNewDesignToStore = (store, designManagementUpload, query) => {
const data = store.readQuery(query);
const sourceData = store.readQuery(query);
const currentDesigns = data.project.issue.designCollection.designs.nodes;
const existingDesigns = groupBy(currentDesigns, 'filename');
const newDesigns = currentDesigns.concat(
designManagementUpload.designs.filter(d => !existingDesigns[d.filename]),
);
const data = produce(sourceData, draftData => {
const currentDesigns = extractDesigns(draftData);
const existingDesigns = groupBy(currentDesigns, 'filename');
const newDesigns = currentDesigns.concat(
designManagementUpload.designs.filter(d => !existingDesigns[d.filename]),
);
let newVersionNode;
const findNewVersions = designManagementUpload.designs.find(design => design.versions);
let newVersionNode;
const findNewVersions = designManagementUpload.designs.find(design => design.versions);
if (findNewVersions) {
const findNewVersionsNodes = findNewVersions.versions.nodes;
if (findNewVersions) {
const findNewVersionsNodes = findNewVersions.versions.nodes;
if (findNewVersionsNodes && findNewVersionsNodes.length) {
newVersionNode = [findNewVersionsNodes[0]];
if (findNewVersionsNodes && findNewVersionsNodes.length) {
newVersionNode = [findNewVersionsNodes[0]];
}
}
}
const newVersions = [
...(newVersionNode || []),
...data.project.issue.designCollection.versions.nodes,
];
const updatedDesigns = {
__typename: 'DesignCollection',
designs: {
__typename: 'DesignConnection',
nodes: newDesigns,
},
versions: {
__typename: 'DesignVersionConnection',
nodes: newVersions,
},
};
const newVersions = [
...(newVersionNode || []),
...draftData.project.issue.designCollection.versions.nodes,
];
data.project.issue.designCollection = updatedDesigns;
const updatedDesigns = {
__typename: 'DesignCollection',
designs: {
__typename: 'DesignConnection',
nodes: newDesigns,
},
versions: {
__typename: 'DesignVersionConnection',
nodes: newVersions,
},
};
// eslint-disable-next-line no-param-reassign
draftData.project.issue.designCollection = updatedDesigns;
});
store.writeQuery({
...query,
......@@ -203,8 +208,14 @@ const addNewDesignToStore = (store, designManagementUpload, query) => {
};
const moveDesignInStore = (store, designManagementMove, query) => {
const data = store.readQuery(query);
data.project.issue.designCollection.designs = designManagementMove.designCollection.designs;
const sourceData = store.readQuery(query);
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.project.issue.designCollection.designs =
designManagementMove.designCollection.designs;
});
store.writeQuery({
...query,
data,
......
......@@ -90,7 +90,7 @@ export default {
if (this.newSnippet) {
return this.projectPath
? `${gon.relative_url_root}${this.projectPath}/-/snippets`
: `${gon.relative_url_root}-/snippets`;
: `${gon.relative_url_root}/-/snippets`;
}
return this.snippet.webUrl;
},
......
......@@ -97,7 +97,7 @@ export default {
text: __('New snippet'),
href: this.snippet.project
? `${this.snippet.project.webUrl}/-/snippets/new`
: `${gon.relative_url_root}-/snippets/new`,
: `${gon.relative_url_root}/-/snippets/new`,
variant: 'success',
category: 'secondary',
cssClass: 'ml-2',
......
......@@ -9,8 +9,8 @@ module Mutations
GraphQL::BOOLEAN_TYPE,
required: true,
description: <<~DESC
Whether or not to set the merge request as a WIP.
DESC
Whether or not to set the merge request as a WIP.
DESC
def resolve(project_path:, iid:, wip: nil)
merge_request = authorized_find!(project_path: project_path, iid: iid)
......
......@@ -40,5 +40,9 @@ module Ci
def self.has_code_coverage?
where(file_type: :code_coverage).exists?
end
def self.find_with_code_coverage
find_by(file_type: :code_coverage)
end
end
end
......@@ -1344,7 +1344,7 @@ class MergeRequest < ApplicationRecord
def has_coverage_reports?
return false unless Feature.enabled?(:coverage_report_view, project)
actual_head_pipeline&.has_reports?(Ci::JobArtifact.coverage_reports)
actual_head_pipeline&.pipeline_artifacts&.has_code_coverage?
end
def has_terraform_reports?
......
......@@ -135,6 +135,10 @@ class Namespace < ApplicationRecord
uniquify.string(path) { |s| Namespace.find_by_path_or_name(s) }
end
def clean_name(value)
value.scan(Gitlab::Regex.group_name_regex_chars).join(' ')
end
def find_by_pages_host(host)
gitlab_host = "." + Settings.pages.host.downcase
host = host.downcase
......
......@@ -12,7 +12,7 @@ module Ci
{
status: :parsed,
key: key(base_pipeline, head_pipeline),
data: head_pipeline.coverage_reports.pick(merge_request.new_paths)
data: Gitlab::Ci::Pipeline::Artifact::CodeCoverage.new(head_pipeline.pipeline_artifacts.find_with_code_coverage).for_files(merge_request.new_paths)
}
rescue => e
Gitlab::ErrorTracking.track_exception(e, project_id: project.id)
......
......@@ -22,9 +22,6 @@ module Projects
end
success
rescue => e
Gitlab::ErrorTracking.track_exception(e)
error(e.message, pass_back: { exception: e })
end
private
......
......@@ -10,14 +10,6 @@ class PagesUpdateConfigurationWorker
project = Project.find_by_id(project_id)
return unless project
result = Projects::UpdatePagesConfigurationService.new(project).execute
# The ConfigurationService swallows all exceptions and wraps them in a status
# we need to keep this while the feature flag still allows running this
# service within a request.
# But we might as well take advantage of sidekiq retries here.
# We should let the service raise after we remove the feature flag
# https://gitlab.com/gitlab-org/gitlab/-/issues/230695
raise result[:exception] if result[:exception]
Projects::UpdatePagesConfigurationService.new(project).execute
end
end
---
title: Add spacing to design management toolbar buttons
merge_request: 38889
author: George Tsiolis
type: changed
---
title: Create a POC for 'immer' library
merge_request: 39738
author:
type: other
......@@ -56,7 +56,7 @@ def note_for_spin_role(spin, role)
return OPTIONAL_REVIEW_TEMPLATE % { role: role.capitalize, category: helper.label_for_category(spin.category) }
end
spin.public_send(role)&.markdown_name(timezone_experiment: spin.timezone_experiment, author: roulette.team_mr_author) # rubocop:disable GitlabSecurity/PublicSend
spin.public_send(role)&.markdown_name(author: roulette.team_mr_author) # rubocop:disable GitlabSecurity/PublicSend
end
def markdown_row_for_spins(category, spins_array)
......
# frozen_string_literal: true
class SchedulePopulateResolvedOnDefaultBranchColumn < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
BATCH_SIZE = 100
DELAY_INTERVAL = 5.minutes.to_i
MIGRATION_CLASS = 'PopulateResolvedOnDefaultBranchColumn'
BASE_MODEL = EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn::Vulnerability
disable_ddl_transaction!
def up
return unless run_migration?
BASE_MODEL.distinct.each_batch(of: BATCH_SIZE, column: :project_id) do |batch, index|
project_ids = batch.pluck(:project_id)
migrate_in(index * DELAY_INTERVAL, MIGRATION_CLASS, project_ids)
end
end
def down; end
private
def run_migration?
Gitlab.ee? && table_exists?(:projects) && table_exists?(:vulnerabilities)
end
end
fdcce45050f972d8edf2c645022f517ff6b9f4c76767e6cebe45a11fe34dd388
\ No newline at end of file
......@@ -2164,10 +2164,7 @@ build_job:
```
Environment variables support for `project:`, `job:`, and `ref` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/202093)
in GitLab 13.3. This is under development, but it is ready for production use. It is deployed
behind the `ci_expand_names_for_cross_pipeline_artifacts` feature flag, which is **disabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../administration/feature_flags.md)
can enable it for your instance.
in GitLab 13.3. [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235761) in GitLab 13.4.
For example:
......
......@@ -39,7 +39,7 @@ Changes to the schema should be committed to `db/structure.sql`. This
file is automatically generated by Rails, so you normally should not
edit this file by hand. If your migration is adding a column to a
table, that column will be added at the bottom. Please do not reorder
columns manually for existing tables as this will cause confusing to
columns manually for existing tables as this will cause confusion to
other people using `db/structure.sql` generated by Rails.
When your local database in your GDK is diverging from the schema from
......
......@@ -647,16 +647,13 @@ a fixed timeout of 60 seconds.
- Click **Create new DAST scan**.
- Click **Delete** in the matching site profile's row.
### Enable or disable On-demand Scans and site profiles
### Enable or disable On-demand Scans
On-demand Scans with site profiles is enabled by default. You can disable On-demand Scans
instance-wide, or disable it for specific projects if you prefer. DAST site profiles are not
available if the On-demand Scans feature is disabled.
On-demand Scans is enabled by default. You can disable On-demand Scans
instance-wide, or disable it for specific projects if you prefer.
Use of On-demand Scans with site profiles requires **both** the following feature flags enabled:
- security_on_demand_scans_feature_flag
- security_on_demand_scans_site_profiles_feature_flag
Use of On-demand Scans requires the `security_on_demand_scans_feature_flag`
feature flag enabled.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can disable or enable the feature flags.
......@@ -681,31 +678,6 @@ Feature.enable(:security_on_demand_scans_feature_flag)
Feature.enable(:security_on_demand_scans_feature_flag, Project.find(<project ID>))
```
#### Enable or disable site profiles
The Site Profiles feature is enabled instance-wide by default. You can disable it instance-wide, or disable it
for specific projects if you prefer.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can disable or enable the feature flag.
To disable Site Profiles:
```ruby
# Instance-wide
Feature.disable(:security_on_demand_scans_site_profiles_feature_flag)
# or by project
Feature.disable(:security_on_demand_scans_site_profiles_feature_flag, Project.find(<project id>))
```
To enable Site Profiles:
```ruby
# Instance-wide
Feature.enable(:security_on_demand_scans_site_profiles_feature_flag)
# or by project
Feature.enable(:security_on_demand_scans_site_profiles_feature_flag, Project.find(<project ID>))
```
## Reports
The DAST tool outputs a report file in JSON format by default. However, this tool can also generate reports in
......
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# rubocop:disable Style/Documentation
class PopulateResolvedOnDefaultBranchColumn
def perform(*); end
end
end
end
Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn.prepend_if_ee('EE::Gitlab::BackgroundMigration::PopulateResolvedOnDefaultBranchColumn')
# frozen_string_literal: true
module Gitlab
module Ci
module Pipeline
module Artifact
class CodeCoverage
def initialize(pipeline_artifact)
@pipeline_artifact = pipeline_artifact
end
def for_files(filenames)
coverage_files = raw_report["files"].select { |key| filenames.include?(key) }
{ files: coverage_files }
end
private
def raw_report
@raw_report ||= Gitlab::Json.parse(@pipeline_artifact.file.read)
end
end
end
end
end
end
......@@ -45,9 +45,7 @@ module Gitlab
has_capability?(project, category, :maintainer, labels)
end
def markdown_name(timezone_experiment: false, author: nil)
return @markdown_name unless timezone_experiment
def markdown_name(author: nil)
"#{@markdown_name} (#{utc_offset_text(author)})"
end
......
......@@ -122,7 +122,11 @@ module Gitlab
end
def group_name_regex
@group_name_regex ||= /\A[\p{Alnum}\u{00A9}-\u{1f9ff}_][\p{Alnum}\p{Pd}\u{00A9}-\u{1f9ff}_()\. ]*\z/.freeze
@group_name_regex ||= /\A#{group_name_regex_chars}\z/.freeze
end
def group_name_regex_chars
@group_name_regex_chars ||= /[\p{Alnum}\u{00A9}-\u{1f9ff}_][\p{Alnum}\p{Pd}\u{00A9}-\u{1f9ff}_()\. ]*/.freeze
end
def group_name_regex_message
......
......@@ -91,7 +91,7 @@ module Gitlab
end
def expiry(event)
return event[:expiry] if event[:expiry].present?
return event[:expiry].days if event[:expiry].present?
event[:aggregation].to_sym == :daily ? DEFAULT_DAILY_KEY_EXPIRY_LENGTH : DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH
end
......
......@@ -16907,18 +16907,12 @@ msgstr ""
msgid "OnDemandScans|Could not run the scan. Please try again."
msgstr ""
msgid "OnDemandScans|Could not run the scan: %{backendErrorMessage}"
msgstr ""
msgid "OnDemandScans|Create a new site profile"
msgstr ""
msgid "OnDemandScans|Create new DAST scan"
msgstr ""
msgid "OnDemandScans|DAST will scan the target URL and any discovered sub URLs."
msgstr ""
msgid "OnDemandScans|Manage profiles"
msgstr ""
......@@ -16940,18 +16934,9 @@ msgstr ""
msgid "OnDemandScans|Passive"
msgstr ""
msgid "OnDemandScans|Passive DAST Scan"
msgstr ""
msgid "OnDemandScans|Please enter a valid URL format, ex: http://www.example.com/home"
msgstr ""
msgid "OnDemandScans|Run scan"
msgstr ""
msgid "OnDemandScans|Run this scan"
msgstr ""
msgid "OnDemandScans|Scan mode"
msgstr ""
......@@ -16967,9 +16952,6 @@ msgstr ""
msgid "OnDemandScans|Site profiles"
msgstr ""
msgid "OnDemandScans|Target URL"
msgstr ""
msgid "OnDemandScans|Use existing site profile"
msgstr ""
......
......@@ -24,5 +24,15 @@ FactoryBot.define do
)
end
end
trait :with_code_coverage_with_multiple_files do
after(:build) do |artifact, _evaluator|
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/pipeline_artifacts/code_coverage_with_multiple_files.json'), 'application/json'
)
end
size { file.size }
end
end
end
......@@ -121,6 +121,12 @@ FactoryBot.define do
end
end
trait :with_coverage_report_artifact do
after(:build) do |pipeline, evaluator|
pipeline.pipeline_artifacts << build(:ci_pipeline_artifact, pipeline: pipeline, project: pipeline.project)
end
end
trait :with_terraform_reports do
status { :success }
......
......@@ -169,7 +169,7 @@ FactoryBot.define do
merge_request.head_pipeline = build(
:ci_pipeline,
:success,
:with_coverage_reports,
:with_coverage_report_artifact,
project: merge_request.source_project,
ref: merge_request.source_branch,
sha: merge_request.diff_head_sha)
......
......@@ -97,7 +97,7 @@ RSpec.describe 'Merge request > User sees diff', :js do
let c = 3;
let d = 3;
}
CONTENT
CONTENT
new_file_content =
<<~CONTENT
......@@ -107,7 +107,7 @@ RSpec.describe 'Merge request > User sees diff', :js do
let c = 3;
let x = 3;
}
CONTENT
CONTENT
file_name = 'xss_file.rs'
......
{
"files": {
"file_a.rb": {
"1": 1,
"2": 1,
"3": 1
},
"file_b.rb": {
"1": 0,
"2": 0,
"3": 0
}
}
}
......@@ -111,7 +111,7 @@ exports[`Design management index page designs renders designs list and header wi
>
<gl-button-stub
category="primary"
class="gl-mr-3 js-select-all"
class="gl-mr-4 js-select-all"
icon=""
size="small"
variant="link"
......
......@@ -200,7 +200,7 @@ describe('Snippet Edit app', () => {
it.each`
projectPath | snippetArg | expectation
${''} | ${[]} | ${`${relativeUrlRoot}-/snippets`}
${''} | ${[]} | ${`${relativeUrlRoot}/-/snippets`}
${'project/path'} | ${[]} | ${`${relativeUrlRoot}project/path/-/snippets`}
${''} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
${'project/path'} | ${[createTestSnippet()]} | ${TEST_WEB_URL}
......
......@@ -404,7 +404,7 @@ module Gitlab
++++
stem:[2+2] is 4
MD
MD
expect(render(input, context)).to include('<pre data-math-style="display" class="code math js-render-math"><code>eta_x gamma</code></pre>')
expect(render(input, context)).to include('<p><code data-math-style="inline" class="code math js-render-math">2+2</code> is 4</p>')
......
......@@ -57,12 +57,12 @@ RSpec.describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
shared_examples 'returns redirect message' do
it do
message = <<~MSG
Project '#{redirect_path}' was moved to '#{project.full_path}'.
Project '#{redirect_path}' was moved to '#{project.full_path}'.
Please update your Git remote:
Please update your Git remote:
git remote set-url origin #{url_to_repo}
MSG
git remote set-url origin #{url_to_repo}
MSG
expect(subject.message).to eq(message)
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Ci::Pipeline::Artifact::CodeCoverage do
let(:pipeline_artifact) { create(:ci_pipeline_artifact, :with_code_coverage_with_multiple_files) }
let(:code_coverage) { described_class.new(pipeline_artifact) }
describe '#for_files' do
subject { code_coverage.for_files(filenames) }
context 'when code coverage has data' do
context 'when filenames is empty' do
let(:filenames) { %w() }
it 'returns hash without coverage' do
expect(subject).to match(files: {})
end
end
context 'when filenames do not match code coverage data' do
let(:filenames) { %w(demo.rb) }
it 'returns hash without coverage' do
expect(subject).to match(files: {})
end
end
context 'when filenames matches code coverage data' do
context 'when asking for one filename' do
let(:filenames) { %w(file_a.rb) }
it 'returns coverage for the given filename' do
expect(subject).to match(files: { "file_a.rb" => { "1" => 1, "2" => 1, "3" => 1 } })
end
end
context 'when asking for multiple filenames' do
let(:filenames) { %w(file_a.rb file_b.rb) }
it 'returns coverage for a the given filenames' do
expect(subject).to match(
files: {
"file_a.rb" => {
"1" => 1,
"2" => 1,
"3" => 1
},
"file_b.rb" => {
"1" => 0,
"2" => 0,
"3" => 0
}
}
)
end
end
end
end
end
end
......@@ -170,47 +170,38 @@ RSpec.describe Gitlab::Danger::Teammate do
end
describe '#markdown_name' do
context 'when timezone_experiment == false' do
it 'returns markdown name as-is' do
expect(subject.markdown_name).to eq(options['markdown_name'])
expect(subject.markdown_name(timezone_experiment: false)).to eq(options['markdown_name'])
end
it 'returns markdown name with timezone info' do
expect(subject.markdown_name).to eq("#{options['markdown_name']} (UTC+2)")
end
context 'when timezone_experiment == true' do
it 'returns markdown name with timezone info' do
expect(subject.markdown_name(timezone_experiment: true)).to eq("#{options['markdown_name']} (UTC+2)")
end
context 'when offset is 1.5' do
let(:tz_offset_hours) { 1.5 }
context 'when offset is 1.5' do
let(:tz_offset_hours) { 1.5 }
it 'returns markdown name with timezone info, not truncated' do
expect(subject.markdown_name(timezone_experiment: true)).to eq("#{options['markdown_name']} (UTC+1.5)")
end
it 'returns markdown name with timezone info, not truncated' do
expect(subject.markdown_name).to eq("#{options['markdown_name']} (UTC+1.5)")
end
end
context 'when author is given' do
where(:tz_offset_hours, :author_offset, :diff_text) do
-12 | -10 | "2 hours behind `@mario`"
-10 | -12 | "2 hours ahead of `@mario`"
-10 | 2 | "12 hours behind `@mario`"
2 | 4 | "2 hours behind `@mario`"
4 | 2 | "2 hours ahead of `@mario`"
2 | 3 | "1 hour behind `@mario`"
3 | 2 | "1 hour ahead of `@mario`"
2 | 2 | "same timezone as `@mario`"
end
context 'when author is given' do
where(:tz_offset_hours, :author_offset, :diff_text) do
-12 | -10 | "2 hours behind `@mario`"
-10 | -12 | "2 hours ahead of `@mario`"
-10 | 2 | "12 hours behind `@mario`"
2 | 4 | "2 hours behind `@mario`"
4 | 2 | "2 hours ahead of `@mario`"
2 | 3 | "1 hour behind `@mario`"
3 | 2 | "1 hour ahead of `@mario`"
2 | 2 | "same timezone as `@mario`"
end
with_them do
it 'returns markdown name with timezone info' do
author = described_class.new(options.merge('username' => 'mario', 'tz_offset_hours' => author_offset))
with_them do
it 'returns markdown name with timezone info' do
author = described_class.new(options.merge('username' => 'mario', 'tz_offset_hours' => author_offset))
floored_offset_hours = subject.__send__(:floored_offset_hours)
utc_offset = floored_offset_hours >= 0 ? "+#{floored_offset_hours}" : floored_offset_hours
floored_offset_hours = subject.__send__(:floored_offset_hours)
utc_offset = floored_offset_hours >= 0 ? "+#{floored_offset_hours}" : floored_offset_hours
expect(subject.markdown_name(timezone_experiment: true, author: author)).to eq("#{options['markdown_name']} (UTC#{utc_offset}, #{diff_text})")
end
expect(subject.markdown_name(author: author)).to eq("#{options['markdown_name']} (UTC#{utc_offset}, #{diff_text})")
end
end
end
......
......@@ -3,14 +3,19 @@
require 'fast_spec_helper'
RSpec.describe Gitlab::Regex do
shared_examples_for 'project/group name regex' do
shared_examples_for 'project/group name chars regex' do
it { is_expected.to match('gitlab-ce') }
it { is_expected.to match('GitLab CE') }
it { is_expected.to match('100 lines') }
it { is_expected.to match('gitlab.git') }
it { is_expected.to match('Český název') }
it { is_expected.to match('Dash – is this') }
end
shared_examples_for 'project/group name regex' do
it_behaves_like 'project/group name chars regex'
it { is_expected.not_to match('?gitlab') }
it { is_expected.not_to match("Users's something") }
end
describe '.project_name_regex' do
......@@ -33,6 +38,16 @@ RSpec.describe Gitlab::Regex do
end
end
describe '.group_name_regex_chars' do
subject { described_class.group_name_regex_chars }
it_behaves_like 'project/group name chars regex'
it 'allows partial matches' do
is_expected.to match(',Valid name wrapped in ivalid chars&')
end
end
describe '.project_name_regex_message' do
subject { described_class.project_name_regex_message }
......
......@@ -45,6 +45,32 @@ RSpec.describe Gitlab::UsageDataCounters::HLLRedisCounter, :clean_gitlab_redis_s
it 'raise error if metrics of unknown aggregation' do
expect { described_class.track_event(entity1, 'unknown', Date.current) } .to raise_error(Gitlab::UsageDataCounters::HLLRedisCounter::UnknownEvent)
end
it 'sets the keys in Redis to expire automatically after 12 weeks' do
described_class.track_event(entity1, "g_analytics_contribution")
Gitlab::Redis::SharedState.with do |redis|
keys = redis.scan_each(match: "g_{analytics}_contribution-*").to_a
expect(keys).not_to be_empty
keys.each do |key|
expect(redis.ttl(key)).to be_within(5.seconds).of(12.weeks)
end
end
end
it 'sets the keys in Redis to expire automatically after 6 weeks by default' do
described_class.track_event(entity1, "g_compliance_dashboard")
Gitlab::Redis::SharedState.with do |redis|
keys = redis.scan_each(match: "g_{compliance}_dashboard-*").to_a
expect(keys).not_to be_empty
keys.each do |key|
expect(redis.ttl(key)).to be_within(5.seconds).of(6.weeks)
end
end
end
end
describe '.unique_events' do
......
......@@ -91,4 +91,22 @@ RSpec.describe Ci::PipelineArtifact, type: :model do
end
end
end
describe '.find_with_code_coverage' do
subject { Ci::PipelineArtifact.find_with_code_coverage }
context 'when pipeline artifact has a coverage report' do
let!(:coverage_report) { create(:ci_pipeline_artifact) }
it 'returns a pipeline artifact with a code coverage' do
expect(subject.file_type).to eq('code_coverage')
end
end
context 'when pipeline artifact does not have a coverage report' do
it 'returns nil' do
expect(subject).to be_nil
end
end
end
end
......@@ -2,12 +2,13 @@
require 'spec_helper'
RSpec.describe Ci::Pipeline, :mailer do
RSpec.describe Ci::Pipeline, :mailer, factory_default: :keep do
include ProjectForksHelper
include StubRequests
let(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:namespace) { create_default(:namespace) }
let_it_be(:project) { create_default(:project, :repository) }
let(:pipeline) do
create(:ci_empty_pipeline, status: :created, project: project)
......@@ -1436,8 +1437,6 @@ RSpec.describe Ci::Pipeline, :mailer do
context 'when repository exists' do
using RSpec::Parameterized::TableSyntax
let(:project) { create(:project, :repository) }
where(:tag, :ref, :result) do
false | 'master' | true
false | 'non-existent-branch' | false
......@@ -1457,6 +1456,7 @@ RSpec.describe Ci::Pipeline, :mailer do
end
context 'when repository does not exist' do
let(:project) { create(:project) }
let(:pipeline) do
create(:ci_empty_pipeline, project: project, ref: 'master')
end
......@@ -1468,8 +1468,6 @@ RSpec.describe Ci::Pipeline, :mailer do
end
context 'with non-empty project' do
let(:project) { create(:project, :repository) }
let(:pipeline) do
create(:ci_pipeline,
project: project,
......@@ -1596,8 +1594,6 @@ RSpec.describe Ci::Pipeline, :mailer do
describe '#modified_paths' do
context 'when old and new revisions are set' do
let(:project) { create(:project, :repository) }
before do
pipeline.update(before_sha: '1234abcd', sha: '2345bcde')
end
......@@ -1866,8 +1862,6 @@ RSpec.describe Ci::Pipeline, :mailer do
end
describe '.latest_pipeline_per_commit' do
let(:project) { create(:project) }
let!(:commit_123_ref_master) do
create(
:ci_empty_pipeline,
......@@ -1962,7 +1956,6 @@ RSpec.describe Ci::Pipeline, :mailer do
end
describe '.last_finished_for_ref_id' do
let(:project) { create(:project, :repository) }
let(:branch) { project.default_branch }
let(:ref) { project.ci_refs.take }
let(:config_source) { Enums::Ci::Pipeline.config_sources[:parameter_source] }
......@@ -2452,7 +2445,6 @@ RSpec.describe Ci::Pipeline, :mailer do
end
describe "#merge_requests_as_head_pipeline" do
let(:project) { create(:project) }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') }
it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
......@@ -2685,7 +2677,8 @@ RSpec.describe Ci::Pipeline, :mailer do
end
describe 'notifications when pipeline success or failed' do
let(:project) { create(:project, :repository) }
let(:namespace) { create(:namespace) }
let(:project) { create(:project, :repository, namespace: namespace) }
let(:pipeline) do
create(:ci_pipeline,
......@@ -3260,7 +3253,8 @@ RSpec.describe Ci::Pipeline, :mailer do
end
describe '#parent_pipeline' do
let(:project) { create(:project) }
let_it_be(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when pipeline is triggered by a pipeline from the same project' do
......@@ -3315,7 +3309,7 @@ RSpec.describe Ci::Pipeline, :mailer do
end
describe '#child_pipelines' do
let(:project) { create(:project) }
let_it_be(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
context 'when pipeline triggered other pipelines on same project' do
......
......@@ -1890,12 +1890,6 @@ RSpec.describe MergeRequest do
subject { merge_request.find_coverage_reports }
context 'when head pipeline has coverage reports' do
let!(:job) do
create(:ci_build, options: { artifacts: { reports: { cobertura: ['cobertura-coverage.xml'] } } }, pipeline: pipeline)
end
let!(:artifacts_metadata) { create(:ci_job_artifact, :metadata, job: job) }
context 'when reactive cache worker is parsing results asynchronously' do
it 'returns status' do
expect(subject[:status]).to eq(:parsing)
......
......@@ -588,6 +588,21 @@ RSpec.describe Namespace do
end
end
describe ".clean_name" do
context "when the name complies with the group name regex" do
it "returns the name as is" do
valid_name = "Hello - World _ (Hi.)"
expect(described_class.clean_name(valid_name)).to eq(valid_name)
end
end
context "when the name does not comply with the group name regex" do
it "sanitizes the name by replacing all invalid char sequences with a space" do
expect(described_class.clean_name("Green'! Test~~~")).to eq("Green Test")
end
end
end
describe "#default_branch_protection" do
let(:namespace) { create(:namespace) }
let(:default_branch_protection) { nil }
......
......@@ -15,7 +15,7 @@ RSpec.describe RuboCop::Cop::ActiveRecordAssociationReload, type: :rubocop do
users = User.all
users.reload
^^^^^^ Use reset instead of reload. For more details check the https://gitlab.com/gitlab-org/gitlab-foss/issues/60218.
PATTERN
PATTERN
end
it 'does not register an offense on reset usage' do
......
......@@ -15,7 +15,11 @@ RSpec.describe Ci::GenerateCoverageReportsService do
let!(:head_pipeline) { merge_request.head_pipeline }
let!(:base_pipeline) { nil }
it 'returns status and data' do
it 'returns status and data', :aggregate_failures do
expect_next_instance_of(Gitlab::Ci::Pipeline::Artifact::CodeCoverage) do |instance|
expect(instance).to receive(:for_files).with(merge_request.new_paths).and_call_original
end
expect(subject[:status]).to eq(:parsed)
expect(subject[:data]).to eq(files: {})
end
......@@ -28,8 +32,7 @@ RSpec.describe Ci::GenerateCoverageReportsService do
let!(:base_pipeline) { nil }
before do
build = create(:ci_build, pipeline: head_pipeline, project: head_pipeline.project)
create(:ci_job_artifact, :coverage_with_corrupted_data, job: build, project: project)
head_pipeline.pipeline_artifacts.destroy_all # rubocop: disable Cop/DestroyAll
end
it 'returns status and error message' do
......
......@@ -48,15 +48,6 @@ RSpec.describe Projects::UpdatePagesConfigurationService do
expect(subject).to include(status: :success)
end
end
context 'when an error occurs' do
it 'returns an error object' do
e = StandardError.new("Failure")
allow(service).to receive(:reload_daemon).and_raise(e)
expect(subject).to eq(status: :error, message: "Failure", exception: e)
end
end
end
context 'when pages are not deployed' do
......
......@@ -119,7 +119,7 @@ RSpec.describe TaskListToggleService do
<<-EOT.strip_heredoc
> > * [ ] Task 1
> * [x] Task 2
EOT
EOT
markdown_html = parse_markdown(markdown)
toggler = described_class.new(markdown, markdown_html,
......@@ -140,7 +140,7 @@ RSpec.describe TaskListToggleService do
* [ ] Task 1
* [x] Task 2
EOT
EOT
markdown_html = parse_markdown(markdown)
toggler = described_class.new(markdown, markdown_html,
......@@ -158,7 +158,7 @@ RSpec.describe TaskListToggleService do
<<-EOT.strip_heredoc
- - [ ] Task 1
- [x] Task 2
EOT
EOT
markdown_html = parse_markdown(markdown)
toggler = described_class.new(markdown, markdown_html,
......@@ -175,7 +175,7 @@ RSpec.describe TaskListToggleService do
<<-EOT.strip_heredoc
1. - [ ] Task 1
- [x] Task 2
EOT
EOT
markdown_html = parse_markdown(markdown)
toggler = described_class.new(markdown, markdown_html,
......
......@@ -15,6 +15,7 @@ require 'rspec/retry'
require 'rspec-parameterized'
require 'shoulda/matchers'
require 'test_prof/recipes/rspec/let_it_be'
require 'test_prof/factory_default'
rspec_profiling_is_configured =
ENV['RSPEC_PROFILING_POSTGRES_URL'].present? ||
......@@ -360,3 +361,6 @@ Rugged::Settings['search_path_global'] = Rails.root.join('tmp/tests').to_s
# Disable timestamp checks for invisible_captcha
InvisibleCaptcha.timestamp_enabled = false
# Initialize FactoryDefault to use create_default helper
TestProf::FactoryDefault.init
# frozen_string_literal: true
RSpec.configure do |config|
config.after do |ex|
TestProf::FactoryDefault.reset unless ex.metadata[:factory_default] == :keep
end
config.after(:all) do
TestProf::FactoryDefault.reset
end
end
......@@ -17,14 +17,6 @@ RSpec.describe PagesUpdateConfigurationWorker do
subject.perform(project.id)
end
it "raises an exception if the service returned an error" do
allow_next_instance_of(Projects::UpdatePagesConfigurationService) do |service|
allow(service).to receive(:execute).and_return({ exception: ":boom:" })
end
expect { subject.perform(project.id) }.to raise_error(":boom:")
end
it_behaves_like "an idempotent worker" do
let(:job_args) { [project.id] }
let(:pages_dir) { Dir.mktmpdir }
......
......@@ -5954,6 +5954,11 @@ immediate@~3.0.5:
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=
immer@^7.0.7:
version "7.0.7"
resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.7.tgz#9dfe713d49bf871cc59aedfce59b1992fa37a977"
integrity sha512-Q8yYwVADJXrNfp1ZUAh4XDHkcoE3wpdpb4mC5abDSajs2EbW8+cGdPyAnglMyLnm7EF6ojD2xBFX7L5i4TIytw==
import-fresh@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册