Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
42afc4d656
commit
1e28c9498f
|
@ -20,6 +20,8 @@ tasks:
|
|||
gdk config set gitlab.rails.port 443
|
||||
gdk config set gitlab.rails.https.enabled true
|
||||
gdk config set webpack.host 127.0.0.1
|
||||
gdk config set webpack.static false
|
||||
gdk config set webpack.live_reload false
|
||||
# make documentation builds available
|
||||
gdk config set gitlab_docs.enabled true
|
||||
# reconfigure GDK
|
||||
|
@ -49,6 +51,8 @@ tasks:
|
|||
gdk config set gitlab.rails.port 443
|
||||
gdk config set gitlab.rails.https.enabled true
|
||||
gdk config set webpack.host 127.0.0.1
|
||||
gdk config set webpack.static false
|
||||
gdk config set webpack.live_reload false
|
||||
# reconfigure GDK
|
||||
echo "$(date) – Reconfiguring GDK" | tee -a /workspace/startup.log
|
||||
gdk reconfigure
|
||||
|
|
|
@ -88,7 +88,7 @@ function renderMermaidEl(el, source) {
|
|||
const iframeEl = document.createElement('iframe');
|
||||
setAttributes(iframeEl, {
|
||||
src: getSandboxFrameSrc(),
|
||||
sandbox: 'allow-scripts',
|
||||
sandbox: 'allow-scripts allow-popups',
|
||||
frameBorder: 0,
|
||||
scrolling: 'no',
|
||||
width: '100%',
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { loadingIconForLegacyJS } from '~/loading_icon_for_legacy_js';
|
||||
import { FILTER_TYPE } from './constants';
|
||||
import DropdownUtils from './dropdown_utils';
|
||||
import FilteredSearchDropdownManager from './filtered_search_dropdown_manager';
|
||||
|
@ -13,7 +14,7 @@ export default class FilteredSearchDropdown {
|
|||
this.filter = filter;
|
||||
this.dropdown = dropdown;
|
||||
this.loadingTemplate = `<div class="filter-dropdown-loading">
|
||||
<span class="spinner"></span>
|
||||
${loadingIconForLegacyJS().outerHTML}
|
||||
</div>`;
|
||||
this.bindEvents();
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ export default {
|
|||
});
|
||||
},
|
||||
newIssueTypeText() {
|
||||
return sprintf(__('New %{issueType}'), { issueType: this.issueType });
|
||||
return sprintf(__('New related %{issueType}'), { issueType: this.issueType });
|
||||
},
|
||||
showToggleIssueStateButton() {
|
||||
const canClose = !this.isClosed && this.canUpdateIssue;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* globals LIVE_RELOAD */
|
||||
const div = document.createElement('div');
|
||||
|
||||
Object.assign(div.style, {
|
||||
|
@ -15,6 +16,10 @@ Object.assign(div.style, {
|
|||
'text-align': 'center',
|
||||
});
|
||||
|
||||
const reloadMessage = LIVE_RELOAD
|
||||
? 'You have live_reload enabled, the page will reload automatically when complete.'
|
||||
: 'You have live_reload disabled, the page will reload automatically in a few seconds.';
|
||||
|
||||
div.innerHTML = `
|
||||
<!-- https://github.com/webpack/media/blob/master/logo/icon-square-big.svg -->
|
||||
<svg height="50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1200">
|
||||
|
@ -30,9 +35,15 @@ div.innerHTML = `
|
|||
Learn more <a href="https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/main/doc/configuration.md#webpack-settings">here</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you have live_reload enabled, the page will reload automatically when complete.<br />
|
||||
Otherwise, please <a href="">reload the page manually in a few seconds</a>
|
||||
${reloadMessage}<br />
|
||||
If it doesn't, please <a href="">reload the page manually</a>.
|
||||
</p>
|
||||
`;
|
||||
|
||||
document.body.append(div);
|
||||
|
||||
if (!LIVE_RELOAD) {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
}
|
||||
|
|
|
@ -993,19 +993,6 @@ input {
|
|||
.top-nav-toggle .dropdown-icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.tanuki-logo .tanuki-left-ear,
|
||||
.tanuki-logo .tanuki-right-ear,
|
||||
.tanuki-logo .tanuki-nose {
|
||||
fill: #e24329;
|
||||
}
|
||||
.tanuki-logo .tanuki-left-eye,
|
||||
.tanuki-logo .tanuki-right-eye {
|
||||
fill: #fc6d26;
|
||||
}
|
||||
.tanuki-logo .tanuki-left-cheek,
|
||||
.tanuki-logo .tanuki-right-cheek {
|
||||
fill: #fca326;
|
||||
}
|
||||
.context-header {
|
||||
position: relative;
|
||||
margin-right: 2px;
|
||||
|
|
|
@ -978,19 +978,6 @@ input {
|
|||
.top-nav-toggle .dropdown-icon {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.tanuki-logo .tanuki-left-ear,
|
||||
.tanuki-logo .tanuki-right-ear,
|
||||
.tanuki-logo .tanuki-nose {
|
||||
fill: #e24329;
|
||||
}
|
||||
.tanuki-logo .tanuki-left-eye,
|
||||
.tanuki-logo .tanuki-right-eye {
|
||||
fill: #fc6d26;
|
||||
}
|
||||
.tanuki-logo .tanuki-left-cheek,
|
||||
.tanuki-logo .tanuki-right-cheek {
|
||||
fill: #fca326;
|
||||
}
|
||||
.context-header {
|
||||
position: relative;
|
||||
margin-right: 2px;
|
||||
|
|
|
@ -514,19 +514,6 @@ label.label-bold {
|
|||
.navbar-empty .brand-header-logo {
|
||||
max-height: 100%;
|
||||
}
|
||||
.tanuki-logo .tanuki-left-ear,
|
||||
.tanuki-logo .tanuki-right-ear,
|
||||
.tanuki-logo .tanuki-nose {
|
||||
fill: #e24329;
|
||||
}
|
||||
.tanuki-logo .tanuki-left-eye,
|
||||
.tanuki-logo .tanuki-right-eye {
|
||||
fill: #fc6d26;
|
||||
}
|
||||
.tanuki-logo .tanuki-left-cheek,
|
||||
.tanuki-logo .tanuki-right-cheek {
|
||||
fill: #fca326;
|
||||
}
|
||||
input::-moz-placeholder {
|
||||
color: #868686;
|
||||
opacity: 1;
|
||||
|
|
|
@ -106,6 +106,8 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
@issue = @noteable = service.execute
|
||||
|
||||
@add_related_issue = add_related_issue
|
||||
|
||||
@merge_request_to_resolve_discussions_of = service.merge_request_to_resolve_discussions_of
|
||||
|
||||
if params[:discussion_to_resolve]
|
||||
|
@ -122,6 +124,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
|
||||
def create
|
||||
create_params = issue_params.merge(
|
||||
add_related_issue: add_related_issue,
|
||||
merge_request_to_resolve_discussions_of: params[:merge_request_to_resolve_discussions_of],
|
||||
discussion_to_resolve: params[:discussion_to_resolve]
|
||||
)
|
||||
|
@ -377,6 +380,11 @@ class Projects::IssuesController < Projects::ApplicationController
|
|||
action_name == 'service_desk'
|
||||
end
|
||||
|
||||
def add_related_issue
|
||||
add_related_issue = project.issues.find_by_iid(params[:add_related_issue])
|
||||
add_related_issue if Ability.allowed?(current_user, :read_issue, add_related_issue)
|
||||
end
|
||||
|
||||
# Overridden in EE
|
||||
def create_vulnerability_issue_feedback(issue); end
|
||||
end
|
||||
|
|
|
@ -38,6 +38,8 @@ module AppearancesHelper
|
|||
def brand_header_logo
|
||||
if current_appearance&.header_logo?
|
||||
image_tag current_appearance.header_logo_path, class: 'brand-header-logo'
|
||||
elsif Feature.enabled?(:ukraine_support_tanuki)
|
||||
render partial: 'shared/logo_ukraine', formats: :svg
|
||||
else
|
||||
render partial: 'shared/logo', formats: :svg
|
||||
end
|
||||
|
|
|
@ -169,7 +169,7 @@ module IssuesHelper
|
|||
end
|
||||
|
||||
def issue_header_actions_data(project, issuable, current_user)
|
||||
new_issuable_params = { issue: { description: _('Related to #%{issue_id}.') % { issue_id: issuable.iid } + "\n\n" } }
|
||||
new_issuable_params = { issue: {}, add_related_issue: issuable.iid }
|
||||
if issuable.incident?
|
||||
new_issuable_params[:issuable_template] = 'incident'
|
||||
new_issuable_params[:issue][:issue_type] = 'incident'
|
||||
|
|
|
@ -135,6 +135,7 @@ class User < ApplicationRecord
|
|||
has_many :u2f_registrations, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :webauthn_registrations
|
||||
has_many :chat_names, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
|
||||
has_many :saved_replies, class_name: '::Users::SavedReply'
|
||||
has_one :user_synced_attributes_metadata, autosave: true
|
||||
has_one :aws_role, class_name: 'Aws::Role'
|
||||
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class SavedReply < ApplicationRecord
|
||||
self.table_name = 'saved_replies'
|
||||
|
||||
belongs_to :user
|
||||
|
||||
validates :user_id, :name, :content, presence: true
|
||||
validates :name,
|
||||
length: { maximum: 255 },
|
||||
uniqueness: { scope: [:user_id] },
|
||||
format: {
|
||||
with: Gitlab::Regex.saved_reply_name_regex,
|
||||
message: Gitlab::Regex.saved_reply_name_regex_message
|
||||
}
|
||||
validates :content, length: { maximum: 10000 }
|
||||
end
|
||||
end
|
|
@ -23,6 +23,7 @@ module Issues
|
|||
|
||||
handle_move_between_ids(@issue)
|
||||
|
||||
@add_related_issue ||= params.delete(:add_related_issue)
|
||||
filter_resolve_discussion_params
|
||||
|
||||
create(@issue, skip_system_notes: skip_system_notes)
|
||||
|
@ -52,6 +53,7 @@ module Issues
|
|||
# Add new items to Issues::AfterCreateService if they can be performed in Sidekiq
|
||||
def after_create(issue)
|
||||
user_agent_detail_service.create
|
||||
handle_add_related_issue(issue)
|
||||
resolve_discussions_with_issue(issue)
|
||||
create_escalation_status(issue)
|
||||
|
||||
|
@ -91,6 +93,12 @@ module Issues
|
|||
def user_agent_detail_service
|
||||
UserAgentDetailService.new(spammable: @issue, spam_params: spam_params)
|
||||
end
|
||||
|
||||
def handle_add_related_issue(issue)
|
||||
return unless @add_related_issue
|
||||
|
||||
IssueLinks::CreateService.new(issue, issue.author, { target_issuable: @add_related_issue }).execute
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<svg width="24" height="24" class="tanuki-logo" viewBox="0 0 24 24">
|
||||
<path d="M4.89929534,0.3165 L7.56629534,8.5025 L16.3922953,8.5025 L19.0592953,0.3165 C19.1962953,-0.1055 19.8432953,-0.1055 19.9792953,0.3165 L23.9122953,12.6095 C23.9722953,12.7935 23.9722953,12.9895 23.9192953,13.1695 L0.0392953418,13.1695 C-0.0143874393,12.9863283 -0.0119492421,12.7912726 0.0462953418,12.6095 L3.97929534,0.3165 C4.11529534,-0.1055 4.76229534,-0.1055 4.89929534,0.3165 Z" id="Path" fill="#005BBB"></path>
|
||||
<path d="M7.20329534,9.0025 L16.7552953,9.0025 L16.8682953,8.6575 L19.5182953,0.5185 L23.4362953,12.7615 C23.4961172,12.9376949 23.435535,13.1323657 23.2862953,13.2435 L23.2852953,13.2455 L11.9852953,21.4655 L11.9792953,21.4715 L0.673295342,13.2455 C0.522422013,13.1321007 0.462258936,12.9374792 0.522295342,12.7615 L4.43929534,0.5185 L7.09029534,8.6585 L7.20329534,9.0025 Z" id="Shape" stroke="#FFFFFF" opacity="0.32" stroke-linejoin="round"></path>
|
||||
<path d="M0.0012953418,12.8575 C-0.0152229638,13.1685309 0.127095079,13.4667211 0.379295342,13.6495 L11.9792953,22.0895 L11.9862953,22.0845 L11.9922953,22.0895 L11.9872953,22.0835 L23.5792953,13.6495 C23.8319507,13.466647 23.9743476,13.1679148 23.9572953,12.8565 L0.0012953418,12.8565 L0.0012953418,12.8575 Z" id="Path" fill="#FFD500"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
|
@ -4,6 +4,16 @@
|
|||
- has_due_date = issuable.has_attribute?(:due_date)
|
||||
- form = local_assigns.fetch(:form)
|
||||
|
||||
- if @add_related_issue
|
||||
.form-group.row
|
||||
.offset-sm-2.col-sm-10
|
||||
.form-check
|
||||
= check_box_tag :add_related_issue, @add_related_issue.iid, true, class: 'form-check-input'
|
||||
= label_tag :add_related_issue, class: 'form-check-label' do
|
||||
- add_related_issue_link = link_to "\##{@add_related_issue.iid}", issue_path(@add_related_issue), class: ['has-tooltip'], title: @add_related_issue.title
|
||||
#{_('Relate to %{issuable_type} %{add_related_issue_link}').html_safe % { issuable_type: @add_related_issue.issue_type, add_related_issue_link: add_related_issue_link }}
|
||||
%p.text-muted= _('Adds this %{issuable_type} as related to the %{issuable_type} it was created from') % { issuable_type: @add_related_issue.issue_type }
|
||||
|
||||
- if issuable.respond_to?(:confidential) && can?(current_user, :set_confidentiality, issuable)
|
||||
.form-group.row
|
||||
.offset-sm-2.col-sm-10
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: ukraine_support_tanuki
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/82050
|
||||
rollout_issue_url:
|
||||
milestone: '14.9'
|
||||
type: development
|
||||
group: group::editor
|
||||
default_enabled: false
|
|
@ -102,7 +102,7 @@ class IncrementalWebpackCompiler {
|
|||
|
||||
setTimeout(() => {
|
||||
devServer.invalidate(() => {
|
||||
if (devServer.sockets) {
|
||||
if (Array.isArray(devServer.webSocketServer && devServer.webSocketServer.clients)) {
|
||||
devServer.sendMessage(devServer.webSocketServer.clients, 'static-changed');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -677,6 +677,7 @@ module.exports = {
|
|||
IS_JH: IS_JH ? 'window.gon && window.gon.jh' : JSON.stringify(false),
|
||||
// This is used by Sourcegraph because these assets are loaded dnamically
|
||||
'process.env.SOURCEGRAPH_PUBLIC_PATH': JSON.stringify(SOURCEGRAPH_PUBLIC_PATH),
|
||||
...(IS_PRODUCTION ? {} : { LIVE_RELOAD: DEV_SERVER_LIVERELOAD }),
|
||||
}),
|
||||
|
||||
/* Pikaday has a optional dependency to moment.
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class CreateSavedReplies < Gitlab::Database::Migration[1.0]
|
||||
enable_lock_retries!
|
||||
|
||||
def up
|
||||
create_table :saved_replies do |t|
|
||||
t.references :user, index: false, null: false, foreign_key: { on_delete: :cascade }
|
||||
t.timestamps_with_timezone null: false
|
||||
t.text :name, null: false, limit: 255
|
||||
t.text :content, null: false, limit: 10000
|
||||
|
||||
t.index [:user_id, :name], name: 'index_saved_replies_on_name_text_pattern_ops', unique: true, opclass: { name: :text_pattern_ops }
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
drop_table :saved_replies, if_exists: true
|
||||
end
|
||||
end
|
|
@ -0,0 +1 @@
|
|||
5931c4981c89d65c5aaca05dc8375c2c21bb595e28354d6623986d906ece165d
|
|
@ -20171,6 +20171,26 @@ CREATE SEQUENCE saml_providers_id_seq
|
|||
|
||||
ALTER SEQUENCE saml_providers_id_seq OWNED BY saml_providers.id;
|
||||
|
||||
CREATE TABLE saved_replies (
|
||||
id bigint NOT NULL,
|
||||
user_id bigint NOT NULL,
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
name text NOT NULL,
|
||||
content text NOT NULL,
|
||||
CONSTRAINT check_0cb57dc22a CHECK ((char_length(content) <= 10000)),
|
||||
CONSTRAINT check_2eb3366d7f CHECK ((char_length(name) <= 255))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE saved_replies_id_seq
|
||||
START WITH 1
|
||||
INCREMENT BY 1
|
||||
NO MINVALUE
|
||||
NO MAXVALUE
|
||||
CACHE 1;
|
||||
|
||||
ALTER SEQUENCE saved_replies_id_seq OWNED BY saved_replies.id;
|
||||
|
||||
CREATE TABLE schema_migrations (
|
||||
version character varying NOT NULL,
|
||||
finished_at timestamp with time zone DEFAULT now()
|
||||
|
@ -22966,6 +22986,8 @@ ALTER TABLE ONLY saml_group_links ALTER COLUMN id SET DEFAULT nextval('saml_grou
|
|||
|
||||
ALTER TABLE ONLY saml_providers ALTER COLUMN id SET DEFAULT nextval('saml_providers_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY saved_replies ALTER COLUMN id SET DEFAULT nextval('saved_replies_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY scim_identities ALTER COLUMN id SET DEFAULT nextval('scim_identities_id_seq'::regclass);
|
||||
|
||||
ALTER TABLE ONLY scim_oauth_access_tokens ALTER COLUMN id SET DEFAULT nextval('scim_oauth_access_tokens_id_seq'::regclass);
|
||||
|
@ -25112,6 +25134,9 @@ ALTER TABLE ONLY saml_group_links
|
|||
ALTER TABLE ONLY saml_providers
|
||||
ADD CONSTRAINT saml_providers_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY saved_replies
|
||||
ADD CONSTRAINT saved_replies_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY schema_migrations
|
||||
ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version);
|
||||
|
||||
|
@ -28894,6 +28919,8 @@ CREATE UNIQUE INDEX index_saml_group_links_on_group_id_and_saml_group_name ON sa
|
|||
|
||||
CREATE INDEX index_saml_providers_on_group_id ON saml_providers USING btree (group_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_saved_replies_on_name_text_pattern_ops ON saved_replies USING btree (user_id, name text_pattern_ops);
|
||||
|
||||
CREATE INDEX index_scim_identities_on_group_id ON scim_identities USING btree (group_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_scim_identities_on_lower_extern_uid_and_group_id ON scim_identities USING btree (lower((extern_uid)::text), group_id);
|
||||
|
@ -32789,6 +32816,9 @@ ALTER TABLE ONLY resource_milestone_events
|
|||
ALTER TABLE ONLY term_agreements
|
||||
ADD CONSTRAINT fk_rails_a88721bcdf FOREIGN KEY (term_id) REFERENCES application_setting_terms(id);
|
||||
|
||||
ALTER TABLE ONLY saved_replies
|
||||
ADD CONSTRAINT fk_rails_a8bf5bf111 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY ci_pipeline_artifacts
|
||||
ADD CONSTRAINT fk_rails_a9e811a466 FOREIGN KEY (pipeline_id) REFERENCES ci_pipelines(id) ON DELETE CASCADE;
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ You can create an issue in many ways in GitLab:
|
|||
|
||||
- [From a project](#from-a-project)
|
||||
- [From a group](#from-a-group)
|
||||
- [From another issue](#from-another-issue)
|
||||
- [From another issue or incident](#from-another-issue-or-incident)
|
||||
- [From an issue board](#from-an-issue-board)
|
||||
- [By sending an email](#by-sending-an-email)
|
||||
- [Using a URL with prefilled values](#using-a-url-with-prefilled-values)
|
||||
|
@ -70,9 +70,10 @@ The newly created issue opens.
|
|||
The project you selected most recently becomes the default for your next visit.
|
||||
This can save you a lot of time and clicks, if you mostly create issues for the same project.
|
||||
|
||||
### From another issue
|
||||
### From another issue or incident
|
||||
|
||||
> New issue becoming linked to the issue of origin [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68226) in GitLab 14.3.
|
||||
> - New issue becoming linked to the issue of origin [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68226) in GitLab 14.3.
|
||||
> - **Relate to…** checkbox [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198494) in GitLab 14.9.
|
||||
|
||||
You can create a new issue from an existing one. The two issues can then be marked as related.
|
||||
|
||||
|
@ -83,10 +84,10 @@ Prerequisites:
|
|||
To create an issue from another issue:
|
||||
|
||||
1. In an existing issue, select the vertical ellipsis (**{ellipsis_v}**).
|
||||
1. Select **New issue**.
|
||||
1. Select **New related issue**.
|
||||
1. Complete the [fields](#fields-in-the-new-issue-form).
|
||||
The new issue's description is prefilled with `Related to #123`, where `123` is the ID of the
|
||||
issue of origin. If you keep this mention in the description, the two issues become
|
||||
The new issue form has a **Relate to issue #123** checkbox, where `123` is the ID of the
|
||||
issue of origin. If you keep this checkbox checked, the two issues become
|
||||
[linked](related_issues.md).
|
||||
1. Select **Create issue**.
|
||||
|
||||
|
@ -160,7 +161,8 @@ To regenerate the email address:
|
|||
|
||||
### Using a URL with prefilled values
|
||||
|
||||
> Ability to use both `issuable_template` and `issue[description]` in the same URL [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80554) in GitLab 14.9.
|
||||
> - Ability to use both `issuable_template` and `issue[description]` in the same URL [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80554) in GitLab 14.9.
|
||||
> - Ability to specify `add_related_issue` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198494) in GitLab 14.9.
|
||||
|
||||
To link directly to the new issue page with prefilled fields, use query
|
||||
string parameters in a URL. You can embed a URL in an external
|
||||
|
@ -173,6 +175,7 @@ HTML page to create issues with certain fields prefilled.
|
|||
| Description template | `issuable_template` | Must be [URL-encoded](../../../api/index.md#namespaced-path-encoding). |
|
||||
| Description | `issue[description]` | Must be [URL-encoded](../../../api/index.md#namespaced-path-encoding). If used in combination with `issuable_template` or a [default issue template](../description_templates.md#set-a-default-template-for-merge-requests-and-issues), the `issue[description]` value is appended to the template. |
|
||||
| Confidential | `issue[confidential]` | If `true`, the issue is marked as confidential. |
|
||||
| Relate to… | `add_related_issue` | A numeric issue ID. If present, the issue form shows a [**Relate to…** checkbox](#from-another-issue-or-incident) to optionally link the new issue to the specified existing issue. |
|
||||
|
||||
Adapt these examples to form your new issue URL with prefilled fields.
|
||||
To create an issue in the GitLab project:
|
||||
|
|
|
@ -460,6 +460,7 @@ reviews: :gitlab_main
|
|||
routes: :gitlab_main
|
||||
saml_group_links: :gitlab_main
|
||||
saml_providers: :gitlab_main
|
||||
saved_replies: :gitlab_main
|
||||
schema_migrations: :gitlab_shared
|
||||
scim_identities: :gitlab_main
|
||||
scim_oauth_access_tokens: :gitlab_main
|
||||
|
|
|
@ -459,6 +459,15 @@ module Gitlab
|
|||
"can contain only lowercase letters, digits, '_' and '-'. " \
|
||||
"Must start with a letter, and cannot end with '-' or '_'"
|
||||
end
|
||||
|
||||
def saved_reply_name_regex
|
||||
@saved_reply_name_regex ||= /\A[a-z]([a-z0-9\-_]*[a-z0-9])?\z/.freeze
|
||||
end
|
||||
|
||||
def saved_reply_name_regex_message
|
||||
"can contain only lowercase letters, digits, '_' and '-'. " \
|
||||
"Must start with a letter, and cannot end with '-' or '_'"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -2394,6 +2394,9 @@ msgstr ""
|
|||
msgid "Adds email participant(s)."
|
||||
msgstr ""
|
||||
|
||||
msgid "Adds this %{issuable_type} as related to the %{issuable_type} it was created from"
|
||||
msgstr ""
|
||||
|
||||
msgid "Adjust how frequently the GitLab UI polls for updates."
|
||||
msgstr ""
|
||||
|
||||
|
@ -24337,9 +24340,6 @@ msgstr ""
|
|||
msgid "New"
|
||||
msgstr ""
|
||||
|
||||
msgid "New %{issueType}"
|
||||
msgstr ""
|
||||
|
||||
msgid "New %{type} in %{project}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -24507,6 +24507,9 @@ msgstr ""
|
|||
msgid "New public deploy key"
|
||||
msgstr ""
|
||||
|
||||
msgid "New related %{issueType}"
|
||||
msgstr ""
|
||||
|
||||
msgid "New release"
|
||||
msgstr ""
|
||||
|
||||
|
@ -30313,6 +30316,9 @@ msgstr ""
|
|||
msgid "Rejected (closed)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Relate to %{issuable_type} %{add_related_issue_link}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Related feature flags"
|
||||
msgstr ""
|
||||
|
||||
|
@ -30322,9 +30328,6 @@ msgstr ""
|
|||
msgid "Related merge requests"
|
||||
msgstr ""
|
||||
|
||||
msgid "Related to #%{issue_id}."
|
||||
msgstr ""
|
||||
|
||||
msgid "Relates to"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
FactoryBot.define do
|
||||
factory :saved_reply, class: 'Users::SavedReply' do
|
||||
sequence(:name) { |n| "saved_reply_#{n}" }
|
||||
content { 'Saved Reply Content' }
|
||||
|
||||
user
|
||||
end
|
||||
end
|
|
@ -26,7 +26,7 @@ RSpec.describe "User views incident" do
|
|||
it 'shows the merge request and incident actions', :js, :aggregate_failures do
|
||||
click_button 'Incident actions'
|
||||
|
||||
expect(page).to have_link('New incident', href: new_project_issue_path(project, { issuable_template: 'incident', issue: { issue_type: 'incident', description: "Related to \##{incident.iid}.\n\n" } }))
|
||||
expect(page).to have_link('New related incident', href: new_project_issue_path(project, { issuable_template: 'incident', issue: { issue_type: 'incident' }, add_related_issue: incident.iid }))
|
||||
expect(page).to have_button('Create merge request')
|
||||
expect(page).to have_button('Close incident')
|
||||
end
|
||||
|
|
|
@ -8,16 +8,19 @@ RSpec.describe 'New/edit issue', :js do
|
|||
let_it_be(:project) { create(:project, :repository) }
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be(:user2) { create(:user) }
|
||||
let_it_be(:guest) { create(:user) }
|
||||
let_it_be(:milestone) { create(:milestone, project: project) }
|
||||
let_it_be(:label) { create(:label, project: project) }
|
||||
let_it_be(:label2) { create(:label, project: project) }
|
||||
let_it_be(:issue) { create(:issue, project: project, assignees: [user], milestone: milestone) }
|
||||
let_it_be(:confidential_issue) { create(:issue, project: project, assignees: [user], milestone: milestone, confidential: true) }
|
||||
|
||||
let(:current_user) { user }
|
||||
|
||||
before_all do
|
||||
project.add_maintainer(user)
|
||||
project.add_maintainer(user2)
|
||||
project.add_guest(guest)
|
||||
end
|
||||
|
||||
before do
|
||||
|
@ -357,6 +360,61 @@ RSpec.describe 'New/edit issue', :js do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'new issue from related issue' do
|
||||
it 'does not offer to link the new issue to any other issues if the URL parameter is absent' do
|
||||
visit new_project_issue_path(project)
|
||||
expect(page).not_to have_selector '#add_related_issue'
|
||||
expect(page).not_to have_text "Relate to"
|
||||
end
|
||||
|
||||
context 'guest' do
|
||||
let(:current_user) { guest }
|
||||
|
||||
it 'does not offer to link the new issue to an issue that the user does not have access to' do
|
||||
visit new_project_issue_path(project, { add_related_issue: confidential_issue.iid })
|
||||
expect(page).not_to have_selector '#add_related_issue'
|
||||
expect(page).not_to have_text "Relate to"
|
||||
end
|
||||
end
|
||||
|
||||
it 'links the new issue and the issue of origin' do
|
||||
visit new_project_issue_path(project, { add_related_issue: issue.iid })
|
||||
expect(page).to have_selector '#add_related_issue'
|
||||
expect(page).to have_text "Relate to issue \##{issue.iid}"
|
||||
expect(page).to have_text 'Adds this issue as related to the issue it was created from'
|
||||
fill_in 'issue_title', with: 'title'
|
||||
click_button 'Create issue'
|
||||
page.within '#related-issues' do
|
||||
expect(page).to have_text "\##{issue.iid}"
|
||||
end
|
||||
end
|
||||
|
||||
it 'links the new incident and the incident of origin' do
|
||||
incident = create(:incident, project: project)
|
||||
visit new_project_issue_path(project, { add_related_issue: incident.iid })
|
||||
expect(page).to have_selector '#add_related_issue'
|
||||
expect(page).to have_text "Relate to incident \##{incident.iid}"
|
||||
expect(page).to have_text 'Adds this incident as related to the incident it was created from'
|
||||
fill_in 'issue_title', with: 'title'
|
||||
click_button 'Create issue'
|
||||
page.within '#related-issues' do
|
||||
expect(page).to have_text "\##{incident.iid}"
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not link the new issue to any other issues if the checkbox is not checked' do
|
||||
visit new_project_issue_path(project, { add_related_issue: issue.iid })
|
||||
expect(page).to have_selector '#add_related_issue'
|
||||
expect(page).to have_text "Relate to issue \##{issue.iid}"
|
||||
uncheck "Relate to issue \##{issue.iid}"
|
||||
fill_in 'issue_title', with: 'title'
|
||||
click_button 'Create issue'
|
||||
page.within '#related-issues' do
|
||||
expect(page).not_to have_text "\##{issue.iid}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'edit issue' do
|
||||
before do
|
||||
visit edit_project_issue_path(project, issue)
|
||||
|
|
|
@ -25,8 +25,8 @@ RSpec.describe 'issue header', :js do
|
|||
click_button 'Issue actions'
|
||||
end
|
||||
|
||||
it 'shows the "New issue", "Report abuse", and "Delete issue" items', :aggregate_failures do
|
||||
expect(page).to have_link 'New issue'
|
||||
it 'shows the "New related issue", "Report abuse", and "Delete issue" items', :aggregate_failures do
|
||||
expect(page).to have_link 'New related issue'
|
||||
expect(page).to have_link 'Report abuse'
|
||||
expect(page).to have_button 'Delete issue'
|
||||
expect(page).not_to have_link 'Submit as spam'
|
||||
|
@ -114,8 +114,8 @@ RSpec.describe 'issue header', :js do
|
|||
click_button 'Issue actions'
|
||||
end
|
||||
|
||||
it 'only shows the "New issue" and "Report abuse" items', :aggregate_failures do
|
||||
expect(page).to have_link 'New issue'
|
||||
it 'only shows the "New related issue" and "Report abuse" items', :aggregate_failures do
|
||||
expect(page).to have_link 'New related issue'
|
||||
expect(page).to have_link 'Report abuse'
|
||||
expect(page).not_to have_link 'Submit as spam'
|
||||
expect(page).not_to have_button 'Delete issue'
|
||||
|
|
|
@ -25,7 +25,7 @@ RSpec.describe "User views issue" do
|
|||
it 'shows the merge request and issue actions', :js, :aggregate_failures do
|
||||
click_button 'Issue actions'
|
||||
|
||||
expect(page).to have_link('New issue', href: new_project_issue_path(project, { issue: { description: "Related to \##{issue.iid}.\n\n" } }))
|
||||
expect(page).to have_link('New related issue', href: new_project_issue_path(project, { add_related_issue: issue.iid }))
|
||||
expect(page).to have_button('Create merge request')
|
||||
expect(page).to have_button('Close issue')
|
||||
end
|
||||
|
|
|
@ -26,7 +26,7 @@ RSpec.describe 'Sandboxed Mermaid rendering', :js do
|
|||
|
||||
wait_for_requests
|
||||
|
||||
expected = %(<iframe src="/-/sandbox/mermaid" sandbox="allow-scripts" frameborder="0" scrolling="no")
|
||||
expected = %(<iframe src="/-/sandbox/mermaid" sandbox="allow-scripts allow-popups" frameborder="0" scrolling="no")
|
||||
expect(page.html).to include(expected)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -171,19 +171,19 @@ describe('HeaderActions component', () => {
|
|||
${'desktop dropdown'} | ${false} | ${findDesktopDropdownItems} | ${findDesktopDropdown}
|
||||
`('$description', ({ isCloseIssueItemVisible, findDropdownItems, findDropdown }) => {
|
||||
describe.each`
|
||||
description | itemText | isItemVisible | canUpdateIssue | canCreateIssue | isIssueAuthor | canReportSpam | canPromoteToEpic | canDestroyIssue
|
||||
${`when user can update ${issueType}`} | ${`Close ${issueType}`} | ${isCloseIssueItemVisible} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${`when user cannot update ${issueType}`} | ${`Close ${issueType}`} | ${false} | ${false} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${`when user can create ${issueType}`} | ${`New ${issueType}`} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${`when user cannot create ${issueType}`} | ${`New ${issueType}`} | ${false} | ${true} | ${false} | ${true} | ${true} | ${true} | ${true}
|
||||
${'when user can promote to epic'} | ${'Promote to epic'} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${'when user cannot promote to epic'} | ${'Promote to epic'} | ${false} | ${true} | ${true} | ${true} | ${true} | ${false} | ${true}
|
||||
${'when user can report abuse'} | ${'Report abuse'} | ${true} | ${true} | ${true} | ${false} | ${true} | ${true} | ${true}
|
||||
${'when user cannot report abuse'} | ${'Report abuse'} | ${false} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${'when user can submit as spam'} | ${'Submit as spam'} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${'when user cannot submit as spam'} | ${'Submit as spam'} | ${false} | ${true} | ${true} | ${true} | ${false} | ${true} | ${true}
|
||||
${`when user can delete ${issueType}`} | ${`Delete ${issueType}`} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${`when user cannot delete ${issueType}`} | ${`Delete ${issueType}`} | ${false} | ${true} | ${true} | ${true} | ${true} | ${true} | ${false}
|
||||
description | itemText | isItemVisible | canUpdateIssue | canCreateIssue | isIssueAuthor | canReportSpam | canPromoteToEpic | canDestroyIssue
|
||||
${`when user can update ${issueType}`} | ${`Close ${issueType}`} | ${isCloseIssueItemVisible} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${`when user cannot update ${issueType}`} | ${`Close ${issueType}`} | ${false} | ${false} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${`when user can create ${issueType}`} | ${`New related ${issueType}`} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${`when user cannot create ${issueType}`} | ${`New related ${issueType}`} | ${false} | ${true} | ${false} | ${true} | ${true} | ${true} | ${true}
|
||||
${'when user can promote to epic'} | ${'Promote to epic'} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${'when user cannot promote to epic'} | ${'Promote to epic'} | ${false} | ${true} | ${true} | ${true} | ${true} | ${false} | ${true}
|
||||
${'when user can report abuse'} | ${'Report abuse'} | ${true} | ${true} | ${true} | ${false} | ${true} | ${true} | ${true}
|
||||
${'when user cannot report abuse'} | ${'Report abuse'} | ${false} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${'when user can submit as spam'} | ${'Submit as spam'} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${'when user cannot submit as spam'} | ${'Submit as spam'} | ${false} | ${true} | ${true} | ${true} | ${false} | ${true} | ${true}
|
||||
${`when user can delete ${issueType}`} | ${`Delete ${issueType}`} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true} | ${true}
|
||||
${`when user cannot delete ${issueType}`} | ${`Delete ${issueType}`} | ${false} | ${true} | ${true} | ${true} | ${true} | ${true} | ${false}
|
||||
`(
|
||||
'$description',
|
||||
({
|
||||
|
|
|
@ -265,7 +265,7 @@ RSpec.describe IssuesHelper do
|
|||
is_issue_author: 'false',
|
||||
issue_path: issue_path(issue),
|
||||
issue_type: 'issue',
|
||||
new_issue_path: new_project_issue_path(project, { issue: { description: "Related to \##{issue.iid}.\n\n" } }),
|
||||
new_issue_path: new_project_issue_path(project, { add_related_issue: issue.iid }),
|
||||
project_path: project.full_path,
|
||||
report_abuse_path: new_abuse_report_path(user_id: issue.author.id, ref_url: issue_url(issue)),
|
||||
submit_as_spam_path: mark_as_spam_project_issue_path(project, issue)
|
||||
|
|
|
@ -990,4 +990,19 @@ RSpec.describe Gitlab::Regex do
|
|||
it { is_expected.not_to match('../../../../../1.2.3') }
|
||||
it { is_expected.not_to match('%2e%2e%2f1.2.3') }
|
||||
end
|
||||
|
||||
describe '.saved_reply_name_regex' do
|
||||
subject { described_class.saved_reply_name_regex }
|
||||
|
||||
it { is_expected.to match('test') }
|
||||
it { is_expected.to match('test123') }
|
||||
it { is_expected.to match('test-test') }
|
||||
it { is_expected.to match('test-test_0123') }
|
||||
it { is_expected.not_to match('test test') }
|
||||
it { is_expected.not_to match('test-') }
|
||||
it { is_expected.not_to match('/z/test_') }
|
||||
it { is_expected.not_to match('.xtest_') }
|
||||
it { is_expected.not_to match('.xt.est_') }
|
||||
it { is_expected.not_to match('0test1') }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -116,6 +116,7 @@ RSpec.describe User do
|
|||
it { is_expected.to have_many(:builds) }
|
||||
it { is_expected.to have_many(:pipelines) }
|
||||
it { is_expected.to have_many(:chat_names).dependent(:destroy) }
|
||||
it { is_expected.to have_many(:saved_replies).class_name('::Users::SavedReply') }
|
||||
it { is_expected.to have_many(:uploads) }
|
||||
it { is_expected.to have_many(:reported_abuse_reports).dependent(:destroy).class_name('AbuseReport') }
|
||||
it { is_expected.to have_many(:custom_attributes).class_name('UserCustomAttribute') }
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Users::SavedReply do
|
||||
let_it_be(:saved_reply) { create(:saved_reply) }
|
||||
|
||||
describe 'validations' do
|
||||
it { is_expected.to validate_presence_of(:user_id) }
|
||||
it { is_expected.to validate_presence_of(:name) }
|
||||
it { is_expected.to validate_presence_of(:content) }
|
||||
it { is_expected.to validate_uniqueness_of(:name).scoped_to([:user_id]) }
|
||||
it { is_expected.to validate_length_of(:name).is_at_most(255) }
|
||||
it { is_expected.to validate_length_of(:content).is_at_most(10000) }
|
||||
end
|
||||
end
|
|
@ -526,6 +526,31 @@ RSpec.describe Issues::CreateService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'add related issue' do
|
||||
let_it_be(:related_issue) { create(:issue, project: project) }
|
||||
|
||||
let(:opts) do
|
||||
{ title: 'A new issue', add_related_issue: related_issue }
|
||||
end
|
||||
|
||||
it 'ignores related issue if not accessible' do
|
||||
expect { issue }.not_to change { IssueLink.count }
|
||||
expect(issue).to be_persisted
|
||||
end
|
||||
|
||||
context 'when user has access to the related issue' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'adds a link to the issue' do
|
||||
expect { issue }.to change { IssueLink.count }.by(1)
|
||||
expect(issue).to be_persisted
|
||||
expect(issue.related_issues(user)).to eq([related_issue])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'checking spam' do
|
||||
let(:params) do
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue