From 07516504537bef518a3f80b60ebca761209feab5 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 4 Jul 2022 15:10:24 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .gitlab-ci.yml | 5 +- .gitlab/ci/pages.gitlab-ci.yml | 2 +- .gitlab/ci/qa.gitlab-ci.yml | 2 +- .gitlab/ci/review-apps/main.gitlab-ci.yml | 4 +- .gitlab/ci/review.gitlab-ci.yml | 4 +- .gitlab/ci/rules.gitlab-ci.yml | 38 +++++----- .../settings/components/access_dropdown.vue | 1 - .../settings/components/access_dropdown.vue | 1 - .../components/source_viewer/constants.js | 1 + .../pajamas/spinner_component.html.haml | 5 ++ app/components/pajamas/spinner_component.rb | 27 +++++++ app/controllers/projects/jobs_controller.rb | 10 +-- app/serializers/ci/job_serializer.rb | 5 -- .../issuable/import_csv/base_service.rb | 3 +- app/services/issues/build_service.rb | 15 ++-- app/views/admin/users/_users.html.haml | 2 +- app/views/dashboard/_activities.html.haml | 2 +- app/views/groups/_activities.html.haml | 2 +- app/views/projects/_activity.html.haml | 2 +- .../development/web_hooks_no_rate_limit.yml | 8 ++ doc/development/pipelines.md | 21 +++--- doc/user/gitlab_com/index.md | 16 +++- doc/user/project/issues/csv_import.md | 35 ++++++--- .../group_attributes_transformer.rb | 28 +++++-- .../1_manage/import_large_github_repo_spec.rb | 7 +- .../gitlab_migration_large_project_spec.rb | 3 + .../pajamas/alert_component_spec.rb | 54 +++++++------- .../pajamas/banner_component_spec.rb | 42 +++++------ .../pajamas/button_component_spec.rb | 74 +++++++++---------- .../components/pajamas/card_component_spec.rb | 30 ++++---- .../pajamas/checkbox_component_spec.rb | 10 +-- .../pajamas/radio_component_spec.rb | 6 +- .../pajamas/spinner_component_spec.rb | 74 +++++++++++++++++++ .../pajamas/toggle_component_spec.rb | 32 ++++---- .../projects/jobs_controller_spec.rb | 22 ------ spec/features/projects/jobs_spec.rb | 18 ----- spec/fixtures/csv_complex.csv | 6 ++ .../group_attributes_transformer_spec.rb | 65 ++++++++++++++-- .../diff/rendered/notebook/diff_file_spec.rb | 6 +- spec/serializers/ci/job_serializer_spec.rb | 32 -------- .../issues/import_csv_service_spec.rb | 24 ++++++ ...able_import_csv_service_shared_examples.rb | 4 + .../gems/ipynbdiff/lib/output_transformer.rb | 27 ++----- vendor/gems/ipynbdiff/lib/transformer.rb | 18 ++++- .../spec/testdata/error_output/expected.md | 2 +- .../error_output/expected_symbols.txt | 2 +- .../spec/testdata/hide_images/expected.md | 4 +- .../testdata/hide_images/expected_symbols.txt | 4 +- .../testdata/ignore_html_output/expected.md | 2 +- .../ignore_html_output/expected_symbols.txt | 2 +- .../spec/testdata/latex_output/expected.md | 2 +- .../latex_output/expected_symbols.txt | 2 +- .../testdata/multiline_png_output/expected.md | 2 +- .../multiline_png_output/expected_symbols.txt | 2 +- .../testdata/percent_decorator/expected.md | 6 +- .../percent_decorator/expected_symbols.txt | 6 +- .../spec/testdata/stream_text/expected.md | 2 +- .../testdata/stream_text/expected_symbols.txt | 2 +- .../ipynbdiff/spec/testdata/svg/expected.md | 4 +- .../spec/testdata/svg/expected_symbols.txt | 4 +- .../spec/testdata/text_output/expected.md | 2 +- .../testdata/text_output/expected_symbols.txt | 2 +- .../spec/testdata/text_png_output/expected.md | 4 +- .../text_png_output/expected_symbols.txt | 4 +- 64 files changed, 497 insertions(+), 356 deletions(-) create mode 100644 app/components/pajamas/spinner_component.html.haml create mode 100644 app/components/pajamas/spinner_component.rb create mode 100644 config/feature_flags/development/web_hooks_no_rate_limit.yml create mode 100644 spec/components/pajamas/spinner_component_spec.rb create mode 100644 spec/fixtures/csv_complex.csv diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1340bd975e3..6853b100ed7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -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' diff --git a/.gitlab/ci/pages.gitlab-ci.yml b/.gitlab/ci/pages.gitlab-ci.yml index 6f96d84b8e3..1f9f57cfc22 100644 --- a/.gitlab/ci/pages.gitlab-ci.yml +++ b/.gitlab/ci/pages.gitlab-ci.yml @@ -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: diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index e665ed4d4ac..8df829a2c84 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -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: diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml index f3cde5d7318..45ada3fd09e 100644 --- a/.gitlab/ci/review-apps/main.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml @@ -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 diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index 86f5ef48522..5c2c3d83277 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -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: diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index a2dd646d895..fb4016a10f0 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -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" diff --git a/app/assets/javascripts/groups/settings/components/access_dropdown.vue b/app/assets/javascripts/groups/settings/components/access_dropdown.vue index b8a269de98a..b3813636631 100644 --- a/app/assets/javascripts/groups/settings/components/access_dropdown.vue +++ b/app/assets/javascripts/groups/settings/components/access_dropdown.vue @@ -180,7 +180,6 @@ export default { activity_project_path(@project) } .loading - = gl_loading_icon(size: 'md') + = render Pajamas::SpinnerComponent.new(size: :md) diff --git a/config/feature_flags/development/web_hooks_no_rate_limit.yml b/config/feature_flags/development/web_hooks_no_rate_limit.yml new file mode 100644 index 00000000000..387aa8bb007 --- /dev/null +++ b/config/feature_flags/development/web_hooks_no_rate_limit.yml @@ -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 diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index c87c3c15207..d4395cc4e4b 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -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). diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index 085456f926a..529b81e2645 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -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`
`100-399` seats: `2,800`
`400` seats or more: `4,000` | +| Ultimate and open source |`999` seats or fewer: `6,000`
`1,000-4,999` seats: `9,000`
`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 | diff --git a/doc/user/project/issues/csv_import.md b/doc/user/project/issues/csv_import.md index 2fe3d78194c..1ae57c9a883 100644 --- a/doc/user/project/issues/csv_import.md +++ b/doc/user/project/issues/csv_import.md @@ -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 diff --git a/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb b/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb index df27275b664..3067e0997c2 100644 --- a/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb +++ b/lib/bulk_imports/groups/transformers/group_attributes_transformer.rb @@ -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 diff --git a/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb index 6585d08d2ac..233a1f5bf42 100644 --- a/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb +++ b/qa/qa/specs/features/api/1_manage/import_large_github_repo_spec.rb @@ -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", diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb index 74faed983c1..b96a002f485 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb @@ -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, diff --git a/spec/components/pajamas/alert_component_spec.rb b/spec/components/pajamas/alert_component_spec.rb index 1e2845c44a8..c60724c7b78 100644 --- a/spec/components/pajamas/alert_component_spec.rb +++ b/spec/components/pajamas/alert_component_spec.rb @@ -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 diff --git a/spec/components/pajamas/banner_component_spec.rb b/spec/components/pajamas/banner_component_spec.rb index 5969f06dbad..26468d80c77 100644 --- a/spec/components/pajamas/banner_component_spec.rb +++ b/spec/components/pajamas/banner_component_spec.rb @@ -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 diff --git a/spec/components/pajamas/button_component_spec.rb b/spec/components/pajamas/button_component_spec.rb index 60c2a2e5a06..a8c96042580 100644 --- a/spec/components/pajamas/button_component_spec.rb +++ b/spec/components/pajamas/button_component_spec.rb @@ -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 diff --git a/spec/components/pajamas/card_component_spec.rb b/spec/components/pajamas/card_component_spec.rb index 65522a9023f..38d23cfca9c 100644 --- a/spec/components/pajamas/card_component_spec.rb +++ b/spec/components/pajamas/card_component_spec.rb @@ -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 diff --git a/spec/components/pajamas/checkbox_component_spec.rb b/spec/components/pajamas/checkbox_component_spec.rb index b2f3a84fbfe..d79c537a30e 100644 --- a/spec/components/pajamas/checkbox_component_spec.rb +++ b/spec/components/pajamas/checkbox_component_spec.rb @@ -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 diff --git a/spec/components/pajamas/radio_component_spec.rb b/spec/components/pajamas/radio_component_spec.rb index 3885d101c7a..8df432746d0 100644 --- a/spec/components/pajamas/radio_component_spec.rb +++ b/spec/components/pajamas/radio_component_spec.rb @@ -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 diff --git a/spec/components/pajamas/spinner_component_spec.rb b/spec/components/pajamas/spinner_component_spec.rb new file mode 100644 index 00000000000..9aac9a0085c --- /dev/null +++ b/spec/components/pajamas/spinner_component_spec.rb @@ -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 diff --git a/spec/components/pajamas/toggle_component_spec.rb b/spec/components/pajamas/toggle_component_spec.rb index b2727dec318..6b0a2d26979 100644 --- a/spec/components/pajamas/toggle_component_spec.rb +++ b/spec/components/pajamas/toggle_component_spec.rb @@ -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 diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 107eb1ed3a3..e4e3151dd12 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -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) diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index f0d41c1dd11..779d8e41a7b 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -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 diff --git a/spec/fixtures/csv_complex.csv b/spec/fixtures/csv_complex.csv new file mode 100644 index 00000000000..60d8aa5d6f7 --- /dev/null +++ b/spec/fixtures/csv_complex.csv @@ -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 diff --git a/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb index c42ca9bef3b..d775cf6b026 100644 --- a/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb +++ b/spec/lib/bulk_imports/groups/transformers/group_attributes_transformer_spec.rb @@ -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 diff --git a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb index c38684a6dc3..b5137f9db6b 100644 --- a/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb +++ b/spec/lib/gitlab/diff/rendered/notebook/diff_file_spec.rb @@ -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('Hello') + expect(nb_file.highlighted_diff_lines[43].rich_text).not_to include('
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>">') + expect(nb_file.highlighted_diff_lines[43].rich_text).to end_with('/div>">') end end end diff --git a/spec/serializers/ci/job_serializer_spec.rb b/spec/serializers/ci/job_serializer_spec.rb index d47c9fdbf24..5f889a10f3d 100644 --- a/spec/serializers/ci/job_serializer_spec.rb +++ b/spec/serializers/ci/job_serializer_spec.rb @@ -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 diff --git a/spec/services/issues/import_csv_service_spec.rb b/spec/services/issues/import_csv_service_spec.rb index fa40b75190f..9ad1d7dba9f 100644 --- a/spec/services/issues/import_csv_service_spec.rb +++ b/spec/services/issues/import_csv_service_spec.rb @@ -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 diff --git a/spec/support/services/issuable_import_csv_service_shared_examples.rb b/spec/support/services/issuable_import_csv_service_shared_examples.rb index 07118198969..0dea6cfb729 100644 --- a/spec/support/services/issuable_import_csv_service_shared_examples.rb +++ b/spec/support/services/issuable_import_csv_service_shared_examples.rb @@ -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') } diff --git a/vendor/gems/ipynbdiff/lib/output_transformer.rb b/vendor/gems/ipynbdiff/lib/output_transformer.rb index 88728df2f17..e7adfbd8c3e 100644 --- a/vendor/gems/ipynbdiff/lib/output_transformer.rb +++ b/vendor/gems/ipynbdiff/lib/output_transformer.rb @@ -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) diff --git a/vendor/gems/ipynbdiff/lib/transformer.rb b/vendor/gems/ipynbdiff/lib/transformer.rb index 153d821db27..64d59eeaea8 100644 --- a/vendor/gems/ipynbdiff/lib/transformer.rb +++ b/vendor/gems/ipynbdiff/lib/transformer.rb @@ -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 diff --git a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md index 5be645de9c9..e6e8a075598 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/error_output/expected.md @@ -5,7 +5,7 @@ y = sin(x) ``` -%%%% Output: error +%% Output --------------------------------------------------------------------------- NameError Traceback (most recent call last) diff --git a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt index 75e35d123d0..5d2f248135d 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/error_output/expected_symbols.txt @@ -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 diff --git a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md b/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md index 89a812740a6..ff63d351a3b 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected.md @@ -3,10 +3,8 @@ ``` python ``` -%%%% Output: display_data +%% Output [Hidden Image Output] -%%%% Output: display_data - [Hidden Image Output] diff --git a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt index b94e9538f58..b8f24f9fba5 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/hide_images/expected_symbols.txt @@ -3,10 +3,8 @@ .cells.0.source -.cells.0.outputs.0 +.cells.0.outputs -.cells.0.outputs.1 - diff --git a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md index 456224f3aff..3085da739ed 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected.md @@ -4,7 +4,7 @@ df[:2] ``` -%%%% Output: execute_result +%% Output x y 0 0.000000 0.000000 diff --git a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt index fa9d412c6dc..3bf319d1fa6 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/ignore_html_output/expected_symbols.txt @@ -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 diff --git a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md index add84ed26a0..194c1f43c42 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected.md @@ -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$ diff --git a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt index 9407e6db702..868adca2712 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/latex_output/expected_symbols.txt @@ -5,6 +5,6 @@ .cells.0.source.1 -.cells.0.outputs.0 +.cells.0.outputs .cells.0.outputs.0.data.text/latex.0 diff --git a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md index 4a880d8ce18..0a69c8370e7 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected.md @@ -4,6 +4,6 @@ Some Image ``` -%%%% Output: display_data +%% Output ![](data:image/png;base64,this_is_an_invalid_hash_for_testing_purposes) diff --git a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt index 26e11781ec1..1b66012ef20 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/multiline_png_output/expected_symbols.txt @@ -4,6 +4,6 @@ .cells.0.source.0 -.cells.0.outputs.0 +.cells.0.outputs .cells.0.outputs.0.data.image/png diff --git a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md b/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md index ecb0029f256..1ece1f2fd06 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected.md @@ -38,12 +38,10 @@ y = np.sin(x) plt.plot(x, y) ``` -%%%% Output: execute_result +%% Output [] -%%%% 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 diff --git a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt index ab70e7bc908..c95665d1903 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/percent_decorator/expected_symbols.txt @@ -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 diff --git a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md b/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md index fb862cbb636..0448bf21111 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected.md @@ -4,6 +4,6 @@ print("G'bye") ``` -%%%% Output: stream +%% Output G'bye diff --git a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt index ed4a8a075d3..be4e2861ea9 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/stream_text/expected_symbols.txt @@ -4,6 +4,6 @@ .cells.0.source.0 -.cells.0.outputs.0 +.cells.0.outputs .cells.0.outputs.0.text.0 diff --git a/vendor/gems/ipynbdiff/spec/testdata/svg/expected.md b/vendor/gems/ipynbdiff/spec/testdata/svg/expected.md index 37269446f5a..a5a167d31c5 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/svg/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/svg/expected.md @@ -10,10 +10,8 @@ svg = """ display(SVG(svg)) ``` -%%%% Output: display_data +%% Output ![](data:image/svg+xml;utf8,) -%%%% Output: display_data - ![](data:image/svg+xml;utf8,) diff --git a/vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt index dd2e412302d..861198a8c92 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/svg/expected_symbols.txt @@ -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 diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md index 924f4939f54..1b6c086ecd5 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/text_output/expected.md @@ -4,6 +4,6 @@ plt.plot(x, y) ``` -%%%% Output: execute_result +%% Output [] diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt index 179b30098a1..a004d852ba4 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/text_output/expected_symbols.txt @@ -4,6 +4,6 @@ .cells.0.source.0 -.cells.0.outputs.0 +.cells.0.outputs .cells.0.outputs.0.data.text/plain.0 diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md index b1dda235951..c77f109378c 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md +++ b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected.md @@ -7,10 +7,8 @@ y = 2 * np.sin(x) plt.plot(x, y) ``` -%%%% Output: execute_result +%% Output [] -%%%% Output: display_data - ![](data:image/png;base64,this_is_an_invalid_hash_for_testing_purposes) diff --git a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt index 5a86e4daa67..49f2d7149d8 100644 --- a/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt +++ b/vendor/gems/ipynbdiff/spec/testdata/text_png_output/expected_symbols.txt @@ -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