提交 ecfcccb6 编写于 作者: G GitLab Bot

Add latest changes from gitlab-org/gitlab@master

上级 3caf5a8a
......@@ -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
......
......@@ -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>
......
<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>
......
......@@ -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>
......
......@@ -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>
......
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),
};
},
......
......@@ -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;
}
}
......@@ -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
......
......@@ -33,7 +33,8 @@ module Resolvers
def preloads
{
assignees: [:assignees]
assignees: [:assignees],
notes: [:ordered_notes, { ordered_notes: [:system_note_metadata, :project, :noteable] }]
}
end
end
......
......@@ -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
......
......@@ -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) }
......
......@@ -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 } }
%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)
&middot;
= 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')
......@@ -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 } }
......@@ -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?
......
......@@ -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
......
......@@ -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)
......
......@@ -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
......
---
title: Enable ability to assign alerts to users with corresponding system notes and todos
merge_request: 34360
author:
type: added
---
title: Bring SAST to Core - brakeman
merge_request: 34217
author:
type: added
......@@ -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).
......@@ -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.
......
......@@ -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.
......
......@@ -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
......
......@@ -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.
......
......@@ -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
......
......@@ -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.
......@@ -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,
......
......@@ -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'
......
......@@ -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
......
......@@ -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 ""
......
......@@ -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');
});
});
});
......@@ -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);
......
......@@ -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),
......
......@@ -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
......
......@@ -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
......
......@@ -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
# 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
......@@ -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,
......
......@@ -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
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册