Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-06-08 09:09:42 +00:00
parent ae192a2f14
commit f4ea1f8998
61 changed files with 800 additions and 269 deletions

View file

@ -52,6 +52,7 @@ qa:nightly-auto-quarantine-dequarantine:
- bundle exec confiner -r .confiner/nightly.yml
allow_failure: true
qa:selectors-as-if-foss:
extends:
- qa:selectors
@ -67,28 +68,6 @@ update-qa-cache:
script:
- echo "Cache has been updated and ready to be uploaded."
populate-qa-tests-var:
image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: prepare
script:
- tooling/bin/qa/check_if_qa_only_spec_changes ${CHANGES_FILE} ${ONLY_QA_CHANGES_FILE}
- '[ -f $ONLY_QA_CHANGES_FILE ] && export QA_TESTS="`cat $ONLY_QA_CHANGES_FILE`"'
- 'echo "QA_TESTS=$QA_TESTS" >> qa_tests_var.env'
- 'echo "QA_TESTS: $QA_TESTS"'
artifacts:
expire_in: 2d
reports:
dotenv: qa_tests_var.env
paths:
- ${CHANGES_FILE}
- ${ONLY_QA_CHANGES_FILE}
- qa_tests_var.env
variables:
CHANGES_FILE: tmp/changed_files.txt
ONLY_QA_CHANGES_FILE: tmp/qa_only_changed_files.txt
needs:
- detect-tests
.package-and-qa-base:
image: ${GITLAB_DEPENDENCY_PROXY}ruby:2.7-alpine
stage: qa
@ -98,6 +77,8 @@ populate-qa-tests-var:
- install_gitlab_gem
- tooling/bin/find_change_diffs ${CHANGES_DIFFS_DIR}
script:
- tooling/bin/qa/check_if_qa_only_spec_changes ${CHANGES_FILE} ${ONLY_QA_CHANGES_FILE}
- '[ -f $ONLY_QA_CHANGES_FILE ] && export QA_TESTS="`cat $ONLY_QA_CHANGES_FILE`"'
- 'echo "QA_TESTS: $QA_TESTS"'
- exit_code=0 && tooling/bin/qa/package_and_qa_check ${CHANGES_DIFFS_DIR} || exit_code=$?
- echo $exit_code
@ -118,13 +99,16 @@ populate-qa-tests-var:
artifacts: false
- job: build-assets-image
artifacts: false
- job: populate-qa-tests-var
- detect-tests
artifacts:
expire_in: 7d
paths:
- ${CHANGES_FILE}
- ${ONLY_QA_CHANGES_FILE}
- ${CHANGES_DIFFS_DIR}/*
variables:
CHANGES_FILE: tmp/changed_files.txt
ONLY_QA_CHANGES_FILE: tmp/qa_only_changed_files.txt
CHANGES_DIFFS_DIR: tmp/diffs
ALLURE_JOB_NAME: $CI_JOB_NAME

View file

@ -9,6 +9,7 @@ import PipelinesService from '~/pipelines/services/pipelines_service';
import PipelineStore from '~/pipelines/stores/pipelines_store';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { s__, __ } from '~/locale';
export default {
PipelineKeyOptions,
@ -170,6 +171,20 @@ export default {
}
},
},
modal: {
actionPrimary: {
text: s__('Pipeline|Run pipeline'),
attributes: {
variant: 'danger',
},
},
actionCancel: {
text: __('Cancel'),
attributes: {
variant: 'default',
},
},
},
};
</script>
<template>
@ -229,9 +244,9 @@ export default {
ref="modal"
:modal-id="modalId"
:title="s__('Pipelines|Are you sure you want to run this pipeline?')"
:ok-title="s__('Pipeline|Run pipeline')"
ok-variant="danger"
@ok="onClickRunPipeline"
:action-primary="$options.modal.actionPrimary"
:action-cancel="$options.modal.actionCancel"
@primary="onClickRunPipeline"
>
<p>
{{

View file

@ -384,14 +384,26 @@ export default {
this.unwatchDiscussions = this.$watch(
() => `${this.diffFiles.length}:${this.$store.state.notes.discussions.length}`,
() => this.setDiscussions(),
() => {
this.setDiscussions();
if (
this.$store.state.notes.doneFetchingBatchDiscussions &&
window.gon?.features?.paginatedMrDiscussions
) {
this.unwatchDiscussions();
}
},
);
this.unwatchRetrievingBatches = this.$watch(
() => `${this.retrievingBatches}:${this.$store.state.notes.discussions.length}`,
() => {
if (!this.retrievingBatches && this.$store.state.notes.discussions.length) {
this.unwatchDiscussions();
if (!window.gon?.features?.paginatedMrDiscussions) {
this.unwatchDiscussions();
}
this.unwatchRetrievingBatches();
}
},

View file

@ -90,7 +90,10 @@ export const fetchDiscussions = ({ commit, dispatch }, { path, filter, persistFi
? { params: { notes_filter: filter, persist_filter: persistFilter } }
: null;
if (window.gon?.features?.paginatedIssueDiscussions) {
if (
window.gon?.features?.paginatedIssueDiscussions ||
window.gon?.features?.paginatedMrDiscussions
) {
return dispatch('fetchDiscussionsBatch', { path, config, perPage: 20 });
}
@ -128,6 +131,7 @@ export const fetchDiscussionsBatch = ({ commit, dispatch }, { path, config, curs
});
}
commit(types.SET_DONE_FETCHING_BATCH_DISCUSSIONS, true);
commit(types.SET_FETCHING_DISCUSSIONS, false);
dispatch('updateResolvableDiscussionsCounts');

View file

@ -14,6 +14,7 @@ export default () => ({
currentDiscussionId: null,
batchSuggestionsInfo: [],
currentlyFetchingDiscussions: false,
doneFetchingBatchDiscussions: false,
/**
* selectedCommentPosition & selectedCommentPositionHover structures are the same as `position.line_range`:
* {

View file

@ -41,6 +41,7 @@ export const SET_SELECTED_COMMENT_POSITION = 'SET_SELECTED_COMMENT_POSITION';
export const SET_SELECTED_COMMENT_POSITION_HOVER = 'SET_SELECTED_COMMENT_POSITION_HOVER';
export const SET_FETCHING_DISCUSSIONS = 'SET_FETCHING_DISCUSSIONS';
export const SET_RESOLVING_DISCUSSION = 'SET_RESOLVING_DISCUSSION';
export const SET_DONE_FETCHING_BATCH_DISCUSSIONS = 'SET_DONE_FETCHING_BATCH_DISCUSSIONS';
// Issue
export const CLOSE_ISSUE = 'CLOSE_ISSUE';

View file

@ -436,4 +436,7 @@ export default {
[types.SET_FETCHING_DISCUSSIONS](state, value) {
state.currentlyFetchingDiscussions = value;
},
[types.SET_DONE_FETCHING_BATCH_DISCUSSIONS](state, value) {
state.doneFetchingBatchDiscussions = value;
},
};

View file

@ -1,7 +1,9 @@
import { initShow } from '~/issues';
import initRelatedIssues from '~/related_issues';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initWorkItemLinks from '~/work_items/components/work_item_links';
initShow();
initSidebarBundle();
initRelatedIssues();
initWorkItemLinks();

View file

@ -2,7 +2,9 @@ import { initShow } from '~/issues';
import { store } from '~/notes/stores';
import initRelatedIssues from '~/related_issues';
import initSidebarBundle from '~/sidebar/sidebar_bundle';
import initWorkItemLinks from '~/work_items/components/work_item_links';
initShow();
initSidebarBundle(store);
initRelatedIssues();
initWorkItemLinks();

View file

@ -23,6 +23,8 @@ import JobItem from './job_item.vue';
export default {
i18n: {
stage: __('Stage:'),
loadingTextLineOne: __('Loading, please wait.'),
loadingTextLineTwo: __('Cue dramatic background music...'),
},
dropdownPopperOpts: {
placement: 'bottom',
@ -133,7 +135,17 @@ export default {
class="gl-align-items-center gl-display-inline-flex gl-z-index-1"
/>
</template>
<gl-loading-icon v-if="isLoading" size="sm" />
<div
v-if="isLoading"
class="gl-display-flex gl-justify-content-center gl-p-3"
data-testid="pipeline-stage-loading-state"
>
<gl-loading-icon size="sm" class="gl-mr-3" />
<div>
<p class="gl-mb-0">{{ $options.i18n.loadingTextLineOne }}</p>
<p class="gl-mb-0">{{ $options.i18n.loadingTextLineTwo }}</p>
</div>
</div>
<ul
v-else
class="js-builds-dropdown-list scrollable-menu"

View file

@ -0,0 +1,28 @@
import Vue from 'vue';
import WorkItemLinks from './work_item_links.vue';
export default function initWorkItemLinks() {
if (!window.gon.features.workItemsHierarchy) {
return;
}
const workItemLinksRoot = document.querySelector('.js-work-item-links-root');
if (!workItemLinksRoot) {
return;
}
// eslint-disable-next-line no-new
new Vue({
el: workItemLinksRoot,
name: 'WorkItemLinksRoot',
components: {
workItemLinks: WorkItemLinks,
},
render: (createElement) =>
createElement('work-item-links', {
props: {
issuableId: parseInt(workItemLinksRoot.dataset.issuableId, 10),
},
}),
});
}

View file

@ -14,11 +14,16 @@ export default {
required: false,
default: null,
},
issuableId: {
type: Number,
required: false,
default: null,
},
},
data() {
return {
isShownAddForm: false,
isOpen: false,
isOpen: true,
children: [],
};
},

View file

@ -184,7 +184,8 @@ module IssuableActions
def paginated_discussions
return if params[:per_page].blank?
return unless issuable.instance_of?(Issue) && Feature.enabled?(:paginated_issue_discussions, project)
return if issuable.instance_of?(Issue) && Feature.disabled?(:paginated_issue_discussions, project)
return if issuable.instance_of?(MergeRequest) && Feature.disabled?(:paginated_mr_discussions, project)
strong_memoize(:paginated_discussions) do
issuable

View file

@ -50,6 +50,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:realtime_labels, project)
push_force_frontend_feature_flag(:work_items, project&.work_items_feature_flag_enabled?)
push_frontend_feature_flag(:work_items_mvc_2)
push_frontend_feature_flag(:work_items_hierarchy, project)
end
around_action :allow_gitaly_ref_name_caching, only: [:discussions]

View file

@ -48,6 +48,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:mr_attention_requests, current_user)
push_frontend_feature_flag(:remove_diff_header_icons, project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
push_frontend_feature_flag(:paginated_mr_discussions, project)
end
before_action do

View file

@ -44,6 +44,7 @@ class ProjectsController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items, @project&.work_items_feature_flag_enabled?)
push_frontend_feature_flag(:work_items_mvc_2)
push_frontend_feature_flag(:package_registry_access_level)
push_frontend_feature_flag(:work_items_hierarchy, @project)
end
before_action only: :edit do

View file

@ -45,7 +45,7 @@ module Registrations
end
def update_params
params.require(:user).permit(:role, :other_role, :setup_for_company)
params.require(:user).permit(:role, :setup_for_company)
end
def requires_confirmation?(user)

View file

@ -338,7 +338,6 @@ class User < ApplicationRecord
delegate :path, to: :namespace, allow_nil: true, prefix: true
delegate :job_title, :job_title=, to: :user_detail, allow_nil: true
delegate :other_role, :other_role=, to: :user_detail, allow_nil: true
delegate :bio, :bio=, to: :user_detail, allow_nil: true
delegate :webauthn_xid, :webauthn_xid=, to: :user_detail, allow_nil: true
delegate :pronouns, :pronouns=, to: :user_detail, allow_nil: true

View file

@ -2,6 +2,9 @@
class UserDetail < ApplicationRecord
extend ::Gitlab::Utils::Override
include IgnorableColumns
ignore_columns :other_role, remove_after: '2022-07-22', remove_with: '15.3'
REGISTRATION_OBJECTIVE_PAIRS = { basics: 0, move_repository: 1, code_storage: 2, exploring: 3, ci: 4, other: 5, joining_team: 6 }.freeze

View file

@ -0,0 +1,2 @@
- if Feature.enabled?(:work_items_hierarchy, @project)
.js-work-item-links-root{ data: { issuable_id: @issue.id } }

View file

@ -65,7 +65,7 @@
- if Feature.enabled?(:paginated_notes, @project)
- add_page_startup_api_call notes_url
- else
- add_page_startup_api_call discussions_path(@merge_request)
- add_page_startup_api_call Feature.enabled?(:paginated_mr_discussions, @project) ? discussions_path(@merge_request, per_page: 20) : discussions_path(@merge_request)
- add_page_startup_api_call widget_project_json_merge_request_path(@project, @merge_request, format: :json)
- add_page_startup_api_call cached_widget_project_json_merge_request_path(@project, @merge_request, format: :json)
#js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request, Feature.enabled?(:paginated_notes, @project)).to_json,

View file

@ -24,11 +24,6 @@
.form-group.col-sm-12
= f.label :role, _('Role'), class: 'label-bold'
= f.select :role, ::User.roles.keys.map { |role| [role.titleize, role] }, { include_blank: _('Select a role') }, class: 'form-control js-user-role-dropdown', autofocus: true, required: true, data: { qa_selector: 'role_dropdown' }
- if Feature.enabled?(:user_other_role_details)
.row
.form-group.col-sm-12.js-other-role-group.hidden
= f.label :other_role, _('What is your job title? (optional)')
= f.text_field :other_role, class: 'form-control'
= render_if_exists "registrations/welcome/jobs_to_be_done", f: f
= render_if_exists "registrations/welcome/setup_for_company", f: f
= render_if_exists "registrations/welcome/joining_project"

View file

@ -17,6 +17,7 @@
= render 'projects/issues/design_management'
= render_if_exists 'projects/issues/work_item_links'
= render_if_exists 'projects/issues/related_issues'
#js-related-merge-requests{ data: { endpoint: expose_path(api_v4_projects_issues_related_merge_requests_path(id: @project.id, issue_iid: issuable.iid)), project_namespace: @project.namespace.path, project_path: @project.path } }

View file

@ -0,0 +1,8 @@
---
name: fe_epic_board_total_weight
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/89390
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364503
milestone: '15.1'
type: development
group: group::product planning
default_enabled: false

View file

@ -0,0 +1,8 @@
---
name: paginated_mr_discussions
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/88905
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/364497
milestone: '15.1'
type: development
group: group::code review
default_enabled: false

View file

@ -1,8 +0,0 @@
---
name: user_other_role_details
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45635
rollout_issue_url: https://gitlab.com/gitlab-org/growth/team-tasks/-/issues/282
milestone: '13.7'
type: development
group: group::conversion
default_enabled: false

View file

@ -0,0 +1,14 @@
# frozen_string_literal: true
class AddSemverColumnToCiRunners < Gitlab::Database::Migration[2.0]
# rubocop:disable Migration/AddLimitToTextColumns
# limit is added in 20220601091805_add_text_limit_to_ci_runners_semver
def up
add_column :ci_runners, :semver, :text, null: true
end
# rubocop:enable Migration/AddLimitToTextColumns
def down
remove_column :ci_runners, :semver
end
end

View file

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddTextLimitToCiRunnersSemver < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
def up
add_text_limit :ci_runners, :semver, 16
end
def down
remove_text_limit :ci_runners, :semver
end
end

View file

@ -0,0 +1,17 @@
# frozen_string_literal: true
class AddIndexOnRunnerIdAndSemverColumns < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'index_ci_runners_on_id_and_semver_cidr'
def up
add_concurrent_index :ci_runners,
'id, (semver::cidr)',
name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :ci_runners, INDEX_NAME
end
end

View file

@ -0,0 +1 @@
3d3c9b4aa88008c907b583db08e1246cd227414147b41f45b63e4ca1cc24de66

View file

@ -0,0 +1 @@
930a6a853626c3a9f5a529105bf4e4cb8cef9b6f948ccb4faaaf0dcb7a5a2544

View file

@ -0,0 +1 @@
c75a7375240fdd785f873b1a39173efec51d23e16808d23c24a6550604f080ad

View file

@ -13082,6 +13082,8 @@ CREATE TABLE ci_runners (
maintainer_note text,
token_expires_at timestamp with time zone,
allowed_plans text[] DEFAULT '{}'::text[] NOT NULL,
semver text,
CONSTRAINT check_a4f24953fd CHECK ((char_length(semver) <= 16)),
CONSTRAINT check_ce275cee06 CHECK ((char_length(maintainer_note) <= 1024))
);
@ -27448,6 +27450,8 @@ CREATE INDEX index_ci_runners_on_created_at_desc_and_id_desc ON ci_runners USING
CREATE INDEX index_ci_runners_on_description_trigram ON ci_runners USING gin (description gin_trgm_ops);
CREATE INDEX index_ci_runners_on_id_and_semver_cidr ON ci_runners USING btree (id, ((semver)::cidr));
CREATE INDEX index_ci_runners_on_locked ON ci_runners USING btree (locked);
CREATE INDEX index_ci_runners_on_runner_type ON ci_runners USING btree (runner_type);

View file

@ -141,6 +141,8 @@ and the existing GLFM parser and render implementations. They may also be
manually updated as necessary to test-drive incomplete implementations.
Regarding the terminology used here:
<!-- vale gitlab.InclusionCultural = NO -->
1. The Markdown snapshot tests can be considered a form of the
[Golden Master Testing approach](https://www.google.com/search?q=golden+master+testing),
which is also referred to as Approval Testing or Characterization Testing.
@ -167,6 +169,11 @@ Regarding the terminology used here:
they are colocated under the `spec/fixtures` directory with the rest of
the fixture data for the GitLab Rails application.
<!-- vale gitlab.InclusionCultural = YES -->
See also the section on [normalization](#normalization) below, which is an important concept used
in the Markdown snapshot testing.
## Parsing and Rendering
The Markdown dialect used in the GitLab application has a dual requirement for rendering:
@ -268,6 +275,49 @@ HTML. (For example, when they are represented as an image.) In these cases, the
conformance test for the example can be skipped by setting `skip_update_example_snapshots: true`
for the example in `glfm_specification/input/gitlab_flavored_markdown/glfm_example_status.yml`.
### Normalization
Versions of the rendered HTML and ProseMirror JSON can vary for a number of reasons.
Differences in styling or HTML structure can occur, but the values of attributes or nodes may
also vary across different test runs or environments. For example:
1. Database record identifiers
1. Namespace or project identifiers
1. Portions of URIs
1. File paths or names
1. Random values
For the [Markdown snapshot testing](#markdown-snapshot-testing) to work
properly, you must account for these differences in a way that ensures the tests are reliable,
and always behave the same across different test runs or environments.
To account for these differences, there is a process called **_normalization_**. Normalization
allows custom regular expressions with
[_capturing groups_](https://ruby-doc.org/core-3.1.2/Regexp.html#class-Regexp-label-Capturing)
to be applied to two different versions of HTML or JSON for a given Markdown example,
and the contents of the captured groups can be replaced with the same fixed values.
Then, the two normalized versions can be compared to each other to ensure all other non-variable
content is identical.
NOTE:
We don't care about verifying specific attribute values here, so
it's OK if the normalizations discard and replace these variable values with fixed values.
Different testing levels have different purposes:
1. [Markdown snapshot testing](#markdown-snapshot-testing) is intended to enforce the structure of
the rendered HTML/JSON, and to ensure that it conforms to the canonical specification.
1. Individual unit tests of the implementation for a specific Markdown example are responsible for
specific and targeted testing of these variable values.
We also use this same regex capture-and-replace normalization approach for
[canonicalization of HTML](#canonicalization-of-html), because it is essentially the same process.
With canonicalization, instead of just replacing variable values, we are removing non-canonical
portions of the HTML.
Refer to [`glfm_example_normalizations.yml`](#glfm_example_normalizationsyml) for a detailed explanation
of how the normalizations are specified.
## Goals
Given the constraints above, we have a few goals related to the GLFM
@ -593,7 +643,7 @@ controls the behavior of the [scripts](#scripts) and [tests](#types-of-markdown-
- The `skip_running_*` control allow Markdown conformance tests or
Markdown snapshot tests to be skipped for individual examples.
- This allows control over skipping this processing or testing of various examples when they
are unimplemented, partially implemented, broken, or cannot be generated or tested for some reason.
are unimplemented, partially implemented, broken, cannot be generated, or cannot be tested for some reason.
- All entries default to false. They can be set to true by specifying a Ruby
value which evaluates as truthy. This could be the boolean `true` value, but ideally should
be a string describing why the example's updating or testing is being skipped.
@ -641,6 +691,63 @@ The following optional entries are supported for each example. They all default
skip_running_snapshot_prosemirror_json_tests: 'An explanation of the reason for skipping.'
```
##### `glfm_example_normalizations.yml`
[`glfm_specification/input/gitlab_flavored_markdown/glfm_example_normalizations.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/input/gitlab_flavored_markdown/glfm_example_normalizations.yml)
controls the [normalization](#normalization) process. It allows one or more `regex`/`replacement` pairs
to be specified for a Markdown example.
- It is manually updated.
- It has a nested structure corresponding to the example and type of entry it refers to.
- It extensively uses [YAML anchors and aliases](https://yaml.org/spec/1.2.2/#692-node-anchors)
to avoid duplication of `regex`/`replacement` pairs and allow them to be shared across multiple examples.
- The YAML anchors use a naming convention based on the index number of the example, to
ensure unique anchor names and avoid naming conflicts.
`glfm_specification/input/gitlab_flavored_markdown/glfm_example_normalizations.yml` sample entries:
```yaml
# NOTE: All YAML anchors which are shared across one or more examples are defined in the `00_shared` section.
00_shared:
00_uri: &00_uri
- regex: '(href|data-src)(=")(.*?)(test-file\.(png|zip)")'
replacement: '\1\2URI_PREFIX\4'
01_01__section_one__example_containing_a_uri__001:
html:
static:
canonical:
01_01_uri: *00_uri
snapshot:
01_01_uri: *00_uri
wysiwyg:
01_01_uri: *00_uri
prosemirror_json:
01_01_uri: *00_uri
07_01__gitlab_specific_markdown__footnotes__001:
# YAML anchors which are only shared within a single example should be defined within the example
shared:
07_01_href: &07_01_href
- regex: '(href)(=")(.+?)(")'
replacement: '\1\2REF\4'
07_01_id: &07_01_id
- regex: '(id)(=")(.+?)(")'
replacement: '\1\2ID\4'
html:
static:
canonical:
07_01_href: *07_01_href
07_01_id: *07_01_id
snapshot:
07_01_href: *07_01_href
07_01_id: *07_01_id
wysiwyg:
07_01_href: *07_01_href
07_01_id: *07_01_id
prosemirror_json:
07_01_href: *07_01_href
07_01_id: *07_01_id
```
#### Output specification files
The `glfm_specification/output` directory contains the CommonMark standard format
@ -654,7 +761,8 @@ are colocated under the same parent folder `glfm_specification` with the other
a mix of manually edited and generated files.
In GFM, `spec.txt` is [located in the test dir](https://github.com/github/cmark-gfm/blob/master/test/spec.txt),
and in CommonMark it's located [in the project root](https://github.com/github/cmark-gfm/blob/master/test/spec.txt). No precedent exists for a standard location. In the future, we may decide to
and in CommonMark it's located [in the project root](https://github.com/github/cmark-gfm/blob/master/test/spec.txt).
No precedent exists for a standard location. In the future, we may decide to
move or copy a hosted version of the rendered HTML `spec.html` version to another location or site.
##### spec.txt

View file

@ -333,7 +333,7 @@ GitLab has the following retention policies for vulnerabilities on non-default b
To view vulnerabilities, either:
- Re-run the pipeline.
- Run a new pipeline.
- Download the related CI job artifacts if they are available.
NOTE:

View file

@ -208,6 +208,54 @@ it 'searches' do
end
```
## Avoid multiple actions in `expect do ... raise_error` blocks
When you wrap multiple actions in a single `expect do ... end.not_to raise_error` or `expect do ... end.to raise_error` block,
it can be hard to debug the actual cause of the failure, because of how the logs are printed. Important information can be truncated
or missing altogether.
For example, if you encapsulate some actions and expectations in a private method in the test, like `expect_owner_permissions_allow_delete_issue`:
```ruby
it "has Owner role with Owner permissions" do
Page::Dashboard::Projects.perform do |projects|
projects.filter_by_name(project.name)
expect(projects).to have_project_with_access_role(project.name, 'Owner')
end
expect_owner_permissions_allow_delete_issue
end
```
Then, in the method itself:
```ruby
#=> Good
def expect_owner_permissions_allow_delete_issue
issue.visit!
Page::Project::Issue::Show.perform(&:delete_issue)
Page::Project::Issue::Index.perform do |index|
expect(index).not_to have_issue(issue)
end
end
#=> Bad
def expect_owner_permissions_allow_delete_issue
expect do
issue.visit!
Page::Project::Issue::Show.perform(&:delete_issue)
Page::Project::Issue::Index.perform do |index|
expect(index).not_to have_issue(issue)
end
end.not_to raise_error
end
```
## Prefer to split tests across multiple files
Our framework includes a couple of parallelization mechanisms that work by executing spec files in parallel.

View file

@ -246,3 +246,33 @@ A banned user can be unbanned using the Admin Area. To do this:
The user's state is set to active and they consume a
[seat](../../subscriptions/self_managed/index.md#billable-users).
### Delete a user
Use the Admin Area to delete users.
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Overview > Users**.
1. Select the **Banned** tab.
1. Optional. Select a user.
1. Select the **{settings}** **User administration** dropdown list.
1. Select **Delete user**.
1. Type the username.
1. Select **Delete user**.
NOTE:
You can only delete a user if there are inherited or direct owners of a group. You cannot delete a user if they are the only group owner.
You can also delete a user and their contributions, such as merge requests, issues, and groups of which they are the only group owner.
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Overview > Users**.
1. Select the **Banned** tab.
1. Optional. Select a user.
1. Select the **{settings}** **User administration** dropdown list.
1. Select **Delete user and contributions**.
1. Type the username.
1. Select **Delete user and contributions**.
NOTE:
Before 15.1, additionally groups of which deleted user were the only owner among direct members were deleted.

View file

@ -1,23 +1,20 @@
# First GitLab-Specific Section with Examples
# GitLab-Specific Markdown
## Strong but with two asterisks
Currently, only some of the GitLab-specific markdown features are
listed in this section. We will eventually add all
GitLab-specific features currently listed as supported in the
[user-facing documentation for GitLab Flavored Markdown](https://docs.gitlab.com/ee/user/markdown.html).
```````````````````````````````` example
**bold**
.
<p><strong>bold</strong></p>
````````````````````````````````
# Second GitLab-Specific Section with Examples
## Strong but with HTML
```````````````````````````````` example
<strong>
bold
</strong>
.
<p><strong>
bold
</strong></p>
There is currently only this single top-level heading, but the
examples may be split into multiple top-level headings in the future.
## Footnotes
See
[the footnotes section of the user-facing documentation for GitLab Flavored Markdown](https://docs.gitlab.com/ee/user/markdown.html#footnotes).
```````````````````````````````` example gitlab footnote
footnote reference tag [^1]
[^1]: footnote text
````````````````````````````````

View file

@ -0,0 +1,27 @@
---
# See the following documentation for more info on normalization:
#
# - https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#normalization
# - https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#glfm_example_normalizationsyml
#
# NOTE: All YAML anchors which are shared across one or more entries are defined in the `00_shared` section.
00_shared:
00_uri: &00_uri
- regex: '(href|data-src)(=")(.*?)(test-file\.(png|zip)")'
replacement: '\1\2URI_PREFIX\4'
07_01__gitlab_specific_markdown__footnotes__001:
html:
static:
shared:
07_01_href: &07_01_href
- regex: '(href)(=")(.+?)(")'
replacement: '\1\2REF\4'
07_01_id: &07_01_id
- regex: '(id)(=")(.+?)(")'
replacement: '\1\2ID\4'
canonical:
07_01_href: *07_01_href
07_01_id: *07_01_id
snapshot:
07_01_href: *07_01_href
07_01_id: *07_01_id

View file

@ -9600,28 +9600,25 @@ Multiple spaces
````````````````````````````````
# First GitLab-Specific Section with Examples
# GitLab-Specific Markdown
## Strong but with two asterisks
Currently, only some of the GitLab-specific markdown features are
listed in this section. We will eventually add all
GitLab-specific features currently listed as supported in the
[user-facing documentation for GitLab Flavored Markdown](https://docs.gitlab.com/ee/user/markdown.html).
```````````````````````````````` example
**bold**
.
<p><strong>bold</strong></p>
````````````````````````````````
There is currently only this single top-level heading, but the
examples may be split into multiple top-level headings in the future.
# Second GitLab-Specific Section with Examples
## Footnotes
## Strong but with HTML
See
[the footnotes section of the user-facing documentation for GitLab Flavored Markdown](https://docs.gitlab.com/ee/user/markdown.html#footnotes).
```````````````````````````````` example
<strong>
bold
</strong>
.
<p><strong>
bold
</strong></p>
```````````````````````````````` example gitlab footnote
footnote reference tag [^1]
[^1]: footnote text
````````````````````````````````
<!-- END TESTS -->

View file

@ -32,8 +32,8 @@ module Gitlab
def mail_metadata
{
mail_uid: mail.message_id,
from_address: mail.from,
to_address: mail.to,
from_address: from,
to_address: to,
mail_key: mail_key,
references: Array(mail.references),
delivered_to: delivered_to.map(&:value),
@ -42,7 +42,7 @@ module Gitlab
# reduced down to what looks like an email in the received headers
received_recipients: recipients_from_received_headers,
meta: {
client_id: "email/#{mail.from.first}",
client_id: "email/#{from.first}",
project: handler&.project&.full_path
}
}
@ -78,7 +78,7 @@ module Gitlab
end
def key_from_to_header
mail.to.find do |address|
to.find do |address|
key = email_class.key_from_address(address)
break key if key
end
@ -112,6 +112,14 @@ module Gitlab
end
end
def from
Array(mail.from)
end
def to
Array(mail.to)
end
def delivered_to
Array(mail[:delivered_to])
end

View file

@ -10918,6 +10918,9 @@ msgstr ""
msgid "CsvParser|Unable to auto-detect delimiter; defaulted to \",\""
msgstr ""
msgid "Cue dramatic background music..."
msgstr ""
msgid "Current"
msgstr ""
@ -23049,6 +23052,9 @@ msgstr ""
msgid "Loading the GitLab IDE..."
msgstr ""
msgid "Loading, please wait."
msgstr ""
msgid "Loading..."
msgstr ""
@ -42744,9 +42750,6 @@ msgstr ""
msgid "What is squashing?"
msgstr ""
msgid "What is your job title? (optional)"
msgstr ""
msgid "What templates can I create?"
msgstr ""

View file

@ -56,7 +56,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/svgs": "2.16.0",
"@gitlab/svgs": "2.17.0",
"@gitlab/ui": "40.7.1",
"@gitlab/visual-review-tools": "1.7.3",
"@rails/actioncable": "6.1.4-7",

View file

@ -41,19 +41,30 @@ This is the recommended option if you would like to contribute to the tests.
Note that tests are using `Chrome` web browser by default so it should be installed and present in `PATH`.
## CI
Tests are executed in merge request pipelines as part of the development lifecycle.
- [Review app environment](../doc/development/testing_guide/review_apps.md)
- [package-and-qa](../doc/development/testing_guide/end_to_end/index.md#testing-code-in-merge-requests)
### Logging
By default tests on CI use `info` log level. `debug` level is still available in case of failure debugging. Logs are stored in jobs artifacts.
### Writing tests
- [Writing tests from scratch tutorial](../doc/development/testing_guide/end_to_end/beginners_guide.md)
- [Best practices](../doc/development/testing_guide/best_practices.md)
- [Using page objects](../doc/development/testing_guide/end_to_end/page_objects.md)
- [Guidelines](../doc/development/testing_guide/index.md)
- [Tests with special setup for local environments](../doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md)
- [Best practices](../doc/development/testing_guide/best_practices.md)
- [Using page objects](../doc/development/testing_guide/end_to_end/page_objects.md)
- [Guidelines](../doc/development/testing_guide/index.md)
- [Tests with special setup for local environments](../doc/development/testing_guide/end_to_end/running_tests_that_require_special_setup.md)
### Run the end-to-end tests in a local development environment
1. Follow the instructions to [install GDK](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/main/doc/index.md), your local GitLab development environment.
1. Navigate to the QA folder and run the following commands.
1. Navigate to the QA folder and run the following commands.
```bash
cd gitlab-development-kit/gitlab/qa
@ -77,7 +88,6 @@ bundle exec bin/qa Test::Instance::All {GDK IP ADDRESS}
- Note: If you want to run tests requiring SSH against GDK, you will need to [modify your GDK setup](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/run_qa_against_gdk.md).
- Note: If this is your first time running GDK, you can use the password pre-set for `root`. [See supported GitLab environment variables](https://gitlab.com/gitlab-org/gitlab-qa/-/blob/master/docs/what_tests_can_be_run.md#supported-gitlab-environment-variables). If you have changed your `root` password, use that when exporting `GITLAB_INITIAL_ROOT_PASSWORD`.
#### Running EE tests
When running EE tests you'll need to have a license available. GitLab engineers can [request a license](https://about.gitlab.com/handbook/developer-onboarding/#working-on-gitlab-ee).
@ -88,12 +98,12 @@ Once you have the license file you can export it as an environment variable and
export EE_LICENSE=$(cat /path/to/gitlab_license)
```
### Running specific tests
#### Running specific tests
You can also supply specific tests to run as another parameter. For example, to
run the repository-related specs, you can execute:
```console
```shell
bundle exec rspec qa/specs/features/browser_ui/3_create/repository
```
@ -114,7 +124,7 @@ When running tests against GDK, the default address is `http://127.0.0.1:3000`.
QA_GITLAB_URL=https://gdk.test:3000 bundle exec rspec
```
### Overriding the authenticated user
#### Overriding the authenticated user
Unless told otherwise, the QA tests will run as the default `root` user seeded
by the GDK.
@ -147,7 +157,7 @@ GITLAB_USERNAME=jsmith GITLAB_PASSWORD=password GITLAB_SANDBOX_NAME=jsmith-qa-sa
All [supported environment variables are here](https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/what_tests_can_be_run.md#supported-environment-variables).
### Sending additional cookies
#### Sending additional cookies
The environment variable `QA_COOKIES` can be set to send additional cookies
on every request. This is necessary on gitlab.com to direct traffic to the
@ -155,6 +165,22 @@ canary fleet. To do this set `QA_COOKIES="gitlab_canary=true"`.
To set multiple cookies, separate them with the `;` character, for example: `QA_COOKIES="cookie1=value;cookie2=value2"`
#### Headless browser
By default tests use headless browser. To override that, `WEBDRIVER_HEADLESS` must be set to `false`:
```shell
WEBDRIVER_HEADLESS=false bundle exec rspec
```
#### Log level
By default, the tests use the `info` log level. To change the test's log level, the environment variable `QA_LOG_LEVEL` can be set:
```shell
QA_LOG_LEVEL=debug bundle exec rspec
```
### Building a Docker image to test
Once you have made changes to the CE/EE repositories, you may want to build a
@ -182,7 +208,6 @@ bundle exec rspec --tag quarantine
`bin/qa` is an additional custom wrapper script that abstracts away some of the more complicated setups that some tests require. This option requires test scenario and test instance's Gitlab address to be specified in the command. For example, to run any `Instance` scenario test, the following command can be used:
```shell
bundle exec bin/qa Test::Instance::All http://localhost:3000
```

View file

@ -73,25 +73,21 @@ module QA
private
def expect_owner_permissions_allow_delete_issue
expect do
issue.visit!
issue.visit!
Page::Project::Issue::Show.perform(&:delete_issue)
Page::Project::Issue::Show.perform(&:delete_issue)
Page::Project::Issue::Index.perform do |index|
expect(index).not_to have_issue(issue)
end
end.not_to raise_error
Page::Project::Issue::Index.perform do |index|
expect(index).not_to have_issue(issue)
end
end
def expect_maintainer_permissions_do_not_allow_delete_issue
expect do
issue.visit!
issue.visit!
Page::Project::Issue::Show.perform do |issue|
expect(issue).not_to have_delete_issue_button
end
end.not_to raise_error
Page::Project::Issue::Show.perform do |issue|
expect(issue).not_to have_delete_issue_button
end
end
end
end

View file

@ -101,6 +101,7 @@ RSpec.describe 'Merge request > User sees versions', :js do
outdated_diff_note.save!
refresh
wait_for_requests
expect(page).to have_css(".diffs .notes[data-discussion-id='#{outdated_diff_note.discussion_id}']")
end

View file

@ -26,6 +26,8 @@ RSpec.describe 'User comments on a diff', :js do
let(:user) { create(:user) }
before do
stub_feature_flags(paginated_notes: false)
project.add_maintainer(user)
sign_in(user)

View file

@ -2012,9 +2012,6 @@
06_15__inlines__textual_content__003:
spec_txt_example_position: 673
source_specification: commonmark
07_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001:
07_01__gitlab_specific_markdown__footnotes__001:
spec_txt_example_position: 674
source_specification: commonmark
08_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001:
spec_txt_example_position: 675
source_specification: commonmark
source_specification: gitlab

View file

@ -130,9 +130,7 @@
</li>
</ul>
wysiwyg: |-
<ul bullet="*"><li><p>foo
</p><ul bullet="*"><li><p>bar
</p><ul bullet="*"><li><p>baz</p></li></ul></li></ul></li></ul>
<ul bullet="*"><li><p>foo</p><ul bullet="*"><li><p>bar</p><ul bullet="*"><li><p>baz</p></li></ul></li></ul></li></ul>
02_01__preliminaries__tabs__010:
canonical: |
<h1>Foo</h1>
@ -1849,7 +1847,7 @@
<p data-sourcepos="2:1-2:5" dir="auto"><em>baz</em></p>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__030:
canonical: |
<script>
@ -1872,7 +1870,7 @@
<p data-sourcepos="5:1-5:4" dir="auto">okay</p>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__032:
canonical: |
<?php
@ -1888,7 +1886,7 @@
<p data-sourcepos="6:1-6:4" dir="auto">okay</p>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__033:
canonical: |
<!DOCTYPE html>
@ -1915,7 +1913,7 @@
<p data-sourcepos="13:1-13:4" dir="auto">okay</p>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__035:
canonical: |2
<!-- foo -->
@ -1927,7 +1925,7 @@
foo --&gt;</span></code></pre>\n<copy-code></copy-code>\n</div>"
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__036:
canonical: |2
<div>
@ -3898,10 +3896,7 @@
</li>
</ul>
wysiwyg: |-
<ul bullet="*"><li><p>foo
</p><ul bullet="*"><li><p>bar
</p><ul bullet="*"><li><p>baz
</p><ul bullet="*"><li><p>boo</p></li></ul></li></ul></li></ul></li></ul>
<ul bullet="*"><li><p>foo</p><ul bullet="*"><li><p>bar</p><ul bullet="*"><li><p>baz</p><ul bullet="*"><li><p>boo</p></li></ul></li></ul></li></ul></li></ul>
05_02__container_blocks__list_items__043:
canonical: |
<ul>
@ -3937,8 +3932,7 @@
</li>
</ol>
wysiwyg: |-
<ol parens="false"><li><p>foo
</p><ul bullet="*"><li><p>bar</p></li></ul></li></ol>
<ol parens="false"><li><p>foo</p><ul bullet="*"><li><p>bar</p></li></ul></li></ol>
05_02__container_blocks__list_items__045:
canonical: |
<ol start="10">
@ -4072,8 +4066,7 @@
<li data-sourcepos="9:1-9:5">baz</li>
</ul>
wysiwyg: |-
Error - check implementation:
Hast node of type "input" not supported by this converter. Please, provide an specification.
<ul bullet="*"><li><p>baz</p></li></ul>
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__050:
canonical: |
<ol>
@ -4190,9 +4183,7 @@
</li>
</ul>
wysiwyg: |-
<ul bullet="*"><li><p>foo
</p><ul bullet="*"><li><p>bar
</p><ul bullet="*"><li><p>baz</p><p>bim</p></li></ul></li></ul></li></ul>
<ul bullet="*"><li><p>foo</p><ul bullet="*"><li><p>bar</p><ul bullet="*"><li><p>baz</p><p>bim</p></li></ul></li></ul></li></ul>
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__056:
canonical: |
<ul>
@ -4216,7 +4207,7 @@
</ul>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__057:
canonical: |
<ul>
@ -4248,7 +4239,7 @@
</div>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__058:
canonical: |
<ul>
@ -4508,8 +4499,7 @@
<li data-sourcepos="5:1-5:3">d</li>
</ul>
wysiwyg: |-
<ul bullet="*"><li><p>a
</p><ul bullet="*"><li><p>b</p><p>c</p></li></ul></li><li><p>d</p></li></ul>
<ul bullet="*"><li><p>a</p><ul bullet="*"><li><p>b</p><p>c</p></li></ul></li><li><p>d</p></li></ul>
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__068:
canonical: |
<ul>
@ -4530,8 +4520,7 @@
<li data-sourcepos="4:1-4:3">c</li>
</ul>
wysiwyg: |-
<ul bullet="*"><li><p>a
</p><blockquote multiline="false"><p>b</p></blockquote></li><li><p>c</p></li></ul>
<ul bullet="*"><li><p>a</p><blockquote multiline="false"><p>b</p></blockquote></li><li><p>c</p></li></ul>
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__069:
canonical: |
<ul>
@ -4558,8 +4547,7 @@
<li data-sourcepos="6:1-6:3">d</li>
</ul>
wysiwyg: |-
<ul bullet="*"><li><p>a
</p><blockquote multiline="false"><p>b</p></blockquote><pre class="content-editor-code-block undefined code highlight"><code>c</code></pre></li><li><p>d</p></li></ul>
<ul bullet="*"><li><p>a</p><blockquote multiline="false"><p>b</p></blockquote><pre class="content-editor-code-block undefined code highlight"><code>c</code></pre></li><li><p>d</p></li></ul>
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__070:
canonical: |
<ul>
@ -4589,8 +4577,7 @@
</li>
</ul>
wysiwyg: |-
<ul bullet="*"><li><p>a
</p><ul bullet="*"><li><p>b</p></li></ul></li></ul>
<ul bullet="*"><li><p>a</p><ul bullet="*"><li><p>b</p></li></ul></li></ul>
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__072:
canonical: |
<ol>
@ -7217,7 +7204,7 @@
<p data-sourcepos="1:1-2:25" dir="auto">foo </p>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
06_11__inlines__raw_html__014:
canonical: |
<p>foo &lt;!-- not a comment -- two hyphens --&gt;</p>
@ -7241,7 +7228,7 @@
<p data-sourcepos="1:1-1:21" dir="auto">foo <?php echo $a; ?></p>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
06_11__inlines__raw_html__017:
canonical: |
<p>foo <!ELEMENT br EMPTY></p>
@ -7249,7 +7236,7 @@
<p data-sourcepos="1:1-1:23" dir="auto">foo </p>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
06_11__inlines__raw_html__018:
canonical: |
<p>foo <![CDATA[>&<]]></p>
@ -7257,7 +7244,7 @@
<p data-sourcepos="1:1-1:19" dir="auto">foo &amp;</p>
wysiwyg: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
06_11__inlines__raw_html__019:
canonical: |
<p>foo <a href="&ouml;"></p>
@ -7462,23 +7449,17 @@
<p data-sourcepos="1:1-1:19" dir="auto">Multiple spaces</p>
wysiwyg: |-
<p>Multiple spaces</p>
07_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001:
canonical: |
<p><strong>bold</strong></p>
07_01__gitlab_specific_markdown__footnotes__001:
canonical: ""
static: |-
<p data-sourcepos="1:1-1:8" dir="auto"><strong>bold</strong></p>
<p data-sourcepos="1:1-1:27" dir="auto">footnote reference tag <sup class="footnote-ref"><a href="#fn-1-2118" id="fnref-1-2118" data-footnote-ref>1</a></sup></p>
<section data-footnotes class="footnotes">
<ol>
<li id="fn-1-2118">
<p data-sourcepos="3:7-3:19">footnote text <a href="#fnref-1-2118" data-footnote-backref aria-label="Back to content" class="footnote-backref"><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p>
</li>
</ol>
</section>
wysiwyg: |-
<p><strong>bold</strong></p>
08_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001:
canonical: |
<p><strong>
bold
</strong></p>
static: |-
<strong>
bold
</strong>
wysiwyg: |-
<p><strong>
bold
</strong></p>
Error - check implementation:
Hast node of type "sup" not supported by this converter. Please, provide an specification.

View file

@ -2195,9 +2195,7 @@
Foo χρῆν
06_15__inlines__textual_content__003: |
Multiple spaces
07_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001: |
**bold**
08_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001: |
<strong>
bold
</strong>
07_01__gitlab_specific_markdown__footnotes__001: |
footnote reference tag [^1]
[^1]: footnote text

View file

@ -233,7 +233,7 @@
"content": [
{
"type": "text",
"text": "foo\n"
"text": "foo"
}
]
},
@ -251,7 +251,7 @@
"content": [
{
"type": "text",
"text": "bar\n"
"text": "bar"
}
]
},
@ -3121,16 +3121,16 @@
Hast node of type "style" not supported by this converter. Please, provide an specification.
04_06__leaf_blocks__html_blocks__029: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__030: |-
Error - check implementation:
Hast node of type "script" not supported by this converter. Please, provide an specification.
04_06__leaf_blocks__html_blocks__031: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__032: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__033: |-
{
"type": "doc",
@ -3142,10 +3142,10 @@
}
04_06__leaf_blocks__html_blocks__034: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__035: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
04_06__leaf_blocks__html_blocks__036: |-
Error - check implementation:
Hast node of type "div" not supported by this converter. Please, provide an specification.
@ -6691,7 +6691,7 @@
"content": [
{
"type": "text",
"text": "foo\n"
"text": "foo"
}
]
},
@ -6709,7 +6709,7 @@
"content": [
{
"type": "text",
"text": "bar\n"
"text": "bar"
}
]
},
@ -6727,7 +6727,7 @@
"content": [
{
"type": "text",
"text": "baz\n"
"text": "baz"
}
]
},
@ -6856,7 +6856,7 @@
"content": [
{
"type": "text",
"text": "foo\n"
"text": "foo"
}
]
},
@ -7100,8 +7100,33 @@
]
}
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__049: |-
Error - check implementation:
Hast node of type "input" not supported by this converter. Please, provide an specification.
{
"type": "doc",
"content": [
{
"type": "bulletList",
"attrs": {
"bullet": "*"
},
"content": [
{
"type": "listItem",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "baz"
}
]
}
]
}
]
}
]
}
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__050: |-
{
"type": "doc",
@ -7346,7 +7371,7 @@
"content": [
{
"type": "text",
"text": "foo\n"
"text": "foo"
}
]
},
@ -7364,7 +7389,7 @@
"content": [
{
"type": "text",
"text": "bar\n"
"text": "bar"
}
]
},
@ -7411,10 +7436,10 @@
}
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__056: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__057: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
05_02__container_blocks__list_items__motivation__task_list_items_extension__lists__058: |-
{
"type": "doc",
@ -8018,7 +8043,7 @@
"content": [
{
"type": "text",
"text": "a\n"
"text": "a"
}
]
},
@ -8091,7 +8116,7 @@
"content": [
{
"type": "text",
"text": "a\n"
"text": "a"
}
]
},
@ -8150,7 +8175,7 @@
"content": [
{
"type": "text",
"text": "a\n"
"text": "a"
}
]
},
@ -8250,7 +8275,7 @@
"content": [
{
"type": "text",
"text": "a\n"
"text": "a"
}
]
},
@ -16576,7 +16601,7 @@
}
06_11__inlines__raw_html__013: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
06_11__inlines__raw_html__014: |-
{
"type": "doc",
@ -16618,13 +16643,13 @@
}
06_11__inlines__raw_html__016: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
06_11__inlines__raw_html__017: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
06_11__inlines__raw_html__018: |-
Error - check implementation:
Hast node of type "comment" not supported by this converter. Please, provide an specification.
Cannot destructure property 'className' of 'hastNode.properties' as it is undefined.
06_11__inlines__raw_html__019: |-
{
"type": "doc",
@ -16988,43 +17013,6 @@
}
]
}
07_01__first_gitlab_specific_section_with_examples__strong_but_with_two_asterisks__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"marks": [
{
"type": "bold"
}
],
"text": "bold"
}
]
}
]
}
08_01__second_gitlab_specific_section_with_examples__strong_but_with_html__001: |-
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"marks": [
{
"type": "bold"
}
],
"text": "\nbold\n"
}
]
}
]
}
07_01__gitlab_specific_markdown__footnotes__001: |-
Error - check implementation:
Hast node of type "sup" not supported by this converter. Please, provide an specification.

View file

@ -1382,6 +1382,29 @@ describe('Actions Notes Store', () => {
],
);
});
it('dispatches `fetchDiscussionsBatch` action if `paginatedMrDiscussions` feature flag is enabled', () => {
window.gon = { features: { paginatedMrDiscussions: true } };
return testAction(
actions.fetchDiscussions,
{ path: 'test-path', filter: 'test-filter', persistFilter: 'test-persist-filter' },
null,
[],
[
{
type: 'fetchDiscussionsBatch',
payload: {
config: {
params: { notes_filter: 'test-filter', persist_filter: 'test-persist-filter' },
},
path: 'test-path',
perPage: 20,
},
},
],
);
});
});
describe('fetchDiscussionsBatch', () => {
@ -1401,6 +1424,7 @@ describe('Actions Notes Store', () => {
null,
[
{ type: mutationTypes.ADD_OR_UPDATE_DISCUSSIONS, payload: { discussion } },
{ type: mutationTypes.SET_DONE_FETCHING_BATCH_DISCUSSIONS, payload: true },
{ type: mutationTypes.SET_FETCHING_DISCUSSIONS, payload: false },
],
[{ type: 'updateResolvableDiscussionsCounts' }],

View file

@ -883,4 +883,16 @@ describe('Notes Store mutations', () => {
expect(state.discussions[0].position).toEqual(position);
});
});
describe('SET_DONE_FETCHING_BATCH_DISCUSSIONS', () => {
it('should set doneFetchingBatchDiscussions', () => {
const state = {
doneFetchingBatchDiscussions: false,
};
mutations.SET_DONE_FETCHING_BATCH_DISCUSSIONS(state, true);
expect(state.doneFetchingBatchDiscussions).toEqual(true);
});
});
});

View file

@ -5,6 +5,7 @@ import CiIcon from '~/vue_shared/components/ci_icon.vue';
import axios from '~/lib/utils/axios_utils';
import PipelineStage from '~/pipelines/components/pipelines_list/pipeline_stage.vue';
import eventHub from '~/pipelines/event_hub';
import waitForPromises from 'helpers/wait_for_promises';
import { stageReply } from '../../mock_data';
const dropdownPath = 'path.json';
@ -58,6 +59,7 @@ describe('Pipelines stage component', () => {
const findDropdownMenuTitle = () =>
wrapper.find('[data-testid="pipeline-stage-dropdown-menu-title"]');
const findMergeTrainWarning = () => wrapper.find('[data-testid="warning-message-merge-trains"]');
const findLoadingState = () => wrapper.find('[data-testid="pipeline-stage-loading-state"]');
const openStageDropdown = () => {
findDropdownToggle().trigger('click');
@ -66,6 +68,29 @@ describe('Pipelines stage component', () => {
});
};
describe('loading state', () => {
beforeEach(async () => {
createComponent({ updateDropdown: true });
mock.onGet(dropdownPath).reply(200, stageReply);
await openStageDropdown();
});
it('displays loading state while jobs are being fetched', () => {
const expectedLoadingText = `${PipelineStage.i18n.loadingTextLineOne} ${PipelineStage.i18n.loadingTextLineTwo}`;
expect(findLoadingState().exists()).toBe(true);
expect(findLoadingState().text()).toBe(expectedLoadingText);
});
it('does not display loading state after jobs have been fetched', async () => {
await waitForPromises();
expect(findLoadingState().exists()).toBe(false);
});
});
describe('default appearance', () => {
beforeEach(() => {
createComponent();

View file

@ -23,32 +23,26 @@ describe('WorkItemLinks', () => {
wrapper.destroy();
});
it('is collapsed by default', () => {
expect(findToggleButton().props('icon')).toBe('angle-down');
expect(findLinksBody().exists()).toBe(false);
it('is expanded by default', () => {
expect(findToggleButton().props('icon')).toBe('angle-up');
expect(findLinksBody().exists()).toBe(true);
});
it('expands on click toggle button', async () => {
findToggleButton().vm.$emit('click');
await nextTick();
expect(findToggleButton().props('icon')).toBe('angle-up');
expect(findLinksBody().exists()).toBe(true);
expect(findToggleButton().props('icon')).toBe('angle-down');
expect(findLinksBody().exists()).toBe(false);
});
it('displays empty state if there are no links', async () => {
findToggleButton().vm.$emit('click');
await nextTick();
it('displays empty state if there are no links', () => {
expect(findEmptyState().exists()).toBe(true);
expect(findToggleAddFormButton().exists()).toBe(true);
});
describe('add link form', () => {
it('displays form on click add button and hides form on cancel', async () => {
findToggleButton().vm.$emit('click');
await nextTick();
expect(findEmptyState().exists()).toBe(true);
findToggleAddFormButton().vm.$emit('click');

View file

@ -11,6 +11,7 @@ RSpec.describe Gitlab::Email::Receiver do
let_it_be(:project) { create(:project) }
let(:handler) { double(:handler, project: project, execute: true, metrics_event: nil, metrics_params: nil) }
let(:client_id) { 'email/jake@example.com' }
it 'correctly finds the mail key' do
expect(Gitlab::Email::Handler).to receive(:for).with(an_instance_of(Mail::Message), 'gitlabhq/gitlabhq+auth_token').and_return(handler)
@ -33,7 +34,7 @@ RSpec.describe Gitlab::Email::Receiver do
metadata = receiver.mail_metadata
expect(metadata.keys).to match_array(%i(mail_uid from_address to_address mail_key references delivered_to envelope_to x_envelope_to meta received_recipients))
expect(metadata[:meta]).to include(client_id: 'email/jake@example.com', project: project.full_path)
expect(metadata[:meta]).to include(client_id: client_id, project: project.full_path)
expect(metadata[meta_key]).to eq(meta_value)
end
end
@ -126,6 +127,49 @@ RSpec.describe Gitlab::Email::Receiver do
it_behaves_like 'failed receive'
end
context "when the email's To field is blank" do
before do
stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.example.com")
end
let(:email_raw) do
<<~EMAIL
Delivered-To: incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com
From: "jake@example.com" <jake@example.com>
Bcc: "support@example.com" <support@example.com>
Email content
EMAIL
end
let(:meta_key) { :delivered_to }
let(:meta_value) { ["incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com"] }
it_behaves_like 'successful receive'
end
context "when the email's From field is blank" do
before do
stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.example.com")
end
let(:email_raw) do
<<~EMAIL
Delivered-To: incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com
To: "support@example.com" <support@example.com>
Email content
EMAIL
end
let(:meta_key) { :delivered_to }
let(:meta_value) { ["incoming+gitlabhq/gitlabhq+auth_token@appmail.example.com"] }
it_behaves_like 'successful receive' do
let(:client_id) { 'email/' }
end
end
context 'when the email was auto generated with X-Autoreply header' do
let(:email_raw) { fixture_file('emails/auto_reply.eml') }
let(:expected_error) { Gitlab::Email::AutoGeneratedEmailError }

View file

@ -5,6 +5,7 @@ require 'spec_helper'
# See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#markdown-snapshot-testing
# for documentation on this spec.
RSpec.describe API::Markdown, 'Snapshot' do
glfm_specification_dir = File.expand_path('../../../glfm_specification', __dir__)
glfm_example_snapshots_dir = File.expand_path('../../fixtures/glfm/example_snapshots', __dir__)
include_context 'API::Markdown Snapshot shared context', glfm_example_snapshots_dir
include_context 'with API::Markdown Snapshot shared context', glfm_specification_dir, glfm_example_snapshots_dir
end

View file

@ -3,6 +3,70 @@
require 'spec_helper'
RSpec.describe Projects::MergeRequestsController do
describe 'GET #discussions' do
let_it_be(:merge_request) { create(:merge_request) }
let_it_be(:project) { merge_request.project }
let_it_be(:user) { merge_request.author }
let_it_be(:discussion) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
let_it_be(:discussion_reply) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project, in_reply_to: discussion) }
let_it_be(:state_event) { create(:resource_state_event, merge_request: merge_request) }
let_it_be(:discussion_2) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
let_it_be(:discussion_3) { create(:diff_note_on_merge_request, noteable: merge_request, project: project) }
before do
login_as(user)
end
context 'pagination' do
def get_discussions(**params)
get discussions_project_merge_request_path(project, merge_request, params: params.merge(format: :json))
end
it 'returns paginated notes and cursor based on per_page param' do
get_discussions(per_page: 2)
discussions = Gitlab::Json.parse(response.body)
notes = discussions.flat_map { |d| d['notes'] }
expect(discussions.count).to eq(2)
expect(notes).to match([
a_hash_including('id' => discussion.id.to_s),
a_hash_including('id' => discussion_reply.id.to_s),
a_hash_including('type' => 'StateNote')
])
cursor = response.header['X-Next-Page-Cursor']
expect(cursor).to be_present
get_discussions(per_page: 1, cursor: cursor)
discussions = Gitlab::Json.parse(response.body)
notes = discussions.flat_map { |d| d['notes'] }
expect(discussions.count).to eq(1)
expect(notes).to match([
a_hash_including('id' => discussion_2.id.to_s)
])
end
context 'when paginated_mr_discussions is disabled' do
before do
stub_feature_flags(paginated_mr_discussions: false)
end
it 'returns all discussions and ignores per_page param' do
get_discussions(per_page: 2)
discussions = Gitlab::Json.parse(response.body)
notes = discussions.flat_map { |d| d['notes'] }
expect(discussions.count).to eq(4)
expect(notes.count).to eq(5)
end
end
end
end
context 'token authentication' do
context 'when public project' do
let_it_be(:public_project) { create(:project, :public) }

View file

@ -53,8 +53,10 @@ end
require 'rainbow/ext/string'
Rainbow.enabled = false
require_relative('../ee/spec/spec_helper') if Gitlab.ee?
# Require JH first because we need override some EE methods with JH methods,
# if we load EE first, we can't find JH modules in prepend_mod method
require_relative('../jh/spec/spec_helper') if Gitlab.jh?
require_relative('../ee/spec/spec_helper') if Gitlab.ee?
# Requires supporting ruby files with custom matchers and macros, etc,
# in spec/support/ and its subdirectories.

View file

@ -4,7 +4,9 @@ require 'spec_helper'
# See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#markdown-snapshot-testing
# for documentation on this spec.
RSpec.shared_context 'API::Markdown Snapshot shared context' do |glfm_example_snapshots_dir|
# rubocop:disable Layout/LineLength
RSpec.shared_context 'with API::Markdown Snapshot shared context' do |glfm_specification_dir, glfm_example_snapshots_dir|
# rubocop:enable Layout/LineLength
include ApiHelpers
markdown_examples, html_examples = %w[markdown.yml html.yml].map do |file_name|
@ -12,7 +14,11 @@ RSpec.shared_context 'API::Markdown Snapshot shared context' do |glfm_example_sn
YAML.safe_load(yaml, symbolize_names: true, aliases: true)
end
if focused_markdown_examples_string = ENV['FOCUSED_MARKDOWN_EXAMPLES']
normalizations_yaml = File.read(
"#{glfm_specification_dir}/input/gitlab_flavored_markdown/glfm_example_normalizations.yml")
normalizations_by_example_name = YAML.safe_load(normalizations_yaml, symbolize_names: true, aliases: true)
if (focused_markdown_examples_string = ENV['FOCUSED_MARKDOWN_EXAMPLES'])
focused_markdown_examples = focused_markdown_examples_string.split(',').map(&:strip).map(&:to_sym)
markdown_examples.select! { |example_name| focused_markdown_examples.include?(example_name) }
end
@ -20,17 +26,38 @@ RSpec.shared_context 'API::Markdown Snapshot shared context' do |glfm_example_sn
markdown_examples.each do |name, markdown|
context "for #{name}" do
let(:html) { html_examples.fetch(name).fetch(:static) }
let(:normalizations) { normalizations_by_example_name.dig(name, :html, :static, :snapshot) }
it "verifies conversion of GLFM to HTML", :unlimited_max_formatted_output_length do
api_url = api "/markdown"
# noinspection RubyResolve
normalized_html = normalize_html(html, normalizations)
post api_url, params: { text: markdown, gfm: true }
expect(response).to be_successful
response_body = Gitlab::Json.parse(response.body)
# Some requests have the HTML in the `html` key, others in the `body` key.
response_html = response_body['body'] ? response_body.fetch('body') : response_body.fetch('html')
# noinspection RubyResolve
normalized_response_html = normalize_html(response_html, normalizations)
expect(response_html).to eq(html)
expect(normalized_response_html).to eq(normalized_html)
end
def normalize_html(html, normalizations)
return html unless normalizations
normalized_html = html.dup
normalizations.each_value do |normalization_entry|
normalization_entry.each do |normalization|
regex = normalization.fetch(:regex)
replacement = normalization.fetch(:replacement)
normalized_html.gsub!(%r{#{regex}}, replacement)
end
end
normalized_html
end
end
end

View file

@ -963,10 +963,10 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.1.0"
"@gitlab/svgs@2.16.0":
version "2.16.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.16.0.tgz#b9551af803487158d232a8b4b806ec2bbc2fb9eb"
integrity sha512-VvaB4ttd5mdE0ntiSZM/70dFeBu4iE4CyVJA72OyCfJLzmJEClsn81eYF44bT53BCy8rrZKgJG3lsvvlbeFCAA==
"@gitlab/svgs@2.17.0":
version "2.17.0"
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.17.0.tgz#56d0d11744859b3e1da80dedab2396a95cd01a02"
integrity sha512-+cmn4ptdOFjSC8ByqD41kj1xSQ9/YFYLq/Es+jy5t12HmUtvYL8YRfNTlvApReSJ8SM7scwleVy4S19M15Siqw==
"@gitlab/ui@40.7.1":
version "40.7.1"