diff --git a/.gitlab/issue_templates/Implementation.md b/.gitlab/issue_templates/Implementation.md index 058834264b0..f57bfae4da3 100644 --- a/.gitlab/issue_templates/Implementation.md +++ b/.gitlab/issue_templates/Implementation.md @@ -59,5 +59,17 @@ Other settings you might want to include when creating the issue. # /epic & --> +## Verification steps + + /label ~"workflow::refinement" /milestone %Backlog diff --git a/app/graphql/types/ci/job_kind_enum.rb b/app/graphql/types/ci/job_kind_enum.rb new file mode 100644 index 00000000000..dd1d80f806c --- /dev/null +++ b/app/graphql/types/ci/job_kind_enum.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Types + module Ci + class JobKindEnum < BaseEnum + graphql_name 'CiJobKind' + + value 'BUILD', value: ::Ci::Build, description: 'Standard CI job.' + value 'BRIDGE', value: ::Ci::Bridge, description: 'Bridge CI job connecting a parent and child pipeline.' + end + end +end diff --git a/app/graphql/types/ci/job_type.rb b/app/graphql/types/ci/job_type.rb index 83054553bd8..f25fc56a588 100644 --- a/app/graphql/types/ci/job_type.rb +++ b/app/graphql/types/ci/job_type.rb @@ -17,6 +17,8 @@ module Types description: 'Duration of the job in seconds.' field :id, ::Types::GlobalIDType[::CommitStatus].as('JobID'), null: true, description: 'ID of the job.' + field :kind, type: ::Types::Ci::JobKindEnum, null: false, + description: 'Indicates the type of job.' field :name, GraphQL::Types::String, null: true, description: 'Name of the job.' field :needs, BuildNeedType.connection_type, null: true, @@ -87,6 +89,12 @@ module Types field :triggered, GraphQL::Types::Boolean, null: true, description: 'Whether the job was triggered.' + def kind + return ::Ci::Build unless [::Ci::Build, ::Ci::Bridge].include?(object.class) + + object.class + end + def pipeline Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Pipeline, object.pipeline_id).find end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index 044404562af..b4ad9db815d 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -134,14 +134,14 @@ module SortingHelper ) end - def milestone_sort_options_hash + def milestones_sort_options_hash { - sort_value_name => sort_title_name_asc, - sort_value_name_desc => sort_title_name_desc, - sort_value_due_date_later => sort_title_due_date_later, sort_value_due_date_soon => sort_title_due_date_soon, + sort_value_due_date_later => sort_title_due_date_later, + sort_value_start_date_soon => sort_title_start_date_soon, sort_value_start_date_later => sort_title_start_date_later, - sort_value_start_date_soon => sort_title_start_date_soon + sort_value_name => sort_title_name_asc, + sort_value_name_desc => sort_title_name_desc } end diff --git a/app/mailers/emails/reviews.rb b/app/mailers/emails/reviews.rb index ddb9e161a80..b98fa8aa6c9 100644 --- a/app/mailers/emails/reviews.rb +++ b/app/mailers/emails/reviews.rb @@ -22,6 +22,10 @@ module Emails review = Review.find_by_id(review_id) @notes = review.notes + @discussions = Discussion.build_discussions(review.discussion_ids, preload_note_diff_file: true) + @include_diff_discussion_stylesheet = @discussions.values.any? do |discussion| + discussion.diff_discussion? && discussion.on_text? + end @author = review.author @author_name = review.author_name @project = review.project diff --git a/app/models/discussion.rb b/app/models/discussion.rb index 8a167034629..9eb3308b901 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -47,6 +47,14 @@ class Discussion grouped_notes.values.map { |notes| build(notes, context_noteable) } end + def self.build_discussions(discussion_ids, context_noteable = nil, preload_note_diff_file: false) + notes = Note.where(discussion_id: discussion_ids).fresh + notes = notes.inc_note_diff_file if preload_note_diff_file + + grouped_notes = notes.group_by { |n| n.discussion_id } + grouped_notes.transform_values { |notes| Discussion.build(notes, context_noteable) } + end + def self.lazy_find(discussion_id) BatchLoader.for(discussion_id).batch do |discussion_ids, loader| results = Note.where(discussion_id: discussion_ids).fresh.to_a.group_by(&:discussion_id) diff --git a/app/models/note.rb b/app/models/note.rb index 4f2e7ebe2c5..8f518735442 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -121,6 +121,7 @@ class Note < ApplicationRecord scope :with_discussion_ids, ->(discussion_ids) { where(discussion_id: discussion_ids) } scope :with_suggestions, -> { joins(:suggestions) } scope :inc_author, -> { includes(:author) } + scope :inc_note_diff_file, -> { includes(:note_diff_file) } scope :with_api_entity_associations, -> { preload(:note_diff_file, :author) } scope :inc_relations_for_view, -> do includes({ project: :group }, { author: :status }, :updated_by, :resolved_by, :award_emoji, diff --git a/app/models/review.rb b/app/models/review.rb index 5a30e2963c8..c621da3b03c 100644 --- a/app/models/review.rb +++ b/app/models/review.rb @@ -14,6 +14,10 @@ class Review < ApplicationRecord participant :author + def discussion_ids + notes.select(:discussion_id) + end + def all_references(current_user = nil, extractor: nil) ext = super diff --git a/app/views/notify/_note_email.html.haml b/app/views/notify/_note_email.html.haml index d5398337320..f2c67b84c80 100644 --- a/app/views/notify/_note_email.html.haml +++ b/app/views/notify/_note_email.html.haml @@ -2,17 +2,18 @@ - diff_limit = local_assigns.fetch(:diff_limit, nil) - target_url = local_assigns.fetch(:target_url, @target_url) - note_style = local_assigns.fetch(:note_style, "") -- skip_stylesheet_link = local_assigns.fetch(:skip_stylesheet_link, false) +- include_stylesheet_link = local_assigns.fetch(:include_stylesheet_link, true) -- discussion = note.discussion if note.part_of_discussion? +- author = local_assigns.fetch(:author) { note.author } +- discussion = local_assigns.fetch(:discussion) { note.discussion } if note.part_of_discussion? %p{ style: "color: #777777;" } = succeed ':' do - = link_to note.author_name, user_url(note.author) + = link_to author.name, user_url(author) - if discussion.nil? = link_to 'commented', target_url - else - - if note.start_of_discussion? + - if discussion.first_note == note started a new - else commented on a @@ -22,10 +23,9 @@ - else = link_to 'discussion', target_url -- if discussion&.diff_discussion? && discussion.on_text? - - unless skip_stylesheet_link - = content_for :head do - = stylesheet_link_tag 'mailers/highlighted_diff_email' +- if include_stylesheet_link && discussion&.diff_discussion? && discussion.on_text? + = content_for :head do + = stylesheet_link_tag 'mailers/highlighted_diff_email' %table.code.gl-mb-5 = render partial: "projects/diffs/email_line", @@ -34,4 +34,4 @@ locals: { diff_file: discussion.diff_file } .md{ style: note_style } - = markdown(note.note, pipeline: :email, author: note.author, current_user: @recipient, issuable_reference_expansion_enabled: true) + = markdown(note.note, pipeline: :email, author: author, current_user: @recipient, issuable_reference_expansion_enabled: true) diff --git a/app/views/notify/_note_email.text.erb b/app/views/notify/_note_email.text.erb index 8e2f7e6f76e..8853519fb8d 100644 --- a/app/views/notify/_note_email.text.erb +++ b/app/views/notify/_note_email.text.erb @@ -1,13 +1,14 @@ <% note = local_assigns.fetch(:note, @note) -%> <% diff_limit = local_assigns.fetch(:diff_limit, nil) -%> <% target_url = local_assigns.fetch(:target_url, @target_url) -%> -<% discussion = note.discussion if note.part_of_discussion? -%> +<% author = local_assigns.fetch(:author) { note.author } -%> +<% discussion = local_assigns.fetch(:discussion) { note.discussion } if note.part_of_discussion? -%> -<%= sanitize_name(note.author_name) -%> +<%= sanitize_name(author.name) -%> <% if discussion.nil? -%> <%= 'commented' -%>: <% else -%> -<% if note.start_of_discussion? -%> +<% if discussion.first_note == note -%> <%= 'started a new discussion' -%> <% else -%> <%= 'commented on a discussion' -%> diff --git a/app/views/notify/new_review_email.html.haml b/app/views/notify/new_review_email.html.haml index d5399c872d5..fdabc204596 100644 --- a/app/views/notify/new_review_email.html.haml +++ b/app/views/notify/new_review_email.html.haml @@ -1,5 +1,6 @@ -= content_for :head do - = stylesheet_link_tag 'mailers/highlighted_diff_email' +- if @include_diff_discussion_stylesheet + = content_for :head do + = stylesheet_link_tag 'mailers/highlighted_diff_email' %table{ border: "0", cellpadding: "0", cellspacing: "0", style: "width:100%;margin:0 auto;border-collapse:separate;border-spacing:0;" } %tbody @@ -15,5 +16,9 @@ %tr %td{ style: "overflow:hidden;font-size:14px;line-height:1.4;display:grid;" } - @notes.each do |note| + -# Get preloaded note discussion + - discussion = @discussions[note.discussion_id] if note.part_of_discussion? + -# Preload project for discussions first note + - discussion.first_note.project = @project if discussion&.first_note - target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{note.id}") - = render 'note_email', note: note, diff_limit: 3, target_url: target_url, note_style: "border-bottom:1px solid #ededed;", skip_stylesheet_link: true + = render 'note_email', note: note, diff_limit: 3, target_url: target_url, note_style: "border-bottom:1px solid #ededed;", include_stylesheet_link: false, discussion: discussion, author: @author diff --git a/app/views/notify/new_review_email.text.erb b/app/views/notify/new_review_email.text.erb index 164735abad0..7bf878aefd0 100644 --- a/app/views/notify/new_review_email.text.erb +++ b/app/views/notify/new_review_email.text.erb @@ -4,8 +4,10 @@ -- <% @notes.each_with_index do |note, index| %> + + <% discussion = @discussions[note.discussion_id] if note.part_of_discussion?%> <% target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{note.id}") %> - <%= render 'note_email', note: note, diff_limit: 3, target_url: target_url %> + <%= render 'note_email', note: note, diff_limit: 3, target_url: target_url, discussion: discussion, author: @author %> <% if index != @notes.length-1 %> -- diff --git a/app/views/shared/_milestones_sort_dropdown.html.haml b/app/views/shared/_milestones_sort_dropdown.html.haml index 29c01343358..1c6eb7aa96b 100644 --- a/app/views/shared/_milestones_sort_dropdown.html.haml +++ b/app/views/shared/_milestones_sort_dropdown.html.haml @@ -1,22 +1,4 @@ -.dropdown.inline.gl-ml-3 - %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } - %span.light - - if @sort.present? - = milestone_sort_options_hash[@sort] - - else - = sort_title_due_date_soon - = sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3') - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort - %li - = link_to page_filter_path(sort: sort_value_due_date_soon) do - = sort_title_due_date_soon - = link_to page_filter_path(sort: sort_value_due_date_later) do - = sort_title_due_date_later - = link_to page_filter_path(sort: sort_value_start_date_soon) do - = sort_title_start_date_soon - = link_to page_filter_path(sort: sort_value_start_date_later) do - = sort_title_start_date_later - = link_to page_filter_path(sort: sort_value_name) do - = sort_title_name_asc - = link_to page_filter_path(sort: sort_value_name_desc) do - = sort_title_name_desc +- milestones_sort_options = milestones_sort_options_hash.map { |value, text| { value: value, text: text, href: page_filter_path(sort: value) } } + +%div{ data: {testid: 'milestone_sort_by_dropdown'} } + = gl_redirect_listbox_tag milestones_sort_options, @sort, class: 'gl-ml-3' diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml index 2c591c78585..8f228120c8e 100644 --- a/config/gitlab_loose_foreign_keys.yml +++ b/config/gitlab_loose_foreign_keys.yml @@ -207,10 +207,6 @@ pages_deployments: - table: ci_builds column: ci_build_id on_delete: async_nullify -project_pages_metadata: - - table: ci_job_artifacts - column: artifacts_archive_id - on_delete: async_nullify requirements_management_test_reports: - table: ci_builds column: build_id diff --git a/db/post_migrate/20220324110247_untrack_deletions_on_ci_job_artifacts.rb b/db/post_migrate/20220324110247_untrack_deletions_on_ci_job_artifacts.rb new file mode 100644 index 00000000000..d178c235e21 --- /dev/null +++ b/db/post_migrate/20220324110247_untrack_deletions_on_ci_job_artifacts.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class UntrackDeletionsOnCiJobArtifacts < Gitlab::Database::Migration[1.0] + include Gitlab::Database::MigrationHelpers::LooseForeignKeyHelpers + + enable_lock_retries! + + def up + untrack_record_deletions(:ci_job_artifacts) + end + + def down + track_record_deletions(:ci_job_artifacts) + end +end diff --git a/db/post_migrate/20220324180717_remove_project_pages_metadata_artifacts_archive_id_column.rb b/db/post_migrate/20220324180717_remove_project_pages_metadata_artifacts_archive_id_column.rb new file mode 100644 index 00000000000..287752b6b5c --- /dev/null +++ b/db/post_migrate/20220324180717_remove_project_pages_metadata_artifacts_archive_id_column.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class RemoveProjectPagesMetadataArtifactsArchiveIdColumn < Gitlab::Database::Migration[1.0] + disable_ddl_transaction! + + def up + remove_column :project_pages_metadata, :artifacts_archive_id + end + + def down + unless column_exists?(:project_pages_metadata, :artifacts_archive_id) + add_column :project_pages_metadata, :artifacts_archive_id, :bigint + end + + add_concurrent_index( + :project_pages_metadata, + :artifacts_archive_id, + name: "index_project_pages_metadata_on_artifacts_archive_id" + ) + end +end diff --git a/db/post_migrate/20220329175119_remove_leftover_ci_job_artifact_deletions.rb b/db/post_migrate/20220329175119_remove_leftover_ci_job_artifact_deletions.rb new file mode 100644 index 00000000000..f0c09598bfb --- /dev/null +++ b/db/post_migrate/20220329175119_remove_leftover_ci_job_artifact_deletions.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class RemoveLeftoverCiJobArtifactDeletions < Gitlab::Database::Migration[1.0] + disable_ddl_transaction! + + def up + # Delete all pending record deletions in the public.ci_job_artifacts until + # there are no more rows left. + loop do + result = execute <<~SQL + DELETE FROM "loose_foreign_keys_deleted_records" + WHERE + ("loose_foreign_keys_deleted_records"."partition", "loose_foreign_keys_deleted_records"."id") IN ( + SELECT "loose_foreign_keys_deleted_records"."partition", "loose_foreign_keys_deleted_records"."id" + FROM "loose_foreign_keys_deleted_records" + WHERE + "loose_foreign_keys_deleted_records"."fully_qualified_table_name" = 'public.ci_job_artifacts' AND + "loose_foreign_keys_deleted_records"."status" = 1 + LIMIT 100 + ) + SQL + + break if result.cmd_tuples == 0 + end + end + + def down + # no-op + end +end diff --git a/db/schema_migrations/20220324110247 b/db/schema_migrations/20220324110247 new file mode 100644 index 00000000000..0bfc9ff4e4e --- /dev/null +++ b/db/schema_migrations/20220324110247 @@ -0,0 +1 @@ +35aaf03898795800027c814a6f907af6f011bd5095cae7188234b46f4b2ebb90 \ No newline at end of file diff --git a/db/schema_migrations/20220324180717 b/db/schema_migrations/20220324180717 new file mode 100644 index 00000000000..b9bac081bcc --- /dev/null +++ b/db/schema_migrations/20220324180717 @@ -0,0 +1 @@ +56706c8d4139c63427838d37b7120c57fd39c7be4caee0ebba39a05cd3c453f7 \ No newline at end of file diff --git a/db/schema_migrations/20220329175119 b/db/schema_migrations/20220329175119 new file mode 100644 index 00000000000..5e0c0306594 --- /dev/null +++ b/db/schema_migrations/20220329175119 @@ -0,0 +1 @@ +ec5dfe48e13cdbce5fc0c67612cddf1a5b555b3c84e1869ee3a7f047f0f3f1a0 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 45f0fae3389..5bbfb65771a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -19278,7 +19278,6 @@ ALTER SEQUENCE project_mirror_data_id_seq OWNED BY project_mirror_data.id; CREATE TABLE project_pages_metadata ( project_id bigint NOT NULL, deployed boolean DEFAULT false NOT NULL, - artifacts_archive_id bigint, pages_deployment_id bigint ); @@ -28666,8 +28665,6 @@ CREATE UNIQUE INDEX index_project_mirror_data_on_project_id ON project_mirror_da CREATE INDEX index_project_mirror_data_on_status ON project_mirror_data USING btree (status); -CREATE INDEX index_project_pages_metadata_on_artifacts_archive_id ON project_pages_metadata USING btree (artifacts_archive_id); - CREATE INDEX index_project_pages_metadata_on_pages_deployment_id ON project_pages_metadata USING btree (pages_deployment_id); CREATE INDEX index_project_pages_metadata_on_project_id_and_deployed_is_true ON project_pages_metadata USING btree (project_id) WHERE (deployed = true); @@ -30968,8 +30965,6 @@ CREATE TRIGGER chat_names_loose_fk_trigger AFTER DELETE ON chat_names REFERENCIN CREATE TRIGGER ci_builds_loose_fk_trigger AFTER DELETE ON ci_builds REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); -CREATE TRIGGER ci_job_artifacts_loose_fk_trigger AFTER DELETE ON ci_job_artifacts REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); - CREATE TRIGGER ci_pipelines_loose_fk_trigger AFTER DELETE ON ci_pipelines REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); CREATE TRIGGER ci_runners_loose_fk_trigger AFTER DELETE ON ci_runners REFERENCING OLD TABLE AS old_table FOR EACH STATEMENT EXECUTE FUNCTION insert_into_loose_foreign_keys_deleted_records(); diff --git a/doc/api/deployments.md b/doc/api/deployments.md index 4a09f9a6605..c2e2b7f87ba 100644 --- a/doc/api/deployments.md +++ b/doc/api/deployments.md @@ -282,7 +282,8 @@ Deployments created by users on GitLab Premium or higher include the `approvals` "web_url": "http://localhost:3000/project_6_bot" }, "status": "approved", - "created_at": "2022-02-24T20:22:30.097Z" + "created_at": "2022-02-24T20:22:30.097Z", + "comment": "Looks good to me" } ], ... @@ -342,20 +343,7 @@ Deployments created by users on GitLab Premium or higher include the `approvals` { "status": "created", "pending_approval_count": 0, - "approvals": [ - { - "user": { - "id": 49, - "username": "project_6_bot", - "name": "****", - "state": "active", - "avatar_url": "https://www.gravatar.com/avatar/e83ac685f68ea07553ad3054c738c709?s=80&d=identicon", - "web_url": "http://localhost:3000/project_6_bot" - }, - "status": "approved", - "created_at": "2022-02-24T20:22:30.097Z" - } - ], + "approvals": [], ... } ``` @@ -420,7 +408,8 @@ Deployments created by users on GitLab Premium or higher include the `approvals` "web_url": "http://localhost:3000/project_6_bot" }, "status": "approved", - "created_at": "2022-02-24T20:22:30.097Z" + "created_at": "2022-02-24T20:22:30.097Z", + "comment": "Looks good to me" } ], ... diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 098788ee810..cee149b3f9f 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -9353,6 +9353,7 @@ Represents the total number of issues and their weights for a particular day. | `duration` | [`Int`](#int) | Duration of the job in seconds. | | `finishedAt` | [`Time`](#time) | When a job has finished running. | | `id` | [`JobID`](#jobid) | ID of the job. | +| `kind` | [`CiJobKind!`](#cijobkind) | Indicates the type of job. | | `manualJob` | [`Boolean`](#boolean) | Whether the job has a manual action. | | `name` | [`String`](#string) | Name of the job. | | `needs` | [`CiBuildNeedConnection`](#cibuildneedconnection) | References to builds that must complete before the jobs run. (see [Connections](#connections)) | @@ -17772,6 +17773,13 @@ Values for YAML processor result. | `INVALID` | Configuration file is not valid. | | `VALID` | Configuration file is valid. | +### `CiJobKind` + +| Value | Description | +| ----- | ----------- | +| `BRIDGE` | Bridge CI job connecting a parent and child pipeline. | +| `BUILD` | Standard CI job. | + ### `CiJobStatus` | Value | Description | diff --git a/doc/api/members.md b/doc/api/members.md index 10072baf2ff..1daa8acb794 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -63,6 +63,15 @@ Example response: "state": "active", "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", + "created_at": "2012-09-22T14:13:35Z", + "created_by": { + "id": 2, + "username": "john_doe", + "name": "John Doe", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": "2012-10-22T14:13:35Z", "access_level": 30, "group_saml_identity": null, @@ -75,6 +84,15 @@ Example response: "state": "active", "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", + "created_at": "2012-09-22T14:13:35Z", + "created_by": { + "id": 1, + "username": "raymond_smith", + "name": "Raymond Smith", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": "2012-10-22T14:13:35Z", "access_level": 30, "email": "john@example.com", @@ -132,6 +150,15 @@ Example response: "state": "active", "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", + "created_at": "2012-09-22T14:13:35Z", + "created_by": { + "id": 2, + "username": "john_doe", + "name": "John Doe", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": "2012-10-22T14:13:35Z", "access_level": 30, "group_saml_identity": null, @@ -144,6 +171,15 @@ Example response: "state": "active", "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", + "created_at": "2012-09-22T14:13:35Z", + "created_by": { + "id": 1, + "username": "raymond_smith", + "name": "Raymond Smith", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": "2012-10-22T14:13:35Z", "access_level": 30, "email": "john@example.com", @@ -161,6 +197,15 @@ Example response: "state": "active", "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", + "created_at": "2012-10-22T14:13:35Z", + "created_by": { + "id": 2, + "username": "john_doe", + "name": "John Doe", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": "2012-11-22T14:13:35Z", "access_level": 30, "group_saml_identity": null, @@ -201,6 +246,14 @@ Example response: "access_level": 30, "email": "john@example.com", "created_at": "2012-10-22T14:13:35Z", + "created_by": { + "id": 2, + "username": "john_doe", + "name": "John Doe", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": null, "group_saml_identity": null, "membership_state": "active" @@ -239,6 +292,15 @@ Example response: "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", "access_level": 30, + "created_at": "2012-10-22T14:13:35Z", + "created_by": { + "id": 2, + "username": "john_doe", + "name": "John Doe", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "email": "john@example.com", "expires_at": null, "group_saml_identity": null, @@ -454,6 +516,15 @@ Example response: "state": "active", "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", + "created_at": "2012-10-22T14:13:35Z", + "created_by": { + "id": 2, + "username": "john_doe", + "name": "John Doe", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": "2012-10-22T14:13:35Z", "access_level": 30, "email": "john@example.com", @@ -492,6 +563,15 @@ Example response: "state": "active", "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", + "created_at": "2012-10-22T14:13:35Z", + "created_by": { + "id": 2, + "username": "john_doe", + "name": "John Doe", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": "2012-10-22T14:13:35Z", "access_level": 40, "email": "john@example.com", @@ -529,6 +609,15 @@ Example response: "state": "active", "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", + "created_at": "2012-10-22T14:13:35Z", + "created_by": { + "id": 2, + "username": "john_doe", + "name": "John Doe", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": "2012-10-22T14:13:35Z", "access_level": 40, "email": "john@example.com", @@ -566,6 +655,15 @@ Example response: "state": "active", "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", "web_url": "http://192.168.1.8:3000/root", + "created_at": "2012-10-22T14:13:35Z", + "created_by": { + "id": 2, + "username": "john_doe", + "name": "John Doe", + "state": "active", + "avatar_url": "https://www.gravatar.com/avatar/c2525a7f58ae3776070e44c106c48e15?s=80&d=identicon", + "web_url": "http://192.168.1.8:3000/root" + }, "expires_at": "2012-10-22", "access_level": 40, "email": "john@example.com", diff --git a/doc/ci/index.md b/doc/ci/index.md index 54ce8b7f299..05e97613384 100644 --- a/doc/ci/index.md +++ b/doc/ci/index.md @@ -76,36 +76,36 @@ Certain operations can only be performed according to the GitLab CI/CD features, grouped by DevOps stage, include: -| Feature | Description | -|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------| -| **Configure** | | -| [Auto DevOps](../topics/autodevops/index.md) | Set up your app's entire lifecycle. | -| [ChatOps](chatops/index.md) | Trigger CI jobs from chat, with results sent back to the channel. | -| [Connect to cloud services](cloud_services/index.md) | Connect to cloud providers using OpenID Connect (OIDC) to retrieve temporary credentials to access services or secrets. | -| **Verify** | | -| [Browser Performance Testing](../user/project/merge_requests/browser_performance_testing.md) | Quickly determine the browser performance impact of pending code changes. | -| [Load Performance Testing](../user/project/merge_requests/load_performance_testing.md) | Quickly determine the server performance impact of pending code changes. | -| [CI services](services/index.md) | Link Docker containers with your base image. | -| [GitLab CI/CD for external repositories](ci_cd_for_external_repos/index.md) | Get the benefits of GitLab CI/CD combined with repositories in GitHub and Bitbucket Cloud. | -| [Interactive Web Terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. | -| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes. | -| [Unit test reports](unit_test_reports.md) | Identify test failures directly on merge requests. | -| [Using Docker images](docker/using_docker_images.md) | Use GitLab and GitLab Runner with Docker to build and test applications. | -| **Release** | | -| [Auto Deploy](../topics/autodevops/stages.md#auto-deploy) | Deploy your application to a production environment in a Kubernetes cluster. | -| [Building Docker images](docker/using_docker_build.md) | Maintain Docker-based projects using GitLab CI/CD. | -| [Canary Deployments](../user/project/canary_deployments.md) | Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature. | -| [Deploy boards](../user/project/deploy_boards.md) | Check the current health and status of each CI/CD environment running on Kubernetes. | -| [Feature Flags](../operations/feature_flags.md) | Deploy your features behind Feature Flags. | -| [GitLab Pages](../user/project/pages/index.md) | Deploy static websites. | -| [GitLab Releases](../user/project/releases/index.md) | Add release notes to Git tags. | -| [Cloud deployment](cloud_deployment/index.md) | Deploy your application to a main cloud provider. | -| **Secure** | | -| [Code Quality](../user/project/merge_requests/code_quality.md) | Analyze your source code quality. | -| [Container Scanning](../user/application_security/container_scanning/index.md) | Check your Docker containers for known vulnerabilities. | -| [Dependency Scanning](../user/application_security/dependency_scanning/index.md) | Analyze your dependencies for known vulnerabilities. | -| [License Compliance](../user/compliance/license_compliance/index.md) | Search your project dependencies for their licenses. | -| [Security Test reports](../user/application_security/index.md) | Check for app vulnerabilities. | +| Feature | Description | +|:---------------------------------------------------------------------------------------------|:------------| +| **Configure** | | +| [Auto DevOps](../topics/autodevops/index.md) | Set up your app's entire lifecycle. | +| [ChatOps](chatops/index.md) | Trigger CI jobs from chat, with results sent back to the channel. | +| [Connect to cloud services](cloud_services/index.md) | Connect to cloud providers using OpenID Connect (OIDC) to retrieve temporary credentials to access services or secrets. | +| **Verify** | | +| [Browser Performance Testing](../user/project/merge_requests/browser_performance_testing.md) | Quickly determine the browser performance impact of pending code changes. | +| [Load Performance Testing](../user/project/merge_requests/load_performance_testing.md) | Quickly determine the server performance impact of pending code changes. | +| [CI services](services/index.md) | Link Docker containers with your base image. | +| [GitLab CI/CD for external repositories](ci_cd_for_external_repos/index.md) | Get the benefits of GitLab CI/CD combined with repositories in GitHub and Bitbucket Cloud. | +| [Interactive Web Terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. | +| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes. | +| [Unit test reports](unit_test_reports.md) | Identify test failures directly on merge requests. | +| [Using Docker images](docker/using_docker_images.md) | Use GitLab and GitLab Runner with Docker to build and test applications. | +| **Release** | | +| [Auto Deploy](../topics/autodevops/stages.md#auto-deploy) | Deploy your application to a production environment in a Kubernetes cluster. | +| [Building Docker images](docker/using_docker_build.md) | Maintain Docker-based projects using GitLab CI/CD. | +| [Canary Deployments](../user/project/canary_deployments.md) | Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature. | +| [Deploy boards](../user/project/deploy_boards.md) | Check the current health and status of each CI/CD environment running on Kubernetes. | +| [Feature Flags](../operations/feature_flags.md) | Deploy your features behind Feature Flags. | +| [GitLab Pages](../user/project/pages/index.md) | Deploy static websites. | +| [GitLab Releases](../user/project/releases/index.md) | Add release notes to Git tags. | +| [Cloud deployment](cloud_deployment/index.md) | Deploy your application to a main cloud provider. | +| **Secure** | | +| [Code Quality](../user/project/merge_requests/code_quality.md) | Analyze your source code quality. | +| [Container Scanning](../user/application_security/container_scanning/index.md) | Check your Docker containers for known vulnerabilities. | +| [Dependency Scanning](../user/application_security/dependency_scanning/index.md) | Analyze your dependencies for known vulnerabilities. | +| [License Compliance](../user/compliance/license_compliance/index.md) | Search your project dependencies for their licenses. | +| [Security Test reports](../user/application_security/index.md) | Check for app vulnerabilities. | ## Examples diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 11f623641c1..8d5f7104301 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -120,7 +120,7 @@ the following table) as these were used for development and testing: | GitLab version | Minimum PostgreSQL version | |----------------|----------------------------| | 13.0 | 11 | -| 14.0 | 12 | +| 14.0 | 12.10 | You must also ensure the following extensions are loaded into every GitLab database. [Read more about this requirement, and troubleshooting](postgresql_extensions.md). diff --git a/doc/update/index.md b/doc/update/index.md index b9951f3997e..cb0c7b63072 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -398,6 +398,8 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap ### 14.8.0 +- If upgrading from a version earlier than 14.6.5, 14.7.4, or 14.8.2, please review the [Critical Security Release: 14.8.2, 14.7.4, and 14.6.5](https://about.gitlab.com/releases/2022/02/25/critical-security-release-gitlab-14-8-2-released/) blog post. + Updating to 14.8.2 or later will reset runner registration tokens for your groups and projects. - The agent server for Kubernetes [is enabled by default](https://about.gitlab.com/releases/2022/02/22/gitlab-14-8-released/#the-agent-server-for-kubernetes-is-enabled-by-default) on Omnibus installations. If you run GitLab at scale, such as [the reference architectures](../administration/reference_architectures/index.md), @@ -421,11 +423,15 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap ### 14.7.0 - See [LFS objects import and mirror issue in GitLab 14.6.0 to 14.7.2](#lfs-objects-import-and-mirror-issue-in-gitlab-1460-to-1472). +- If upgrading from a version earlier than 14.6.5, 14.7.4, or 14.8.2, please review the [Critical Security Release: 14.8.2, 14.7.4, and 14.6.5](https://about.gitlab.com/releases/2022/02/25/critical-security-release-gitlab-14-8-2-released/) blog post. + Updating to 14.7.4 or later will reset runner registration tokens for your groups and projects. ### 14.6.0 - See [LFS objects import and mirror issue in GitLab 14.6.0 to 14.7.2](#lfs-objects-import-and-mirror-issue-in-gitlab-1460-to-1472). - +- If upgrading from a version earlier than 14.6.5, 14.7.4, or 14.8.2, please review the [Critical Security Release: 14.8.2, 14.7.4, and 14.6.5](https://about.gitlab.com/releases/2022/02/25/critical-security-release-gitlab-14-8-2-released/) blog post. + Updating to 14.6.5 or later will reset runner registration tokens for your groups and projects. + ### 14.5.0 - When `make` is run, Gitaly builds are now created in `_build/bin` and no longer in the root directory of the source directory. If you diff --git a/lib/api/entities/member.rb b/lib/api/entities/member.rb index 87f03adba31..7ce1e73a043 100644 --- a/lib/api/entities/member.rb +++ b/lib/api/entities/member.rb @@ -6,6 +6,7 @@ module API expose :user, merge: true, using: UserBasic expose :access_level expose :created_at + expose :created_by, with: UserBasic, expose_nil: false expose :expires_at end end diff --git a/spec/features/groups/milestones_sorting_spec.rb b/spec/features/groups/milestones_sorting_spec.rb index a06e64fdee0..22d7ff91d41 100644 --- a/spec/features/groups/milestones_sorting_spec.rb +++ b/spec/features/groups/milestones_sorting_spec.rb @@ -17,7 +17,7 @@ RSpec.describe 'Milestones sorting', :js do sign_in(user) end - it 'visit group milestones and sort by due_date_asc' do + it 'visit group milestones and sort by due_date_asc', :js do visit group_milestones_path(group) expect(page).to have_button('Due soon') @@ -27,13 +27,13 @@ RSpec.describe 'Milestones sorting', :js do expect(page.all('ul.content-list > li strong > a').map(&:text)).to eq(['v2.0', 'v2.0', 'v3.0', 'v1.0', 'v1.0']) end - click_button 'Due soon' + within '[data-testid=milestone_sort_by_dropdown]' do + click_button 'Due soon' + expect(find('.gl-new-dropdown-contents').all('.gl-new-dropdown-item-text-wrapper p').map(&:text)).to eq(['Due soon', 'Due later', 'Start soon', 'Start later', 'Name, ascending', 'Name, descending']) - expect(find('ul.dropdown-menu-sort li').all('a').map(&:text)).to eq(['Due soon', 'Due later', 'Start soon', 'Start later', 'Name, ascending', 'Name, descending']) - - click_link 'Due later' - - expect(page).to have_button('Due later') + click_button 'Due later' + expect(page).to have_button('Due later') + end # assert descending sorting within '.milestones' do diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb index 565c61cfaa0..2ad820e4a06 100644 --- a/spec/features/projects/milestones/milestones_sorting_spec.rb +++ b/spec/features/projects/milestones/milestones_sorting_spec.rb @@ -5,49 +5,55 @@ require 'spec_helper' RSpec.describe 'Milestones sorting', :js do let(:user) { create(:user) } let(:project) { create(:project, name: 'test', namespace: user.namespace) } + let(:milestones_for_sort_by) do + { + 'Due later' => %w[b c a], + 'Name, ascending' => %w[a b c], + 'Name, descending' => %w[c b a], + 'Start later' => %w[a c b], + 'Start soon' => %w[b c a], + 'Due soon' => %w[a c b] + } + end + + let(:ordered_milestones) do + ['Due soon', 'Due later', 'Start soon', 'Start later', 'Name, ascending', 'Name, descending'] + end before do - # Milestones - create(:milestone, - due_date: 10.days.from_now, - created_at: 2.hours.ago, - title: "aaa", project: project) - create(:milestone, - due_date: 11.days.from_now, - created_at: 1.hour.ago, - title: "bbb", project: project) + create(:milestone, start_date: 7.days.from_now, due_date: 10.days.from_now, title: "a", project: project) + create(:milestone, start_date: 6.days.from_now, due_date: 11.days.from_now, title: "c", project: project) + create(:milestone, start_date: 5.days.from_now, due_date: 12.days.from_now, title: "b", project: project) sign_in(user) end - it 'visit project milestones and sort by due_date_asc' do + it 'visit project milestones and sort by various orders' do visit project_milestones_path(project) expect(page).to have_button('Due soon') - # assert default sorting + # assert default sorting order within '.milestones' do - expect(page.all('ul.content-list > li').first.text).to include('aaa') - expect(page.all('ul.content-list > li').last.text).to include('bbb') + expect(page.all('ul.content-list > li strong > a').map(&:text)).to eq(%w[a c b]) end - click_button 'Due soon' + # assert milestones listed for given sort order + selected_sort_order = 'Due soon' + milestones_for_sort_by.each do |sort_by, expected_milestones| + within '[data-testid=milestone_sort_by_dropdown]' do + click_button selected_sort_order + milestones = find('.gl-new-dropdown-contents').all('.gl-new-dropdown-item-text-wrapper p').map(&:text) + expect(milestones).to eq(ordered_milestones) - sort_options = find('ul.dropdown-menu-sort li').all('a').collect(&:text) + click_button sort_by + expect(page).to have_button(sort_by) + end - expect(sort_options[0]).to eq('Due soon') - expect(sort_options[1]).to eq('Due later') - expect(sort_options[2]).to eq('Start soon') - expect(sort_options[3]).to eq('Start later') - expect(sort_options[4]).to eq('Name, ascending') - expect(sort_options[5]).to eq('Name, descending') + within '.milestones' do + expect(page.all('ul.content-list > li strong > a').map(&:text)).to eq(expected_milestones) + end - click_link 'Due later' - - expect(page).to have_button('Due later') - - within '.milestones' do - expect(page.all('ul.content-list > li').first.text).to include('bbb') - expect(page.all('ul.content-list > li').last.text).to include('aaa') + selected_sort_order = sort_by end end end diff --git a/spec/graphql/types/ci/job_kind_enum_spec.rb b/spec/graphql/types/ci/job_kind_enum_spec.rb new file mode 100644 index 00000000000..b48d20b71e2 --- /dev/null +++ b/spec/graphql/types/ci/job_kind_enum_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CiJobKind'] do + it 'exposes some job type values' do + expect(described_class.values.keys).to match_array( + (%w[BRIDGE BUILD]) + ) + end +end diff --git a/spec/graphql/types/ci/job_type_spec.rb b/spec/graphql/types/ci/job_type_spec.rb index 47d697ab8b8..655c3636883 100644 --- a/spec/graphql/types/ci/job_type_spec.rb +++ b/spec/graphql/types/ci/job_type_spec.rb @@ -21,6 +21,7 @@ RSpec.describe Types::Ci::JobType do downstreamPipeline finished_at id + kind manual_job name needs diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index e2ee63078bb..b6ad66f41b5 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -2181,18 +2181,46 @@ RSpec.describe Notify do context 'when diff note' do let!(:notes) { create_list(:diff_note_on_merge_request, 3, review: review, project: project, author: review.author, noteable: merge_request) } - it 'links to notes' do + it 'links to notes and discussions', :aggregate_failures do + reply_note = create(:diff_note_on_merge_request, review: review, project: project, author: review.author, noteable: merge_request, in_reply_to: notes.first) + review.notes.each do |note| # Text part expect(subject.text_part.body.raw_source).to include( project_merge_request_url(project, merge_request, anchor: "note_#{note.id}") ) + + if note == reply_note + expect(subject.text_part.body.raw_source).to include("commented on a discussion on #{note.discussion.file_path}") + else + expect(subject.text_part.body.raw_source).to include("started a new discussion on #{note.discussion.file_path}") + end end end it 'includes only one link to the highlighted_diff_email' do expect(subject.html_part.body.raw_source).to include('assets/mailers/highlighted_diff_email').once end + + it 'avoids N+1 cached queries when rendering html', :use_sql_query_cache, :request_store do + control_count = ActiveRecord::QueryRecorder.new(query_recorder_debug: true, skip_cached: false) do + subject.html_part + end + + create_list(:diff_note_on_merge_request, 3, review: review, project: project, author: review.author, noteable: merge_request) + + expect { described_class.new_review_email(recipient.id, review.id).html_part }.not_to exceed_all_query_limit(control_count) + end + + it 'avoids N+1 cached queries when rendering text', :use_sql_query_cache, :request_store do + control_count = ActiveRecord::QueryRecorder.new(query_recorder_debug: true, skip_cached: false) do + subject.text_part + end + + create_list(:diff_note_on_merge_request, 3, review: review, project: project, author: review.author, noteable: merge_request) + + expect { described_class.new_review_email(recipient.id, review.id).text_part }.not_to exceed_all_query_limit(control_count) + end end it 'contains review author name' do diff --git a/spec/migrations/20220329175119_remove_leftover_ci_job_artifact_deletions_spec.rb b/spec/migrations/20220329175119_remove_leftover_ci_job_artifact_deletions_spec.rb new file mode 100644 index 00000000000..13884007af2 --- /dev/null +++ b/spec/migrations/20220329175119_remove_leftover_ci_job_artifact_deletions_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true +require 'spec_helper' + +require_migration! + +RSpec.describe RemoveLeftoverCiJobArtifactDeletions do + let(:deleted_records) { table(:loose_foreign_keys_deleted_records) } + + target_table_name = Ci::JobArtifact.table_name + + let(:pending_record1) do + deleted_records.create!( + id: 1, + fully_qualified_table_name: "public.#{target_table_name}", + primary_key_value: 1, + status: 1 + ) + end + + let(:pending_record2) do + deleted_records.create!( + id: 2, + fully_qualified_table_name: "public.#{target_table_name}", + primary_key_value: 2, + status: 1 + ) + end + + let(:other_pending_record1) do + deleted_records.create!( + id: 3, + fully_qualified_table_name: 'public.projects', + primary_key_value: 1, + status: 1 + ) + end + + let(:other_pending_record2) do + deleted_records.create!( + id: 4, + fully_qualified_table_name: 'public.ci_builds', + primary_key_value: 1, + status: 1 + ) + end + + let(:processed_record1) do + deleted_records.create!( + id: 5, + fully_qualified_table_name: 'public.external_pull_requests', + primary_key_value: 3, + status: 2 + ) + end + + let(:other_processed_record1) do + deleted_records.create!( + id: 6, + fully_qualified_table_name: 'public.ci_builds', + primary_key_value: 2, + status: 2 + ) + end + + let!(:persisted_ids_before) do + [ + pending_record1, + pending_record2, + other_pending_record1, + other_pending_record2, + processed_record1, + other_processed_record1 + ].map(&:id).sort + end + + let!(:persisted_ids_after) do + [ + other_pending_record1, + other_pending_record2, + processed_record1, + other_processed_record1 + ].map(&:id).sort + end + + def all_ids + deleted_records.all.map(&:id).sort + end + + it 'deletes pending external_pull_requests records' do + expect { migrate! }.to change { all_ids }.from(persisted_ids_before).to(persisted_ids_after) + end +end diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index bd0397e0396..2b046142b59 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -700,10 +700,6 @@ RSpec.describe Ci::JobArtifact do MSG end - it_behaves_like 'it has loose foreign keys' do - let(:factory_name) { :ci_job_artifact } - end - context 'loose foreign key on ci_job_artifacts.project_id' do it_behaves_like 'cleanup by a loose foreign key' do let!(:parent) { create(:project) } diff --git a/spec/requests/api/graphql/ci/job_spec.rb b/spec/requests/api/graphql/ci/job_spec.rb index b0514a0a963..ddb2664d353 100644 --- a/spec/requests/api/graphql/ci/job_spec.rb +++ b/spec/requests/api/graphql/ci/job_spec.rb @@ -52,6 +52,7 @@ RSpec.describe 'Query.project(fullPath).pipelines.job(id)' do 'name' => job_2.name, 'allowFailure' => job_2.allow_failure, 'duration' => 25, + 'kind' => 'BUILD', 'queuedDuration' => 2.0, 'status' => job_2.status.upcase ) diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb index b191b585d06..2d1bb45390b 100644 --- a/spec/requests/api/graphql/ci/jobs_spec.rb +++ b/spec/requests/api/graphql/ci/jobs_spec.rb @@ -155,6 +155,56 @@ RSpec.describe 'Query.project.pipeline' do end end + describe '.jobs.kind' do + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + + let(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + pipeline(iid: "#{pipeline.iid}") { + stages { + nodes { + groups{ + nodes { + jobs { + nodes { + kind + } + } + } + } + } + } + } + } + } + ) + end + + context 'when the job is a build' do + it 'returns BUILD' do + create(:ci_build, pipeline: pipeline) + + post_graphql(query, current_user: user) + + job_data = graphql_data_at(:project, :pipeline, :stages, :nodes, :groups, :nodes, :jobs, :nodes).first + expect(job_data['kind']).to eq 'BUILD' + end + end + + context 'when the job is a bridge' do + it 'returns BRIDGE' do + create(:ci_bridge, pipeline: pipeline) + + post_graphql(query, current_user: user) + + job_data = graphql_data_at(:project, :pipeline, :stages, :nodes, :groups, :nodes, :jobs, :nodes).first + expect(job_data['kind']).to eq 'BRIDGE' + end + end + end + describe '.jobs.artifacts' do let_it_be(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 561d81f9860..6bacb3a59b2 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -11,16 +11,16 @@ RSpec.describe API::Members do let(:project) do create(:project, :public, creator_id: maintainer.id, namespace: maintainer.namespace) do |project| - project.add_developer(developer) project.add_maintainer(maintainer) + project.add_developer(developer, current_user: maintainer) project.request_access(access_requester) end end let!(:group) do create(:group, :public) do |group| - group.add_developer(developer) group.add_owner(maintainer) + group.add_developer(developer, maintainer) create(:group_member, :minimal_access, source: group, user: user_with_minimal_access) group.request_access(access_requester) end @@ -50,6 +50,10 @@ RSpec.describe API::Members do expect(json_response).to be_an Array expect(json_response.size).to eq(2) expect(json_response.map { |u| u['id'] }).to match_array [maintainer.id, developer.id] + expect(json_response).to contain_exactly( + a_hash_including('created_by' => a_hash_including('id' => maintainer.id)), + hash_not_including('created_by') + ) end end end diff --git a/spec/views/shared/_milestones_sort_dropdown.html.haml_spec.rb b/spec/views/shared/_milestones_sort_dropdown.html.haml_spec.rb new file mode 100644 index 00000000000..2fc286d607a --- /dev/null +++ b/spec/views/shared/_milestones_sort_dropdown.html.haml_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'shared/_milestones_sort_dropdown.html.haml' do + describe 'render' do + describe 'when a sort option is not selected' do + it 'renders a default sort option' do + render 'shared/milestones_sort_dropdown' + + expect(rendered).to have_content 'Due soon' + end + end + + describe 'when a sort option is selected' do + before do + assign(:sort, 'due_date_desc') + + render 'shared/milestones_sort_dropdown' + end + + it 'renders the selected sort option' do + expect(rendered).to have_content 'Due later' + end + end + end +end