Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-03 09:10:37 +00:00
parent bbc0882f57
commit 058bd6be52
36 changed files with 553 additions and 49 deletions

View File

@ -8,6 +8,7 @@ gl-emoji {
} }
.user-status-emoji { .user-status-emoji {
margin-left: $gl-padding-4;
margin-right: $gl-padding-4; margin-right: $gl-padding-4;
gl-emoji { gl-emoji {

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
module IncidentManagement
class TimelineEventTagsFinder
def initialize(user, timeline_event, params = {})
@user = user
@timeline_event = timeline_event
@params = params
end
def execute
return ::IncidentManagement::TimelineEventTag.none unless allowed?
timeline_event.timeline_event_tags
end
private
attr_reader :user, :timeline_event, :params
def allowed?
Ability.allowed?(user, :read_incident_management_timeline_event_tag, timeline_event)
end
end
end

View File

@ -18,6 +18,10 @@ module Mutations
required: true, required: true,
description: 'Timestamp of when the event occurred.' description: 'Timestamp of when the event occurred.'
argument :timeline_event_tag_names, [GraphQL::Types::String],
required: false,
description: copy_field_description(Types::IncidentManagement::TimelineEventType, :timeline_event_tags)
def resolve(incident_id:, **args) def resolve(incident_id:, **args)
incident = authorized_find!(id: incident_id) incident = authorized_find!(id: incident_id)

View File

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Resolvers
module IncidentManagement
class TimelineEventTagsResolver < BaseResolver
include LooksAhead
type ::Types::IncidentManagement::TimelineEventTagType.connection_type, null: true
def resolve(**args)
apply_lookahead(::IncidentManagement::TimelineEventTagsFinder.new(current_user, object, args).execute)
end
end
end
end

View File

@ -22,11 +22,17 @@ module Resolvers
prepare: ->(id, ctx) { id.model_id } prepare: ->(id, ctx) { id.model_id }
end end
def resolve(**args) def resolve_with_lookahead(**args)
incident = args[:incident_id].find incident = args[:incident_id].find
apply_lookahead(::IncidentManagement::TimelineEventsFinder.new(current_user, incident, args).execute) apply_lookahead(::IncidentManagement::TimelineEventsFinder.new(current_user, incident, args).execute)
end end
def preloads
{
timeline_event_tags: [:timeline_event_tags]
}
end
end end
end end
end end

View File

@ -0,0 +1,23 @@
# frozen_string_literal: true
module Types
module IncidentManagement
class TimelineEventTagType < BaseObject
graphql_name 'TimelineEventTagType'
description 'Describes a tag on an incident management timeline event.'
authorize :read_incident_management_timeline_event_tag
field :id,
Types::GlobalIDType[::IncidentManagement::TimelineEventTag],
null: false,
description: 'ID of the timeline event tag.'
field :name,
GraphQL::Types::String,
null: false,
description: 'Name of the timeline event tag.'
end
end
end

View File

@ -53,6 +53,13 @@ module Types
null: false, null: false,
description: 'Timestamp when the event occurred.' description: 'Timestamp when the event occurred.'
field :timeline_event_tags,
::Types::IncidentManagement::TimelineEventTagType.connection_type,
null: true,
description: 'Tags for the incident timeline event.',
extras: [:lookahead],
resolver: Resolvers::IncidentManagement::TimelineEventTagsResolver
field :created_at, field :created_at,
Types::TimeType, Types::TimeType,
null: false, null: false,

View File

@ -47,7 +47,7 @@ module Types
mount_mutation Mutations::DependencyProxy::ImageTtlGroupPolicy::Update mount_mutation Mutations::DependencyProxy::ImageTtlGroupPolicy::Update
mount_mutation Mutations::DependencyProxy::GroupSettings::Update mount_mutation Mutations::DependencyProxy::GroupSettings::Update
mount_mutation Mutations::Environments::CanaryIngress::Update mount_mutation Mutations::Environments::CanaryIngress::Update
mount_mutation Mutations::IncidentManagement::TimelineEvent::Create mount_mutation Mutations::IncidentManagement::TimelineEvent::Create, alpha: { milestone: '15.6' }
mount_mutation Mutations::IncidentManagement::TimelineEvent::PromoteFromNote mount_mutation Mutations::IncidentManagement::TimelineEvent::PromoteFromNote
mount_mutation Mutations::IncidentManagement::TimelineEvent::Update mount_mutation Mutations::IncidentManagement::TimelineEvent::Update
mount_mutation Mutations::IncidentManagement::TimelineEvent::Destroy mount_mutation Mutations::IncidentManagement::TimelineEvent::Destroy

View File

@ -20,6 +20,8 @@ module IncidentManagement
validates :name, uniqueness: { scope: :project_id } validates :name, uniqueness: { scope: :project_id }
validates :name, length: { maximum: 255 } validates :name, length: { maximum: 255 }
scope :by_names, -> (tag_names) { where(name: tag_names) }
def self.pluck_names def self.pluck_names
pluck(:name) pluck(:name)
end end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
module IncidentManagement
class TimelineEventTagPolicy < ::BasePolicy
delegate { @subject.project }
end
end

View File

@ -254,7 +254,6 @@ class ProjectPolicy < BasePolicy
enable :change_namespace enable :change_namespace
enable :change_visibility_level enable :change_visibility_level
enable :rename_project
enable :remove_project enable :remove_project
enable :archive_project enable :archive_project
enable :remove_fork_project enable :remove_fork_project
@ -497,6 +496,7 @@ class ProjectPolicy < BasePolicy
enable :push_to_delete_protected_branch enable :push_to_delete_protected_branch
enable :update_snippet enable :update_snippet
enable :admin_snippet enable :admin_snippet
enable :rename_project
enable :admin_project_member enable :admin_project_member
enable :admin_note enable :admin_note
enable :admin_wiki enable :admin_wiki
@ -846,6 +846,10 @@ class ProjectPolicy < BasePolicy
enable :view_package_registry_project_settings enable :view_package_registry_project_settings
end end
rule { can?(:read_project) }.policy do
enable :read_incident_management_timeline_event_tag
end
private private
def user_is_user? def user_is_user?

View File

@ -99,6 +99,9 @@ module IncidentManagement
if timeline_event.save(context: validation_context) if timeline_event.save(context: validation_context)
add_system_note(timeline_event) add_system_note(timeline_event)
create_timeline_event_tag_links(timeline_event, params[:timeline_event_tag_names])
track_usage_event(:incident_management_timeline_event_created, user.id) track_usage_event(:incident_management_timeline_event_created, user.id)
success(timeline_event) success(timeline_event)
@ -126,6 +129,22 @@ module IncidentManagement
def validation_context def validation_context
:user_input if !auto_created && params[:promoted_from_note].blank? :user_input if !auto_created && params[:promoted_from_note].blank?
end end
def create_timeline_event_tag_links(timeline_event, tag_names)
return unless params[:timeline_event_tag_names]
tags = project.incident_management_timeline_event_tags.by_names(tag_names)
tag_links = tags.select(:id).map do |tag|
{
timeline_event_id: timeline_event.id,
timeline_event_tag_id: tag.id,
created_at: DateTime.current
}
end
IncidentManagement::TimelineEventTagLink.insert_all(tag_links) if tag_links.any?
end
end end
end end
end end

View File

@ -0,0 +1,8 @@
---
name: allow_audit_event_type_filtering
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/102502
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/373833
milestone: '15.6'
type: development
group: group::compliance
default_enabled: false

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
class AddFileNameIndexToPackagesRpmRepositoryFiles < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
NEW_INDEX_NAME = 'index_packages_rpm_repository_files_on_project_id_and_file_name'
OLD_INDEX_NAME = 'index_packages_rpm_repository_files_on_project_id'
def up
add_concurrent_index :packages_rpm_repository_files, %i[project_id file_name], name: NEW_INDEX_NAME
remove_concurrent_index :packages_rpm_repository_files, :project_id, name: OLD_INDEX_NAME
end
def down
add_concurrent_index :packages_rpm_repository_files, :project_id, name: OLD_INDEX_NAME
remove_concurrent_index :packages_rpm_repository_files, %i[project_id file_name], name: NEW_INDEX_NAME
end
end

View File

@ -0,0 +1 @@
d7ec9ab32c5f58805bec64bea9bd32aedbd80f678d6b8e8c6914aa26523dcc95

View File

@ -29943,7 +29943,7 @@ CREATE INDEX index_packages_project_id_name_partial_for_nuget ON packages_packag
CREATE INDEX index_packages_rpm_metadata_on_package_id ON packages_rpm_metadata USING btree (package_id); CREATE INDEX index_packages_rpm_metadata_on_package_id ON packages_rpm_metadata USING btree (package_id);
CREATE INDEX index_packages_rpm_repository_files_on_project_id ON packages_rpm_repository_files USING btree (project_id); CREATE INDEX index_packages_rpm_repository_files_on_project_id_and_file_name ON packages_rpm_repository_files USING btree (project_id, file_name);
CREATE INDEX index_packages_tags_on_package_id ON packages_tags USING btree (package_id); CREATE INDEX index_packages_tags_on_package_id ON packages_tags USING btree (package_id);

View File

@ -4882,6 +4882,10 @@ Input type: `TerraformStateUnlockInput`
### `Mutation.timelineEventCreate` ### `Mutation.timelineEventCreate`
WARNING:
**Introduced** in 15.6.
This feature is in Alpha. It can be changed or removed at any time.
Input type: `TimelineEventCreateInput` Input type: `TimelineEventCreateInput`
#### Arguments #### Arguments
@ -4892,6 +4896,7 @@ Input type: `TimelineEventCreateInput`
| <a id="mutationtimelineeventcreateincidentid"></a>`incidentId` | [`IssueID!`](#issueid) | Incident ID of the timeline event. | | <a id="mutationtimelineeventcreateincidentid"></a>`incidentId` | [`IssueID!`](#issueid) | Incident ID of the timeline event. |
| <a id="mutationtimelineeventcreatenote"></a>`note` | [`String!`](#string) | Text note of the timeline event. | | <a id="mutationtimelineeventcreatenote"></a>`note` | [`String!`](#string) | Text note of the timeline event. |
| <a id="mutationtimelineeventcreateoccurredat"></a>`occurredAt` | [`Time!`](#time) | Timestamp of when the event occurred. | | <a id="mutationtimelineeventcreateoccurredat"></a>`occurredAt` | [`Time!`](#time) | Timestamp of when the event occurred. |
| <a id="mutationtimelineeventcreatetimelineeventtagnames"></a>`timelineEventTagNames` | [`[String!]`](#string) | Tags for the incident timeline event. |
#### Fields #### Fields
@ -9320,6 +9325,29 @@ The edge type for [`TimeTrackingTimelogCategory`](#timetrackingtimelogcategory).
| <a id="timetrackingtimelogcategoryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="timetrackingtimelogcategoryedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="timetrackingtimelogcategoryedgenode"></a>`node` | [`TimeTrackingTimelogCategory`](#timetrackingtimelogcategory) | The item at the end of the edge. | | <a id="timetrackingtimelogcategoryedgenode"></a>`node` | [`TimeTrackingTimelogCategory`](#timetrackingtimelogcategory) | The item at the end of the edge. |
#### `TimelineEventTagTypeConnection`
The connection type for [`TimelineEventTagType`](#timelineeventtagtype).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="timelineeventtagtypeconnectionedges"></a>`edges` | [`[TimelineEventTagTypeEdge]`](#timelineeventtagtypeedge) | A list of edges. |
| <a id="timelineeventtagtypeconnectionnodes"></a>`nodes` | [`[TimelineEventTagType]`](#timelineeventtagtype) | A list of nodes. |
| <a id="timelineeventtagtypeconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `TimelineEventTagTypeEdge`
The edge type for [`TimelineEventTagType`](#timelineeventtagtype).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="timelineeventtagtypeedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="timelineeventtagtypeedgenode"></a>`node` | [`TimelineEventTagType`](#timelineeventtagtype) | The item at the end of the edge. |
#### `TimelineEventTypeConnection` #### `TimelineEventTypeConnection`
The connection type for [`TimelineEventType`](#timelineeventtype). The connection type for [`TimelineEventType`](#timelineeventtype).
@ -18924,6 +18952,17 @@ Explains why we could not generate a timebox report.
| <a id="timeboxreporterrorcode"></a>`code` | [`TimeboxReportErrorReason`](#timeboxreporterrorreason) | Machine readable code, categorizing the error. | | <a id="timeboxreporterrorcode"></a>`code` | [`TimeboxReportErrorReason`](#timeboxreporterrorreason) | Machine readable code, categorizing the error. |
| <a id="timeboxreporterrormessage"></a>`message` | [`String`](#string) | Human readable message explaining what happened. | | <a id="timeboxreporterrormessage"></a>`message` | [`String`](#string) | Human readable message explaining what happened. |
### `TimelineEventTagType`
Describes a tag on an incident management timeline event.
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="timelineeventtagtypeid"></a>`id` | [`IncidentManagementTimelineEventTagID!`](#incidentmanagementtimelineeventtagid) | ID of the timeline event tag. |
| <a id="timelineeventtagtypename"></a>`name` | [`String!`](#string) | Name of the timeline event tag. |
### `TimelineEventType` ### `TimelineEventType`
Describes an incident management timeline event. Describes an incident management timeline event.
@ -18942,6 +18981,7 @@ Describes an incident management timeline event.
| <a id="timelineeventtypenotehtml"></a>`noteHtml` | [`String`](#string) | HTML note of the timeline event. | | <a id="timelineeventtypenotehtml"></a>`noteHtml` | [`String`](#string) | HTML note of the timeline event. |
| <a id="timelineeventtypeoccurredat"></a>`occurredAt` | [`Time!`](#time) | Timestamp when the event occurred. | | <a id="timelineeventtypeoccurredat"></a>`occurredAt` | [`Time!`](#time) | Timestamp when the event occurred. |
| <a id="timelineeventtypepromotedfromnote"></a>`promotedFromNote` | [`Note`](#note) | Note from which the timeline event was created. | | <a id="timelineeventtypepromotedfromnote"></a>`promotedFromNote` | [`Note`](#note) | Note from which the timeline event was created. |
| <a id="timelineeventtypetimelineeventtags"></a>`timelineEventTags` | [`TimelineEventTagTypeConnection`](#timelineeventtagtypeconnection) | Tags for the incident timeline event. (see [Connections](#connections)) |
| <a id="timelineeventtypeupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp when the event updated. | | <a id="timelineeventtypeupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp when the event updated. |
| <a id="timelineeventtypeupdatedbyuser"></a>`updatedByUser` | [`UserCore`](#usercore) | User that updated the timeline event. | | <a id="timelineeventtypeupdatedbyuser"></a>`updatedByUser` | [`UserCore`](#usercore) | User that updated the timeline event. |
@ -22533,6 +22573,12 @@ A `IncidentManagementTimelineEventID` is a global ID. It is encoded as a string.
An example `IncidentManagementTimelineEventID` is: `"gid://gitlab/IncidentManagement::TimelineEvent/1"`. An example `IncidentManagementTimelineEventID` is: `"gid://gitlab/IncidentManagement::TimelineEvent/1"`.
### `IncidentManagementTimelineEventTagID`
A `IncidentManagementTimelineEventTagID` is a global ID. It is encoded as a string.
An example `IncidentManagementTimelineEventTagID` is: `"gid://gitlab/IncidentManagement::TimelineEventTag/1"`.
### `Int` ### `Int`
Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. Represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.

View File

@ -20,8 +20,8 @@ Returns a list of group iterations.
GET /groups/:id/iterations GET /groups/:id/iterations
GET /groups/:id/iterations?state=opened GET /groups/:id/iterations?state=opened
GET /groups/:id/iterations?state=closed GET /groups/:id/iterations?state=closed
GET /groups/:id/iterations?title=1.0
GET /groups/:id/iterations?search=version GET /groups/:id/iterations?search=version
GET /groups/:id/iterations?include_ancestors=false
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |

View File

@ -22,8 +22,8 @@ Returns a list of project iterations.
GET /projects/:id/iterations GET /projects/:id/iterations
GET /projects/:id/iterations?state=opened GET /projects/:id/iterations?state=opened
GET /projects/:id/iterations?state=closed GET /projects/:id/iterations?state=closed
GET /projects/:id/iterations?title=1.0
GET /projects/:id/iterations?search=version GET /projects/:id/iterations?search=version
GET /projects/:id/iterations?include_ancestors=false
``` ```
| Attribute | Type | Required | Description | | Attribute | Type | Required | Description |

View File

@ -1144,7 +1144,7 @@ The `output_example_snapshots` directory contains files which are generated by t
`glfm_specification/input` directory. `glfm_specification/input` directory.
The `output-specification.rb` script generates The `output-specification.rb` script generates
`output_snapshot_examples/glfm_snapshot_spec.md` and `output_snapshot_examples/glfm_snapshot_spec.html`. `output_snapshot_examples/snapshot_spec.md` and `output_snapshot_examples/snapshot_spec.html`.
These files are Markdown specification files containing examples generated based on input files, These files are Markdown specification files containing examples generated based on input files,
similar to the `output_spec/spec.txt` and `output_spec/spec.html`, with the following differences: similar to the `output_spec/spec.txt` and `output_spec/spec.html`, with the following differences:
@ -1166,9 +1166,9 @@ key in `glfm_specification/input/gitlab_flavored_markdown/glfm_example_status.ym
can be used to disable automatic generation of some examples. They can instead can be used to disable automatic generation of some examples. They can instead
be manually edited as necessary to help drive the implementations. be manually edited as necessary to help drive the implementations.
##### `glfm_snapshot_spec.md` ##### `snapshot_spec.md`
[`glfm_specification/output_example_snapshots/glfm_snapshot_spec.md`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/output_example_snapshots/glfm_snapshot_spec.md) [`glfm_specification/output_example_snapshots/snapshot_spec.md`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/output_example_snapshots/snapshot_spec.md)
is a Markdown file, containing standard Markdown + canonical HTML examples like [`spec.txt`](#spectxt). is a Markdown file, containing standard Markdown + canonical HTML examples like [`spec.txt`](#spectxt).
It is generated or updated by the `update-specification.rb` script, using the It is generated or updated by the `update-specification.rb` script, using the
@ -1180,26 +1180,26 @@ scripts such as `update-example-snapshots.rb`.
It is similar to [`spec.txt`](#spectxt), with the following differences: It is similar to [`spec.txt`](#spectxt), with the following differences:
1. [`spec.txt`](#spectxt) contains only examples for GitLab Flavored Markdown, but 1. [`spec.txt`](#spectxt) contains only examples for GitLab Flavored Markdown, but
`glfm_snapshot_spec.md` also contains the full superset of examples from the `snapshot_spec.md` also contains the full superset of examples from the
"GitHub Flavored Markdown" (<abbr title="GitHub Flavored Markdown">GFM</abbr>)[specification](https://github.github.com/gfm/) "GitHub Flavored Markdown" (<abbr title="GitHub Flavored Markdown">GFM</abbr>)[specification](https://github.github.com/gfm/)
and the [CommonMark specification](https://spec.commonmark.org/0.30/) specifications. and the [CommonMark specification](https://spec.commonmark.org/0.30/) specifications.
1. [`spec.txt`](#spectxt) represents the full GLFM specification, including additional header sections 1. [`spec.txt`](#spectxt) represents the full GLFM specification, including additional header sections
containing only explanatory prose and no examples, but `glfm_snapshot_spec.md` consists of only containing only explanatory prose and no examples, but `snapshot_spec.md` consists of only
header sections which contain examples. This is because its purpose is to serve as input for header sections which contain examples. This is because its purpose is to serve as input for
the other [`output example snapshot files`](#output-example-snapshot-files) - it is not intended the other [`output example snapshot files`](#output-example-snapshot-files) - it is not intended
to serve as an actual [specification file](#output-specification-files) to serve as an actual [specification file](#output-specification-files)
like [`spec.txt`](#spectxt) or [`spec.html`](#spechtml). like [`spec.txt`](#spectxt) or [`spec.html`](#spechtml).
##### `glfm_snapshot_spec.html` ##### `snapshot_spec.html`
[`glfm_specification/output_snapshot_examples/glfm_snapshot_spec.html`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/output_snapshot_examples/glfm_snapshot_spec.html) [`glfm_specification/output_snapshot_examples/snapshot_spec.html`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/glfm_specification/output_snapshot_examples/snapshot_spec.html)
is an HTML file, rendered based on `glfm_snapshot_spec.md`. It is is an HTML file, rendered based on `snapshot_spec.md`. It is
generated (or updated) by the `update-specification.rb` script at the same time as generated (or updated) by the `update-specification.rb` script at the same time as
`glfm_snapshot_spec.md`. `snapshot_spec.md`.
NOTE: NOTE:
The formatting of this HTML is currently not identical to the GFM and CommonMark The formatting of this HTML is currently not identical to the GFM and CommonMark
HTML-rendered specification. It is only the raw output of running `glfm_snapshot_spec.md` through HTML-rendered specification. It is only the raw output of running `snapshot_spec.md` through
the GitLab Markdown renderer. Properly formatting the HTML will require the GitLab Markdown renderer. Properly formatting the HTML will require
duplicating or reusing the Lua script and template from the CommonMark project: duplicating or reusing the Lua script and template from the CommonMark project:
[CommonMark Makefile](https://github.com/commonmark/commonmark-spec/blob/master/Makefile#L11) [CommonMark Makefile](https://github.com/commonmark/commonmark-spec/blob/master/Makefile#L11)

View File

@ -30,7 +30,14 @@ module API
requires :file_name, type: String, desc: 'Repository metadata file name' requires :file_name, type: String, desc: 'Repository metadata file name'
end end
get 'repodata/*file_name', requirements: { file_name: API::NO_SLASH_URL_PART_REGEX } do get 'repodata/*file_name', requirements: { file_name: API::NO_SLASH_URL_PART_REGEX } do
not_found! authorize_read_package!(authorized_user_project)
repository_file = Packages::Rpm::RepositoryFile.find_by_project_id_and_file_name!(
authorized_user_project.id,
"#{params['file_name']}.#{params['format']}"
)
present_carrierwave_file!(repository_file.file)
end end
desc 'Download RPM package files' desc 'Download RPM package files'

View File

@ -4,9 +4,10 @@ FactoryBot.define do
factory :rpm_repository_file, class: 'Packages::Rpm::RepositoryFile' do factory :rpm_repository_file, class: 'Packages::Rpm::RepositoryFile' do
project project
file_name { 'repomd.xml' } file_name { '364c77dd49e8f814d56e621d0b3306c4fd0696dcad506f527329b818eb0f5db3-repomd.xml' }
file_sha1 { 'efae869b4e95d54796a46481f3a211d6a88d0323' } file_sha1 { 'efae869b4e95d54796a46481f3a211d6a88d0323' }
file_md5 { 'ddf8a75330c896a8d7709e75f8b5982a' } file_md5 { 'ddf8a75330c896a8d7709e75f8b5982a' }
file_sha256 { '364c77dd49e8f814d56e621d0b3306c4fd0696dcad506f527329b818eb0f5db3' }
size { 3127.kilobytes } size { 3127.kilobytes }
status { :default } status { :default }
@ -15,7 +16,11 @@ FactoryBot.define do
end end
transient do transient do
file_fixture { 'spec/fixtures/packages/rpm/repodata/repomd.xml' } file_fixture do
# rubocop:disable Layout/LineLength
'spec/fixtures/packages/rpm/repodata/364c77dd49e8f814d56e621d0b3306c4fd0696dcad506f527329b818eb0f5db3-repomd.xml'
# rubocop:enable Layout/LineLength
end
end end
after(:build) do |package_file, evaluator| after(:build) do |package_file, evaluator|

View File

@ -0,0 +1,58 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::TimelineEventTagsFinder do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:incident) { create(:incident, project: project) }
let_it_be(:timeline_event) do
create(:incident_management_timeline_event, project: project, incident: incident, occurred_at: Time.current)
end
let_it_be(:timeline_event_tag) do
create(:incident_management_timeline_event_tag, project: project)
end
let_it_be(:timeline_event_tag_link) do
create(:incident_management_timeline_event_tag_link,
timeline_event: timeline_event,
timeline_event_tag: timeline_event_tag)
end
let(:params) { {} }
describe '#execute' do
subject(:execute) { described_class.new(user, timeline_event, params).execute }
context 'when user has permissions' do
before do
project.add_guest(user)
end
it 'returns tags on the event' do
is_expected.to match_array([timeline_event_tag])
end
context 'when event does not have tags' do
let(:timeline_event) do
create(:incident_management_timeline_event, project: project, incident: incident, occurred_at: Time.current)
end
it 'returns empty result' do
is_expected.to match_array([])
end
end
context 'when timeline event is nil' do
let(:timeline_event) { nil }
it { is_expected.to eq(IncidentManagement::TimelineEventTag.none) }
end
end
context 'when user does not have permissions' do
it { is_expected.to eq(IncidentManagement::TimelineEventTag.none) }
end
end
end

View File

@ -1,4 +1,7 @@
<repomd xmlns="http://gitlab.com/api/v4/projects/1/packages/rpm/repodata/repomd.xml" xmlns:rpm="http://gitlab.com/api/v4/projects/1/packages/rpm/repodata/repomd.xml"> <repomd
xmlns="http://gitlab.com/api/v4/projects/1/packages/rpm/repodata/364c77dd49e8f814d56e621d0b3306c4fd0696dcad506f527329b818eb0f5db3-repomd.xml"
xmlns:rpm="http://gitlab.com/api/v4/projects/1/packages/rpm/repodata/364c77dd49e8f814d56e621d0b3306c4fd0696dcad506f527329b818eb0f5db3-repomd.xml"
>
<revision>1644602779</revision> <revision>1644602779</revision>
<data type="filelists"> <data type="filelists">
<checksum type="sha256">6503673de76312406ff8ecb06d9733c32b546a65abae4d4170d9b51fb75bf253</checksum> <checksum type="sha256">6503673de76312406ff8ecb06d9733c32b546a65abae4d4170d9b51fb75bf253</checksum>

View File

@ -6,6 +6,9 @@ RSpec.describe Mutations::IncidentManagement::TimelineEvent::Create do
let_it_be(:current_user) { create(:user) } let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:incident) { create(:incident, project: project) } let_it_be(:incident) { create(:incident, project: project) }
let_it_be(:timeline_event_tag) do
create(:incident_management_timeline_event_tag, project: project)
end
let(:args) { { note: 'note', occurred_at: Time.current } } let(:args) { { note: 'note', occurred_at: Time.current } }
@ -39,6 +42,18 @@ RSpec.describe Mutations::IncidentManagement::TimelineEvent::Create do
it_behaves_like 'responding with an incident timeline errors', it_behaves_like 'responding with an incident timeline errors',
errors: ["Occurred at can't be blank and Timeline text can't be blank"] errors: ["Occurred at can't be blank and Timeline text can't be blank"]
end end
context 'when timeline event tags are passed' do
let(:args) do
{
note: 'note',
occurred_at: Time.current,
timeline_event_tag_names: [timeline_event_tag.name.to_s]
}
end
it_behaves_like 'creating an incident timeline event'
end
end end
it_behaves_like 'failing to create an incident timeline event' it_behaves_like 'failing to create an incident timeline event'

View File

@ -0,0 +1,92 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Resolvers::IncidentManagement::TimelineEventTagsResolver do
include GraphqlHelpers
let_it_be(:current_user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:incident) { create(:incident, project: project) }
let_it_be(:timeline_event) do
create(:incident_management_timeline_event, project: project, incident: incident)
end
let_it_be(:timeline_event_with_no_tags) do
create(:incident_management_timeline_event, project: project, incident: incident)
end
let_it_be(:timeline_event_tag) do
create(:incident_management_timeline_event_tag, project: project)
end
let_it_be(:timeline_event_tag2) do
create(:incident_management_timeline_event_tag, project: project, name: 'Test tag 2')
end
let_it_be(:timeline_event_tag_link) do
create(:incident_management_timeline_event_tag_link,
timeline_event: timeline_event,
timeline_event_tag: timeline_event_tag)
end
let(:resolver) { described_class }
subject(:resolved_timeline_event_tags) do
sync(resolve_timeline_event_tags(timeline_event, current_user: current_user).to_a)
end
before do
project.add_guest(current_user)
end
specify do
expect(resolver).to have_nullable_graphql_type(
Types::IncidentManagement::TimelineEventTagType.connection_type
)
end
it 'returns timeline event tags', :aggregate_failures do
expect(resolved_timeline_event_tags.length).to eq(1)
expect(resolved_timeline_event_tags.first).to be_a(::IncidentManagement::TimelineEventTag)
end
context 'when timeline event is nil' do
subject(:resolved_timeline_event_tags) do
sync(resolve_timeline_event_tags(nil, current_user: current_user).to_a)
end
it 'returns no timeline event tags' do
expect(resolved_timeline_event_tags).to be_empty
end
end
context 'when there is no timeline event tag link' do
subject(:resolved_timeline_event_tags) do
sync(resolve_timeline_event_tags(timeline_event_with_no_tags, current_user: current_user).to_a)
end
it 'returns no timeline event tags' do
expect(resolved_timeline_event_tags).to be_empty
end
end
context 'when user does not have permissions' do
let(:non_member) { create(:user) }
subject(:resolved_timeline_event_tags) do
sync(resolve_timeline_event_tags(timeline_event, current_user: non_member).to_a)
end
it 'returns no timeline event tags' do
expect(resolved_timeline_event_tags).to be_empty
end
end
private
def resolve_timeline_event_tags(obj, context = { current_user: current_user })
resolve(resolver, obj: obj, args: {}, ctx: context, arg_style: :internal_prepared)
end
end

View File

@ -0,0 +1,18 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe GitlabSchema.types['TimelineEventTagType'] do
specify { expect(described_class.graphql_name).to eq('TimelineEventTagType') }
specify { expect(described_class).to require_graphql_authorizations(:read_incident_management_timeline_event_tag) }
it 'exposes the expected fields' do
expected_fields = %i[
id
name
]
expect(described_class).to have_graphql_fields(*expected_fields)
end
end

View File

@ -21,6 +21,7 @@ RSpec.describe GitlabSchema.types['TimelineEventType'] do
occurred_at occurred_at
created_at created_at
updated_at updated_at
timeline_event_tags
] ]
expect(described_class).to have_graphql_fields(*expected_fields) expect(described_class).to have_graphql_fields(*expected_fields)

View File

@ -39,4 +39,21 @@ RSpec.describe IncidentManagement::TimelineEventTag do
it { expect(described_class::START_TIME_TAG_NAME).to eq('Start time') } it { expect(described_class::START_TIME_TAG_NAME).to eq('Start time') }
it { expect(described_class::END_TIME_TAG_NAME).to eq('End time') } it { expect(described_class::END_TIME_TAG_NAME).to eq('End time') }
end end
describe '#by_names scope' do
let_it_be(:project) { create(:project) }
let_it_be(:project2) { create(:project) }
let_it_be(:tag1) { create(:incident_management_timeline_event_tag, name: 'Test tag 1', project: project) }
let_it_be(:tag2) { create(:incident_management_timeline_event_tag, name: 'Test tag 2', project: project) }
let_it_be(:tag3) { create(:incident_management_timeline_event_tag, name: 'Test tag 3', project: project2) }
it 'returns two matching tags' do
expect(described_class.by_names(['Test tag 1', 'Test tag 2'])).to contain_exactly(tag1, tag2)
end
it 'returns tags on the project' do
expect(project2.incident_management_timeline_event_tags.by_names(['Test tag 1',
'Test tag 3'])).to contain_exactly(tag3)
end
end
end end

View File

@ -533,6 +533,24 @@ RSpec.describe ProjectPolicy do
end end
end end
context 'with timeline event tags' do
context 'when user is member of the project' do
it 'allows access to timeline event tags' do
expect(described_class.new(owner, project)).to be_allowed(:read_incident_management_timeline_event_tag)
expect(described_class.new(developer, project)).to be_allowed(:read_incident_management_timeline_event_tag)
expect(described_class.new(admin, project)).to be_allowed(:read_incident_management_timeline_event_tag)
end
end
context 'when user is not a member of the project' do
let(:project) { private_project }
it 'disallows access to the timeline event tags' do
expect(described_class.new(non_member, project)).to be_disallowed(:read_incident_management_timeline_event_tag)
end
end
end
context 'reading a project' do context 'reading a project' do
it 'allows access when a user has read access to the repo' do it 'allows access when a user has read access to the repo' do
expect(described_class.new(owner, project)).to be_allowed(:read_project) expect(described_class.new(owner, project)).to be_allowed(:read_project)

View File

@ -10,8 +10,16 @@ RSpec.describe 'Creating an incident timeline event' do
let_it_be(:incident) { create(:incident, project: project) } let_it_be(:incident) { create(:incident, project: project) }
let_it_be(:event_occurred_at) { Time.current } let_it_be(:event_occurred_at) { Time.current }
let_it_be(:note) { 'demo note' } let_it_be(:note) { 'demo note' }
let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') }
let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') }
let(:input) do
{ incident_id: incident.to_global_id.to_s,
note: note,
occurred_at: event_occurred_at,
timeline_event_tag_names: [tag1.name] }
end
let(:input) { { incident_id: incident.to_global_id.to_s, note: note, occurred_at: event_occurred_at } }
let(:mutation) do let(:mutation) do
graphql_mutation(:timeline_event_create, input) do graphql_mutation(:timeline_event_create, input) do
<<~QL <<~QL
@ -22,6 +30,7 @@ RSpec.describe 'Creating an incident timeline event' do
author { id username } author { id username }
incident { id title } incident { id title }
note note
timelineEventTags { nodes { name } }
editable editable
action action
occurredAt occurredAt
@ -64,4 +73,18 @@ RSpec.describe 'Creating an incident timeline event' do
it_behaves_like 'timeline event mutation responds with validation error', it_behaves_like 'timeline event mutation responds with validation error',
error_message: 'Timeline text is too long (maximum is 280 characters)' error_message: 'Timeline text is too long (maximum is 280 characters)'
end end
context 'when timeline event tags are passed' do
it 'creates incident timeline event with tags', :aggregate_failures do
post_graphql_mutation(mutation, current_user: user)
timeline_event_response = mutation_response['timelineEvent']
tag_names = timeline_event_response['timelineEventTags']['nodes']
expect(response).to have_gitlab_http_status(:success)
expect(timeline_event_response).to include(
'timelineEventTags' => { 'nodes' => tag_names }
)
end
end
end end

View File

@ -48,6 +48,7 @@ RSpec.describe 'getting incident timeline events' do
note note
noteHtml noteHtml
promotedFromNote { id body } promotedFromNote { id body }
timelineEventTags { nodes { name } }
editable editable
action action
occurredAt occurredAt
@ -100,6 +101,7 @@ RSpec.describe 'getting incident timeline events' do
'id' => promoted_from_note.to_global_id.to_s, 'id' => promoted_from_note.to_global_id.to_s,
'body' => promoted_from_note.note 'body' => promoted_from_note.note
}, },
'timelineEventTags' => { 'nodes' => [] },
'editable' => true, 'editable' => true,
'action' => timeline_event.action, 'action' => timeline_event.action,
'occurredAt' => timeline_event.occurred_at.iso8601, 'occurredAt' => timeline_event.occurred_at.iso8601,
@ -108,6 +110,47 @@ RSpec.describe 'getting incident timeline events' do
) )
end end
context 'when timelineEvent tags are linked' do
let_it_be(:tag1) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 1') }
let_it_be(:tag2) { create(:incident_management_timeline_event_tag, project: project, name: 'Tag 2') }
let_it_be(:timeline_event_tag_link) do
create(:incident_management_timeline_event_tag_link,
timeline_event: timeline_event,
timeline_event_tag: tag1)
end
it_behaves_like 'a working graphql query'
it 'returns the set tags' do
expect(timeline_events.first['timelineEventTags']['nodes'].first['name']).to eq(tag1.name)
end
context 'when different timeline events are loaded' do
it 'avoids N+1 queries' do
control = ActiveRecord::QueryRecorder.new do
post_graphql(query, current_user: current_user)
end
new_event = create(:incident_management_timeline_event,
incident: incident,
project: project,
updated_by_user: updated_by_user,
promoted_from_note: promoted_from_note,
note: "Referencing #{issue.to_reference(full: true)} - Full URL #{issue_url}"
)
create(:incident_management_timeline_event_tag_link,
timeline_event: new_event,
timeline_event_tag: tag2
)
expect(incident.incident_management_timeline_events.length).to eq(3)
expect(post_graphql(query, current_user: current_user)).not_to exceed_query_limit(control)
expect(timeline_events.count).to eq(3)
end
end
end
context 'when filtering by id' do context 'when filtering by id' do
let(:params) { { incident_id: incident.to_global_id.to_s, id: timeline_event.to_global_id.to_s } } let(:params) { { incident_id: incident.to_global_id.to_s, id: timeline_event.to_global_id.to_s } }

View File

@ -3426,18 +3426,6 @@ RSpec.describe API::Projects do
end end
context 'when authenticated as project owner' do context 'when authenticated as project owner' do
it 'updates name' do
project_param = { name: 'bar' }
put api("/projects/#{project.id}", user), params: project_param
expect(response).to have_gitlab_http_status(:ok)
project_param.each_pair do |k, v|
expect(json_response[k.to_s]).to eq(v)
end
end
it 'updates visibility_level' do it 'updates visibility_level' do
project_param = { visibility: 'public' } project_param = { visibility: 'public' }
@ -3795,10 +3783,16 @@ RSpec.describe API::Projects do
expect(json_response['message']['path']).to eq(['has already been taken']) expect(json_response['message']['path']).to eq(['has already been taken'])
end end
it 'does not update name' do it 'updates name' do
project_param = { name: 'bar' } project_param = { name: 'bar' }
put api("/projects/#{project3.id}", user4), params: project_param
expect(response).to have_gitlab_http_status(:forbidden) put api("/projects/#{project.id}", user), params: project_param
expect(response).to have_gitlab_http_status(:ok)
project_param.each_pair do |k, v|
expect(json_response[k.to_s]).to eq(v)
end
end end
it 'does not update visibility_level' do it 'does not update visibility_level' do

View File

@ -37,7 +37,7 @@ RSpec.describe API::RpmProjectPackages do
it_behaves_like 'returning response status', status it_behaves_like 'returning response status', status
end end
shared_examples 'a deploy token for RPM requests' do shared_examples 'a deploy token for RPM requests' do |success_status = :not_found|
context 'with deploy token headers' do context 'with deploy token headers' do
before do before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) project.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
@ -46,7 +46,7 @@ RSpec.describe API::RpmProjectPackages do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) } let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
context 'when token is valid' do context 'when token is valid' do
it_behaves_like 'returning response status', :not_found it_behaves_like 'returning response status', success_status
end end
context 'when token is invalid' do context 'when token is invalid' do
@ -57,7 +57,7 @@ RSpec.describe API::RpmProjectPackages do
end end
end end
shared_examples 'a job token for RPM requests' do shared_examples 'a job token for RPM requests' do |success_status = :not_found|
context 'with job token headers' do context 'with job token headers' do
let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) } let(:headers) { basic_auth_header(::Gitlab::Auth::CI_JOB_USER, job.token) }
@ -67,7 +67,7 @@ RSpec.describe API::RpmProjectPackages do
end end
context 'with valid token' do context 'with valid token' do
it_behaves_like 'returning response status', :not_found it_behaves_like 'returning response status', success_status
end end
context 'with invalid token' do context 'with invalid token' do
@ -84,10 +84,10 @@ RSpec.describe API::RpmProjectPackages do
end end
end end
shared_examples 'a user token for RPM requests' do shared_examples 'a user token for RPM requests' do |success_status = :not_found|
context 'with valid project' do context 'with valid project' do
where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do where(:visibility_level, :user_role, :member, :user_token, :shared_examples_name, :expected_status) do
'PUBLIC' | :developer | true | true | 'process rpm packages upload/download' | :not_found 'PUBLIC' | :developer | true | true | 'process rpm packages upload/download' | success_status
'PUBLIC' | :guest | true | true | 'process rpm packages upload/download' | :forbidden 'PUBLIC' | :guest | true | true | 'process rpm packages upload/download' | :forbidden
'PUBLIC' | :developer | true | false | 'rejects rpm packages access' | :unauthorized 'PUBLIC' | :developer | true | false | 'rejects rpm packages access' | :unauthorized
'PUBLIC' | :guest | true | false | 'rejects rpm packages access' | :unauthorized 'PUBLIC' | :guest | true | false | 'rejects rpm packages access' | :unauthorized
@ -96,7 +96,7 @@ RSpec.describe API::RpmProjectPackages do
'PUBLIC' | :developer | false | false | 'rejects rpm packages access' | :unauthorized 'PUBLIC' | :developer | false | false | 'rejects rpm packages access' | :unauthorized
'PUBLIC' | :guest | false | false | 'rejects rpm packages access' | :unauthorized 'PUBLIC' | :guest | false | false | 'rejects rpm packages access' | :unauthorized
'PUBLIC' | :anonymous | false | true | 'process rpm packages upload/download' | :unauthorized 'PUBLIC' | :anonymous | false | true | 'process rpm packages upload/download' | :unauthorized
'PRIVATE' | :developer | true | true | 'process rpm packages upload/download' | :not_found 'PRIVATE' | :developer | true | true | 'process rpm packages upload/download' | success_status
'PRIVATE' | :guest | true | true | 'rejects rpm packages access' | :forbidden 'PRIVATE' | :guest | true | true | 'rejects rpm packages access' | :forbidden
'PRIVATE' | :developer | true | false | 'rejects rpm packages access' | :unauthorized 'PRIVATE' | :developer | true | false | 'rejects rpm packages access' | :unauthorized
'PRIVATE' | :guest | true | false | 'rejects rpm packages access' | :unauthorized 'PRIVATE' | :guest | true | false | 'rejects rpm packages access' | :unauthorized
@ -124,13 +124,15 @@ RSpec.describe API::RpmProjectPackages do
end end
describe 'GET /api/v4/projects/:project_id/packages/rpm/repodata/:filename' do describe 'GET /api/v4/projects/:project_id/packages/rpm/repodata/:filename' do
let(:url) { "/projects/#{project.id}/packages/rpm/repodata/#{package_name}" } let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
let(:repository_file) { create(:rpm_repository_file, project: project) }
let(:url) { "/projects/#{project.id}/packages/rpm/repodata/#{repository_file.file_name}" }
subject { get api(url), headers: headers } subject { get api(url), headers: headers }
it_behaves_like 'a job token for RPM requests' it_behaves_like 'a job token for RPM requests', :success
it_behaves_like 'a deploy token for RPM requests' it_behaves_like 'a deploy token for RPM requests', :success
it_behaves_like 'a user token for RPM requests' it_behaves_like 'a user token for RPM requests', :success
end end
describe 'GET /api/v4/projects/:id/packages/rpm/:package_file_id/:filename' do describe 'GET /api/v4/projects/:id/packages/rpm/:package_file_id/:filename' do

View File

@ -8,6 +8,9 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be_with_refind(:incident) { create(:incident, project: project) } let_it_be_with_refind(:incident) { create(:incident, project: project) }
let_it_be(:comment) { create(:note, project: project, noteable: incident) } let_it_be(:comment) { create(:note, project: project, noteable: incident) }
let_it_be(:timeline_event_tag) do
create(:incident_management_timeline_event_tag, name: 'Test tag 1', project: project)
end
let(:args) do let(:args) do
{ {
@ -134,6 +137,25 @@ RSpec.describe IncidentManagement::TimelineEvents::CreateService do
end end
end end
context 'when timeline event tag names are passed' do
let(:args) do
{
note: 'note',
occurred_at: Time.current,
action: 'new comment',
promoted_from_note: comment,
timeline_event_tag_names: ['Test tag 1']
}
end
it_behaves_like 'success response'
it 'matches the tag name' do
result = execute.payload[:timeline_event]
expect(result.timeline_event_tags.first).to eq(timeline_event_tag)
end
end
context 'with editable param' do context 'with editable param' do
let(:args) do let(:args) do
{ {

View File

@ -68,7 +68,7 @@ RSpec.shared_context 'ProjectPolicy context' do
admin_project admin_project_member admin_snippet admin_terraform_state admin_project admin_project_member admin_snippet admin_terraform_state
admin_wiki create_deploy_token destroy_deploy_token admin_wiki create_deploy_token destroy_deploy_token
push_to_delete_protected_branch read_deploy_token update_snippet push_to_delete_protected_branch read_deploy_token update_snippet
destroy_upload admin_member_access_request destroy_upload admin_member_access_request rename_project
] ]
end end
@ -83,7 +83,7 @@ RSpec.shared_context 'ProjectPolicy context' do
let(:base_owner_permissions) do let(:base_owner_permissions) do
%i[ %i[
archive_project change_namespace change_visibility_level destroy_issue archive_project change_namespace change_visibility_level destroy_issue
destroy_merge_request manage_owners remove_fork_project remove_project rename_project destroy_merge_request manage_owners remove_fork_project remove_project
set_issue_created_at set_issue_iid set_issue_updated_at set_issue_created_at set_issue_iid set_issue_updated_at
set_note_created_at set_note_created_at
] ]