Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-07-04 15:10:24 +00:00
parent 46b08e61d2
commit 0751650453
64 changed files with 497 additions and 356 deletions

View File

@ -41,10 +41,9 @@ workflow:
QA_IMAGE: "${CI_REGISTRY}/${CI_PROJECT_PATH}/gitlab-ee-qa:${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
# Also run (detached) merge request pipelines.
- if: '$CI_MERGE_REQUEST_IID'
# For the 2-hourly scheduled pipelines, we set specific variables.
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "2-hourly"'
# For the maintenance scheduled pipelines, we set specific variables.
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "maintenance"'
variables:
RUBY_VERSION: "2.7"
CRYSTALBALL: "true"
# For `$CI_DEFAULT_BRANCH` branch, create a pipeline (this includes on schedules, pushes, merges, etc.).
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'

View File

@ -15,7 +15,7 @@ pages:
- job: "compile-production-assets"
- job: "compile-storybook"
# `update-tests-metadata` only runs on GitLab.com's EE schedules pipelines
# while `pages` runs for all the 2-hourly schedules.
# while `pages` runs for all the maintenance scheduled pipelines.
- job: "update-tests-metadata"
optional: true
before_script:

View File

@ -73,7 +73,7 @@ populate-qa-tests-var:
image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: prepare
script:
- export QA_TESTS=$(scripts/determine-qa-tests --files $CHANGES_FILE --labels $CI_MERGE_REQUEST_LABELS)
- export QA_TESTS=$(scripts/determine-qa-tests --files $CHANGES_FILE --labels "$CI_MERGE_REQUEST_LABELS")
- 'echo "QA_TESTS=$QA_TESTS" >> qa_tests_var.env'
- 'echo "QA_TESTS: $QA_TESTS"'
artifacts:

View File

@ -20,7 +20,7 @@ review-build-cng-env:
extends:
- .default-retry
- .review:rules:review-build-cng
image: ${GITLAB_DEPENDENCY_PROXY}ruby:${RUBY_VERSION}-alpine3.13
image: ${GITLAB_DEPENDENCY_PROXY}ruby:3.0-alpine3.13
stage: prepare
needs: []
before_script:
@ -79,7 +79,7 @@ review-build-cng:
DOMAIN: "-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}"
GITLAB_HELM_CHART_REF: "a6a609a19166f00b1a7774374041cd38a9f7e20d"
environment:
name: review/${CI_COMMIT_REF_SLUG}${FREQUENCY}
name: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # No separator for SCHEDULE_TYPE so it's compatible as before and looks nice without it
url: https://gitlab-${CI_ENVIRONMENT_SLUG}.${REVIEW_APPS_DOMAIN}
on_stop: review-stop
auto_stop_in: 48 hours

View File

@ -8,7 +8,7 @@ review-cleanup:
image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images:gitlab-gcloud-helm3.5-kubectl1.17
stage: prepare
environment:
name: review/${CI_COMMIT_REF_SLUG}${FREQUENCY}
name: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # No separator for SCHEDULE_TYPE so it's compatible as before and looks nice without it
action: stop
before_script:
- source scripts/utils.sh
@ -33,7 +33,7 @@ start-review-app-pipeline:
# They need to be explicitly passed on to the child pipeline.
# https://docs.gitlab.com/ee/ci/pipelines/multi_project_pipelines.html#pass-cicd-variables-to-a-downstream-pipeline-by-using-the-variables-keyword
variables:
FREQUENCY: $FREQUENCY
SCHEDULE_TYPE: $SCHEDULE_TYPE
DAST_RUN: $DAST_RUN
trigger:
include:

View File

@ -91,11 +91,11 @@
.if-fork-merge-request: &if-fork-merge-request
if: '$CI_PROJECT_NAMESPACE !~ /^gitlab(-org)?($|\/)/ && $CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_LABELS !~ /pipeline:run-all-rspec/'
.if-default-branch-schedule-2-hourly: &if-default-branch-schedule-2-hourly
if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "2-hourly"'
.if-default-branch-schedule-maintenance: &if-default-branch-schedule-maintenance
if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "maintenance"'
.if-default-branch-schedule-nightly: &if-default-branch-schedule-nightly
if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "nightly"'
if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "nightly"'
.if-security-schedule: &if-security-schedule
if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_PIPELINE_SOURCE == "schedule"'
@ -106,17 +106,14 @@
.if-dot-com-ee-schedule: &if-dot-com-ee-schedule
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_PIPELINE_SOURCE == "schedule"'
.if-dot-com-ee-schedule-child-pipeline: &if-dot-com-ee-schedule-child-pipeline
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_PIPELINE_SOURCE == "parent_pipeline" && $FREQUENCY'
.if-dot-com-ee-schedule-maintenance: &if-dot-com-ee-schedule-maintenance
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "maintenance"'
.if-dot-com-ee-2-hourly-schedule: &if-dot-com-ee-2-hourly-schedule
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "2-hourly"'
.if-dot-com-ee-schedule-nightly: &if-dot-com-ee-schedule-nightly
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_PIPELINE_SOURCE == "schedule" && $SCHEDULE_TYPE == "nightly"'
.if-dot-com-ee-nightly-schedule: &if-dot-com-ee-nightly-schedule
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "nightly"'
.if-dot-com-ee-nightly-schedule-child-pipeline: &if-dot-com-ee-nightly-schedule-child-pipeline
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_PIPELINE_SOURCE == "parent_pipeline" && $FREQUENCY == "nightly"'
.if-dot-com-ee-schedule-nightly-child-pipeline: &if-dot-com-ee-schedule-nightly-child-pipeline
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_PIPELINE_SOURCE == "parent_pipeline" && $SCHEDULE_TYPE == "nightly"'
.if-dot-com-gitlab-org-default-branch: &if-dot-com-gitlab-org-default-branch
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH'
@ -625,7 +622,7 @@
################
.shared:rules:update-cache:
rules:
- <<: *if-default-branch-schedule-2-hourly
- <<: *if-default-branch-schedule-maintenance
- <<: *if-security-schedule
- <<: *if-merge-request-labels-update-caches
@ -884,7 +881,7 @@
###############
.pages:rules:
rules:
- <<: *if-dot-com-ee-2-hourly-schedule
- <<: *if-dot-com-ee-schedule-maintenance
############
# QA rules #
@ -1384,7 +1381,7 @@
- <<: *if-merge-request
changes: *code-backstage-patterns
when: always
- <<: *if-default-branch-schedule-2-hourly
- <<: *if-default-branch-schedule-maintenance
- <<: *if-merge-request-labels-run-all-rspec
when: always
@ -1608,13 +1605,13 @@
rules:
- if: '$DAST_DISABLED || $GITLAB_FEATURES !~ /\bdast\b/'
when: never
- <<: *if-dot-com-ee-nightly-schedule-child-pipeline
- <<: *if-dot-com-ee-schedule-nightly-child-pipeline
.reports:rules:package_hunter-yarn:
rules:
- if: "$PACKAGE_HUNTER_USER == null || $PACKAGE_HUNTER_USER == ''"
when: never
- <<: *if-default-branch-schedule-2-hourly
- <<: *if-default-branch-schedule-maintenance
- <<: *if-merge-request
changes: ["yarn.lock"]
@ -1622,7 +1619,7 @@
rules:
- if: "$PACKAGE_HUNTER_USER == null || $PACKAGE_HUNTER_USER == ''"
when: never
- <<: *if-default-branch-schedule-2-hourly
- <<: *if-default-branch-schedule-maintenance
- <<: *if-merge-request
changes: ["Gemfile.lock"]
@ -1807,12 +1804,11 @@
when: never
- <<: *if-merge-request-labels-jh-contribution
.setup:rules:generate-frontend-fixtures-mapping:
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-ee-2-hourly-schedule
- <<: *if-dot-com-ee-schedule-maintenance
- changes:
- ".gitlab/ci/setup.gitlab-ci.yml"
- ".gitlab/ci/test-metadata.gitlab-ci.yml"
@ -1844,7 +1840,7 @@
rules:
- <<: *if-not-ee
when: never
- <<: *if-dot-com-ee-2-hourly-schedule
- <<: *if-dot-com-ee-schedule-maintenance
- changes:
- ".gitlab/ci/test-metadata.gitlab-ci.yml"
- "scripts/rspec_helpers.sh"

View File

@ -180,7 +180,6 @@ export default {
<gl-dropdown-item
v-for="group in groups"
:key="`${group.id}${group.name}`"
fingerprint
data-testid="group-dropdown-item"
:avatar-url="group.avatar_url"
is-check-item

View File

@ -351,7 +351,6 @@ export default {
<gl-dropdown-item
v-for="group in groups"
:key="`${group.id}${group.name}`"
fingerprint
data-testid="group-dropdown-item"
:avatar-url="group.avatar_url"
is-check-item

View File

@ -45,6 +45,7 @@ export const ROUGE_TO_HLJS_LANGUAGE_MAP = {
haskell: 'haskell',
haxe: 'haxe',
http: 'http',
html: 'xml',
hylang: 'hy',
ini: 'ini',
isbl: 'isbl',

View File

@ -0,0 +1,5 @@
.gl-spinner-container{ class: @class }
- if @inline
%span{ class: spinner_class, aria: {label: @label} }
- else
%div{ class: spinner_class, aria: {label: @label} }

View File

@ -0,0 +1,27 @@
# frozen_string_literal: true
module Pajamas
class SpinnerComponent < Pajamas::Component
# @param [String] class
# @param [Symbol] color
# @param [Boolean] inline
# @param [String] label
# @param [Symbol] size
def initialize(class: '', color: :dark, inline: false, label: _("Loading"), size: :sm)
@class = binding.local_variable_get(:class)
@color = filter_attribute(color.to_sym, COLOR_OPTIONS)
@inline = inline
@label = label.presence
@size = filter_attribute(size.to_sym, SIZE_OPTIONS)
end
private
def spinner_class
["gl-spinner", "gl-spinner-#{@size}", "gl-spinner-#{@color}"]
end
COLOR_OPTIONS = [:light, :dark].freeze
SIZE_OPTIONS = [:sm, :md, :lg, :xl].freeze
end
end

View File

@ -5,14 +5,14 @@ class Projects::JobsController < Projects::ApplicationController
include ContinueParams
include ProjectStatsRefreshConflictsGuard
urgency :low, [:index, :show, :trace, :retry, :play, :cancel, :unschedule, :status, :erase, :raw]
urgency :low, [:index, :show, :trace, :retry, :play, :cancel, :unschedule, :erase, :raw]
before_action :find_job_as_build, except: [:index, :play, :show]
before_action :find_job_as_processable, only: [:play, :show]
before_action :authorize_read_build_trace!, only: [:trace, :raw]
before_action :authorize_read_build!
before_action :authorize_update_build!,
except: [:index, :show, :status, :raw, :trace, :erase, :cancel, :unschedule]
except: [:index, :show, :raw, :trace, :erase, :cancel, :unschedule]
before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
@ -124,12 +124,6 @@ class Projects::JobsController < Projects::ApplicationController
end
end
def status
render json: Ci::JobSerializer
.new(project: @project, current_user: @current_user)
.represent_status(@build.present(current_user: current_user))
end
def erase
if @build.erase(erased_by: current_user)
redirect_to project_job_path(project, @build),

View File

@ -3,10 +3,5 @@
module Ci
class JobSerializer < BaseSerializer
entity Ci::JobEntity
def represent_status(resource)
data = represent(resource, { only: [:status] })
data.fetch(:status, {})
end
end
end

View File

@ -23,7 +23,8 @@ module Issuable
with_csv_lines.each do |row, line_no|
issuable_attributes = {
title: row[:title],
description: row[:description]
description: row[:description],
due_date: row[:due_date]
}
if create_issuable(issuable_attributes).persisted?

View File

@ -81,8 +81,9 @@ module Issues
::Issue
end
def allowed_issue_params
allowed_params = [
def public_params
# Additional params may be assigned later (in a CreateService for example)
public_issue_params = [
:title,
:description,
:confidential
@ -90,17 +91,17 @@ module Issues
params[:work_item_type] = WorkItems::Type.find_by(id: params[:work_item_type_id]) if params[:work_item_type_id].present? # rubocop: disable CodeReuse/ActiveRecord
allowed_params << :milestone_id if can?(current_user, :admin_issue, project)
allowed_params << :issue_type if create_issue_type_allowed?(project, params[:issue_type])
allowed_params << :work_item_type if create_issue_type_allowed?(project, params[:work_item_type]&.base_type)
public_issue_params << :milestone_id if can?(current_user, :admin_issue, project)
public_issue_params << :issue_type if create_issue_type_allowed?(project, params[:issue_type])
public_issue_params << :work_item_type if create_issue_type_allowed?(project, params[:work_item_type]&.base_type)
params.slice(*allowed_params)
params.slice(*public_issue_params)
end
def build_issue_params
{ author: current_user }
.merge(issue_params_with_info_from_discussions)
.merge(allowed_issue_params)
.merge(public_params)
.with_indifferent_access
end

View File

@ -64,6 +64,6 @@
= gl_redirect_listbox_tag admin_users_sort_options(filter: params[:filter], search_query: params[:search_query]), @sort, data: { right: true }
#js-admin-users-app{ data: admin_users_data_attributes(@users) }
= gl_loading_icon(size: 'lg', css_class: 'gl-my-7')
= render Pajamas::SpinnerComponent.new(size: :lg, class: 'gl-my-7')
= paginate_collection @users

View File

@ -6,4 +6,4 @@
.content_list
.loading
= gl_loading_icon(size: 'md')
= render Pajamas::SpinnerComponent.new(size: :md)

View File

@ -6,4 +6,4 @@
.content_list
.loading
= gl_loading_icon(size: 'md')
= render Pajamas::SpinnerComponent.new(size: :md)

View File

@ -11,4 +11,4 @@
.content_list.project-activity{ :"data-href" => activity_project_path(@project) }
.loading
= gl_loading_icon(size: 'md')
= render Pajamas::SpinnerComponent.new(size: :md)

View File

@ -0,0 +1,8 @@
---
name: web_hooks_no_rate_limit
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90868
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366084
milestone: '15.2'
type: development
group: group::integrations
default_enabled: false

View File

@ -171,7 +171,7 @@ Our current RSpec tests parallelization setup is as follows:
1. The `retrieve-tests-metadata` job in the `prepare` stage ensures we have a
`knapsack/report-master.json` file:
- The `knapsack/report-master.json` file is fetched from the latest `main` pipeline which runs `update-tests-metadata`
(for now it's the 2-hourly scheduled master pipeline), if it's not here we initialize the file with `{}`.
(for now it's the 2-hourly `maintenance` scheduled master pipeline), if it's not here we initialize the file with `{}`.
1. Each `[rspec|rspec-ee] [migration|unit|integration|system|geo] n m` job are run with
`knapsack rspec` and should have an evenly distributed share of tests:
- It works because the jobs have access to the `knapsack/report-master.json`
@ -305,7 +305,7 @@ If these commands return `undercover: ✅ No coverage is missing in latest chang
Our test suite runs against Ruby 2 in merge requests and default branch pipelines.
We do run our test suite against Ruby 3 on 2-hourly scheduled pipelines, as GitLab.com will soon run on Ruby 3.
We also run our test suite against Ruby 3 on another 2-hourly scheduled pipelines, as GitLab.com will soon run on Ruby 3.
## PostgreSQL versions testing
@ -318,12 +318,13 @@ We also run our test suite against PG11 upon specific database library changes i
### Current versions testing
| Where? | PostgreSQL version |
| ------ | ------------------ |
| MRs | 12, 11 for DB library changes |
| `main` (non-scheduled pipelines) | 12, 11 for DB library changes |
| 2-hourly scheduled pipelines | 12, 11 for DB library changes |
| `nightly` scheduled pipelines | 12, 11, 13 |
| Where? | PostgreSQL version | Ruby version |
| ------ | ------------------ | ------------ |
| Merge requests | 12 (default version), 11 for DB library changes | 2.7 (default version) |
| `master` branch commits | 12 (default version), 11 for DB library changes | 2.7 (default version) |
| `maintenance` scheduled pipelines (every 2 hours at even hour) | 12 (default version), 11 for DB library changes | 2.7 (default version) |
| `maintenance` scheduled pipelines (every 2 hours at odd hour) | 12 (default version), 11 for DB library changes | 3.0 (set in the schedule variables) |
| `nightly` scheduled pipelines | 12 (default version), 11, 13 | 2.7 (default version) |
### Long-term plan
@ -618,7 +619,7 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/yaml_optimizat
| `if-default-refs` | Matches if the pipeline is for `master`, `main`, `/^[\d-]+-stable(-ee)?$/` (stable branches), `/^\d+-\d+-auto-deploy-\d+$/` (auto-deploy branches), `/^security\//` (security branches), merge requests, and tags. | Note that jobs aren't created for branches with this default configuration. |
| `if-master-refs` | Matches if the current branch is `master` or `main`. | |
| `if-master-push` | Matches if the current branch is `master` or `main` and pipeline source is `push`. | |
| `if-master-schedule-2-hourly` | Matches if the current branch is `master` or `main` and pipeline runs on a 2-hourly schedule. | |
| `if-master-schedule-maintenance` | Matches if the current branch is `master` or `main` and pipeline runs on a 2-hourly schedule. | |
| `if-master-schedule-nightly` | Matches if the current branch is `master` or `main` and pipeline runs on a nightly schedule. | |
| `if-auto-deploy-branches` | Matches if the current branch is an auto-deploy one. | |
| `if-master-or-tag` | Matches if the pipeline is for the `master` or `main` branch or for a tag. | |
@ -705,7 +706,7 @@ This works well for the following reasons:
- `.yarn-cache`
- `.assets-compile-cache` (the key includes `${NODE_ENV}` so it's actually two different caches).
1. These cache definitions are composed of [multiple atomic caches](../ci/caching/index.md#use-multiple-caches).
1. Only the following jobs, running in 2-hourly scheduled pipelines, are pushing (that is, updating) to the caches:
1. Only the following jobs, running in 2-hourly `maintenance` scheduled pipelines, are pushing (that is, updating) to the caches:
- `update-setup-test-env-cache`, defined in [`.gitlab/ci/rails.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rails.gitlab-ci.yml).
- `update-gitaly-binaries-cache`, defined in [`.gitlab/ci/rails.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rails.gitlab-ci.yml).
- `update-rubocop-cache`, defined in [`.gitlab/ci/rails.gitlab-ci.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.gitlab/ci/rails.gitlab-ci.yml).

View File

@ -230,11 +230,23 @@ also load certain page content directly from common public CDN hostnames.
## Webhooks
The following limits apply for [webhooks](../project/integrations/webhooks.md):
The following limits apply for [webhooks](../project/integrations/webhooks.md).
### Rate limits
The number of times a webhook can be called per minute, per top-level namespace.
The limit varies depending on your plan and the number of seats in your subscription.
| Plan | Default for GitLab.com |
|----------------------|-------------------------|
| Free | `500` |
| Premium | `99` seats or fewer: `1,600`<br>`100-399` seats: `2,800`<br>`400` seats or more: `4,000` |
| Ultimate and open source |`999` seats or fewer: `6,000`<br>`1,000-4,999` seats: `9,000`<br>`5,000` seats or more: `13,000` |
### Other limits
| Setting | Default for GitLab.com |
|----------------------|-------------------------|
| Webhook rate limit | `500` calls per minute for GitLab Free, unlimited for GitLab Premium and GitLab Ultimate. Webhook rate limits are applied per top-level namespace. |
| Number of webhooks | `100` per project, `50` per group |
| Maximum payload size | 25 MB |

View File

@ -6,9 +6,20 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Importing issues from CSV **(FREE)**
Issues can be imported to a project by uploading a CSV file with the columns
`title` and `description`. Other columns are **not** imported. If you want to
retain columns such as labels and milestones, consider the [Move Issue feature](managing_issues.md#move-an-issue).
You can import issues to a project by uploading a CSV file with the following columns:
| Name | Required? | Description |
|:--------------|:-----------------------|:-------------------------------------------------|
| `title` | **{check-circle}** Yes | Issue title. |
| `description` | **{check-circle}** Yes | Issue description. |
| `due_date` | **{dotted-circle}** No | Issue due date in `YYYY-MM-DD` format. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91317) in GitLab 15.2. |
Data in other columns is not imported.
You can use the `description` field to embed [quick actions](../quick_actions.md) to add other data to the issue.
For example, labels, assignees, and milestones.
Alternatively, you can [move an issue](managing_issues.md#move-an-issue). Moving issues preserves more data.
The user uploading the CSV file is set as the author of the imported issues.
@ -44,16 +55,22 @@ To import issues, GitLab requires CSV files have a specific format:
| double-quote character | The double-quote (`"`) character is used to quote fields, enabling the use of the column separator in a field (see the third line in the sample CSV data below). To insert a double-quote (`"`) in a quoted field use two double-quote characters in succession (`""`). |
| data rows | After the header row, following rows must use the same column order. The issue title is required, but the description is optional. |
If you have special characters in a field, (such as `\n` or `,`), surround the
characters with double quotes (`"`).
If you have special characters (for example, `,` or `\n`) or multiple lines in a field (for example,
when using [quick actions](../quick_actions.md)), surround the characters with double quotes (`"`).
When using [quick actions](../quick_actions.md), each action must be on a separate line.
Sample CSV data:
```plaintext
title,description
My Issue Title,My Issue Description
Another Title,"A description, with a comma"
"One More Title","One More Description"
title,description,due date
My Issue Title,My Issue Description,2022-06-28
Another Title,"A description, with a comma",
"One More Title","One More Description",
An Issue with Quick Actions,"Hey can we change the frontend?
/assign @sjones
/label ~frontend ~documentation",
```
### File size

View File

@ -7,10 +7,15 @@ module BulkImports
def transform(context, data)
import_entity = context.entity
if import_entity.destination_namespace.present?
namespace = Namespace.find_by_full_path(import_entity.destination_namespace)
end
data
.then { |data| transform_name(import_entity, namespace, data) }
.then { |data| transform_path(import_entity, data) }
.then { |data| transform_full_path(data) }
.then { |data| transform_parent(context, import_entity, data) }
.then { |data| transform_parent(context, import_entity, namespace, data) }
.then { |data| transform_visibility_level(data) }
.then { |data| transform_project_creation_level(data) }
.then { |data| transform_subgroup_creation_level(data) }
@ -18,6 +23,20 @@ module BulkImports
private
def transform_name(import_entity, namespace, data)
if namespace.present?
namespace_children_names = namespace.children.pluck(:name) # rubocop: disable CodeReuse/ActiveRecord
if namespace_children_names.include?(data['name'])
data['name'] = Uniquify.new(1).string(-> (counter) { "#{data['name']}(#{counter})" }) do |base|
namespace_children_names.include?(base)
end
end
end
data
end
def transform_path(import_entity, data)
data['path'] = import_entity.destination_name.parameterize
data
@ -28,11 +47,8 @@ module BulkImports
data
end
def transform_parent(context, import_entity, data)
unless import_entity.destination_namespace.blank?
namespace = Namespace.find_by_full_path(import_entity.destination_namespace)
data['parent_id'] = namespace.id
end
def transform_parent(context, import_entity, namespace, data)
data['parent_id'] = namespace.id if namespace.present?
data
end

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true
# Lifesize project import test executed from https://gitlab.com/gitlab-org/manage/import/import-metrics
# rubocop:disable Rails/Pluck
module QA
# Only executes in custom job/pipeline
# https://gitlab.com/gitlab-org/manage/import/import-github-performance
#
RSpec.describe 'Manage', :github, :requires_admin, only: { job: 'large-github-import' } do
describe 'Project import' do
let(:logger) { Runtime::Logger.logger }
@ -108,7 +107,6 @@ module QA
# rubocop:disable RSpec/InstanceVariable
after do |example|
user.remove_via_api! unless example.exception
next unless defined?(@import_time)
# save data for comparison notification creation
@ -117,6 +115,7 @@ module QA
{
importer: :github,
import_time: @import_time,
errors: imported_project.project_import_status[:failed_relations],
reported_stats: @stats,
source: {
name: "GitHub",

View File

@ -1,5 +1,7 @@
# frozen_string_literal: true
# Lifesize project import test executed from https://gitlab.com/gitlab-org/manage/import/import-metrics
# rubocop:disable Rails/Pluck, Layout/LineLength, RSpec/MultipleMemoizedHelpers
module QA
RSpec.describe "Manage", requires_admin: 'uses admin API client for resource creation',
@ -114,6 +116,7 @@ module QA
{
importer: :gitlab,
import_time: @import_time,
errors: imported_group.import_details.sum([]) { |details| details[:failures] },
source: {
name: "GitLab Source",
project_name: source_project.path_with_namespace,

View File

@ -14,11 +14,11 @@ RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do
end
it 'renders alert body' do
expect(rendered_component).to have_content(body)
expect(page).to have_content(body)
end
it 'renders actions' do
expect(rendered_component).to have_content(actions)
expect(page).to have_content(actions)
end
end
@ -28,20 +28,20 @@ RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do
end
it 'does not set a title' do
expect(rendered_component).not_to have_selector('.gl-alert-title')
expect(rendered_component).to have_selector('.gl-alert-icon-no-title')
expect(page).not_to have_selector('.gl-alert-title')
expect(page).to have_selector('.gl-alert-icon-no-title')
end
it 'renders the default variant' do
expect(rendered_component).to have_selector('.gl-alert-info')
expect(rendered_component).to have_selector("[data-testid='information-o-icon']")
expect(rendered_component).not_to have_selector('.gl-alert-no-icon')
expect(page).to have_selector('.gl-alert-info')
expect(page).to have_selector("[data-testid='information-o-icon']")
expect(page).not_to have_selector('.gl-alert-no-icon')
end
it 'renders a dismiss button' do
expect(rendered_component).to have_selector('.gl-dismiss-btn.js-close')
expect(rendered_component).to have_selector("[data-testid='close-icon']")
expect(rendered_component).not_to have_selector('.gl-alert-not-dismissible')
expect(page).to have_selector('.gl-dismiss-btn.js-close')
expect(page).to have_selector("[data-testid='close-icon']")
expect(page).not_to have_selector('.gl-alert-not-dismissible')
end
end
@ -61,17 +61,17 @@ RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do
end
it 'sets the title' do
expect(rendered_component).to have_selector('.gl-alert-title')
expect(rendered_component).to have_content('_title_')
expect(rendered_component).not_to have_selector('.gl-alert-icon-no-title')
expect(page).to have_selector('.gl-alert-title')
expect(page).to have_content('_title_')
expect(page).not_to have_selector('.gl-alert-icon-no-title')
end
it 'sets the alert_class' do
expect(rendered_component).to have_selector('._alert_class_')
expect(page).to have_selector('._alert_class_')
end
it 'sets the alert_data' do
expect(rendered_component).to have_selector('[data-feature-id="_feature_id_"][data-dismiss-endpoint="_dismiss_endpoint_"]')
expect(page).to have_selector('[data-feature-id="_feature_id_"][data-dismiss-endpoint="_dismiss_endpoint_"]')
end
end
@ -81,12 +81,12 @@ RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do
end
it 'has the "not dismissible" class' do
expect(rendered_component).to have_selector('.gl-alert-not-dismissible')
expect(page).to have_selector('.gl-alert-not-dismissible')
end
it 'does not render the dismiss button' do
expect(rendered_component).not_to have_selector('.gl-dismiss-btn.js-close')
expect(rendered_component).not_to have_selector("[data-testid='close-icon']")
expect(page).not_to have_selector('.gl-dismiss-btn.js-close')
expect(page).not_to have_selector("[data-testid='close-icon']")
end
end
@ -96,12 +96,12 @@ RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do
end
it 'has the hidden icon class' do
expect(rendered_component).to have_selector('.gl-alert-no-icon')
expect(page).to have_selector('.gl-alert-no-icon')
end
it 'does not render the icon' do
expect(rendered_component).not_to have_selector('.gl-alert-icon')
expect(rendered_component).not_to have_selector("[data-testid='information-o-icon']")
expect(page).not_to have_selector('.gl-alert-icon')
expect(page).not_to have_selector("[data-testid='information-o-icon']")
end
end
@ -118,13 +118,13 @@ RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do
end
it 'does not have "not dismissible" class' do
expect(rendered_component).not_to have_selector('.gl-alert-not-dismissible')
expect(page).not_to have_selector('.gl-alert-not-dismissible')
end
it 'renders a dismiss button and data' do
expect(rendered_component).to have_selector('.gl-dismiss-btn.js-close._close_button_class_')
expect(rendered_component).to have_selector("[data-testid='close-icon']")
expect(rendered_component).to have_selector('[data-testid="_close_button_testid_"]')
expect(page).to have_selector('.gl-dismiss-btn.js-close._close_button_class_')
expect(page).to have_selector("[data-testid='close-icon']")
expect(page).to have_selector('[data-testid="_close_button_testid_"]')
end
end
@ -137,8 +137,8 @@ RSpec.describe Pajamas::AlertComponent, :aggregate_failures, type: :component do
with_them do
it 'renders the variant' do
expect(rendered_component).to have_selector(".gl-alert-#{variant}")
expect(rendered_component).to have_selector("[data-testid='#{described_class::ICONS[variant]}-icon']")
expect(page).to have_selector(".gl-alert-#{variant}")
expect(page).to have_selector("[data-testid='#{described_class::ICONS[variant]}-icon']")
end
end
end

View File

@ -19,22 +19,22 @@ RSpec.describe Pajamas::BannerComponent, type: :component do
end
it 'renders its content' do
expect(rendered_component).to have_text content
expect(page).to have_text content
end
it 'renders its title' do
expect(rendered_component).to have_css "h1[class='gl-banner-title']", text: title
expect(page).to have_css "h1[class='gl-banner-title']", text: title
end
it 'renders a close button' do
expect(rendered_component).to have_css "button.gl-banner-close"
expect(page).to have_css "button.gl-banner-close"
end
describe 'button_text and button_link' do
let(:options) { { button_text: 'Learn more', button_link: '/learn-more' } }
it 'define the primary action' do
expect(rendered_component).to have_css "a.btn-confirm.gl-button[href='/learn-more']", text: 'Learn more'
expect(page).to have_css "a.btn-confirm.gl-button[href='/learn-more']", text: 'Learn more'
end
end
@ -42,14 +42,14 @@ RSpec.describe Pajamas::BannerComponent, type: :component do
let(:options) { { banner_options: { class: "baz", data: { foo: "bar" } } } }
it 'are on the banner' do
expect(rendered_component).to have_css ".gl-banner.baz[data-foo='bar']"
expect(page).to have_css ".gl-banner.baz[data-foo='bar']"
end
context 'with custom classes' do
let(:options) { { variant: :introduction, banner_options: { class: 'extra special' } } }
it 'don\'t conflict with internal banner_classes' do
expect(rendered_component).to have_css '.extra.special.gl-banner-introduction.gl-banner'
expect(page).to have_css '.extra.special.gl-banner-introduction.gl-banner'
end
end
end
@ -58,14 +58,14 @@ RSpec.describe Pajamas::BannerComponent, type: :component do
let(:options) { { close_options: { class: "js-foo", data: { uid: "123" } } } }
it 'are on the close button' do
expect(rendered_component).to have_css "button.gl-banner-close.js-foo[data-uid='123']"
expect(page).to have_css "button.gl-banner-close.js-foo[data-uid='123']"
end
end
describe 'embedded' do
context 'by default (false)' do
it 'keeps the banner\'s borders' do
expect(rendered_component).not_to have_css ".gl-banner.gl-border-none"
expect(page).not_to have_css ".gl-banner.gl-border-none"
end
end
@ -73,7 +73,7 @@ RSpec.describe Pajamas::BannerComponent, type: :component do
let(:options) { { embedded: true } }
it 'removes the banner\'s borders' do
expect(rendered_component).to have_css ".gl-banner.gl-border-none"
expect(page).to have_css ".gl-banner.gl-border-none"
end
end
end
@ -81,7 +81,7 @@ RSpec.describe Pajamas::BannerComponent, type: :component do
describe 'variant' do
context 'by default (promotion)' do
it 'applies no variant class' do
expect(rendered_component).to have_css "[class='gl-banner']"
expect(page).to have_css "[class='gl-banner']"
end
end
@ -89,11 +89,11 @@ RSpec.describe Pajamas::BannerComponent, type: :component do
let(:options) { { variant: :introduction } }
it "applies the introduction class to the banner" do
expect(rendered_component).to have_css ".gl-banner.gl-banner-introduction"
expect(page).to have_css ".gl-banner.gl-banner-introduction"
end
it "applies the confirm class to the close button" do
expect(rendered_component).to have_css ".gl-banner-close.btn-confirm.btn-confirm-tertiary"
expect(page).to have_css ".gl-banner-close.btn-confirm.btn-confirm-tertiary"
end
end
@ -101,21 +101,21 @@ RSpec.describe Pajamas::BannerComponent, type: :component do
let(:options) { { variant: :foobar } }
it 'ignores the unknown variant' do
expect(rendered_component).to have_css "[class='gl-banner']"
expect(page).to have_css "[class='gl-banner']"
end
end
end
describe 'illustration' do
it 'has none by default' do
expect(rendered_component).not_to have_css ".gl-banner-illustration"
expect(page).not_to have_css ".gl-banner-illustration"
end
context 'with svg_path' do
let(:options) { { svg_path: 'logo.svg' } }
it 'renders an image as illustration' do
expect(rendered_component).to have_css ".gl-banner-illustration img"
expect(page).to have_css ".gl-banner-illustration img"
end
end
end
@ -131,15 +131,15 @@ RSpec.describe Pajamas::BannerComponent, type: :component do
end
it 'renders the slot content as illustration' do
expect(rendered_component).to have_css ".gl-banner-illustration svg"
expect(page).to have_css ".gl-banner-illustration svg"
end
context 'and conflicting svg_path' do
let(:options) { { svg_path: 'logo.svg' } }
it 'uses the slot content' do
expect(rendered_component).to have_css ".gl-banner-illustration svg"
expect(rendered_component).not_to have_css ".gl-banner-illustration img"
expect(page).to have_css ".gl-banner-illustration svg"
expect(page).not_to have_css ".gl-banner-illustration img"
end
end
end
@ -154,15 +154,15 @@ RSpec.describe Pajamas::BannerComponent, type: :component do
end
it 'renders the slot content as the primary action' do
expect(rendered_component).to have_css "a.special", text: 'Special'
expect(page).to have_css "a.special", text: 'Special'
end
context 'and conflicting button_text and button_link' do
let(:options) { { button_text: 'Not special', button_link: '/' } }
it 'uses the slot content' do
expect(rendered_component).to have_css "a.special[href='#']", text: 'Special'
expect(rendered_component).not_to have_css "a.btn[href='/']"
expect(page).to have_css "a.special[href='#']", text: 'Special'
expect(page).not_to have_css "a.btn[href='/']"
end
end
end

View File

@ -17,25 +17,25 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
end
it 'renders its content' do
expect(rendered_component).to have_text content
expect(page).to have_text content
end
it 'adds default styling' do
expect(rendered_component).to have_css ".btn.btn-default.btn-md.gl-button"
expect(page).to have_css ".btn.btn-default.btn-md.gl-button"
end
describe 'button_options' do
let(:options) { { button_options: { id: 'baz', data: { foo: 'bar' } } } }
it 'are added to the button' do
expect(rendered_component).to have_css ".gl-button#baz[data-foo='bar']"
expect(page).to have_css ".gl-button#baz[data-foo='bar']"
end
context 'with custom classes' do
let(:options) { { variant: :danger, category: :tertiary, button_options: { class: 'custom-class' } } }
it 'don\'t conflict with internal button_classes' do
expect(rendered_component).to have_css '.gl-button.btn-danger.btn-danger-tertiary.custom-class'
expect(page).to have_css '.gl-button.btn-danger.btn-danger-tertiary.custom-class'
end
end
@ -43,7 +43,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { button_options: { type: 'submit' } } }
it 'overrides type' do
expect(rendered_component).to have_css '[type="submit"]'
expect(page).to have_css '[type="submit"]'
end
end
end
@ -52,14 +52,14 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { button_text_classes: 'custom-text-class' } }
it 'is added to the button text' do
expect(rendered_component).to have_css ".gl-button-text.custom-text-class"
expect(page).to have_css ".gl-button-text.custom-text-class"
end
end
describe 'disabled' do
context 'by default (false)' do
it 'does not have disabled styling and behavior' do
expect(rendered_component).not_to have_css ".disabled[disabled='disabled'][aria-disabled='true']"
expect(page).not_to have_css ".disabled[disabled='disabled'][aria-disabled='true']"
end
end
@ -67,7 +67,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { disabled: true } }
it 'has disabled styling and behavior' do
expect(rendered_component).to have_css ".disabled[disabled='disabled'][aria-disabled='true']"
expect(page).to have_css ".disabled[disabled='disabled'][aria-disabled='true']"
end
end
end
@ -75,11 +75,11 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
describe 'loading' do
context 'by default (false)' do
it 'is not disabled' do
expect(rendered_component).not_to have_css ".disabled[disabled='disabled']"
expect(page).not_to have_css ".disabled[disabled='disabled']"
end
it 'does not render a spinner' do
expect(rendered_component).not_to have_css ".gl-spinner[aria-label='Loading']"
expect(page).not_to have_css ".gl-spinner[aria-label='Loading']"
end
end
@ -87,11 +87,11 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { loading: true } }
it 'is disabled' do
expect(rendered_component).to have_css ".disabled[disabled='disabled']"
expect(page).to have_css ".disabled[disabled='disabled']"
end
it 'renders a spinner' do
expect(rendered_component).to have_css ".gl-spinner[aria-label='Loading']"
expect(page).to have_css ".gl-spinner[aria-label='Loading']"
end
end
end
@ -99,7 +99,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
describe 'block' do
context 'by default (false)' do
it 'is inline' do
expect(rendered_component).not_to have_css ".btn-block"
expect(page).not_to have_css ".btn-block"
end
end
@ -107,7 +107,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { block: true } }
it 'is block element' do
expect(rendered_component).to have_css ".btn-block"
expect(page).to have_css ".btn-block"
end
end
end
@ -115,7 +115,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
describe 'selected' do
context 'by default (false)' do
it 'does not have selected styling and behavior' do
expect(rendered_component).not_to have_css ".selected"
expect(page).not_to have_css ".selected"
end
end
@ -123,7 +123,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { selected: true } }
it 'has selected styling and behavior' do
expect(rendered_component).to have_css ".selected"
expect(page).to have_css ".selected"
end
end
end
@ -136,8 +136,8 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
with_them do
it 'renders the button in correct variant && category' do
expect(rendered_component).to have_css(".#{described_class::VARIANT_CLASSES[variant]}")
expect(rendered_component).to have_css(".#{described_class::VARIANT_CLASSES[variant]}-tertiary")
expect(page).to have_css(".#{described_class::VARIANT_CLASSES[variant]}")
expect(page).to have_css(".#{described_class::VARIANT_CLASSES[variant]}-tertiary")
end
end
end
@ -149,8 +149,8 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
with_them do
it 'renders the button in correct variant && category' do
expect(rendered_component).to have_css(".#{described_class::VARIANT_CLASSES[variant]}")
expect(rendered_component).not_to have_css(".#{described_class::VARIANT_CLASSES[variant]}-tertiary")
expect(page).to have_css(".#{described_class::VARIANT_CLASSES[variant]}")
expect(page).not_to have_css(".#{described_class::VARIANT_CLASSES[variant]}-tertiary")
end
end
end
@ -162,8 +162,8 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
with_them do
it 'renders the button in correct variant && category' do
expect(rendered_component).to have_css(".#{described_class::VARIANT_CLASSES[variant]}")
expect(rendered_component).not_to have_css(".#{described_class::VARIANT_CLASSES[variant]}-primary")
expect(page).to have_css(".#{described_class::VARIANT_CLASSES[variant]}")
expect(page).not_to have_css(".#{described_class::VARIANT_CLASSES[variant]}-primary")
end
end
end
@ -172,7 +172,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
describe 'size' do
context 'by default (medium)' do
it 'applies medium class' do
expect(rendered_component).to have_css ".btn-md"
expect(page).to have_css ".btn-md"
end
end
@ -180,22 +180,22 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { size: :small } }
it "applies the small class to the button" do
expect(rendered_component).to have_css ".btn-sm"
expect(page).to have_css ".btn-sm"
end
end
end
describe 'icon' do
it 'has none by default' do
expect(rendered_component).not_to have_css ".gl-icon"
expect(page).not_to have_css ".gl-icon"
end
context 'with icon' do
let(:options) { { icon: 'star-o', icon_classes: 'custom-icon' } }
it 'renders an icon with custom CSS class' do
expect(rendered_component).to have_css "svg.gl-icon.gl-button-icon.custom-icon[data-testid='star-o-icon']"
expect(rendered_component).not_to have_css ".btn-icon"
expect(page).to have_css "svg.gl-icon.gl-button-icon.custom-icon[data-testid='star-o-icon']"
expect(page).not_to have_css ".btn-icon"
end
end
@ -204,7 +204,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { icon: 'star-o' } }
it 'adds a "btn-icon" CSS class' do
expect(rendered_component).to have_css ".btn.btn-icon"
expect(page).to have_css ".btn.btn-icon"
end
end
@ -213,8 +213,8 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { icon: 'star-o', loading: true } }
it 'renders only a loading icon' do
expect(rendered_component).not_to have_css "svg.gl-icon.gl-button-icon.custom-icon[data-testid='star-o-icon']"
expect(rendered_component).to have_css ".gl-spinner[aria-label='Loading']"
expect(page).not_to have_css "svg.gl-icon.gl-button-icon.custom-icon[data-testid='star-o-icon']"
expect(page).to have_css ".gl-spinner[aria-label='Loading']"
end
end
end
@ -222,7 +222,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
describe 'type' do
context 'by default (without href)' do
it 'has type "button"' do
expect(rendered_component).to have_css "button[type='button']"
expect(page).to have_css "button[type='button']"
end
end
@ -233,7 +233,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
with_them do
it 'has the correct type' do
expect(rendered_component).to have_css "button[type='#{type}']"
expect(page).to have_css "button[type='#{type}']"
end
end
end
@ -242,7 +242,7 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { type: :madeup } }
it 'has type "button"' do
expect(rendered_component).to have_css "button[type='button']"
expect(page).to have_css "button[type='button']"
end
end
@ -250,22 +250,22 @@ RSpec.describe Pajamas::ButtonComponent, type: :component do
let(:options) { { href: 'https://example.com', type: :reset } }
it 'ignores type' do
expect(rendered_component).not_to have_css "[type]"
expect(page).not_to have_css "[type]"
end
end
end
describe 'link button' do
it 'renders a button tag with type="button" when "href" is not set' do
expect(rendered_component).to have_css "button[type='button']"
expect(page).to have_css "button[type='button']"
end
context 'when "href" is provided' do
let(:options) { { href: 'https://gitlab.com', target: '_blank' } }
it "renders a link instead of the button" do
expect(rendered_component).not_to have_css "button[type='button']"
expect(rendered_component).to have_css "a[href='https://gitlab.com'][target='_blank']"
expect(page).not_to have_css "button[type='button']"
expect(page).to have_css "a[href='https://gitlab.com'][target='_blank']"
end
end
end

View File

@ -16,15 +16,15 @@ RSpec.describe Pajamas::CardComponent, :aggregate_failures, type: :component do
end
it 'renders card header' do
expect(rendered_component).to have_content(header)
expect(page).to have_content(header)
end
it 'renders card body' do
expect(rendered_component).to have_content(body)
expect(page).to have_content(body)
end
it 'renders footer' do
expect(rendered_component).to have_content(footer)
expect(page).to have_content(footer)
end
end
@ -34,13 +34,13 @@ RSpec.describe Pajamas::CardComponent, :aggregate_failures, type: :component do
end
it 'does not have a header or footer' do
expect(rendered_component).not_to have_selector('.gl-card-header')
expect(rendered_component).not_to have_selector('.gl-card-footer')
expect(page).not_to have_selector('.gl-card-header')
expect(page).not_to have_selector('.gl-card-footer')
end
it 'renders the card and body' do
expect(rendered_component).to have_selector('.gl-card')
expect(rendered_component).to have_selector('.gl-card-body')
expect(page).to have_selector('.gl-card')
expect(page).to have_selector('.gl-card-body')
end
end
@ -58,23 +58,23 @@ RSpec.describe Pajamas::CardComponent, :aggregate_failures, type: :component do
end
it 'renders card options' do
expect(rendered_component).to have_selector('._card_class_')
expect(rendered_component).to have_selector('[data-testid="_card_testid_"]')
expect(page).to have_selector('._card_class_')
expect(page).to have_selector('[data-testid="_card_testid_"]')
end
it 'renders header options' do
expect(rendered_component).to have_selector('._header_class_')
expect(rendered_component).to have_selector('[data-testid="_header_testid_"]')
expect(page).to have_selector('._header_class_')
expect(page).to have_selector('[data-testid="_header_testid_"]')
end
it 'renders body options' do
expect(rendered_component).to have_selector('._body_class_')
expect(rendered_component).to have_selector('[data-testid="_body_testid_"]')
expect(page).to have_selector('._body_class_')
expect(page).to have_selector('[data-testid="_body_testid_"]')
end
it 'renders footer options' do
expect(rendered_component).to have_selector('._footer_class_')
expect(rendered_component).to have_selector('[data-testid="_footer_testid_"]')
expect(page).to have_selector('._footer_class_')
expect(page).to have_selector('[data-testid="_footer_testid_"]')
end
end
end

View File

@ -10,7 +10,7 @@ RSpec.describe Pajamas::CheckboxComponent, :aggregate_failures, type: :component
RSpec.shared_examples 'it renders unchecked checkbox with value of `1`' do
it 'renders unchecked checkbox with value of `1`' do
expect(rendered_component).to have_unchecked_field(label, with: '1')
expect(page).to have_unchecked_field(label, with: '1')
end
end
@ -31,7 +31,7 @@ RSpec.describe Pajamas::CheckboxComponent, :aggregate_failures, type: :component
include_examples 'it does not render help text'
it 'renders hidden input with value of `0`' do
expect(rendered_component).to have_field('user[view_diffs_file_by_file]', type: 'hidden', with: '0')
expect(page).to have_field('user[view_diffs_file_by_file]', type: 'hidden', with: '0')
end
end
@ -61,15 +61,15 @@ RSpec.describe Pajamas::CheckboxComponent, :aggregate_failures, type: :component
include_examples 'it renders help text'
it 'renders checked checkbox with value of `yes`' do
expect(rendered_component).to have_checked_field(label, with: checked_value, class: checkbox_options[:class])
expect(page).to have_checked_field(label, with: checked_value, class: checkbox_options[:class])
end
it 'adds CSS class to label' do
expect(rendered_component).to have_selector('label.label-foo-bar')
expect(page).to have_selector('label.label-foo-bar')
end
it 'renders hidden input with value of `no`' do
expect(rendered_component).to have_field('user[view_diffs_file_by_file]', type: 'hidden', with: unchecked_value)
expect(page).to have_field('user[view_diffs_file_by_file]', type: 'hidden', with: unchecked_value)
end
end

View File

@ -13,7 +13,7 @@ RSpec.describe Pajamas::RadioComponent, :aggregate_failures, type: :component do
RSpec.shared_examples 'it renders unchecked radio' do
it 'renders unchecked radio' do
expect(rendered_component).to have_unchecked_field(label)
expect(page).to have_unchecked_field(label)
end
end
@ -58,11 +58,11 @@ RSpec.describe Pajamas::RadioComponent, :aggregate_failures, type: :component do
include_examples 'it renders help text'
it 'renders checked radio' do
expect(rendered_component).to have_checked_field(label, class: radio_options[:class])
expect(page).to have_checked_field(label, class: radio_options[:class])
end
it 'adds CSS class to label' do
expect(rendered_component).to have_selector('label.label-foo-bar')
expect(page).to have_selector('label.label-foo-bar')
end
end

View File

@ -0,0 +1,74 @@
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Pajamas::SpinnerComponent, type: :component do
let(:options) { {} }
before do
render_inline(described_class.new(**options))
end
describe 'class' do
let(:options) { { class: 'gl-my-6' } }
it 'has the correct custom class' do
expect(page).to have_css '.gl-spinner-container.gl-my-6'
end
end
describe 'color' do
context 'by default' do
it 'is dark' do
expect(page).to have_css '.gl-spinner.gl-spinner-dark'
end
end
context 'set to light' do
let(:options) { { color: :light } }
it 'is light' do
expect(page).to have_css '.gl-spinner.gl-spinner-light'
end
end
end
describe 'inline' do
context 'by default' do
it 'renders a div' do
expect(page).to have_css 'div.gl-spinner'
end
end
context 'set to true' do
let(:options) { { inline: true } }
it 'renders a span' do
expect(page).to have_css 'span.gl-spinner'
end
end
end
describe 'label' do
context 'by default' do
it 'has "Loading" as aria-label' do
expect(page).to have_css '.gl-spinner[aria-label="Loading"]'
end
end
context 'when set to something else' do
let(:options) { { label: "Sending" } }
it 'has a custom aria-label' do
expect(page).to have_css '.gl-spinner[aria-label="Sending"]'
end
end
end
describe 'size' do
let(:options) { { size: :lg } }
it 'has the correct size class' do
expect(page).to have_css '.gl-spinner.gl-spinner-lg'
end
end
end

View File

@ -8,31 +8,31 @@ RSpec.describe Pajamas::ToggleComponent, type: :component do
end
it 'renders a toggle container with provided class' do
expect(rendered_component).to have_selector "[class='js-feature-toggle']"
expect(page).to have_selector "[class='js-feature-toggle']"
end
it 'does not set a name' do
expect(rendered_component).not_to have_selector('[data-name]')
expect(page).not_to have_selector('[data-name]')
end
it 'sets default is-checked attributes' do
expect(rendered_component).to have_selector('[data-is-checked="false"]')
expect(page).to have_selector('[data-is-checked="false"]')
end
it 'sets default disabled attributes' do
expect(rendered_component).to have_selector('[data-disabled="false"]')
expect(page).to have_selector('[data-disabled="false"]')
end
it 'sets default is-loading attributes' do
expect(rendered_component).to have_selector('[data-is-loading="false"]')
expect(page).to have_selector('[data-is-loading="false"]')
end
it 'does not set a label' do
expect(rendered_component).not_to have_selector('[data-label]')
expect(page).not_to have_selector('[data-label]')
end
it 'does not set a label position' do
expect(rendered_component).not_to have_selector('[data-label-position]')
expect(page).not_to have_selector('[data-label-position]')
end
end
@ -52,35 +52,35 @@ RSpec.describe Pajamas::ToggleComponent, type: :component do
end
it 'sets the custom class' do
expect(rendered_component).to have_selector('.js-custom-gl-toggle')
expect(page).to have_selector('.js-custom-gl-toggle')
end
it 'sets the custom name' do
expect(rendered_component).to have_selector('[data-name="toggle-name"]')
expect(page).to have_selector('[data-name="toggle-name"]')
end
it 'sets the custom is-checked attributes' do
expect(rendered_component).to have_selector('[data-is-checked="true"]')
expect(page).to have_selector('[data-is-checked="true"]')
end
it 'sets the custom disabled attributes' do
expect(rendered_component).to have_selector('[data-disabled="true"]')
expect(page).to have_selector('[data-disabled="true"]')
end
it 'sets the custom is-loading attributes' do
expect(rendered_component).to have_selector('[data-is-loading="true"]')
expect(page).to have_selector('[data-is-loading="true"]')
end
it 'sets the custom label' do
expect(rendered_component).to have_selector('[data-label="Custom label"]')
expect(page).to have_selector('[data-label="Custom label"]')
end
it 'sets the custom label position' do
expect(rendered_component).to have_selector('[data-label-position="top"]')
expect(page).to have_selector('[data-label-position="top"]')
end
it 'sets custom data attributes' do
expect(rendered_component).to have_selector('[data-foo="bar"]')
expect(page).to have_selector('[data-foo="bar"]')
end
end
@ -101,7 +101,7 @@ RSpec.describe Pajamas::ToggleComponent, type: :component do
end
with_them do
it { expect(rendered_component).to have_selector("[data-label-position='#{position}']", count: count) }
it { expect(page).to have_selector("[data-label-position='#{position}']", count: count) }
end
end
end

View File

@ -752,28 +752,6 @@ RSpec.describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
describe 'GET status.json' do
let(:job) { create(:ci_build, pipeline: pipeline) }
let(:status) { job.detailed_status(double('user')) }
before do
get :status, params: {
namespace_id: project.namespace,
project_id: project,
id: job.id
},
format: :json
end
it 'return a detailed job status in json' do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['text']).to eq status.text
expect(json_response['label']).to eq status.label
expect(json_response['icon']).to eq status.icon
expect(json_response['favicon']).to match_asset_path "/assets/ci_favicons/#{status.favicon}.png"
end
end
describe 'POST retry' do
before do
project.add_developer(user)

View File

@ -1207,22 +1207,4 @@ RSpec.describe 'Jobs', :clean_gitlab_redis_shared_state do
it { expect(page.status_code).to eq(404) }
end
end
describe "GET /:project/jobs/:id/status" do
context "Job from project" do
before do
visit status_project_job_path(project, job)
end
it { expect(page.status_code).to eq(200) }
end
context "Job from other project" do
before do
visit status_project_job_path(project, job2)
end
it { expect(page.status_code).to eq(404) }
end
end
end

6
spec/fixtures/csv_complex.csv vendored Normal file
View File

@ -0,0 +1,6 @@
title,description,due date
Issue in 中文,Test description,
"Hello","World",
"Title with quote""","Description
/assign @csv_assignee
/estimate 1h",2022-06-28
1 title description due date
2 Issue in 中文 Test description
3 Hello World
4 Title with quote" Description /assign @csv_assignee /estimate 1h 2022-06-28

View File

@ -4,12 +4,12 @@ require 'spec_helper'
RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
describe '#transform' do
let_it_be(:user) { create(:user) }
let_it_be(:parent) { create(:group) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
let_it_be(:entity) do
create(
let(:bulk_import) { build_stubbed(:bulk_import) }
let(:entity) do
build_stubbed(
:bulk_import_entity,
bulk_import: bulk_import,
source_full_path: 'source/full/path',
@ -18,8 +18,8 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
)
end
let_it_be(:tracker) { create(:bulk_import_tracker, entity: entity) }
let_it_be(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:tracker) { build_stubbed(:bulk_import_tracker, entity: entity) }
let(:context) { BulkImports::Pipeline::Context.new(tracker) }
let(:data) do
{
@ -87,14 +87,63 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
end
context 'when destination namespace is empty' do
it 'does not set parent id' do
entity.update!(destination_namespace: '')
before do
entity.destination_namespace = ''
end
it 'does not set parent id' do
transformed_data = subject.transform(context, data)
expect(transformed_data).not_to have_key('parent_id')
end
end
end
describe 'group name transformation' do
context 'when destination namespace is empty' do
before do
entity.destination_namespace = ''
end
it 'does not transform name' do
transformed_data = subject.transform(context, data)
expect(transformed_data['name']).to eq('Source Group Name')
end
end
context 'when destination namespace is present' do
context 'when destination namespace does not have a group with same name' do
it 'does not transform name' do
transformed_data = subject.transform(context, data)
expect(transformed_data['name']).to eq('Source Group Name')
end
end
context 'when destination namespace already have a group with the same name' do
before do
create(:group, parent: parent, name: 'Source Group Name', path: 'group_1')
create(:group, parent: parent, name: 'Source Group Name(1)', path: 'group_2')
create(:group, parent: parent, name: 'Source Group Name(2)', path: 'group_3')
create(:group, parent: parent, name: 'Source Group Name(1)(1)', path: 'group_4')
end
it 'makes the name unique by appeding a counter', :aggregate_failures do
transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name'))
expect(transformed_data['name']).to eq('Source Group Name(3)')
transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name(2)'))
expect(transformed_data['name']).to eq('Source Group Name(2)(1)')
transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name(1)'))
expect(transformed_data['name']).to eq('Source Group Name(1)(2)')
transformed_data = subject.transform(context, data.merge('name' => 'Source Group Name(1)(1)'))
expect(transformed_data['name']).to eq('Source Group Name(1)(1)(1)')
end
end
end
end
end
end

View File

@ -144,7 +144,7 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFile do
context 'has image' do
it 'replaces rich text with img to the embedded image' do
expect(nb_file.highlighted_diff_lines[58].rich_text).to include('<img')
expect(nb_file.highlighted_diff_lines[56].rich_text).to include('<img')
end
it 'adds image to src' do
@ -159,11 +159,11 @@ RSpec.describe Gitlab::Diff::Rendered::Notebook::DiffFile do
let(:commit) { project.commit("4963fefc990451a8ad34289ce266b757456fc88c") }
it 'prevents injected html to be rendered as html' do
expect(nb_file.highlighted_diff_lines[45].rich_text).not_to include('<div>Hello')
expect(nb_file.highlighted_diff_lines[43].rich_text).not_to include('<div>Hello')
end
it 'keeps the injected html as part of the string' do
expect(nb_file.highlighted_diff_lines[45].rich_text).to end_with('/div&gt;">')
expect(nb_file.highlighted_diff_lines[43].rich_text).to end_with('/div&gt;">')
end
end
end

View File

@ -28,36 +28,4 @@ RSpec.describe Ci::JobSerializer do
end
end
end
describe '#represent_status' do
context 'for a failed build' do
let(:resource) { create(:ci_build, :failed) }
let(:status) { resource.detailed_status(double('user')) }
subject { serializer.represent_status(resource) }
it 'serializes only status' do
expect(subject[:text]).to eq(status.text)
expect(subject[:label]).to eq('failed')
expect(subject[:tooltip]).to eq('failed - (unknown failure)')
expect(subject[:icon]).to eq(status.icon)
expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end
end
context 'for any other type of build' do
let(:resource) { create(:ci_build, :success) }
let(:status) { resource.detailed_status(double('user')) }
subject { serializer.represent_status(resource) }
it 'serializes only status' do
expect(subject[:text]).to eq(status.text)
expect(subject[:label]).to eq('passed')
expect(subject[:tooltip]).to eq('passed')
expect(subject[:icon]).to eq(status.icon)
expect(subject[:favicon]).to match_asset_path("/assets/ci_favicons/#{status.favicon}.png")
end
end
end
end

View File

@ -5,6 +5,7 @@ require 'spec_helper'
RSpec.describe Issues::ImportCsvService do
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:assignee) { create(:user, username: 'csv_assignee') }
let(:service) do
uploader = FileUploader.new(project)
uploader.store!(file)
@ -16,4 +17,27 @@ RSpec.describe Issues::ImportCsvService do
let(:issuables) { project.issues }
let(:email_method) { :import_issues_csv_email }
end
describe '#execute' do
let(:file) { fixture_file_upload('spec/fixtures/csv_complex.csv') }
subject { service.execute }
it 'sets all issueable attributes and executes quick actions' do
project.add_developer(user)
project.add_developer(assignee)
expect { subject }.to change { issuables.count }.by 3
expect(issuables.reload).to include(
have_attributes(
title: 'Title with quote"',
description: 'Description',
time_estimate: 3600,
assignees: include(assignee),
due_date: Date.new(2022, 6, 28)
)
)
end
end
end

View File

@ -37,6 +37,10 @@ RSpec.shared_examples 'issuable import csv service' do |issuable_type|
end
describe '#execute' do
before do
project.add_developer(user)
end
context 'invalid file extension' do
let(:file) { fixture_file_upload('spec/fixtures/banana_sample.gif') }

View File

@ -19,25 +19,14 @@ module IpynbDiff
end
def transform(output, symbol)
transformed = case (output_type = output['output_type'])
when 'error'
transform_error(output['traceback'], symbol / 'traceback')
when 'execute_result', 'display_data'
transform_non_error(ORDERED_KEYS[output_type], output['data'], symbol / 'data')
when 'stream'
transform_element('text', output['text'], symbol)
end
transformed ? decorate_output(transformed, output, symbol) : []
end
def decorate_output(output_rows, output, symbol)
[
_,
_(symbol, %(%%%% Output: #{output['output_type']})),
_,
*output_rows
]
case (output_type = output['output_type'])
when 'error'
transform_error(output['traceback'], symbol / 'traceback')
when 'execute_result', 'display_data'
transform_non_error(ORDERED_KEYS[output_type], output['data'], symbol / 'data')
when 'stream'
transform_element('text', output['text'], symbol)
end
end
def transform_error(traceback, symbol)

View File

@ -20,7 +20,7 @@ module IpynbDiff
def initialize(include_frontmatter: true, hide_images: false)
@include_frontmatter = include_frontmatter
@hide_images = hide_images
@output_transformer = OutputTransformer.new(hide_images: hide_images)
@out_transformer = OutputTransformer.new(hide_images: hide_images)
end
def validate_notebook(notebook)
@ -75,9 +75,19 @@ module IpynbDiff
_(symbol / 'source', %(``` #{notebook.dig('metadata', 'kernelspec', 'language') || ''})),
symbolize_array(symbol / 'source', cell['source'], &:rstrip),
_(nil, '```'),
cell['outputs'].map.with_index do |output, idx|
@output_transformer.transform(output, symbol / ['outputs', idx])
end
transform_outputs(cell['outputs'], symbol)
]
end
def transform_outputs(outputs, symbol)
transformed = outputs.map
.with_index { |output, i| @out_transformer.transform(output, symbol / ['outputs', i]) }
.compact
.map { |el| [_, el] }
[
transformed.empty? ? [] : [_, _(symbol / 'outputs', '%% Output')],
transformed
]
end

View File

@ -5,7 +5,7 @@
y = sin(x)
```
%%%% Output: error
%% Output
---------------------------------------------------------------------------
NameError Traceback (most recent call last)

View File

@ -5,7 +5,7 @@
.cells.0.source.1
.cells.0.outputs.0
.cells.0.outputs
.cells.0.outputs.0.traceback.0
.cells.0.outputs.0.traceback.1

View File

@ -3,10 +3,8 @@
``` python
```
%%%% Output: display_data
%% Output
[Hidden Image Output]
%%%% Output: display_data
[Hidden Image Output]

View File

@ -3,10 +3,8 @@
.cells.0.source
.cells.0.outputs.0
.cells.0.outputs
.cells.0.outputs.1

View File

@ -4,7 +4,7 @@
df[:2]
```
%%%% Output: execute_result
%% Output
x y
0 0.000000 0.000000

View File

@ -4,7 +4,7 @@
.cells.0.source.0
.cells.0.outputs.0
.cells.0.outputs
.cells.0.outputs.0.data.text/plain.0
.cells.0.outputs.0.data.text/plain.1

View File

@ -5,6 +5,6 @@ from IPython.display import display, Math
display(Math(r'Dims: {}x{}m \\ Area: {}m^2 \\ Volume: {}m^3'.format(1, round(2,2), 3, 4)))
```
%%%% Output: display_data
%% Output
$\displaystyle Dims: 1x2m \\ Area: 3m^2 \\ Volume: 4m^3$

View File

@ -5,6 +5,6 @@
.cells.0.source.1
.cells.0.outputs.0
.cells.0.outputs
.cells.0.outputs.0.data.text/latex.0

View File

@ -4,6 +4,6 @@
Some Image
```
%%%% Output: display_data
%% Output
![](data:image/png;base64,this_is_an_invalid_hash_for_testing_purposes)

View File

@ -4,6 +4,6 @@
.cells.0.source.0
.cells.0.outputs.0
.cells.0.outputs
.cells.0.outputs.0.data.image/png

View File

@ -38,12 +38,10 @@ y = np.sin(x)
plt.plot(x, y)
```
%%%% Output: execute_result
%% Output
[<matplotlib.lines.Line2D at 0x123e39370>]
%%%% Output: display_data
![](data:image/png;base64,some_invalid_base64_image_here)
%% Cell type:code id:dc1178cd-c46d-4da3-9ab5-08f000699884 tags:
@ -58,7 +56,7 @@ df = pd.DataFrame({"x": x, "y": y})
df[:2]
```
%%%% Output: execute_result
%% Output
x y
0 0.000000 0.000000

View File

@ -38,12 +38,10 @@
.cells.4.source.3
.cells.4.outputs.0
.cells.4.outputs
.cells.4.outputs.0.data.text/plain.0
.cells.4.outputs.1
.cells.4.outputs.1.data.image/png
.cells.5
@ -58,7 +56,7 @@
.cells.6.source.0
.cells.6.outputs.0
.cells.6.outputs
.cells.6.outputs.0.data.text/plain.0
.cells.6.outputs.0.data.text/plain.1

View File

@ -4,6 +4,6 @@
print("G'bye")
```
%%%% Output: stream
%% Output
G'bye

View File

@ -4,6 +4,6 @@
.cells.0.source.0
.cells.0.outputs.0
.cells.0.outputs
.cells.0.outputs.0.text.0

View File

@ -10,10 +10,8 @@ svg = """<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
display(SVG(svg))
```
%%%% Output: display_data
%% Output
![](data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="50"/></svg>)
%%%% Output: display_data
![](data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="50"/></svg>)

View File

@ -10,10 +10,8 @@
.cells.0.source.6
.cells.0.outputs.0
.cells.0.outputs
.cells.0.outputs.0.data.image/svg+xml
.cells.0.outputs.1
.cells.0.outputs.1.data.image/svg+xml

View File

@ -4,6 +4,6 @@
plt.plot(x, y)
```
%%%% Output: execute_result
%% Output
[<matplotlib.lines.Line2D at 0x12a4e43d0>]

View File

@ -4,6 +4,6 @@
.cells.0.source.0
.cells.0.outputs.0
.cells.0.outputs
.cells.0.outputs.0.data.text/plain.0

View File

@ -7,10 +7,8 @@ y = 2 * np.sin(x)
plt.plot(x, y)
```
%%%% Output: execute_result
%% Output
[<matplotlib.lines.Line2D at 0x12a4e43d0>]
%%%% Output: display_data
![](data:image/png;base64,this_is_an_invalid_hash_for_testing_purposes)

View File

@ -7,10 +7,8 @@
.cells.0.source.3
.cells.0.outputs.0
.cells.0.outputs
.cells.0.outputs.0.data.text/plain.0
.cells.0.outputs.1
.cells.0.outputs.1.data.image/png