diff options
43 files changed, 305 insertions, 200 deletions
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index 812cf8425a9..c41adb18819 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -273,9 +273,6 @@ rules: - <<: *if-not-ee when: never - - <<: *if-master-push - changes: *code-backstage-qa-patterns - - <<: *if-master-schedule-2-hourly - <<: *if-security-merge-request changes: *code-backstage-qa-patterns - <<: *if-merge-request-title-as-if-foss @@ -291,9 +288,6 @@ rules: - <<: *if-not-ee when: never - - <<: *if-master-push - changes: *code-backstage-patterns - - <<: *if-master-schedule-2-hourly - <<: *if-security-merge-request changes: *code-backstage-patterns - <<: *if-merge-request-title-as-if-foss @@ -364,9 +358,6 @@ rules: - <<: *if-not-ee when: never - - <<: *if-master-push - changes: *code-qa-patterns - - <<: *if-master-schedule-2-hourly - <<: *if-security-merge-request changes: *code-qa-patterns - <<: *if-merge-request-title-as-if-foss @@ -412,9 +403,6 @@ rules: - <<: *if-not-ee when: never - - <<: *if-master-push - changes: *code-backstage-patterns - - <<: *if-master-schedule-2-hourly - <<: *if-security-merge-request changes: *code-backstage-patterns - <<: *if-merge-request-title-as-if-foss diff --git a/app/assets/javascripts/alert_management/components/alert_details.vue b/app/assets/javascripts/alert_management/components/alert_details.vue index 50410380332..ed6b4b7fdb2 100644 --- a/app/assets/javascripts/alert_management/components/alert_details.vue +++ b/app/assets/javascripts/alert_management/components/alert_details.vue @@ -15,7 +15,8 @@ import { s__ } from '~/locale'; import query from '../graphql/queries/details.query.graphql'; import { fetchPolicies } from '~/lib/graphql'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; +import highlightCurrentUser from '~/behaviors/markdown/highlight_current_user'; +import initUserPopovers from '~/user_popovers'; import { ALERTS_SEVERITY_LABELS, trackAlertsDetailsViewsOptions } from '../constants'; import createIssueQuery from '../graphql/mutations/create_issue_from_alert.graphql'; import { visitUrl, joinPaths } from '~/lib/utils/url_utility'; @@ -51,7 +52,6 @@ export default { AlertSidebar, SystemNote, }, - mixins: [glFeatureFlagsMixin()], props: { alertId: { type: String, @@ -116,6 +116,12 @@ export default { 'right-sidebar-expanded': true, }); }, + updated() { + this.$nextTick(() => { + highlightCurrentUser(this.$el.querySelectorAll('.gfm-project_member')); + initUserPopovers(this.$el.querySelectorAll('.js-user-link')); + }); + }, methods: { dismissError() { this.isErrorDismissed = true; @@ -187,7 +193,7 @@ export default { <div v-if="alert" class="alert-management-details gl-relative" - :class="{ 'pr-8': sidebarCollapsed }" + :class="{ 'pr-sm-8': sidebarCollapsed }" > <div class="gl-display-flex gl-justify-content-space-between gl-align-items-baseline gl-px-1 py-3 py-md-4 gl-border-b-1 gl-border-b-gray-200 gl-border-b-solid flex-column flex-sm-row" @@ -294,8 +300,8 @@ export default { </div> <div class="gl-pl-2" data-testid="service">{{ alert.service }}</div> </div> - <template v-if="glFeatures.alertAssignee"> - <div v-if="alert.notes" class="issuable-discussion"> + <template> + <div v-if="alert.notes.nodes" class="issuable-discussion py-5"> <ul class="notes main-notes-list timeline"> <system-note v-for="note in alert.notes.nodes" :key="note.id" :note="note" /> </ul> diff --git a/app/assets/javascripts/alert_management/components/alert_sidebar.vue b/app/assets/javascripts/alert_management/components/alert_sidebar.vue index 38a250abc30..dcd22e2062e 100644 --- a/app/assets/javascripts/alert_management/components/alert_sidebar.vue +++ b/app/assets/javascripts/alert_management/components/alert_sidebar.vue @@ -1,5 +1,4 @@ <script> -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import SidebarHeader from './sidebar/sidebar_header.vue'; import SidebarTodo from './sidebar/sidebar_todo.vue'; import SidebarStatus from './sidebar/sidebar_status.vue'; @@ -12,7 +11,6 @@ export default { SidebarTodo, SidebarStatus, }, - mixins: [glFeatureFlagsMixin()], props: { sidebarCollapsed: { type: Boolean, @@ -50,14 +48,13 @@ export default { @alert-sidebar-error="$emit('alert-sidebar-error', $event)" /> <sidebar-assignees - v-if="glFeatures.alertAssignee" :project-path="projectPath" :alert="alert" + :sidebar-collapsed="sidebarCollapsed" @alert-refresh="$emit('alert-refresh')" @toggle-sidebar="$emit('toggle-sidebar')" @alert-sidebar-error="$emit('alert-sidebar-error', $event)" /> - <!-- TODO: Remove after adding extra attribute blocks to sidebar --> <div class="block"></div> </div> </aside> diff --git a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue b/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue index bd9bf32df12..453a3901665 100644 --- a/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue +++ b/app/assets/javascripts/alert_management/components/sidebar/sidebar_assignees.vue @@ -51,6 +51,10 @@ export default { required: false, default: true, }, + sidebarCollapsed: { + type: Boolean, + required: false, + }, }, data() { return { @@ -62,10 +66,19 @@ export default { }; }, computed: { - assignedUsers() { - return this.alert.assignees.nodes.length > 0 - ? this.alert.assignees.nodes[0].username - : s__('AlertManagement|Unassigned'); + currentUser() { + return gon?.current_username; + }, + userName() { + return this.alert?.assignees?.nodes[0]?.username; + }, + assignedUser() { + return this.userName || s__('AlertManagement|None'); + }, + sortedUsers() { + return this.users + .map(user => ({ ...user, active: this.isActive(user.username) })) + .sort((a, b) => (a.active === b.active ? 0 : a.active ? -1 : 1)); // eslint-disable-line no-nested-ternary }, dropdownClass() { return this.isDropdownShowing ? 'show' : 'gl-display-none'; @@ -115,7 +128,7 @@ export default { per_page: 20, active: true, current_user: true, - project_id: gon.current_project_id, + project_id: gon?.current_project_id, }, }) .then(({ data }) => { @@ -159,12 +172,11 @@ export default { <div ref="status" class="sidebar-collapsed-icon" @click="$emit('toggle-sidebar')"> <gl-icon name="user" :size="14" /> <gl-loading-icon v-if="isUpdating" /> - <p v-else class="collapse-truncated-title px-1">{{ assignedUsers }}</p> </div> <gl-tooltip :target="() => $refs.status" boundary="viewport" placement="left"> <gl-sprintf :message="s__('AlertManagement|Alert assignee(s): %{assignees}')"> <template #assignees> - {{ assignedUsers }} + {{ assignedUser }} </template> </gl-sprintf> </gl-tooltip> @@ -187,7 +199,7 @@ export default { <div class="dropdown dropdown-menu-selectable" :class="dropdownClass"> <gl-dropdown ref="dropdown" - :text="assignedUsers" + :text="assignedUser" class="w-100" toggle-class="dropdown-menu-toggle" variant="outline-default" @@ -195,7 +207,7 @@ export default { @hide="hideDropdown" > <div class="dropdown-title"> - <span class="alert-title">{{ s__('AlertManagement|Assign Assignees') }}</span> + <span class="alert-title">{{ s__('AlertManagement|Assign To') }}</span> <gl-button :aria-label="__('Close')" variant="link" @@ -215,34 +227,25 @@ export default { </div> <div class="dropdown-content dropdown-body"> <template v-if="userListValid"> - <gl-dropdown-item @click="updateAlertAssignees('')"> + <gl-dropdown-item + :active="!userName" + active-class="is-active" + @click="updateAlertAssignees('')" + > {{ s__('AlertManagement|Unassigned') }} </gl-dropdown-item> <gl-dropdown-divider /> <gl-dropdown-header class="mt-0"> - {{ s__('AlertManagement|Assignee(s)') }} + {{ s__('AlertManagement|Assignee') }} </gl-dropdown-header> - - <template v-for="user in users"> - <sidebar-assignee - v-if="isActive(user.username)" - :key="user.username" - :user="user" - :active="true" - @update-alert-assignees="updateAlertAssignees" - /> - </template> - <gl-dropdown-divider /> - <template v-for="user in users"> - <sidebar-assignee - v-if="!isActive(user.username)" - :key="user.username" - :user="user" - :active="false" - @update-alert-assignees="updateAlertAssignees" - /> - </template> + <sidebar-assignee + v-for="user in sortedUsers" + :key="user.username" + :user="user" + :active="user.active" + @update-alert-assignees="updateAlertAssignees" + /> </template> <gl-dropdown-item v-else-if="userListEmpty"> {{ s__('AlertManagement|No Matching Results') }} @@ -253,16 +256,21 @@ export default { </div> <gl-loading-icon v-if="isUpdating" :inline="true" /> - <p - v-else-if="!isDropdownShowing" - class="value gl-m-0" - :class="{ 'no-value': !alert.assignees.nodes }" - > - <span v-if="alert.assignees.nodes" class="gl-text-gray-700" data-testid="assigned-users">{{ - assignedUsers + <p v-else-if="!isDropdownShowing" class="value gl-m-0" :class="{ 'no-value': !userName }"> + <span v-if="userName" class="gl-text-gray-700" data-testid="assigned-users">{{ + assignedUser }}</span> - <span v-else> - {{ s__('AlertManagement|None') }} + <span v-else class="gl-display-flex gl-align-items-center"> + {{ s__('AlertManagement|None -') }} + <gl-button + class="gl-pl-2" + href="#" + variant="link" + data-testid="unassigned-users" + @click="updateAlertAssignees(currentUser)" + > + {{ s__('AlertManagement| assign yourself') }} + </gl-button> </span> </p> </div> diff --git a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue index e8d07daea46..9042d51aecf 100644 --- a/app/assets/javascripts/alert_management/components/system_notes/system_note.vue +++ b/app/assets/javascripts/alert_management/components/system_notes/system_note.vue @@ -16,6 +16,13 @@ export default { noteAnchorId() { return `note_${this.note?.id?.split('/').pop()}`; }, + noteAuthor() { + const { + author, + author: { id }, + } = this.note; + return { ...author, id: id?.split('/').pop() }; + }, iconHtml() { return spriteIcon('user'); }, @@ -29,7 +36,7 @@ export default { <div class="timeline-icon" v-html="iconHtml"></div> <div class="timeline-content"> <div class="note-header"> - <note-header :author="note.author" :created-at="note.createdAt" :note-id="note.id"> + <note-header :author="noteAuthor" :created-at="note.createdAt" :note-id="note.id"> <span v-html="note.bodyHtml"></span> </note-header> </div> diff --git a/app/assets/javascripts/registry/explorer/stores/mutations.js b/app/assets/javascripts/registry/explorer/stores/mutations.js index 848c556e738..706f6489287 100644 --- a/app/assets/javascripts/registry/explorer/stores/mutations.js +++ b/app/assets/javascripts/registry/explorer/stores/mutations.js @@ -1,5 +1,5 @@ import * as types from './mutation_types'; -import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; +import { parseIntPagination, normalizeHeaders, parseBoolean } from '~/lib/utils/common_utils'; import { IMAGE_DELETE_SCHEDULED_STATUS, IMAGE_FAILED_DELETED_STATUS } from '../constants/index'; export default { @@ -7,8 +7,8 @@ export default { state.config = { ...config, expirationPolicy: config.expirationPolicy ? JSON.parse(config.expirationPolicy) : undefined, - isGroupPage: config.isGroupPage !== undefined, - isAdmin: config.isAdmin !== undefined, + isGroupPage: parseBoolean(config.isGroupPage), + isAdmin: parseBoolean(config.isAdmin), }; }, diff --git a/app/assets/stylesheets/pages/alert_management/details.scss b/app/assets/stylesheets/pages/alert_management/details.scss index c96127f05d4..591a26e5941 100644 --- a/app/assets/stylesheets/pages/alert_management/details.scss +++ b/app/assets/stylesheets/pages/alert_management/details.scss @@ -51,13 +51,23 @@ } .assignee-dropdown-item { - button { + .dropdown-item { display: flex; align-items: center; &::before { top: 50% !important; } + + &.is-active { + &:last-child { + border-bottom: 1px solid $gray-200; + } + } } } + + .note-header-info { + margin-top: 1px; + } } diff --git a/app/controllers/projects/alert_management_controller.rb b/app/controllers/projects/alert_management_controller.rb index c5c0d6ee344..054dc8e6a35 100644 --- a/app/controllers/projects/alert_management_controller.rb +++ b/app/controllers/projects/alert_management_controller.rb @@ -2,9 +2,6 @@ class Projects::AlertManagementController < Projects::ApplicationController before_action :authorize_read_alert_management_alert! - before_action do - push_frontend_feature_flag(:alert_assignee, project) - end def index end diff --git a/app/graphql/resolvers/alert_management/alert_resolver.rb b/app/graphql/resolvers/alert_management/alert_resolver.rb index 237f0b8999d..71a7615685a 100644 --- a/app/graphql/resolvers/alert_management/alert_resolver.rb +++ b/app/graphql/resolvers/alert_management/alert_resolver.rb @@ -33,7 +33,8 @@ module Resolvers def preloads { - assignees: [:assignees] + assignees: [:assignees], + notes: [:ordered_notes, { ordered_notes: [:system_note_metadata, :project, :noteable] }] } end end diff --git a/app/graphql/types/alert_management/alert_type.rb b/app/graphql/types/alert_management/alert_type.rb index cb55a9323b1..8215ccb152c 100644 --- a/app/graphql/types/alert_management/alert_type.rb +++ b/app/graphql/types/alert_management/alert_type.rb @@ -91,10 +91,8 @@ module Types null: true, description: 'Assignees of the alert' - def assignees - return User.none unless Feature.enabled?(:alert_assignee, object.project) - - object.assignees + def notes + object.ordered_notes end end end diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb index 8dade750385..51efe350774 100644 --- a/app/models/alert_management/alert.rb +++ b/app/models/alert_management/alert.rb @@ -32,6 +32,7 @@ module AlertManagement has_many :assignees, through: :alert_assignees has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent + has_many :ordered_notes, -> { fresh }, as: :noteable, class_name: 'Note' has_many :user_mentions, class_name: 'AlertManagement::AlertUserMention', foreign_key: :alert_management_alert_id has_internal_id :iid, scope: :project, init: ->(s) { s.project.alert_management_alerts.maximum(:iid) } diff --git a/app/views/groups/registry/repositories/index.html.haml b/app/views/groups/registry/repositories/index.html.haml index 41cb073686a..2cac8e653e5 100644 --- a/app/views/groups/registry/repositories/index.html.haml +++ b/app/views/groups/registry/repositories/index.html.haml @@ -12,6 +12,6 @@ "containers_error_image" => image_path('illustrations/docker-error-state.svg'), "registry_host_url_with_port" => escape_once(registry_config.host_port), "garbage_collection_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'container-registry-garbage-collection'), - "is_admin": current_user&.admin, - is_group_page: true, + "is_admin": current_user&.admin.to_s, + is_group_page: "true", character_error: @character_error.to_s } } diff --git a/app/views/projects/registry/repositories/_tag.html.haml b/app/views/projects/registry/repositories/_tag.html.haml deleted file mode 100644 index 9594c9184a2..00000000000 --- a/app/views/projects/registry/repositories/_tag.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -%tr.tag - %td - = escape_once(tag.name) - = clipboard_button(text: "#{tag.location}") - %td - - if tag.revision - %span.has-tooltip{ title: "#{tag.revision}" } - = tag.short_revision - - else - \- - %td - - if tag.total_size - = number_to_human_size(tag.total_size) - · - = pluralize(tag.layers.size, "layer") - - else - .light - \- - %td - - if tag.created_at - = time_ago_with_tooltip tag.created_at - - else - .light - \- - - if can?(current_user, :update_container_image, @project) - %td.content - .controls.d-none.d-sm-block.float-right - = link_to project_registry_repository_tag_path(@project, tag.repository, tag.name), - method: :delete, - class: 'btn btn-remove has-tooltip', - title: 'Remove tag', - data: { confirm: 'Are you sure you want to delete this tag?' } do - = icon('trash cred') diff --git a/app/views/projects/registry/repositories/index.html.haml b/app/views/projects/registry/repositories/index.html.haml index 7c3fe9eeca5..8540ce30060 100644 --- a/app/views/projects/registry/repositories/index.html.haml +++ b/app/views/projects/registry/repositories/index.html.haml @@ -15,5 +15,5 @@ "registry_host_url_with_port" => escape_once(registry_config.host_port), "expiration_policy_help_page_path" => help_page_path('user/packages/container_registry/index', anchor: 'expiration-policy'), "garbage_collection_help_page_path" => help_page_path('administration/packages/container_registry', anchor: 'container-registry-garbage-collection'), - "is_admin": current_user&.admin, + "is_admin": current_user&.admin.to_s, character_error: @character_error.to_s } } diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index d47673286a2..79a00b00fa6 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -22,7 +22,7 @@ - if release .text-secondary - = icon('rocket') + = sprite_icon("rocket", size: 12) = _("Release") = link_to release.name, project_releases_path(@project, anchor: release.tag), class: 'tag-release-link' - if release.description.present? diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index a1c56cdb64f..ab4bd88cfe5 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -53,7 +53,8 @@ .selectbox.hide-collapsed = f.hidden_field 'milestone_id', value: milestone[:id], id: nil = dropdown_tag('Milestone', options: { title: _('Assign milestone'), toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: _('Search milestones'), data: { show_no: true, field_name: "#{issuable_type}[milestone_id]", project_id: issuable_sidebar[:project_id], issuable_id: issuable_sidebar[:id], milestones: issuable_sidebar[:project_milestones_path], ability_name: issuable_type, issue_update: issuable_sidebar[:issuable_json_path], use_id: true, default_no: true, selected: milestone[:title], null_default: true, display: 'static' }}) - + - if @project.group.present? + = render_if_exists 'shared/issuable/iteration_select', { can_edit: can_edit_issuable, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type } #issuable-time-tracker.block // Fallback while content is loading .title.hide-collapsed diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index a951c0a5119..31505d2d9fb 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -16,7 +16,7 @@ - recent_releases, total_count, more_count = recent_releases_with_counts(milestone) - unless total_count.zero? .text-tertiary.append-bottom-5.milestone-release-links - = icon('rocket') + = sprite_icon("rocket", size: 12) = n_('Release', 'Releases', total_count) - recent_releases.each do |release| = link_to release.name, project_releases_path(release.project, anchor: release.tag) diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index 2163cb9aaff..160f6487439 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -140,7 +140,7 @@ .block.releases .sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } } %strong - = icon('rocket') + = sprite_icon("rocket", size: 16) %span= total_count .title.hide-collapsed= n_('Release', 'Releases', total_count) .hide-collapsed diff --git a/changelogs/unreleased/alert-system-notes-tool-tip.yml b/changelogs/unreleased/alert-system-notes-tool-tip.yml new file mode 100644 index 00000000000..3129a4ff521 --- /dev/null +++ b/changelogs/unreleased/alert-system-notes-tool-tip.yml @@ -0,0 +1,5 @@ +--- +title: Enable ability to assign alerts to users with corresponding system notes and todos +merge_request: 34360 +author: +type: added diff --git a/changelogs/unreleased/rf-brakeman-to-core.yml b/changelogs/unreleased/rf-brakeman-to-core.yml new file mode 100644 index 00000000000..3f2d4536815 --- /dev/null +++ b/changelogs/unreleased/rf-brakeman-to-core.yml @@ -0,0 +1,5 @@ +--- +title: Bring SAST to Core - brakeman +merge_request: 34217 +author: +type: added diff --git a/doc/administration/auth/cognito.md b/doc/administration/auth/cognito.md index 1674cce1390..b4df6446835 100644 --- a/doc/administration/auth/cognito.md +++ b/doc/administration/auth/cognito.md @@ -58,6 +58,8 @@ Include the code block in the `/etc/gitlab/gitlab.rb` file: gitlab_rails['omniauth_providers'] = [ { "name" => "cognito", + # "label" => "Cognito", + # "icon" => nil, # Optional icon URL "app_id" => "CLIENT ID", "app_secret" => "CLIENT SECRET", "args" => { @@ -86,3 +88,5 @@ Include the code block in the `/etc/gitlab/gitlab.rb` file: Your sign-in page should now display a Cognito button below the regular sign-in form. To begin the authentication process, click the icon, and AWS Cognito will ask the user to sign in and authorize the GitLab application. If successful, the user will be redirected and signed in to your GitLab instance. + +For more information, see the [Initial OmniAuth Configuration](../../integration/omniauth.md#initial-omniauth-configuration). diff --git a/doc/development/database/strings_and_the_text_data_type.md b/doc/development/database/strings_and_the_text_data_type.md index 8038663392f..0e77e3972e0 100644 --- a/doc/development/database/strings_and_the_text_data_type.md +++ b/doc/development/database/strings_and_the_text_data_type.md @@ -276,7 +276,7 @@ end ## Text limit constraints on large tables -If you have to clean up a text column for a really [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/migration_helpers.rb#L12) +If you have to clean up a text column for a really [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/rubocop-migrations.yml#L3) (for example, the `artifacts` in `ci_builds`), your background migration will go on for a while and it will need an additional [background migration cleaning up](../background_migrations.md#cleaning-up) in the release after adding the data migration. diff --git a/doc/development/database_review.md b/doc/development/database_review.md index 3927f0c2669..5405a8808f0 100644 --- a/doc/development/database_review.md +++ b/doc/development/database_review.md @@ -81,9 +81,9 @@ the following preparations into account. - Ensure the down method reverts the changes in `db/structure.sql`. - Update the migration output whenever you modify the migrations during the review process. - Add tests for the migration in `spec/migrations` if necessary. See [Testing Rails migrations at GitLab](testing_guide/testing_migrations_guide.md) for more details. -- When [high-traffic](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/migration_helpers.rb#L12) tables are involved in the migration, use the [`with_lock_retries`](migration_style_guide.md#retry-mechanism-when-acquiring-database-locks) helper method. Review the relevant [examples in our documentation](migration_style_guide.md#examples) for use cases and solutions. +- When [high-traffic](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/rubocop-migrations.yml#L3) tables are involved in the migration, use the [`with_lock_retries`](migration_style_guide.md#retry-mechanism-when-acquiring-database-locks) helper method. Review the relevant [examples in our documentation](migration_style_guide.md#examples) for use cases and solutions. - Ensure RuboCop checks are not disabled unless there's a valid reason to. -- When adding an index to a [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/migration_helpers.rb#L9), +- When adding an index to a [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/rubocop-migrations.yml#L3), test its execution using `CREATE INDEX CONCURRENTLY` in the `#database-lab` Slack channel and add the execution time to the MR description: - Execution time largely varies between `#database-lab` and GitLab.com, but an elevated execution time from `#database-lab` can give a hint that the execution on GitLab.com will also be considerably high. diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 74c03ec72b4..7d3d9dac174 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -569,7 +569,7 @@ has been deprecated and will be removed in a later release. NOTE: **Note:** If a backport adding a column with a default value is needed for %12.9 or earlier versions, -it should use `add_column_with_default` helper. If a [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/migration_helpers.rb#L12) +it should use `add_column_with_default` helper. If a [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/rubocop-migrations.yml#L3) is involved, backporting to %12.9 is contraindicated. ## Changing the column default diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md index 9fd3097091a..05b80cdb4a6 100644 --- a/doc/development/pipelines.md +++ b/doc/development/pipelines.md @@ -397,7 +397,6 @@ Consult the [Review Apps](testing_guide/review_apps.md) dedicated page for more The `* as-if-foss` jobs allows to run GitLab's test suite "as-if-FOSS", meaning as if the jobs would run in the context of the `gitlab-org/gitlab-foss` project. These jobs are only created in the following cases: -- `master` commits (pushes and scheduled pipelines). - `gitlab-org/security/gitlab` merge requests. - Merge requests which include `RUN AS-IF-FOSS` in their title. - Merge requests that changes the CI configuration. diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md index 858de8e7e36..407899b23d5 100644 --- a/doc/development/what_requires_downtime.md +++ b/doc/development/what_requires_downtime.md @@ -130,7 +130,7 @@ class CleanupUsersUpdatedAtRename < ActiveRecord::Migration[4.2] end ``` -NOTE: **Note:** If you're renaming a [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/migration_helpers.rb#L9), please carefully consider the state when the first migration has run but the second cleanup migration hasn't been run yet. +NOTE: **Note:** If you're renaming a [large table](https://gitlab.com/gitlab-org/gitlab/-/blob/master/rubocop/rubocop-migrations.yml#L3), please carefully consider the state when the first migration has run but the second cleanup migration hasn't been run yet. With [Canary](https://about.gitlab.com/handbook/engineering/infrastructure/library/canary/) it is possible that the system runs in this state for a significant amount of time. ## Changing Column Constraints diff --git a/doc/user/project/operations/alert_management.md b/doc/user/project/operations/alert_management.md index 7aaebd0da08..0d1d725692c 100644 --- a/doc/user/project/operations/alert_management.md +++ b/doc/user/project/operations/alert_management.md @@ -92,3 +92,54 @@ alert by clicking the **View Issue** button. Closing a GitLab issue associated with an alert changes the alert's status to Resolved. See [Alert Management statuses](#alert-management-statuses) for more details about statuses. + +### Update an Alert's assignee + +NOTE: **Note:** +We currently only support a single assignee per alert. + +The Alert Management detail view allows users to update the Alert Assignee(s). + +> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1. + +In large teams, where there is shared ownership of an alert, it can be difficult +to track who is investigating and working on it. The Alert Management detail view +enables you to update the Alert Assignee(s): + +NOTE: **Note:** +GitLab currently only supports a single assignee per alert. + +1. To display the list of current alerts, click + **{cloud-gear}** **Operations > Alerts**: + + ![Alert Management List View Assignee(s)](img/alert_list_assignees_v13_1.png) + +1. Select your desired alert to display its **Alert Management Details View**: + + ![Alert Management Details View Assignee(s)](img/alert_details_assignees_v13_1.png) + +1. If the right sidebar is not expanded, click + **{angle-double-right}** **Expand sidebar** to expand it. +1. In the right sidebar, locate the **Assignee** and click **Edit**. From the + dropdown menu, select each user you want to assign to the alert. GitLab creates + a [To-Do list item](../../todos.md) for each user. + + ![Alert Management Details View Assignee(s)](img/alert_todo_assignees_v13_1.png) + +To remove an assignee, click **Edit** next to the **Assignee** dropdown menu and +deselect the user from the list of assignees, or click **Unassigned**. + +## Use cases for assigning alerts + +Consider a team formed by different sections of monitoring, collaborating on a +single application. After an alert surfaces, it's extremely important to +route the alert to the team members who can address and resolve the alert. + +Assigning Alerts to multiple assignees eases collaboration and delegation. All +assignees are shown in your team's workflows, and all assignees receive +notifications, simplifying communication and ownership of the alert. + +After completing their portion of investigating or fixing the alert, users can +unassign their account from the alert when their role is complete. +The [alerts status](#alert-management-statuses) can be updated to +reflect if the alert has been resolved. diff --git a/doc/user/project/operations/img/alert_details_assignees_v13_1.png b/doc/user/project/operations/img/alert_details_assignees_v13_1.png Binary files differnew file mode 100644 index 00000000000..dab4eac384a --- /dev/null +++ b/doc/user/project/operations/img/alert_details_assignees_v13_1.png diff --git a/doc/user/project/operations/img/alert_list_assignees_v13_1.png b/doc/user/project/operations/img/alert_list_assignees_v13_1.png Binary files differnew file mode 100644 index 00000000000..db1e0d8dcb7 --- /dev/null +++ b/doc/user/project/operations/img/alert_list_assignees_v13_1.png diff --git a/doc/user/project/operations/img/alert_todo_assignees_v13_1.png b/doc/user/project/operations/img/alert_todo_assignees_v13_1.png Binary files differnew file mode 100644 index 00000000000..637f8be5d25 --- /dev/null +++ b/doc/user/project/operations/img/alert_todo_assignees_v13_1.png diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md index c809b97abf6..58d143fb32b 100644 --- a/doc/user/project/releases/index.md +++ b/doc/user/project/releases/index.md @@ -269,11 +269,16 @@ Here is an example of a Release Evidence object: { "release": { "id": 5, - "tag": "v4.0", + "tag_name": "v4.0", "name": "New release", - "project_id": 45, - "project_name": "Project name", - "released_at": "2019-06-28 13:23:40 UTC", + "project": { + "id": 20, + "name": "Project name", + "created_at": "2019-04-14T11:12:13.940Z", + "description": "Project description" + }, + "created_at": "2019-06-28 13:23:40 UTC", + "description": "Release description", "milestones": [ { "id": 11, @@ -313,7 +318,7 @@ Here is an example of a Release Evidence object: } ``` -### Enabling Release Evidence display **(CORE ONLY)** +### Diabling Release Evidence display **(CORE ONLY)** This feature comes with the `:release_evidence_collection` feature flag enabled by default in GitLab self-managed instances. To turn it off, diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml index 8448cf5ef4a..ec7b34d17b5 100644 --- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml @@ -81,7 +81,6 @@ brakeman-sast: - if: $SAST_DISABLED || $SAST_DISABLE_DIND == 'false' when: never - if: $CI_COMMIT_BRANCH && - $GITLAB_FEATURES =~ /\bsast\b/ && $SAST_DEFAULT_ANALYZERS =~ /brakeman/ exists: - 'config/routes.rb' diff --git a/lib/gitlab/instrumentation/redis_interceptor.rb b/lib/gitlab/instrumentation/redis_interceptor.rb index a485fcaaea1..a36aade59c3 100644 --- a/lib/gitlab/instrumentation/redis_interceptor.rb +++ b/lib/gitlab/instrumentation/redis_interceptor.rb @@ -61,15 +61,14 @@ module Gitlab # 4. "Binary" string (i.e. may contain zero byte) # 5. Array of binary string - size = if result.is_a? Array - # This count is an approximation that omits the Redis protocol overhead - # of type prefixes, length prefixes and line endings. - result.inject(0) { |sum, y| sum + y.to_s.bytesize } - else - result.to_s.bytesize - end - - instrumentation_class.increment_read_bytes(size) + if result.is_a? Array + # Redis can return nested arrays, e.g. from XRANGE or GEOPOS, so we use recursion here. + result.each { |x| measure_read_size(x) } + else + # This count is an approximation that omits the Redis protocol overhead + # of type prefixes, length prefixes and line endings. + instrumentation_class.increment_read_bytes(result.to_s.bytesize) + end end # That's required so it knows which GitLab Redis instance diff --git a/locale/gitlab.pot b/locale/gitlab.pot index fd603418b98..ba12b7a68cb 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1852,6 +1852,9 @@ msgid_plural "Alerts" msgstr[0] "" msgstr[1] "" +msgid "AlertManagement| assign yourself" +msgstr "" + msgid "AlertManagement|Acknowledged" msgstr "" @@ -1876,7 +1879,7 @@ msgstr "" msgid "AlertManagement|All alerts" msgstr "" -msgid "AlertManagement|Assign Assignees" +msgid "AlertManagement|Assign To" msgstr "" msgid "AlertManagement|Assign status" @@ -1885,9 +1888,6 @@ msgstr "" msgid "AlertManagement|Assignee" msgstr "" -msgid "AlertManagement|Assignee(s)" -msgstr "" - msgid "AlertManagement|Assignees" msgstr "" @@ -1942,6 +1942,9 @@ msgstr "" msgid "AlertManagement|None" msgstr "" +msgid "AlertManagement|None -" +msgstr "" + msgid "AlertManagement|Open" msgstr "" @@ -2937,6 +2940,9 @@ msgstr "" msgid "Assign" msgstr "" +msgid "Assign Iteration" +msgstr "" + msgid "Assign custom color like #FF0000" msgstr "" @@ -9461,6 +9467,9 @@ msgstr "" msgid "Failed to set due date because the date format is invalid." msgstr "" +msgid "Failed to set iteration on this issue. Please try again." +msgstr "" + msgid "Failed to signing using smartcard authentication" msgstr "" @@ -12464,6 +12473,9 @@ msgstr "" msgid "It's you" msgstr "" +msgid "Iteration" +msgstr "" + msgid "Iteration changed to" msgstr "" @@ -14894,6 +14906,9 @@ msgstr "" msgid "No grouping" msgstr "" +msgid "No iteration" +msgstr "" + msgid "No iterations to show" msgstr "" diff --git a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js b/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js index c1b9746c308..5dbd83dbdac 100644 --- a/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js +++ b/spec/frontend/alert_management/components/alert_managment_sidebar_assignees_spec.js @@ -127,7 +127,7 @@ describe('Alert Details Sidebar Assignees', () => { it('stops updating and cancels loading when the request fails', () => { jest.spyOn(wrapper.vm.$apollo, 'mutate').mockReturnValue(Promise.reject(new Error())); wrapper.vm.updateAlertAssignees('root'); - expect(wrapper.find('[data-testid="assigned-users"]').text()).toBe('Unassigned'); + expect(wrapper.find('[data-testid="unassigned-users"]').text()).toBe('assign yourself'); }); }); }); diff --git a/spec/frontend/alert_management/components/alert_sidebar_spec.js b/spec/frontend/alert_management/components/alert_sidebar_spec.js index 5fa6048de1b..80c4d9e0650 100644 --- a/spec/frontend/alert_management/components/alert_sidebar_spec.js +++ b/spec/frontend/alert_management/components/alert_sidebar_spec.js @@ -14,7 +14,6 @@ describe('Alert Details Sidebar', () => { function mountComponent({ sidebarCollapsed = true, mountMethod = shallowMount, - alertAssignee = false, stubs = {}, alert = {}, } = {}) { @@ -24,9 +23,6 @@ describe('Alert Details Sidebar', () => { sidebarCollapsed, projectPath: 'projectPath', }, - provide: { - glFeatures: { alertAssignee }, - }, stubs, }); } @@ -48,14 +44,9 @@ describe('Alert Details Sidebar', () => { expect(wrapper.props('sidebarCollapsed')).toBe(true); }); - it('should not render side bar assignee dropdown by default', () => { - expect(wrapper.find(SidebarAssignees).exists()).toBe(false); - }); - - it('should render side bar assignee dropdown if feature flag enabled', () => { + it('should render side bar assignee dropdown', () => { mountComponent({ mountMethod: mount, - alertAssignee: true, alert: mockAlert, }); expect(wrapper.find(SidebarAssignees).exists()).toBe(true); diff --git a/spec/frontend/registry/explorer/stores/mutations_spec.js b/spec/frontend/registry/explorer/stores/mutations_spec.js index 43b2ba84218..4ca0211cdc3 100644 --- a/spec/frontend/registry/explorer/stores/mutations_spec.js +++ b/spec/frontend/registry/explorer/stores/mutations_spec.js @@ -12,11 +12,14 @@ describe('Mutations Registry Explorer Store', () => { it('should set the initial state', () => { const payload = { endpoint: 'foo', - isGroupPage: true, + isGroupPage: '', expirationPolicy: { foo: 'bar' }, - isAdmin: true, + isAdmin: '', + }; + const expectedState = { + ...mockState, + config: { ...payload, isGroupPage: false, isAdmin: false }, }; - const expectedState = { ...mockState, config: payload }; mutations[types.SET_INITIAL_STATE](mockState, { ...payload, expirationPolicy: JSON.stringify(payload.expirationPolicy), diff --git a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb index 32c58d9bda1..25506d63091 100644 --- a/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb +++ b/spec/lib/gitlab/instrumentation/redis_interceptor_spec.rb @@ -24,6 +24,9 @@ describe Gitlab::Instrumentation::RedisInterceptor, :clean_gitlab_redis_shared_s # Exercise counting of a bulk reply [[:set, 'foo', 'bar' * 100]] | [:get, 'foo'] | 3 + 3 | 3 * 100 + + # Nested array response: ['123456-89', ['foo', 'bar']] + [[:xadd, 'mystream', '123456-89', 'foo', 'bar']] | [:xrange, 'mystream', '-', '+'] | 6 + 8 + 1 + 1 | 9 + 3 + 3 end with_them do diff --git a/spec/models/alert_management/alert_spec.rb b/spec/models/alert_management/alert_spec.rb index c7e21dddd53..27b8bb48073 100644 --- a/spec/models/alert_management/alert_spec.rb +++ b/spec/models/alert_management/alert_spec.rb @@ -8,6 +8,7 @@ describe AlertManagement::Alert do it { is_expected.to belong_to(:issue) } it { is_expected.to have_many(:assignees).through(:alert_assignees) } it { is_expected.to have_many(:notes) } + it { is_expected.to have_many(:ordered_notes) } it { is_expected.to have_many(:user_mentions) } end diff --git a/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb index 2a90714cc81..4c048caaeee 100644 --- a/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb +++ b/spec/requests/api/graphql/project/alert_management/alert/assignees_spec.rb @@ -75,17 +75,4 @@ describe 'getting Alert Management Alert Assignees' do expect(third_assignees.length).to eq(1) expect(third_assignees.first).to include('username' => current_user.username) end - - context 'with alert_assignee flag disabled' do - before do - stub_feature_flags(alert_assignee: false) - end - - it 'excludes assignees' do - post_graphql(query, current_user: current_user) - - expect(first_assignees).to be_empty - expect(second_assignees).to be_empty - end - end end diff --git a/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb new file mode 100644 index 00000000000..df6bfa8c97b --- /dev/null +++ b/spec/requests/api/graphql/project/alert_management/alert/notes_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'getting Alert Management Alert Notes' do + include GraphqlHelpers + + let_it_be(:project) { create(:project) } + let_it_be(:current_user) { create(:user) } + let_it_be(:first_alert) { create(:alert_management_alert, project: project, assignees: [current_user]) } + let_it_be(:second_alert) { create(:alert_management_alert, project: project) } + let_it_be(:first_system_note) { create(:note_on_alert, noteable: first_alert, project: project) } + let_it_be(:second_system_note) { create(:note_on_alert, noteable: first_alert, project: project) } + + let(:params) { {} } + + let(:fields) do + <<~QUERY + nodes { + iid + notes { + nodes { + id + } + } + } + QUERY + end + + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field('alertManagementAlerts', params, fields) + ) + end + + let(:alerts_result) { graphql_data.dig('project', 'alertManagementAlerts', 'nodes') } + let(:notes_result) { alerts_result.map { |alert| [alert['iid'], alert['notes']['nodes']] }.to_h } + let(:first_notes_result) { notes_result[first_alert.iid.to_s] } + let(:second_notes_result) { notes_result[second_alert.iid.to_s] } + + before do + project.add_developer(current_user) + end + + it 'returns the notes ordered by createdAt' do + post_graphql(query, current_user: current_user) + + expect(first_notes_result.length).to eq(2) + expect(first_notes_result.first).to include('id' => first_system_note.to_global_id.to_s) + expect(first_notes_result.second).to include('id' => second_system_note.to_global_id.to_s) + expect(second_notes_result).to be_empty + end + + it 'avoids N+1 queries' do + base_count = ActiveRecord::QueryRecorder.new do + post_graphql(query, current_user: current_user) + end + + # An N+1 would mean a new alert would increase the query count + create(:alert_management_alert, project: project) + + expect { post_graphql(query, current_user: current_user) }.not_to exceed_query_limit(base_count) + expect(alerts_result.length).to eq(3) + end +end diff --git a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb index fe84046d8e4..c591895f295 100644 --- a/spec/requests/api/graphql/project/alert_management/alerts_spec.rb +++ b/spec/requests/api/graphql/project/alert_management/alerts_spec.rb @@ -10,7 +10,6 @@ describe 'getting Alert Management Alerts' do let_it_be(:resolved_alert) { create(:alert_management_alert, :all_fields, :resolved, project: project, issue: nil, severity: :low) } let_it_be(:triggered_alert) { create(:alert_management_alert, :all_fields, project: project, severity: :critical, payload: payload) } let_it_be(:other_project_alert) { create(:alert_management_alert, :all_fields) } - let_it_be(:system_note) { create(:note_on_alert, noteable: triggered_alert, project: project) } let(:params) { {} } @@ -77,8 +76,6 @@ describe 'getting Alert Management Alerts' do 'updatedAt' => triggered_alert.updated_at.strftime('%Y-%m-%dT%H:%M:%SZ') ) - expect(first_alert['notes']['nodes'].first).to include('id' => system_note.to_global_id.to_s) - expect(second_alert).to include( 'iid' => resolved_alert.iid.to_s, 'issueIid' => nil, diff --git a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb index feb3ba46353..f26af6cb766 100644 --- a/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/group_and_project_boards_query_shared_examples.rb @@ -45,44 +45,37 @@ RSpec.shared_examples 'group and project boards query' do end describe 'sorting and pagination' do + let(:data_path) { [board_parent_type, :boards] } + + def pagination_query(params, page_info) + graphql_query_for( + board_parent_type, + { 'fullPath' => board_parent.full_path }, + query_graphql_field('boards', params, "#{page_info} edges { node { id } }") + ) + end + + def pagination_results_data(data) + data.map { |board| board.dig('node', 'id') } + end + context 'when using default sorting' do let!(:board_B) { create(:board, resource_parent: board_parent, name: 'B') } let!(:board_C) { create(:board, resource_parent: board_parent, name: 'C') } let!(:board_a) { create(:board, resource_parent: board_parent, name: 'a') } let!(:board_A) { create(:board, resource_parent: board_parent, name: 'A') } - - before do - post_graphql(query, current_user: current_user) - end - - it_behaves_like 'a working graphql query' + let(:boards) { [board_a, board_A, board_B, board_C] } context 'when ascending' do - let(:boards) { [board_a, board_A, board_B, board_C] } - let(:expected_boards) do - if board_parent.multiple_issue_boards_available? - boards - else - [boards.first] - end - end - - it 'sorts boards' do - expect(grab_names).to eq expected_boards.map(&:name) - end - - context 'when paginating' do - let(:params) { 'first: 2' } - - it 'sorts boards' do - expect(grab_names).to eq expected_boards.first(2).map(&:name) - - cursored_query = query("after: \"#{end_cursor}\"") - post_graphql(cursored_query, current_user: current_user) - - response_data = Gitlab::Json.parse(response.body)['data'][board_parent_type]['boards']['edges'] - - expect(grab_names(response_data)).to eq expected_boards.drop(2).first(2).map(&:name) + it_behaves_like 'sorted paginated query' do + let(:sort_param) { } + let(:first_param) { 2 } + let(:expected_results) do + if board_parent.multiple_issue_boards_available? + boards.map { |board| board.to_global_id.to_s } + else + [boards.first.to_global_id.to_s] + end end end end |