提交 5956978e 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 c1159121
......@@ -8,16 +8,10 @@ function buildDocsLinkStart(path) {
return `<a href="${escape(path)}" target="_blank" rel="noopener noreferrer">`;
}
const NoteableType = {
Issue: 'issue',
Epic: 'epic',
MergeRequest: 'merge_request',
};
const NoteableTypeText = {
[NoteableType.Issue]: __('issue'),
[NoteableType.Epic]: __('epic'),
[NoteableType.MergeRequest]: __('merge request'),
Issue: __('issue'),
Epic: __('epic'),
MergeRequest: __('merge request'),
};
export default {
......@@ -39,7 +33,8 @@ export default {
noteableType: {
type: String,
required: false,
default: NoteableType.Issue,
// eslint-disable-next-line @gitlab/require-i18n-strings
default: 'Issue',
},
lockedNoteableDocsPath: {
type: String,
......
......@@ -37,7 +37,7 @@
- if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths, unless: -> { current_path?('projects/graphs#charts') }) do
= link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
= link_to project_tree_path(@project), class: 'shortcuts-tree', data: { qa_selector: "repository_link" } do
.nav-icon-container
= sprite_icon('doc-text')
%span.nav-item-name#js-onboarding-repo-link
......@@ -58,11 +58,11 @@
= _('Commits')
= nav_link(html_options: {class: branches_tab_class}) do
= link_to project_branches_path(@project), class: 'qa-branches-link', id: 'js-onboarding-branches-link' do
= link_to project_branches_path(@project), data: { qa_selector: "branches_link" }, id: 'js-onboarding-branches-link' do
= _('Branches')
= nav_link(controller: [:tags]) do
= link_to project_tags_path(@project) do
= link_to project_tags_path(@project), data: { qa_selector: "tags_link" } do
= _('Tags')
= nav_link(path: 'graphs#show') do
......
......@@ -3,6 +3,6 @@
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-create wide',
dropdown_class: 'dropdown-menu-selectable capitalize-header',
data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes' }})
data: { field_name: 'protected_tag[create_access_levels_attributes][0][access_level]', input_id: 'create_access_levels_attributes', qa_selector: 'access_levels_dropdown' }})
= render 'projects/protected_tags/shared/create_protected_tag'
......@@ -25,4 +25,4 @@
= yield :create_access_levels
.card-footer
= f.submit 'Protect', class: 'btn-success btn', disabled: true
= f.submit 'Protect', class: 'btn-success btn', disabled: true, data: { qa_selector: 'protect_tag_button' }
......@@ -6,7 +6,7 @@
footer_content: true,
data: { show_no: true, show_any: true, show_upcoming: true,
selected: params[:protected_tag_name],
project_id: @project.try(:id) } }) do
project_id: @project.try(:id), qa_selector: 'tags_dropdown' } }) do
%ul.dropdown-footer-list
%li
......
- expanded = expanded_by_default?
%section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded) }
%section.settings.no-animate#js-protected-tags-settings{ class: ('expanded' if expanded), data: { qa_selector: 'protected_tag_settings_content' } }
.settings-header
%h4
Protected Tags
......
......@@ -24,7 +24,7 @@
%li
= link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value)
- if can?(current_user, :admin_tag, @project)
= link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn' do
= link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn', data: { qa_selector: "new_tag_button" } do
= s_('TagsPage|New tag')
= link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn d-none d-sm-inline-block has-tooltip' do
= icon("rss")
......
......@@ -14,7 +14,7 @@
.form-group.row
= label_tag :tag_name, nil, class: 'col-form-label col-sm-2'
.col-sm-10
= text_field_tag :tag_name, params[:tag_name], required: true, autofocus: true, class: 'form-control'
= text_field_tag :tag_name, params[:tag_name], required: true, autofocus: true, class: 'form-control', data: { qa_selector: "tag_name_field" }
.form-group.row
= label_tag :ref, 'Create from', class: 'col-form-label col-sm-2'
.col-sm-10.create-from
......@@ -29,7 +29,7 @@
.form-group.row
= label_tag :message, nil, class: 'col-form-label col-sm-2'
.col-sm-10
= text_area_tag :message, @message, required: false, class: 'form-control', rows: 5
= text_area_tag :message, @message, required: false, class: 'form-control', rows: 5, data: { qa_selector: "tag_message_field" }
.form-text.text-muted
= tag_description_help_text
%hr
......@@ -47,10 +47,10 @@
= s_('TagsPage|Optionally, create a public Release of your project, based on this tag. Release notes are displayed on the %{releases_page_link_start}Releases%{link_end} page. %{docs_link_start}More information%{link_end}').html_safe % replacements
= render layout: 'shared/md_preview', locals: { url: preview_markdown_path(@project), referenced_users: true } do
= render 'shared/zen', attr: :release_description, classes: 'note-textarea', placeholder: s_('TagsPage|Write your release notes or drag files here…'), current_text: @release_description
= render 'shared/zen', attr: :release_description, classes: 'note-textarea', placeholder: s_('TagsPage|Write your release notes or drag files here…'), current_text: @release_description, qa_selector: 'release_notes_field'
= render 'shared/notes/hints'
.form-actions
= button_tag s_('TagsPage|Create tag'), class: 'btn btn-success'
= button_tag s_('TagsPage|Create tag'), class: 'btn btn-success', data: { qa_selector: "create_tag_button" }
= link_to s_('TagsPage|Cancel'), project_tags_path(@project), class: 'btn btn-cancel'
-# haml-lint:disable InlineJavaScript
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
......@@ -9,7 +9,7 @@
.top-area.multi-line.flex-wrap
.nav-text
.title
%span.item-title.ref-name
%span.item-title.ref-name{ data: { qa_selector: 'tag_name_content' } }
= icon('tag')
= @tag.name
- if protected_tag?(@project, @tag)
......@@ -56,12 +56,12 @@
%i.fa.fa-trash-o
- if @tag.message.present?
%pre.wrap
%pre.wrap{ data: { qa_selector: 'tag_message_content' } }
= strip_signature(@tag.message)
.append-bottom-default.prepend-top-default
- if @release.description.present?
.description.md
.description.md{ data: { qa_selector: 'tag_release_notes_content' } }
= markdown_field(@release, :description)
- else
= s_('TagsPage|This tag has no release notes.')
......@@ -14,6 +14,6 @@
supports_autocomplete: supports_autocomplete,
qa_selector: qa_selector }
- else
= text_area_tag attr, current_text, class: classes, placeholder: placeholder
= text_area_tag attr, current_text, data: { qa_selector: qa_selector }, class: classes, placeholder: placeholder
%a.zen-control.zen-control-leave.js-zen-leave.gl-text-gray-700{ href: "#" }
= sprite_icon('compress', size: 16)
---
title: Enclose `release-cli` steps in an array
merge_request: 34913
author:
type: fixed
---
title: Fix confidential warning not showing the issuable type
merge_request: 34988
author:
type: fixed
......@@ -10,7 +10,9 @@ the previous version you were using.
First, roll back the code or package. For source installations this involves
checking out the older version (branch or tag). For Omnibus installations this
means installing the older `.deb` or `.rpm` package. Then, restore from a backup.
means installing the older
[`.deb` or `.rpm` package](https://packages.gitlab.com/gitlab). Then, restore from a
backup.
Follow the instructions in the
[Backup and Restore](../raketasks/backup_restore.md#restore-gitlab)
documentation.
......
......@@ -76,7 +76,7 @@ As soon as a requirement is reopened, it no longer appears in the **Archived** t
## Search for a requirement from the requirements list page
> - Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/212543) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
You can search for a requirement from the list of requirements using filtered search bar (similar to
that of Issues and Merge Requests) based on following parameters:
......@@ -96,7 +96,8 @@ You can also sort requirements list by:
## Allow requirements to be satisfied from a CI job
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2859) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2859) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.1.
> - [Added](https://gitlab.com/gitlab-org/gitlab/-/issues/215514) ability to specify individual requirements and their statuses in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.2.
GitLab supports [requirements test
reports](../../../ci/pipelines/job_artifacts.md#artifactsreportsrequirements-ultimate) now.
......@@ -132,6 +133,32 @@ the requirement report is checked for the "all passed" record
(`{"*":"passed"}`), and on success, it marks all existing open requirements as
Satisfied.
#### Specifying individual requirements
It is possible to specify individual requirements and their statuses.
If the following requirements exist:
- `REQ-1` (with IID `1`)
- `REQ-2` (with IID `2`)
- `REQ-3` (with IID `3`)
It is possible to specify that the first requirement passed, and the second failed.
Valid values are "passed" and "failed".
By omitting a requirement IID (in this case `REQ-3`'s IID `3`), no result is noted.
```yaml
requirements_confirmation:
when: manual
allow_failure: false
script:
- mkdir tmp
- echo "{\"1\":\"passed\", \"2\":\"failed\"}" > tmp/requirements.json
artifacts:
reports:
requirements: tmp/requirements.json
```
### Add the manual job to CI conditionally
To configure your CI to include the manual job only when there are some open
......
......@@ -16,7 +16,7 @@ module Gitlab
command = BASE_COMMAND.dup
config.each { |k, v| command.concat(" --#{k.to_s.dasherize} \"#{v}\"") }
command
[command]
end
end
end
......
......@@ -254,6 +254,12 @@ module QA
autoload :Show, 'qa/page/project/pipeline/show'
end
module Tag
autoload :Index, 'qa/page/project/tag/index'
autoload :New, 'qa/page/project/tag/new'
autoload :Show, 'qa/page/project/tag/show'
end
module Job
autoload :Show, 'qa/page/project/job/show'
end
......@@ -273,6 +279,7 @@ module QA
autoload :Runners, 'qa/page/project/settings/runners'
autoload :MergeRequest, 'qa/page/project/settings/merge_request'
autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories'
autoload :ProtectedTags, 'qa/page/project/settings/protected_tags'
autoload :VisibilityFeaturesPermissions, 'qa/page/project/settings/visibility_features_permissions'
module Services
......
......@@ -8,7 +8,7 @@ module QA
page.has_css?('.dropdown-input-field', wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
find('.dropdown-input-field').set(item)
click_link item
click_on item
end
end
end
......
# frozen_string_literal: true
module QA
module Page
module Project
module Settings
class ProtectedTags < Page::Base
include Page::Component::DropdownFilter
view 'app/views/projects/protected_tags/shared/_dropdown.html.haml' do
element :tags_dropdown
end
view 'app/views/projects/protected_tags/_create_protected_tag.html.haml' do
element :access_levels_dropdown
end
view 'app/views/projects/protected_tags/shared/_create_protected_tag.html.haml' do
element :protect_tag_button
end
def set_tag(tag_name)
click_element :tags_dropdown
filter_and_select(tag_name)
end
def choose_access_level_role(role)
click_element :access_levels_dropdown
click_on role
end
def click_protect_tag_button
click_element :protect_tag_button
end
end
end
end
end
end
QA::Page::Project::Settings::ProtectedTags.prepend_if_ee('QA::EE::Page::Project::Settings::ProtectedTags')
......@@ -23,6 +23,10 @@ module QA
element :deploy_keys_settings
end
view 'app/views/projects/protected_tags/shared/_index.html.haml' do
element :protected_tag_settings_content
end
def expand_deploy_tokens(&block)
expand_section(:deploy_tokens_settings) do
Settings::DeployTokens.perform(&block)
......@@ -46,6 +50,12 @@ module QA
MirroringRepositories.perform(&block)
end
end
def expand_protected_tags(&block)
expand_section(:protected_tag_settings_content) do
ProtectedTags.perform(&block)
end
end
end
end
end
......
......@@ -14,15 +14,16 @@ module QA
include QA::Page::Project::SubMenus::Common
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :project_menu_repo
element :repository_link
element :branches_link
element :tags_link
end
end
end
def click_repository
within_sidebar do
click_element(:project_menu_repo)
click_element(:repository_link)
end
end
......@@ -34,11 +35,19 @@ module QA
end
end
def go_to_repository_tags
hover_repository do
within_submenu do
click_element(:tags_link)
end
end
end
private
def hover_repository
within_sidebar do
find_element(:project_menu_repo).hover
find_element(:repository_link).hover
yield
end
......
# frozen_string_literal: true
module QA
module Page
module Project
module Tag
class Index < Page::Base
view 'app/views/projects/tags/index.html.haml' do
element :new_tag_button
end
def click_new_tag_button
click_element :new_tag_button
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module Page
module Project
module Tag
class New < Page::Base
view 'app/views/projects/tags/new.html.haml' do
element :tag_name_field
element :tag_message_field
element :release_notes_field
element :create_tag_button
end
view 'app/views/shared/_zen.html.haml' do
# This partial adds the `release_notes_field` selector passed from 'app/views/projects/tags/new.html.haml'
# The checks below ensure that required lines are not removed without updating this page object
element :_, "qa_selector = local_assigns.fetch(:qa_selector, '')" # rubocop:disable QA/ElementWithPattern
element :_, "text_area_tag attr, current_text, data: { qa_selector: qa_selector }" # rubocop:disable QA/ElementWithPattern
end
def fill_tag_name(text)
fill_element(:tag_name_field, text)
end
def fill_tag_message(text)
fill_element(:tag_message_field, text)
end
def fill_release_notes(text)
fill_element(:release_notes_field, text)
end
def click_create_tag_button
click_element :create_tag_button
end
end
end
end
end
end
# frozen_string_literal: true
module QA
module Page
module Project
module Tag
class Show < Page::Base
view 'app/views/projects/tags/show.html.haml' do
element :tag_name_content
element :tag_message_content
element :tag_release_notes_content
end
def has_tag_name?(text)
has_element?(:tag_name_content, text: text)
end
def has_tag_message?(text)
has_element?(:tag_message_content, text: text)
end
def has_tag_release_notes?(text)
has_element?(:tag_release_notes_content, text: text)
end
end
end
end
end
end
......@@ -96,7 +96,11 @@ module QA
end
def has_file?(file_path)
repository_tree.any? { |file| file[:path] == file_path }
response = repository_tree
raise ResourceNotFoundError, "#{response[:message]}" if response.is_a?(Hash) && response.has_key?(:message)
response.any? { |file| file[:path] == file_path }
end
def api_get_path
......
# frozen_string_literal: true
module QA
context 'Manage' do
describe 'Repository tags' do
let(:project) do
Resource::Project.fabricate_via_api! do |project|
project.name = 'project-for-tags'
project.initialize_with_readme = true
end
end
let(:developer_user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1) }
let(:maintainer_user) { Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_2, Runtime::Env.gitlab_qa_password_2) }
let(:tag_name) { 'v0.0.1' }
let(:tag_message) { 'Version 0.0.1' }
let(:tag_release_notes) { 'Release It!' }
shared_examples 'successful tag creation' do |user|
it "can be created by #{user}" do
Flow::Login.sign_in(as: send(user))
create_tag_for_project(project, tag_name, tag_message, tag_release_notes)
Page::Project::Tag::Show.perform do |show|
expect(show).to have_tag_name(tag_name)
expect(show).to have_tag_message(tag_message)
expect(show).to have_tag_release_notes(tag_release_notes)
expect(show).not_to have_element(:create_tag_button)
end
end
end
shared_examples 'unsuccessful tag creation' do |user|
it "cannot be created by an unauthorized #{user}" do
Flow::Login.sign_in(as: send(user))
create_tag_for_project(project, tag_name, tag_message, tag_release_notes)
Page::Project::Tag::New.perform do |new_tag|
expect(new_tag).to have_content('You are not allowed to create this tag as it is protected.')
expect(new_tag).to have_element(:create_tag_button)
end
end
end
context 'when not protected' do
before do
add_members_to_project(project)
end
it_behaves_like 'successful tag creation', :developer_user
it_behaves_like 'successful tag creation', :maintainer_user
end
context 'when protected' do
before do
add_members_to_project(project)
Flow::Login.sign_in
protect_tag_for_project(project, 'v*', 'Maintainers')
Page::Main::Menu.perform(&:sign_out)
end
it_behaves_like 'unsuccessful tag creation', :developer_user
it_behaves_like 'successful tag creation', :maintainer_user
end
def create_tag_for_project(project, name, message, release_notes)
project.visit!
Page::Project::Menu.perform(&:go_to_repository_tags)
Page::Project::Tag::Index.perform(&:click_new_tag_button)
Page::Project::Tag::New.perform do |new_tag|
new_tag.fill_tag_name(name)
new_tag.fill_tag_message(message)
new_tag.fill_release_notes(release_notes)
new_tag.click_create_tag_button
end
end
def protect_tag_for_project(project, tag, role)
project.visit!
Page::Project::Menu.perform(&:go_to_repository_settings)
Page::Project::Settings::Repository.perform do |setting|
setting.expand_protected_tags do |protected_tags|
protected_tags.set_tag(tag)
protected_tags.choose_access_level_role(role)
protected_tags.click_protect_tag_button
end
end
end
def add_members_to_project(project)
@developer_user = developer_user
@maintainer_user = maintainer_user
project.add_member(@developer_user, Resource::Members::AccessLevel::DEVELOPER)
project.add_member(@maintainer_user, Resource::Members::AccessLevel::MAINTAINER)
end
end
end
end
......@@ -546,7 +546,7 @@ describe('RepoEditor', () => {
store.state.viewer = viewerTypes.diff;
// we delay returning the file to make sure editor doesn't initialize before we fetch file content
await waitUsingRealTimer(10);
await waitUsingRealTimer(30);
return 'rawFileData123\n';
});
......@@ -555,8 +555,7 @@ describe('RepoEditor', () => {
vm.file = f;
// use the real timer to accurately simulate the race condition
await waitUsingRealTimer(20);
await waitForEditorSetup();
expect(vm.model.getModel().getValue()).toBe('rawFileData123\n');
});
......@@ -575,14 +574,13 @@ describe('RepoEditor', () => {
})
.mockImplementationOnce(async () => {
// we delay returning fileB content to make sure the editor doesn't initialize prematurely
await waitUsingRealTimer(10);
await waitUsingRealTimer(30);
return 'fileB-rawContent\n';
});
vm.file = fileA;
// use the real timer to accurately simulate the race condition
await waitUsingRealTimer(20);
await waitForEditorSetup();
expect(vm.model.getModel().getValue()).toBe('fileB-rawContent\n');
});
});
......
......@@ -60,9 +60,12 @@ describe('Issue Warning Component', () => {
});
});
it('renders information about confidential issue', () => {
it('renders information about confidential issue', async () => {
expect(findConfidentialBlock().exists()).toBe(true);
expect(findConfidentialBlock().element).toMatchSnapshot();
await wrapper.vm.$nextTick();
expect(findConfidentialBlock(wrapper).text()).toContain('This is a confidential issue.');
});
it('renders warning icon', () => {
......@@ -142,13 +145,13 @@ describe('Issue Warning Component', () => {
it('renders confidential & locked messages with noteable "epic"', async () => {
wrapperLocked.setProps({
noteableType: 'epic',
noteableType: 'Epic',
});
wrapperConfidential.setProps({
noteableType: 'epic',
noteableType: 'Epic',
});
wrapperLockedAndConfidential.setProps({
noteableType: 'epic',
noteableType: 'Epic',
});
await wrapperLocked.vm.$nextTick();
......@@ -167,13 +170,13 @@ describe('Issue Warning Component', () => {
it('renders confidential & locked messages with noteable "merge request"', async () => {
wrapperLocked.setProps({
noteableType: 'merge_request',
noteableType: 'MergeRequest',
});
wrapperConfidential.setProps({
noteableType: 'merge_request',
noteableType: 'MergeRequest',
});
wrapperLockedAndConfidential.setProps({
noteableType: 'merge_request',
noteableType: 'MergeRequest',
});
await wrapperLocked.vm.$nextTick();
......
......@@ -19,7 +19,7 @@ describe Gitlab::Ci::Build::Releaser do
end
it 'generates the script' do
expect(subject).to eq('release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA"')
expect(subject).to eq(['release-cli create --name "Release $CI_COMMIT_SHA" --description "Created using the release-cli $EXTRA_DESCRIPTION" --tag-name "release-$CI_COMMIT_SHA" --ref "$CI_COMMIT_SHA"'])
end
end
......@@ -43,7 +43,7 @@ describe Gitlab::Ci::Build::Releaser do
end
it 'generates the script' do
expect(subject).to eq(result)
expect(subject).to eq([result])
end
end
end
......
......@@ -62,7 +62,7 @@ describe Gitlab::Ci::Build::Step do
let(:job) { create(:ci_build, :release_options) }
it 'returns the release-cli command line' do
expect(subject.script).to eq("release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\"")
expect(subject.script).to eq(["release-cli create --name \"Release $CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\" --tag-name \"release-$CI_COMMIT_SHA\" --ref \"$CI_COMMIT_SHA\""])
end
end
......
......@@ -668,7 +668,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
{
"name" => "release",
"script" =>
"release-cli create --ref \"$CI_COMMIT_SHA\" --name \"Release $CI_COMMIT_SHA\" --tag-name \"release-$CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\"",
["release-cli create --ref \"$CI_COMMIT_SHA\" --name \"Release $CI_COMMIT_SHA\" --tag-name \"release-$CI_COMMIT_SHA\" --description \"Created using the release-cli $EXTRA_DESCRIPTION\""],
"timeout" => 3600,
"when" => "on_success",
"allow_failure" => false
......
......@@ -81,17 +81,6 @@ describe Spam::SpamVerdictService do
end
end
context 'and one is supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
allow(service).to receive(:spam_verdict).and_return(BLOCK_USER)
end
it 'renders the more restrictive verdict' do
expect(subject).to eq BLOCK_USER
end
end
context 'and none are supported' do
before do
allow(service).to receive(:akismet_verdict).and_return('nonsense')
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册