Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-09-05 18:08:04 +00:00
parent fb4d571aaf
commit 04a542b641
110 changed files with 779 additions and 424 deletions

View File

@ -164,7 +164,7 @@ Dangerfile
/config/upgrade_path.yml
# Secure & Threat Management ownership delineation
# https://about.gitlab.com/handbook/engineering/development/threat-management/delineate-secure-threat-management.html#technical-boundaries
# https://handbook.gitlab.com/handbook/engineering/development/sec/delineate-sec/#technical-boundaries
^[Threat Insights backend] @gitlab-org/govern/threat-insights-backend-team
/development/sec/cyclonedx_property_taxonomy.md
/app/finders/security/
@ -181,6 +181,17 @@ Dangerfile
/ee/spec/policies/vulnerabilities/
/ee/spec/policies/vulnerability*.rb
^[Threat Insights frontend] @gitlab-org/govern/threat-insights-frontend-team
/app/assets/javascripts/vue_merge_request_widget/widgets/security_reports/**
/ee/app/assets/javascripts/vue_merge_request_widget/widgets/security_reports/**
/ee/app/assets/javascripts/security_dashboard/**
/ee/spec/frontend/security_dashboard/**
/ee/app/assets/javascripts/vulnerabilities/**
/spec/frontend/vue_merge_request_widget/widgets/security_reports/**
/ee/spec/frontend/vue_merge_request_widget/widgets/security_reports/**
/ee/app/assets/javascripts/dependencies/**
/ee/spec/frontend/dependencies/**
^[Composition Analysis backend] @gitlab-org/secure/composition-analysis-be
/app/events/package_metadata/
/app/models/concerns/enums/package_metadata.rb

View File

@ -116,7 +116,6 @@ Database/AvoidUsingPluckWithoutLimit:
- 'ee/app/models/ee/project_group_link.rb'
- 'ee/app/models/ee/projects/wiki_repository.rb'
- 'ee/app/models/ee/uploads/local.rb'
- 'ee/app/models/ee/user.rb'
- 'ee/app/models/embedding/application_record.rb'
- 'ee/app/models/geo/base_registry.rb'
- 'ee/app/models/geo/container_repository_registry.rb'

View File

@ -208,7 +208,6 @@ Style/RedundantSelf:
- 'ee/lib/elastic/instance_proxy_util.rb'
- 'ee/lib/elastic/latest/commit_config.rb'
- 'ee/lib/elastic/latest/issue_config.rb'
- 'ee/lib/elastic/latest/merge_request_config.rb'
- 'ee/lib/elastic/latest/note_config.rb'
- 'ee/lib/elastic/migration.rb'
- 'ee/lib/gem_extensions/elasticsearch/model/indexing/instance_methods.rb'

View File

@ -1,6 +1,5 @@
<script>
import {
GlAvatar,
GlButton,
GlButtonGroup,
GlLabel,
@ -40,7 +39,6 @@ export default {
),
},
components: {
GlAvatar,
GlButton,
GlButtonGroup,
GlLabel,
@ -361,18 +359,17 @@ export default {
'gl-mt-3 gl-rotate-90': list.collapsed,
}"
>
<gl-avatar
<img
v-gl-tooltip.hover.bottom
:title="listAssignee"
:label="list.assignee.name"
:alt="list.assignee.name"
:src="list.assignee.avatarUrl"
:entity-name="list.assignee.name"
:size="24"
class="gl-mr-3"
class="avatar s20"
height="20"
width="20"
/>
</a>
<!-- EE end -->
<div
class="board-title-text"
:class="{

View File

@ -1,5 +1,5 @@
<script>
import { GlButton, GlIcon, GlLink, GlTooltip } from '@gitlab/ui';
import { GlButton, GlLink, GlTooltip } from '@gitlab/ui';
import { createAlert } from '~/alert';
import { __, s__, sprintf } from '~/locale';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
@ -12,7 +12,6 @@ export default {
components: {
CiIcon,
GlButton,
GlIcon,
GlLink,
GlTooltip,
},
@ -27,31 +26,19 @@ export default {
},
data() {
return {
isHovered: false,
isJobLogVisible: false,
isLoadingAction: false,
};
},
computed: {
canReadBuild() {
return this.job.userPermissions.readBuild;
},
canRetryJob() {
return this.job.retryable && this.job.userPermissions.updateBuild && !this.isBridgeJob;
},
detailsPath() {
return this.job?.detailedStatus?.detailsPath;
},
isBridgeJob() {
return this.job.kind === BRIDGE_KIND;
},
jobChevronName() {
return this.isJobLogVisible ? 'chevron-down' : 'chevron-right';
},
jobTrace() {
if (this.canReadBuild) {
return this.job?.trace?.htmlSummary || this.$options.i18n.noTraceText;
}
return this.$options.i18n.cannotReadBuild;
},
parsedJobId() {
return getIdFromGraphQLId(this.job.id);
},
@ -65,12 +52,6 @@ export default {
},
},
methods: {
setActiveRow() {
this.isHovered = true;
},
resetActiveRow() {
this.isHovered = false;
},
async retryJob() {
try {
this.isLoadingAction = true;
@ -95,47 +76,34 @@ export default {
this.isLoadingAction = false;
}
},
toggleJobLog(event) {
// Do not toggle the log visibility when clicking on a link
if (event.target.tagName === 'A') {
return;
}
this.isJobLogVisible = !this.isJobLogVisible;
},
},
i18n: {
cannotReadBuild: s__("Job|You do not have permission to read this job's log."),
cannotRetry: s__('Job|You do not have permission to run this job again.'),
cannotRetryTrigger: s__('Job|You cannot rerun trigger jobs from this list.'),
jobActionTooltipText: s__('Pipelines|Retry %{jobName} Job'),
noTraceText: s__('Job|No job log'),
retry: __('Retry'),
retryError: __('There was an error while retrying this job'),
},
};
</script>
<template>
<div class="container-fluid gl-grid-rows-auto">
<div
class="row gl-my-3 gl-flex gl-cursor-pointer gl-items-center"
:aria-pressed="isJobLogVisible"
role="button"
tabindex="0"
data-testid="widget-row"
@click="toggleJobLog"
@keyup.enter="toggleJobLog"
@keyup.space="toggleJobLog"
@mouseover="setActiveRow"
@mouseout="resetActiveRow"
>
<div class="col-6 gl-text-left gl-font-bold gl-text-gray-900">
<gl-icon :name="jobChevronName" />
<div class="container-fluid gl-my-1 gl-grid-rows-auto">
<div class="row gl-flex gl-items-center" data-testid="widget-row">
<div class="align-items-center col-6 gl-flex gl-font-bold gl-text-gray-900">
<ci-icon :status="job.detailedStatus" />
{{ job.name }}
<gl-link
class="gl-ml-2 !gl-text-gray-900 !gl-no-underline"
:href="detailsPath"
data-testid="job-name-link"
>{{ job.name }}</gl-link
>
</div>
<div class="col-2 gl-text-left">{{ job.stage.name }}</div>
<div class="col-2 gl-text-left">
<gl-link :href="job.detailedStatus.detailsPath">#{{ parsedJobId }}</gl-link>
<div class="col-2 gl-flex gl-items-center" data-testid="job-stage-name">
{{ job.stage.name }}
</div>
<div class="col-2 gl-flex gl-items-center">
<gl-link :href="detailsPath" data-testid="job-id-link">#{{ parsedJobId }}</gl-link>
</div>
<gl-tooltip v-if="!canRetryJob" :target="() => $refs.retryBtn" placement="top">
{{ tooltipErrorText }}
@ -155,12 +123,5 @@ export default {
</span>
</div>
</div>
<div v-if="isJobLogVisible" class="row">
<pre
v-safe-html="jobTrace"
class="gl-w-full gl-bg-gray-900 gl-text-white"
data-testid="job-log"
></pre>
</div>
</div>
</template>

View File

@ -154,7 +154,7 @@ export default {
</script>
<template>
<div>
<div class="gl-mb-4">
<gl-loading-icon v-if="isInitialLoading" class="gl-p-4" />
<div v-else-if="!hasFailedJobs" class="gl-p-4">{{ $options.i18n.noFailedJobs }}</div>
<div v-else class="container-fluid gl-grid-rows-auto">
@ -162,7 +162,7 @@ export default {
<div
v-for="col in $options.columns"
:key="col.text"
class="gl-text-left gl-font-bold"
class="gl-flex gl-font-bold"
:class="col.class"
data-testid="header"
>

View File

@ -90,6 +90,7 @@ export default {
class="expandable-card"
:class="{ 'is-collapsed gl-border-white hover:gl-border-gray-100': !isExpanded }"
data-testid="failed-jobs-card"
@click="toggleWidget"
>
<template #title>
<gl-button

View File

@ -28,9 +28,6 @@ query getPipelineFailedJobs($fullPath: ID!, $pipelineIid: ID!) {
id
name
}
trace {
htmlSummary
}
userPermissions {
readBuild
updateBuild

View File

@ -125,7 +125,7 @@ export default {
:img-src="authorAvatar"
:img-alt="authorName"
:img-size="32"
class="gl-my-2 gl-mr-4 gl-hidden gl-self-start sm:gl-block"
class="avatar-cell gl-my-2 gl-mr-4 gl-hidden sm:gl-block"
/>
</div>
<div

View File

@ -110,9 +110,9 @@ export function membersBeforeSave(members) {
const autoCompleteAvatar = member.avatar_url || member.username.charAt(0).toUpperCase();
const avatarShapeClass = member.type === GROUP_TYPE ? '' : 'gl-avatar-circle';
const imgAvatar = `<img src="${member.avatar_url}" alt="${member.username}" class="gl-avatar gl-avatar-s24 gl-mr-2 ${avatarShapeClass}"/>`;
const txtAvatar = `<div class="gl-avatar gl-avatar-s24 gl-mr-2 gl-justify-center gl-items-center ${avatarShapeClass}">${autoCompleteAvatar}</div>`;
const rectAvatarClass = member.type === GROUP_TYPE ? 'rect-avatar' : '';
const imgAvatar = `<img src="${member.avatar_url}" alt="${member.username}" class="avatar ${rectAvatarClass} avatar-inline s24 gl-mr-2"/>`;
const txtAvatar = `<div class="avatar ${rectAvatarClass} avatar-inline s24 gl-mr-2">${autoCompleteAvatar}</div>`;
const avatarIcon = member.mentionsDisabled
? spriteIcon('notifications-off', 's16 vertical-align-middle gl-ml-2')
: '';

View File

@ -1,11 +1,10 @@
<script>
import { GlAvatar, GlIcon } from '@gitlab/ui';
import { GlIcon } from '@gitlab/ui';
import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { __, sprintf } from '~/locale';
export default {
components: {
GlAvatar,
GlIcon,
},
props: {
@ -44,12 +43,13 @@ export default {
</script>
<template>
<span class="gl-relative">
<gl-avatar
:label="user.name"
:src="avatarUrl"
:size="imgSize"
<span class="position-relative">
<img
:alt="assigneeAlt"
:src="avatarUrl"
:width="imgSize"
:class="`s${imgSize}`"
class="avatar avatar-inline m-0"
data-testid="avatar-image"
/>
<gl-icon v-if="hasMergeIcon" name="warning-solid" aria-hidden="true" class="merge-icon" />

View File

@ -1,12 +1,11 @@
<script>
// NOTE! For the first iteration, we are simply copying the implementation of Assignees
// It will soon be overhauled in Issue https://gitlab.com/gitlab-org/gitlab/-/issues/233736
import { GlAvatar, GlIcon } from '@gitlab/ui';
import { GlIcon } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
export default {
components: {
GlAvatar,
GlIcon,
},
props: {
@ -34,8 +33,14 @@ export default {
</script>
<template>
<span class="gl-relative">
<gl-avatar :label="user.name" :src="avatarUrl" :size="imgSize" :alt="reviewerAlt" />
<span class="position-relative">
<img
:alt="reviewerAlt"
:src="avatarUrl"
:width="imgSize"
:class="`s${imgSize}`"
class="avatar avatar-inline m-0"
/>
<gl-icon
v-if="hasMergeIcon"
name="warning-solid"

View File

@ -1,5 +1,5 @@
query getTokenAccessGroupsAndProjects($search: String) {
groups(search: $search, first: 4) {
groups(search: $search, sort: "id_asc", first: 10) {
nodes {
id
name
@ -7,7 +7,7 @@ query getTokenAccessGroupsAndProjects($search: String) {
fullPath
}
}
projects(search: $search, first: 4) {
projects(search: $search, sort: "id_asc", first: 10) {
nodes {
id
name

View File

@ -252,12 +252,12 @@ function UsersSelect(currentUser, els, options = {}) {
});
};
collapsedAssigneeTemplate = template(
`<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="gl-avatar gl-avatar-circle gl-avatar-s24" alt="" src="<%- avatar %>"> </a> <% } else { %> ${spriteIcon(
`<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> ${spriteIcon(
'user',
)} <% } %>`,
);
assigneeTemplate = template(
`<% if (username) { %> <a class="author-link gl-font-bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="gl-avatar gl-avatar-circle gl-avatar-s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself">
`<% if (username) { %> <a class="author-link gl-font-bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself">
${sprintf(s__('UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}'), {
openingTag: '<a href="#" class="js-assign-yourself">',
closingTag: '</a>',
@ -637,7 +637,7 @@ function UsersSelect(currentUser, els, options = {}) {
)}</a></li>`;
} else {
// 0 margin, because it's now handled by a wrapper
img = `<img src='${avatar}' class='gl-avatar gl-avatar-circle gl-avatar-s32 !gl-m-0' width='32' />`;
img = `<img src='${avatar}' class='avatar avatar-inline !gl-m-0' width='32' />`;
}
return userSelect.renderRow(

View File

@ -1,10 +1,9 @@
<script>
import { GlAvatar, GlTooltipDirective, GlLink } from '@gitlab/ui';
import { GlTooltipDirective, GlLink } from '@gitlab/ui';
export default {
name: 'MrWidgetAuthor',
components: {
GlAvatar,
GlLink,
},
directives: {
@ -38,9 +37,9 @@ export default {
:title="showAuthorName ? null : author.name"
class="mr-widget-author"
>
<gl-avatar :src="avatarUrl" :size="16" :alt="author.name" /><span
<img :src="avatarUrl" :alt="author.name" class="avatar avatar-inline s16" /><span
v-if="showAuthorName"
class="author gl-ml-2"
class="author"
>{{ author.name }}</span
>
</gl-link>

View File

@ -124,7 +124,7 @@ export default {
v-gl-tooltip
:title="collapsed ? expandDetailsTooltip : collapseDetailsTooltip"
:aria-label="collapsed ? expandDetailsTooltip : collapseDetailsTooltip"
:icon="collapsed ? 'chevron-lg-down' : 'chevron-lg-up'"
:icon="collapsed ? 'chevron-down' : 'chevron-up'"
category="tertiary"
size="small"
class="gl-align-top"

View File

@ -382,7 +382,7 @@ export default {
:title="collapseButtonLabel"
:aria-expanded="`${!isCollapsed}`"
:aria-label="collapseButtonLabel"
:icon="isCollapsed ? 'chevron-lg-down' : 'chevron-lg-up'"
:icon="isCollapsed ? 'chevron-down' : 'chevron-up'"
category="tertiary"
data-testid="toggle-button"
size="small"

View File

@ -10,7 +10,6 @@ import {
GlTooltip,
GlButton,
GlSprintf,
GlAvatar,
} from '@gitlab/ui';
import { debounce } from 'lodash';
import axios from '~/lib/utils/axios_utils';
@ -44,7 +43,6 @@ export default {
GlTooltip,
GlButton,
GlSprintf,
GlAvatar,
SidebarAssignee,
},
props: {
@ -284,7 +282,7 @@ export default {
>
<div v-if="userName" class="gl-mt-2 gl-inline-flex" data-testid="assigned-users">
<span class="gl-relative gl-mr-4">
<gl-avatar :src="userImg" :size="32" :alt="userName" />
<img :alt="userName" :src="userImg" :width="32" class="avatar avatar-inline s32 gl-m-0" />
</span>
<span class="gl-flex gl-flex-col gl-overflow-hidden">
<strong class="dropdown-menu-user-full-name">

View File

@ -82,7 +82,7 @@ export default {
return !(hasContent && this.isCollapsible && this.collapsed);
},
toggleIcon() {
return this.collapsed ? 'chevron-lg-down' : 'chevron-lg-up';
return this.collapsed ? 'chevron-down' : 'chevron-up';
},
toggleLabel() {
return this.collapsed ? __('Expand') : __('Collapse');

View File

@ -1,8 +1,86 @@
// Left over of avatar. Still used in email templates.
.avatar {
$avatar-sizes: (
16: (
font-size: 10px,
line-height: 16px,
border-radius: $border-radius-small
),
18: (
border-radius: $border-radius-small
),
20: (
border-radius: $border-radius-small
),
24: (
font-size: 12px,
line-height: 24px,
border-radius: $gl-border-radius-base
),
26: (
font-size: 20px,
line-height: 1.33,
border-radius: $gl-border-radius-base
),
32: (
font-size: 14px,
line-height: 32px,
border-radius: $gl-border-radius-base
),
40: (
font-size: 16px,
line-height: 38px,
border-radius: $gl-border-radius-base
),
48: (
font-size: 20px,
line-height: 48px,
border-radius: $border-radius-large
),
60: (
font-size: 32px,
line-height: 60px,
border-radius: $border-radius-large
),
64: (
font-size: 28px,
line-height: 64px,
border-radius: $border-radius-large
),
90: (
font-size: 36px,
line-height: 90px,
border-radius: $border-radius-large
),
96: (
font-size: 36px,
line-height: 94px,
border-radius: $border-radius-large
),
100: (
font-size: 36px,
line-height: 98px,
border-radius: $border-radius-large
),
160: (
font-size: 96px,
line-height: 158px,
border-radius: $border-radius-large
)
);
.avatar,
.avatar-container {
float: left;
margin-right: $gl-padding;
border-radius: $avatar-radius;
@each $size, $size-config in $avatar-sizes {
&.s#{$size} {
@include avatar-size(#{$size}px, if($size < 48, 8px, 16px));
}
}
}
.avatar {
transition-property: none;
width: 40px;
height: 40px;
@ -11,6 +89,18 @@
overflow: hidden;
box-shadow: inset 0 0 0 1px var(--gl-avatar-border-color-default);
&.avatar-inline {
float: none;
display: inline-block;
margin-left: 2px;
flex-shrink: 0;
&.s16,
&.s24 {
margin-right: 4px;
}
}
&.center {
font-size: 14px;
line-height: 1.8em;
@ -23,6 +113,80 @@
}
}
.identicon {
text-align: center;
vertical-align: top;
color: var(--gl-text-color-strong);
background-color: var(--gl-avatar-fallback-background-color-neutral);
// Sizes
@each $size, $size-config in $avatar-sizes {
$keys: map-keys($size-config);
&.s#{$size} {
@each $key in $keys {
// We don't want `border-radius` to be included here.
@if ($key != 'border-radius') {
#{$key}: map-get($size-config, #{$key});
}
}
}
}
// Background colors
@for $i from 1 through length($gl-avatar-identicon-bgs) {
&.bg#{$i} {
color: nth($gl-avatar-identicon-texts, $i);
background-color: nth($gl-avatar-identicon-bgs, $i);
}
}
}
.avatar-container {
overflow: hidden;
display: flex;
a {
width: 100%;
height: 100%;
display: flex;
text-decoration: none;
}
.avatar {
border-radius: 0;
border: 0;
height: auto;
width: 100%;
margin: 0;
align-self: center;
}
&.s40 {
min-width: 40px;
min-height: 40px;
}
&.s64 {
min-width: 64px;
min-height: 64px;
}
}
.rect-avatar {
border-radius: $border-radius-small;
@each $size, $size-config in $avatar-sizes {
&.s#{$size} {
border-radius: map-get($size-config, 'border-radius');
.avatar {
border-radius: map-get($size-config, 'border-radius');
}
}
}
}
.avatar-counter {
@include avatar-counter();
}

View File

@ -505,10 +505,6 @@
top: calc(50% + 5px);
left: 3rem;
}
.gl-avatar {
margin-right: 0;
}
}
}

View File

@ -164,6 +164,10 @@ ul.content-list {
.badge-secondary {
color: $gl-text-color-secondary;
}
.avatar-cell {
align-self: flex-start;
}
}
ul.content-list.content-list-items-padding > li,
@ -190,6 +194,14 @@ ul.controls {
&:last-child {
margin-right: 0;
}
.author-link {
.avatar-inline {
margin-left: 0;
margin-right: 0;
margin-bottom: 0;
}
}
}
.issuable-pipeline-broken a,

View File

@ -172,6 +172,16 @@
.gl-tabs-nav li a.gl-tab-nav-item-active {
box-shadow: inset 0 -2px 0 0 var(--ide-highlight-accent, $gl-text-color);
}
// for other themes, suppress different avatar default colors for simplicity
.avatar-container {
&,
.avatar {
color: var(--ide-text-color, $gl-text-color);
background-color: var(--ide-highlight-background, $white);
border-color: var(--ide-highlight-background, rgba($black, $gl-avatar-border-opacity));
}
}
}
input[type='text'],

View File

@ -11,10 +11,6 @@
}
}
.home-panel-metadata {
line-height: $gl-btn-line-height;
}
.home-panel-description {
@include media-breakpoint-up(md) {
font-size: $gl-font-size-large;

View File

@ -863,6 +863,11 @@ $ide-commit-header-height: 48px;
}
.ide-context-header {
.avatar-container {
flex: 0 0 auto;
margin-right: 0;
}
.ide-sidebar-project-title {
margin-left: $ide-tree-text-start - $ide-project-avatar-end;
}

View File

@ -46,15 +46,6 @@
}
}
.user-item {
padding: 5px;
flex-basis: 20%;
.user-link {
display: inline-block;
}
}
.issuable-gutter-toggle {
@include media-breakpoint-down(sm) {
margin-left: $btn-side-margin;

View File

@ -376,6 +376,16 @@
table-layout: fixed;
}
.avatar-container {
@include avatar-size(40px, 10px);
min-height: 40px;
min-width: 40px;
.identicon.s48 {
font-size: 16px;
}
}
.updated-note {
@include media-breakpoint-up(sm) {
margin-top: $gl-spacing-scale-2;

View File

@ -140,6 +140,15 @@ table.pipeline-project-metrics tr td {
flex-basis: 100%;
}
.avatar-container {
flex-shrink: 0;
> a {
width: 100%;
text-decoration: none;
}
}
.title {
font-size: $gl-font-size;
}

View File

@ -65,6 +65,7 @@
}
.gl-avatar:not(.gl-avatar-identicon),
.avatar-container,
.avatar {
background: rgba($gray-950, 0.04);
}

View File

@ -21,6 +21,10 @@
margin-top: $gl-spacing-scale-2;
}
.avatar-inline {
margin-bottom: 0;
}
.has-warning {
.description {
color: $gray-900;

View File

@ -476,6 +476,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
def get_diffs_count
return @merge_request.context_commits_diff.raw_diffs.size if show_only_context_commits?
return @merge_request.merge_request_diffs.find_by_id(params[:diff_id])&.size if params[:diff_id]
return @merge_request.merge_head_diff.size if @merge_request.diffable_merge_ref? && params[:start_sha].blank?
@merge_request.diff_size
end

View File

@ -118,7 +118,7 @@ module Projects
# overridden in EE
def permitted_project_params
{
[
incident_management_setting_attributes: ::Gitlab::Tracking::IncidentManagement.tracking_keys.keys,
error_tracking_setting_attributes: [
@ -128,7 +128,7 @@ module Projects
:token,
{ project: [:slug, :name, :organization_slug, :organization_name, :sentry_project_id] }
]
}
]
end
end
end

View File

@ -7,6 +7,14 @@ module Resolvers
type ::Types::Ci::JobType.connection_type, null: true
argument :job_kind, ::Types::Ci::JobKindEnum,
required: false,
description: 'Filter jobs by kind.'
argument :retried, ::GraphQL::Types::Boolean,
required: false,
description: 'Filter jobs by retry-status.'
argument :security_report_types, [Types::Security::ReportTypeEnum],
required: false,
description: 'Filter jobs by the type of security report they produce.'
@ -15,25 +23,22 @@ module Resolvers
required: false,
description: 'Filter jobs by status.'
argument :retried, ::GraphQL::Types::Boolean,
required: false,
description: 'Filter jobs by retry-status.'
argument :when_executed, [::GraphQL::Types::String],
required: false,
description: 'Filter jobs by when they are executed.'
argument :job_kind, ::Types::Ci::JobKindEnum,
required: false,
description: 'Filter jobs by kind.'
def resolve(statuses: nil, security_report_types: [], retried: nil, when_executed: nil, job_kind: nil)
def resolve(
job_kind: nil,
retried: nil,
security_report_types: [],
statuses: nil,
when_executed: nil)
jobs = init_collection(security_report_types)
jobs = jobs.with_status(statuses) if statuses.present?
jobs = jobs.retried if retried
jobs = jobs.with_when_executed(when_executed) if when_executed.present?
jobs = jobs.latest if retried == false
jobs = jobs.retried if retried
jobs = jobs.with_status(statuses) if statuses.present?
jobs = jobs.with_type(job_kind) if job_kind
jobs = jobs.with_when_executed(when_executed) if when_executed.present?
jobs
end

View File

@ -94,7 +94,7 @@ module AvatarsHelper
has_tooltip = options[:has_tooltip].nil? ? true : options[:has_tooltip]
data_attributes = options[:data] || {}
css_class = %W[gl-avatar gl-avatar-circle gl-avatar-s#{avatar_size}].push(*options[:css_class])
css_class = %W[avatar s#{avatar_size}].push(*options[:css_class])
alt_text = user_name ? "#{user_name}'s avatar" : "default avatar"
if has_tooltip

View File

@ -29,7 +29,7 @@ module ProjectsHelper
default_opts = { size: 16 }
opts = default_opts.merge(opts)
classes = %W[gl-avatar gl-avatar-circle gl-avatar-s#{opts[:size]}]
classes = %W[avatar avatar-inline s#{opts[:size]}]
classes << opts[:avatar_class] if opts[:avatar_class]
avatar = avatar_icon_for_user(author, opts[:size])

View File

@ -9,7 +9,7 @@ module SidebarsHelper
end
def scope_avatar_classes(object)
%w[gl-avatar gl-avatar-s32].tap do |klasses|
%w[avatar-container rect-avatar s32].tap do |klasses|
klass = sidebar_attributes_for_object(object).fetch(:scope_avatar_class, nil)
klasses << klass if klass
end

View File

@ -7,4 +7,8 @@ module WikiPageVersionHelper
user = wiki_page_version.author
user.nil? ? "mailto:#{wiki_page_version.author_email}" : Gitlab::UrlBuilder.build(user)
end
def wiki_page_version_author_avatar(wiki_page_version)
image_tag(avatar_icon_for_email(wiki_page_version.author_email, 24), class: "avatar s24 float-none !gl-mr-0")
end
end

View File

@ -295,7 +295,8 @@ module ApplicationSettingImplementation
ai_action_api_rate_limit: 160,
code_suggestions_api_rate_limit: 60,
require_personal_access_token_expiry: true,
pages_extra_deployments_default_expiry_seconds: 86400
pages_extra_deployments_default_expiry_seconds: 86400,
scan_execution_policies_action_limit: 10
}.tap do |hsh|
hsh.merge!(non_production_defaults) unless Rails.env.production?
end

View File

@ -35,8 +35,8 @@ module Ci
})
elsif component_path.invalid_usage_for_latest?
ServiceResponse.error(
message: 'The ~latest version reference is not supported for non-catalog resources. ' \
'Use a tag, branch, or SHA instead',
message: "#{error_prefix} The ~latest version reference is not supported for non-catalog resources. " \
'Use a tag, branch, or commit SHA instead.',
reason: :invalid_usage)
else
ServiceResponse.error(message: "#{error_prefix} content not found", reason: :content_not_found)
@ -63,7 +63,7 @@ module Ci
strong_memoize_attr :component_path_class
def error_prefix
"component '#{address}' -"
"Component '#{address}' -"
end
end
end

View File

@ -24,8 +24,8 @@
- add_page_specific_style 'page_bundles/commit_description'
%li{ class: ["commit flex-row", ("js-toggle-container" if collapsible)], id: "commit-#{commit.short_id}" }
.gl-self-start.gl-hidden.sm:gl-block.gl-mr-3
= author_avatar(commit, size: 32, has_tooltip: false)
.avatar-cell.gl-hidden.sm:gl-block
= author_avatar(commit, size: 40, has_tooltip: false)
.commit-detail.flex-list.gl-flex.gl-justify-between.gl-items-center.gl-grow.gl-min-w-0
.commit-content{ data: { testid: 'commit-content' } }
@ -46,7 +46,7 @@
- if commit.different_committer? && commit.committer
- commit_committer_link = commit_committer_link(commit)
- commit_committer_timeago = time_ago_with_tooltip(commit.committed_date, placement: 'bottom')
- commit_committer_avatar = commit_committer_avatar(commit.committer, size: 16, has_tooltip: false)
- commit_committer_avatar = commit_committer_avatar(commit.committer, size: 18, has_tooltip: false)
- commit_text = _('%{commit_author_link} authored %{commit_authored_timeago} and %{commit_committer_avatar} %{commit_committer_link} committed %{commit_committer_timeago}') % { commit_author_link: commit_author_link, commit_authored_timeago: commit_authored_timeago, commit_committer_avatar: commit_committer_avatar, commit_committer_link: commit_committer_link, commit_committer_timeago: commit_committer_timeago }
- else
- commit_text = _('%{commit_author_link} authored %{commit_authored_timeago}') % { commit_author_link: commit_author_link, commit_authored_timeago: commit_authored_timeago }

View File

@ -1,3 +0,0 @@
- return unless can?(current_user, :read_observability, @project)
.js-observability-settings

View File

@ -2,7 +2,7 @@
- breadcrumb_title _('Monitor Settings')
- @force_desktop_expanded_sidebar = true
= render 'projects/settings/operations/observability'
= render_if_exists 'projects/settings/operations/observability'
= render 'projects/settings/operations/error_tracking'
= render 'projects/settings/operations/alert_management'
= render 'projects/settings/operations/incidents'

View File

@ -4,7 +4,7 @@
%li.filter-dropdown-item{ class: ('js-current-user' if user == current_user) }
= render Pajamas::ButtonComponent.new(variant: :link, button_text_classes: 'gl-flex gl-items-center') do
.gl-shrink-0
= user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 32, has_tooltip: false)
= user_avatar_without_link(user: user, lazy: avatar[:lazy], url: avatar[:url], size: 40, has_tooltip: false)
.gl-flex.gl-flex-col
%span.gl-font-bold.gl-whitespace-normal.gl-break-words
= user.name

View File

@ -24,6 +24,6 @@
- assignees.each do |assignee|
= link_to polymorphic_path(issuable_type_args, { milestone_title: @milestone.title, assignee_id: assignee.id, state: 'all' }),
class: 'has-tooltip', title: _("Assigned to %{assignee_name}") % { assignee_name: assignee.name }, data: { container: 'body' } do
- image_tag(avatar_icon_for_user(assignee, 16), class: "gl-avatar gl-avatar-circle gl-avatar-s16", alt: '')
- image_tag(avatar_icon_for_user(assignee, 16), class: "avatar s16", alt: '')
= render_if_exists "shared/milestones/issuable_weight", issuable: issuable

View File

@ -523,6 +523,13 @@ vulnerability_exports:
- table: users
column: author_id
on_delete: async_delete
vulnerability_external_issue_links:
- table: projects
column: project_id
on_delete: async_delete
- table: users
column: author_id
on_delete: async_delete
vulnerability_feedback:
- table: ci_pipelines
column: pipeline_id

View File

@ -8,4 +8,7 @@ description: Stores information about the security scans that are a part of Ci::
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23669
milestone: '12.8'
gitlab_schema: gitlab_sec
sharding_key_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/454948
# `desired_sharding_key` is `project_id`. However, the configuration can't be provided here
# because the backfill migration was processed manually.
# This migration is custom due to data consistency issues and cross-DB backfill.
desired_sharding_key_migration_job_name: BackfillProjectIdToSecurityScans

View File

@ -8,9 +8,7 @@ description: Stores information about connections between external issue tracker
and vulnerabilities
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48465
milestone: '13.7'
gitlab_schema: gitlab_main_cell
allow_cross_foreign_keys:
- gitlab_main_clusterwide
gitlab_schema: gitlab_sec
desired_sharding_key:
project_id:
references: projects

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
class AddObservabilityAlertsEnabledToProjectSettings < Gitlab::Database::Migration[2.2]
milestone '17.4'
def up
add_column :project_settings, :observability_alerts_enabled, :boolean, default: true, null: false
end
def down
remove_column :project_settings, :observability_alerts_enabled
end
end

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class AddPolicyActionLimitApplicationSetting < Gitlab::Database::Migration[2.2]
milestone '17.4'
enable_lock_retries!
def change
add_column :application_settings, :security_policies, :jsonb, default: {}, null: false
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class AddSecurityPoliciesHashConstraintToApplicationSettings < Gitlab::Database::Migration[2.2]
disable_ddl_transaction!
milestone '17.4'
CONSTRAINT_NAME = 'check_application_settings_security_policies_is_hash'
def up
add_check_constraint(
:application_settings,
"(jsonb_typeof(security_policies) = 'object')",
CONSTRAINT_NAME
)
end
def down
remove_check_constraint :application_settings, CONSTRAINT_NAME
end
end

View File

@ -10,31 +10,35 @@ class QueueBackfillVulnerabilityExternalIssueLinksProjectId < Gitlab::Database::
SUB_BATCH_SIZE = 100
def up
queue_batched_background_migration(
MIGRATION,
:vulnerability_external_issue_links,
:id,
:project_id,
:vulnerabilities,
:project_id,
:vulnerability_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(
MIGRATION,
:vulnerability_external_issue_links,
:id,
[
Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.with_suppressed do
queue_batched_background_migration(
MIGRATION,
:vulnerability_external_issue_links,
:id,
:project_id,
:vulnerabilities,
:project_id,
:vulnerability_id
]
)
:vulnerability_id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
end
def down
Gitlab::Database::QueryAnalyzers::RestrictAllowedSchemas.with_suppressed do
delete_batched_background_migration(
MIGRATION,
:vulnerability_external_issue_links,
:id,
[
:project_id,
:vulnerabilities,
:project_id,
:vulnerability_id
]
)
end
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class RemoveProjectsVulnerabilityExternalIssueLinksProjectIdFk < Gitlab::Database::Migration[2.2]
milestone '17.4'
disable_ddl_transaction!
FOREIGN_KEY_NAME = "fk_abd093bb21"
def up
with_lock_retries do
remove_foreign_key_if_exists(:vulnerability_external_issue_links, :projects,
name: FOREIGN_KEY_NAME, reverse_lock_order: true)
end
end
def down
add_concurrent_foreign_key(:vulnerability_external_issue_links, :projects,
name: FOREIGN_KEY_NAME, column: :project_id,
target_column: :id, on_delete: :cascade)
end
end

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
class RemoveUsersVulnerabilityExternalIssueLinksAuthorIdFk < Gitlab::Database::Migration[2.2]
milestone '17.4'
disable_ddl_transaction!
FOREIGN_KEY_NAME = "fk_rails_e5ba7f7b13"
def up
with_lock_retries do
remove_foreign_key_if_exists(:vulnerability_external_issue_links, :users,
name: FOREIGN_KEY_NAME, reverse_lock_order: true)
end
end
def down
add_concurrent_foreign_key(:vulnerability_external_issue_links, :users,
name: FOREIGN_KEY_NAME, column: :author_id,
target_column: :id, on_delete: :nullify)
end
end

View File

@ -0,0 +1 @@
bfdfc1454a7d21a2d15b473edf8b7e6d4dde88cb8ebef07dff0e89cfa522e98e

View File

@ -0,0 +1 @@
ade03637a598a0ad8e6416cf5a0accfb074dca82a8ebade15509b2e9216196bc

View File

@ -0,0 +1 @@
a68ceb0f06cbee9806f007d27c86c43d34694f48ae2ed624ab110c3ecc8c7478

View File

@ -0,0 +1 @@
94b8b6456c58d120a912adfca337c71e4abc2aec0005d67785b21cfd068db48d

View File

@ -0,0 +1 @@
709583f3caaea6656c95ac146da29439aeb6ac7dea7606fb52abbaf857496dd5

View File

@ -6050,6 +6050,7 @@ CREATE TABLE application_settings (
cluster_agents jsonb DEFAULT '{}'::jsonb NOT NULL,
observability_backend_ssl_verification_enabled boolean DEFAULT true NOT NULL,
pages jsonb DEFAULT '{}'::jsonb NOT NULL,
security_policies jsonb DEFAULT '{}'::jsonb NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
CONSTRAINT app_settings_ext_pipeline_validation_service_url_text_limit CHECK ((char_length(external_pipeline_validation_service_url) <= 255)),
@ -6106,6 +6107,7 @@ CREATE TABLE application_settings (
CONSTRAINT check_application_settings_package_registry_is_hash CHECK ((jsonb_typeof(package_registry) = 'object'::text)),
CONSTRAINT check_application_settings_rate_limits_is_hash CHECK ((jsonb_typeof(rate_limits) = 'object'::text)),
CONSTRAINT check_application_settings_rate_limits_unauth_git_http_is_hash CHECK ((jsonb_typeof(rate_limits_unauthenticated_git_http) = 'object'::text)),
CONSTRAINT check_application_settings_security_policies_is_hash CHECK ((jsonb_typeof(security_policies) = 'object'::text)),
CONSTRAINT check_application_settings_service_ping_settings_is_hash CHECK ((jsonb_typeof(service_ping_settings) = 'object'::text)),
CONSTRAINT check_b8c74ea5b3 CHECK ((char_length(deactivation_email_additional_text) <= 1000)),
CONSTRAINT check_cdfbd99405 CHECK ((char_length(security_txt_content) <= 2048)),
@ -16710,6 +16712,7 @@ CREATE TABLE project_settings (
allow_merge_without_pipeline boolean DEFAULT false NOT NULL,
duo_features_enabled boolean DEFAULT true NOT NULL,
require_reauthentication_to_approve boolean,
observability_alerts_enabled boolean DEFAULT true NOT NULL,
CONSTRAINT check_1a30456322 CHECK ((char_length(pages_unique_domain) <= 63)),
CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)),
CONSTRAINT check_3ca5cbffe6 CHECK ((char_length(issue_branch_template) <= 255)),
@ -34039,9 +34042,6 @@ ALTER TABLE ONLY identities
ALTER TABLE ONLY boards
ADD CONSTRAINT fk_ab0a250ff6 FOREIGN KEY (iteration_cadence_id) REFERENCES iterations_cadences(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_external_issue_links
ADD CONSTRAINT fk_abd093bb21 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
ALTER TABLE ONLY audit_events_streaming_http_instance_namespace_filters
ADD CONSTRAINT fk_abe44125bc FOREIGN KEY (audit_events_instance_external_audit_event_destination_id) REFERENCES audit_events_instance_external_audit_event_destinations(id) ON DELETE CASCADE;
@ -36319,9 +36319,6 @@ ALTER TABLE ONLY incident_management_escalation_policies
ALTER TABLE ONLY software_license_policies
ADD CONSTRAINT fk_rails_e5b77d620e FOREIGN KEY (scan_result_policy_id) REFERENCES scan_result_policies(id) ON DELETE CASCADE;
ALTER TABLE ONLY vulnerability_external_issue_links
ADD CONSTRAINT fk_rails_e5ba7f7b13 FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
ALTER TABLE ONLY approval_merge_request_rule_sources
ADD CONSTRAINT fk_rails_e605a04f76 FOREIGN KEY (approval_merge_request_rule_id) REFERENCES approval_merge_request_rules(id) ON DELETE CASCADE;

View File

@ -731,7 +731,7 @@ You might receive an error message similar to the following when using the `~lat
version qualifier to reference a component hosted by a [catalog project](#set-a-component-project-as-a-catalog-project):
```plaintext
This GitLab CI configuration is invalid: component 'gitlab.com/my-namespace/my-project/my-component@~latest' - content not found
This GitLab CI configuration is invalid: Component 'gitlab.com/my-namespace/my-project/my-component@~latest' - content not found
```
The `~latest` behavior [was updated](https://gitlab.com/gitlab-org/gitlab/-/issues/442238)

View File

@ -176,8 +176,6 @@ is identified after release, the following steps must still be completed:
### In the release following the required stop
1. Update `Gitlab::Database::MIN_SCHEMA_GITLAB_VERSION` in `lib/gitlab/database.rb` to the
new required stop versions. Do not change `Gitlab::Database::MIN_SCHEMA_VERSION`.
1. In the `charts` project, update the
[upgrade check hook](https://docs.gitlab.com/charts/development/upgrade_stop.html)
to the required stop version.

View File

@ -6,19 +6,24 @@ group: unassigned
# Documenting experimental and beta features
Some features are not generally available and are instead considered
[experimental or beta](../../policy/experiment-beta-support.md).
When you document an [experiment or beta](../../policy/experiment-beta-support.md) feature:
When you document a feature in one of these three statuses:
- Include the status in the [product availability details](styleguide/availability_details.md#status).
- Include [feature flag details](feature_flags.md) if behind a feature flag.
- [Update the feature status](styleguide/availability_details.md#changed-feature-status) when it changes.
- Add the tier badge after the page or topic title.
- Do not include `(Experiment)` or `(Beta)` in the left nav.
- Ensure the history lists the feature's status.
## When features become generally available
These features are usually behind a feature flag, which follow [these documentation guidelines](feature_flags.md).
When the feature changes from experiment or beta to generally available:
If you add details of how users should enroll, or how to contact the team with issues,
the `FLAG:` note should be above these details.
- Remove the **Status** from the product availability details.
- Remove any language about the feature not being ready for production.
- Update the [history](../documentation/styleguide/availability_details.md#history).
## Features that require user enrollment or feedback
To include details about how users should enroll or leave feedback,
add it below the `FLAG:` note.
For example:
@ -42,26 +47,10 @@ the list of users testing this feature, do this thing. If you find a bug,
[open an issue](https://link).
```
## When features become generally available
When the feature changes from experiment or beta to generally available, remove:
- The **Status** from the availability details.
- Any language about the feature not being ready for production in the body
description.
- The feature flag information if available.
Ensure the history is up-to-date by adding a note about the production release.
## GitLab Duo features
Follow these guidelines when you document GitLab Duo features.
NOTE:
As of August, 2024, all GitLab Duo features except Code Suggestions have forward-looking tier statements, for example,
`For a limited time, Premium. In the future, Premium with GitLab Duo Pro.`
Use these type of statements until further notice.
### Experiment
When documenting a GitLab Duo experiment:

View File

@ -57,22 +57,21 @@ For tier, choose one:
Document add-ons by using the phrase `with` and the add-on.
For example, `with GitLab Duo Pro`.
As of August, 2024, all GitLab Duo features except Code Suggestions have
forward-looking tier statements. Use these type of statements until further notice.
The possibilities are:
```markdown
**Tier:** Premium with GitLab Duo Pro
**Tier:** Premium with GitLab Duo Pro or Ultimate with GitLab Duo Pro or Enterprise
**Tier:** Ultimate with GitLab Duo Pro
**Tier:** Ultimate with GitLab Duo Pro or Enterprise
**Tier:** Ultimate with GitLab Duo Enterprise
**Tier:** For a limited time, Premium or Ultimate. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md).
**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md).
**Tier:** For a limited time, Ultimate. In the future, Ultimate with [GitLab Duo Enterprise](../../subscriptions/subscription-add-ons.md).
```
Do not use a period at the end of the phrase.
You might have to differentiate which add-on applies for each offering (GitLab.com, Dedicated, self-managed). If you have to differentiate, use this format:
```markdown
**Tier: GitLab.com and Self-managed:** Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md) **GitLab Dedicated:** GitLab Duo Pro or Enterprise
**Tier: GitLab.com and Self-managed:** For a limited time, Premium or Ultimate. In the future, Premium with GitLab Duo Pro or Ultimate with [GitLab Duo Pro or Enterprise](../../subscriptions/subscription-add-ons.md). **GitLab Dedicated:** GitLab Duo Pro or Enterprise.
```
NOTE:

View File

@ -94,13 +94,18 @@ the following sections and tables provide an alternative.
## Scan execution policy schema
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/472213) in GitLab 17.4 [with flags](../../../administration/feature_flags.md) named `scan_execution_policy_action_limit` (for projects) and `scan_execution_policy_action_limit_group` (for groups). Disabled by default.
FLAG:
The availability of this feature is controlled by a feature flag.
For more information, see the history.
| Field | Type | Required | Possible values | Description |
|-------|------|----------|-----------------|-------------|
| `name` | `string` | true | | Name of the policy. Maximum of 255 characters.|
| `description` (optional) | `string` | true | | Description of the policy. |
| `enabled` | `boolean` | true | `true`, `false` | Flag to enable (`true`) or disable (`false`) the policy. |
| `rules` | `array` of rules | true | | List of rules that the policy applies. |
| `actions` | `array` of actions | true | | List of actions that the policy enforces. |
| `actions` | `array` of actions | true | | List of actions that the policy enforces. Limited to a maximum of 10 in GitLab 18.0 and later. |
## `pipeline` rule type

View File

@ -131,7 +131,6 @@ then process the merge request according to your standard workflow.
Vulnerability Resolution is enabled for the following list of vulnerability classes. If you see that the button is disabled, that means that the CWE is not part of the supported list at this time. We are actively testing and expanding coverage for Vulnerability Resolution to more classes of vulnerabilities.
<br><br>
<details><summary style="color:#5943b6"><a>View the complete list of supported CWEs for Vulnerability Resolution</a></summary>
<ul>

View File

@ -189,6 +189,9 @@ For a quick overview, items that are exported include:
- Emoji reactions
- Project and inherited group members, as long as the user has the Maintainer role for the
exported project's group or is an administrator
- Some merge request approval rules:
- [Approvals for protected branches](../merge_requests/approvals/rules.md#approvals-for-protected-branches).
- [Users that are eligible approvers](../merge_requests/approvals/rules.md#eligible-approvers).
#### Project items that are not exported

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::BackupExecutor do
let(:context) { build_fake_context }

View File

@ -1,8 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
require 'thor'
RSpec.describe Gitlab::Backup::Cli::Commands::BackupSubcommand do
describe "#executor_options" do
it "returns the expected hash" do

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::Dependencies do
let(:bin_path) { Dir.mktmpdir('dependencies', temp_path) }

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::Metadata::BackupMetadata do
subject(:metadata) { build(:backup_metadata) }

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::RestoreExecutor do
let(:context) { build_fake_context }
let(:backup_id) { "1715018771_2024_05_06_17.0.0-pre" }

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::Shell::Command do
let(:envdata) do
{ 'CUSTOM' => 'data' }

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::Shell::Pipeline do
let(:command) { Gitlab::Backup::Cli::Shell::Command }
let(:printf_command) { command.new('printf "3\n2\n1"') }

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::SourceContext do
subject(:context) { described_class.new }

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::Targets::ObjectStorage::Google do
let(:gitlab_config) { class_double("GitlabSettings::Settings") }
let(:supported_object_store) do

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::Tasks::Task do
let(:options) { nil }
let(:context) { build_fake_context }

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::Utils::PgDump do
let(:cmd_args) { pg_dump.send(:cmd_args) }
let(:database_name) { 'gitlab_database' }

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli::Utils::Tar do
subject(:tar) { described_class.new }

View File

@ -1,7 +1,5 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Backup::Cli do
it "has a version number" do
expect(Gitlab::Backup::Cli::VERSION).not_to be nil

View File

@ -1,10 +1,9 @@
# frozen_string_literal: true
require "gitlab/backup/cli"
require 'active_support/all'
require 'tmpdir'
require 'fileutils'
require 'factory_bot'
require 'bundler/setup'
Bundler.require(:default, :development, :test)
require 'thor'
require 'gitlab/rspec/next_instance_of'
ENV["RAILS_ENV"] ||= "test"

View File

@ -216,7 +216,7 @@ module Keeps
end
def before_cuttoff_milestone?(milestone)
Gem::Version.new(milestone) <= Gem::Version.new(::Gitlab::Database::MIN_SCHEMA_GITLAB_VERSION)
Gem::Version.new(milestone) <= Gem::Version.new(::Gitlab::Database.min_schema_gitlab_version)
end
def each_batched_background_migration

View File

@ -90,7 +90,7 @@ module Banzai
avatar = user_avatar_without_link(
user: user,
user_email: email,
css_class: 'gl-mr-2',
css_class: 'avatar-inline',
has_tooltip: false,
only_path: false
)

View File

@ -14,7 +14,7 @@ module Gitlab
:chat_data, :allow_mirror_update, :bridge, :content, :dry_run, :logger, :execution_policy_dry_run,
# These attributes are set by Chains during processing:
:config_content, :yaml_processor_result, :workflow_rules_result, :pipeline_seed,
:pipeline_config, :pipeline_execution_policies, :partition_id
:pipeline_config, :execution_policy_pipelines, :partition_id
) do
include Gitlab::Utils::StrongMemoize

View File

@ -38,7 +38,7 @@ module Gitlab
def no_pipeline_to_create?
# If there are security policy pipelines,
# they will be merged onto the pipeline in PipelineExecutionPolicies::MergeJobs
stage_names.empty? && @command.pipeline_execution_policies.blank?
stage_names.empty? && @command.execution_policy_pipelines.blank?
end
def stage_names

View File

@ -32,9 +32,6 @@ module Gitlab
# https://gitlab.com/gitlab-org/gitlab-foss/issues/61974
MAX_TEXT_SIZE_LIMIT = 1_000_000
# Migrations before this version may have been removed
MIN_SCHEMA_GITLAB_VERSION = '17.3'
# Schema we store dynamically managed partitions in (e.g. for time partitioning)
DYNAMIC_PARTITIONS_SCHEMA = :gitlab_partitions_dynamic
@ -142,6 +139,18 @@ module Gitlab
Gitlab::Runtime.max_threads + headroom
end
# Expose path information so that we can use it to make sure migrations are
# healthy
def self.upgrade_path
path_data = YAML.safe_load_file(Rails.root.join('config/upgrade_path.yml'))
Gitlab::Utils::UpgradePath.new(path_data, Gitlab.version_info)
end
# Migrations before this version may have been removed.
def self.min_schema_gitlab_version
upgrade_path.last_required_stop
end
# Database configured. Returns true even if the database is shared
def self.has_config?(database_name)
ActiveRecord::Base.configurations

View File

@ -15,7 +15,7 @@ task schema_version_check: :environment do
# But a database with existing migrations less than our min version is not
if schema_version > 0 && schema_version < minimum_migration_version
raise "Your current database version is too old to be migrated. " \
"You should upgrade to GitLab #{Gitlab::Database::MIN_SCHEMA_GITLAB_VERSION} before moving to this version. " \
"You should upgrade to GitLab #{Gitlab::Database.min_schema_gitlab_version} before moving to this version. " \
"Please see https://docs.gitlab.com/ee/policy/maintenance.html#upgrade-recommendations"
end
end

View File

@ -37063,6 +37063,12 @@ msgstr ""
msgid "Observability|Create issues from this page to view them as related items here."
msgstr ""
msgid "Observability|Creates alerts automatically for Observability-related errors."
msgstr ""
msgid "Observability|Enable Observability alerts"
msgstr ""
msgid "Observability|Enable tracing, metrics, or logs on your project."
msgstr ""
@ -37129,10 +37135,7 @@ msgstr ""
msgid "Observability|Usage by day"
msgstr ""
msgid "Observability|View our %{docsLink} for further instructions on how to use these features."
msgstr ""
msgid "Observability|documentation"
msgid "Observability|View our %{documentation} for further instructions on how to use these features."
msgstr ""
msgid "Oct"
@ -49105,6 +49108,9 @@ msgstr ""
msgid "SecurityOrchestration|Policy editor"
msgstr ""
msgid "SecurityOrchestration|Policy exceeds the maximum of %{limit} actions"
msgstr ""
msgid "SecurityOrchestration|Policy is invalid"
msgstr ""
@ -64158,6 +64164,9 @@ msgstr ""
msgid "disabled"
msgstr ""
msgid "documentation"
msgstr ""
msgid "does not belong to the top-level group of the namespace."
msgstr ""

View File

@ -1,4 +1,4 @@
ARG GDK_SHA=c6f57a57d8d3463241fd37be965ba5697138dc72
ARG GDK_SHA=59f9233de457781829a606c11e775b4809677dc3
# Use tag prefix when running on 'stable' branch to make sure 'protected' image is used which is not deleted by registry cleanup
ARG GDK_BASE_TAG_PREFIX

View File

@ -225,7 +225,7 @@ module QA
def expand_merge_checks
within_element('.mr-widget-section') do
click_element('chevron-lg-down-icon')
click_element('chevron-down-icon')
end
end

View File

@ -143,6 +143,42 @@ RSpec.describe 'User creates a merge request', :js, feature_category: :code_revi
end
end
context 'when source and target both have a commit with the same content' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let(:title) { 'Some feature' }
before do
project.add_maintainer(user)
sign_in(user)
# create a commit with identical content on source and target
project.repository.create_file(user, 'bbb.txt', 'zzzz', message: 'Commit on target', branch_name: 'feature')
project.repository.create_file(user, 'bbb.txt', 'zzzz', message: 'Commit on src', branch_name: 'fix')
end
it "contains the correct changes count", :sidekiq_inline do
visit(project_new_merge_request_path(project))
compare_source_and_target('fix', 'feature')
fill_in('Title', with: title)
click_button('Create merge request')
page.within('.diffs-tab') do
expect(page).to have_content('Changes 2')
end
click_on 'Changes'
wait_for_requests
page.within('.diffs-tab') do
expect(page).to have_content('Changes 2')
end
end
end
private
def compare_source_and_target(source_branch, target_branch)

View File

@ -1,6 +1,5 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlIcon, GlLink } from '@gitlab/ui';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
@ -44,13 +43,10 @@ describe('FailedJobDetails component', () => {
});
};
const findArrowIcon = () => wrapper.findComponent(GlIcon);
const findJobId = () => wrapper.findComponent(GlLink);
const findJobLog = () => wrapper.findByTestId('job-log');
const findJobName = () => wrapper.findByText(defaultProps.job.name);
const findJobId = () => wrapper.findByTestId('job-id-link');
const findJobName = () => wrapper.findByTestId('job-name-link');
const findRetryButton = () => wrapper.findByTestId('retry-button');
const findRow = () => wrapper.findByTestId('widget-row');
const findStageName = () => wrapper.findByText(defaultProps.job.stage.name);
const findStageName = () => wrapper.findByTestId('job-stage-name');
beforeEach(() => {
mockRetryResponse = jest.fn();
@ -76,10 +72,6 @@ describe('FailedJobDetails component', () => {
expect(findJobId().exists()).toBe(true);
expect(findJobId().text()).toContain(String(jobId));
});
it('does not renders the job lob', () => {
expect(findJobLog().exists()).toBe(false);
});
});
describe('Retry action', () => {
@ -178,77 +170,4 @@ describe('FailedJobDetails component', () => {
});
});
});
describe('Job log', () => {
describe('without permissions', () => {
beforeEach(async () => {
createComponent({ props: { job: { ...job, userPermissions: { readBuild: false } } } });
await findRow().trigger('click');
});
it('does not renders the received html of the job log', () => {
expect(findJobLog().html()).not.toContain(defaultProps.job.trace.htmlSummary);
});
it('shows a permission error message', () => {
expect(findJobLog().text()).toBe("You do not have permission to read this job's log.");
});
});
describe('with permissions', () => {
beforeEach(() => {
createComponent();
});
describe('when clicking on the row', () => {
beforeEach(async () => {
await findRow().trigger('click');
});
describe('while collapsed', () => {
it('expands the job log', () => {
expect(findJobLog().exists()).toBe(true);
});
it('renders the down arrow', () => {
expect(findArrowIcon().props().name).toBe('chevron-down');
});
it('renders the received html of the job log', () => {
expect(findJobLog().html()).toContain(defaultProps.job.trace.htmlSummary);
});
});
describe('while expanded', () => {
it('collapes the job log', async () => {
expect(findJobLog().exists()).toBe(true);
await findRow().trigger('click');
expect(findJobLog().exists()).toBe(false);
});
it('renders the right arrow', async () => {
expect(findArrowIcon().props().name).toBe('chevron-down');
await findRow().trigger('click');
expect(findArrowIcon().props().name).toBe('chevron-right');
});
});
});
describe('when clicking on a link element within the row', () => {
it('does not expands/collapse the job log', async () => {
expect(findJobLog().exists()).toBe(false);
expect(findArrowIcon().props().name).toBe('chevron-right');
await findJobId().vm.$emit('click');
expect(findJobLog().exists()).toBe(false);
expect(findArrowIcon().props().name).toBe('chevron-right');
});
});
});
});
});

View File

@ -20,9 +20,6 @@ export const job = {
id: '1',
name: 'build',
},
trace: {
htmlSummary: '<h1>Hello</h1>',
},
userPermissions: {
readBuild: true,
updateBuild: true,

View File

@ -1,8 +1,8 @@
import { GlModal } from '@gitlab/ui';
import { GlModal, GlSprintf } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';
// eslint-disable-next-line no-restricted-imports
import Vuex from 'vuex';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper';
import DeployFreezeTable from '~/deploy_freeze/components/deploy_freeze_table.vue';
import createStore from '~/deploy_freeze/store';
import { RECEIVE_FREEZE_PERIODS_SUCCESS } from '~/deploy_freeze/store/mutation_types';
@ -15,13 +15,13 @@ describe('Deploy freeze table', () => {
let wrapper;
let store;
const createComponent = () => {
const createComponent = (mountFn = mountExtended) => {
store = createStore({
projectId: '8',
timezoneData: timezoneDataFixture,
});
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = mountExtended(DeployFreezeTable, {
wrapper = mountFn(DeployFreezeTable, {
attachTo: document.body,
store,
});
@ -35,24 +35,33 @@ describe('Deploy freeze table', () => {
const findDeleteDeployFreezeModal = () => wrapper.findComponent(GlModal);
const findCount = () => wrapper.findByTestId('crud-count');
beforeEach(() => {
createComponent();
});
describe('When mounting', () => {
beforeEach(() => {
createComponent(shallowMountExtended);
});
it('dispatches fetchFreezePeriods when mounted', () => {
expect(store.dispatch).toHaveBeenCalledWith('fetchFreezePeriods');
it('dispatches fetchFreezePeriods when mounted', () => {
expect(store.dispatch).toHaveBeenCalledWith('fetchFreezePeriods');
});
});
describe('Renders correct data', () => {
it('displays empty', () => {
expect(findEmptyFreezePeriods().exists()).toBe(true);
expect(findEmptyFreezePeriods().text()).toMatchInterpolatedText(
'No deploy freezes exist for this project. To add one, select Add deploy freeze above.',
);
describe('without empty data', () => {
beforeEach(() => {
createComponent(shallowMountExtended);
});
it('displays empty', () => {
expect(findEmptyFreezePeriods().exists()).toBe(true);
expect(wrapper.findComponent(GlSprintf).attributes('message')).toBe(
'No deploy freezes exist for this project. To add one, select %{strongStart}Add deploy freeze%{strongEnd} above.',
);
});
});
describe('with data', () => {
beforeEach(async () => {
createComponent();
store.commit(RECEIVE_FREEZE_PERIODS_SUCCESS, freezePeriodsFixture);
await nextTick();
});
@ -106,6 +115,10 @@ describe('Deploy freeze table', () => {
});
describe('Table click actions', () => {
beforeEach(() => {
createComponent();
});
it('displays add deploy freeze button', () => {
expect(findAddDeployFreezeButton().exists()).toBe(true);
expect(findAddDeployFreezeButton().text()).toBe('Add deploy freeze');

View File

@ -557,8 +557,7 @@ describe('GfmAutoComplete', () => {
expect(membersBeforeSave([{ ...mockGroup, avatar_url: null }])).toEqual([
{
username: 'my-group',
avatarTag:
'<div class="gl-avatar gl-avatar-s24 gl-mr-2 gl-justify-center gl-items-center ">M</div>',
avatarTag: '<div class="avatar rect-avatar avatar-inline s24 gl-mr-2">M</div>',
title: 'My Group (2)',
search: 'MyGroup my-group',
icon: '',
@ -571,7 +570,7 @@ describe('GfmAutoComplete', () => {
{
username: 'my-group',
avatarTag:
'<img src="./group.jpg" alt="my-group" class="gl-avatar gl-avatar-s24 gl-mr-2 "/>',
'<img src="./group.jpg" alt="my-group" class="avatar rect-avatar avatar-inline s24 gl-mr-2"/>',
title: 'My Group (2)',
search: 'MyGroup my-group',
icon: '',
@ -584,7 +583,7 @@ describe('GfmAutoComplete', () => {
{
username: 'my-group',
avatarTag:
'<img src="./group.jpg" alt="my-group" class="gl-avatar gl-avatar-s24 gl-mr-2 "/>',
'<img src="./group.jpg" alt="my-group" class="avatar rect-avatar avatar-inline s24 gl-mr-2"/>',
title: 'My Group',
search: 'MyGroup my-group',
icon: '<svg class="s16 vertical-align-middle gl-ml-2"><use xlink:href="/icons.svg#notifications-off" /></svg>',
@ -601,7 +600,7 @@ describe('GfmAutoComplete', () => {
{
username: 'my-user',
avatarTag:
'<img src="./users.jpg" alt="my-user" class="gl-avatar gl-avatar-s24 gl-mr-2 gl-avatar-circle"/>',
'<img src="./users.jpg" alt="my-user" class="avatar avatar-inline s24 gl-mr-2"/>',
title: 'My User',
search: 'MyUser my-user',
icon: '',

View File

@ -260,8 +260,9 @@ describe('GroupItemComponent', () => {
expect(vm.$el.querySelector('.folder-toggle-wrap')).toBeDefined();
expect(vm.$el.querySelector('.folder-toggle-wrap .item-type-icon')).toBeDefined();
expect(vm.$el.querySelector('.gl-avatar')).toBeDefined();
expect(vm.$el.querySelector('.gl-avatar a.no-expand')).toBeDefined();
expect(vm.$el.querySelector('.avatar-container')).toBeDefined();
expect(vm.$el.querySelector('.avatar-container a.no-expand')).toBeDefined();
expect(vm.$el.querySelector('.avatar-container .avatar')).toBeDefined();
expect(vm.$el.querySelector('.title')).toBeDefined();
expect(vm.$el.querySelector('.title a.no-expand')).toBeDefined();

View File

@ -242,7 +242,7 @@ describe('RelatedIssuesBlock', () => {
it('is expanded by default', () => {
const toggleButton = findToggleButton();
expect(toggleButton.props('icon')).toBe('chevron-lg-up');
expect(toggleButton.props('icon')).toBe('chevron-up');
expect(toggleButton.props('disabled')).toBe(false);
expect(toggleButton.attributes('aria-expanded')).toBe('true');
expect(findRelatedIssuesBody().exists()).toBe(true);
@ -254,7 +254,7 @@ describe('RelatedIssuesBlock', () => {
const toggleButton = findToggleButton();
expect(toggleButton.props('icon')).toBe('chevron-lg-down');
expect(toggleButton.props('icon')).toBe('chevron-down');
expect(toggleButton.attributes('aria-expanded')).toBe('false');
expect(findRelatedIssuesBody().exists()).toBe(false);
});

View File

@ -1,5 +1,4 @@
import { shallowMount } from '@vue/test-utils';
import { GlAvatar } from '@gitlab/ui';
import { TEST_HOST } from 'helpers/test_constants';
import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
import userDataMock from '../../user_data_mock';
@ -27,7 +26,7 @@ describe('AssigneeAvatar', () => {
window.gon = { default_avatar_url: TEST_DEFAULT_AVATAR_URL };
});
const findImg = () => wrapper.findComponent(GlAvatar);
const findImg = () => wrapper.find('img');
it('does not show warning icon if assignee can merge', () => {
createComponent();

View File

@ -1,4 +1,4 @@
import { GlAvatar, GlIcon } from '@gitlab/ui';
import { GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { trimText } from 'helpers/text_helper';
import UsersMockHelper from 'helpers/user_mock_data_helper';
@ -79,10 +79,8 @@ describe('Assignee component', () => {
const assignee = collapsedChildren.at(0);
expect(collapsedChildren.length).toBe(1);
expect(assignee.findComponent(GlAvatar).attributes('src')).toBe(UsersMock.user.avatar);
expect(assignee.findComponent(GlAvatar).attributes('alt')).toBe(
`${UsersMock.user.name}'s avatar`,
);
expect(assignee.find('.avatar').attributes('src')).toBe(UsersMock.user.avatar);
expect(assignee.find('.avatar').attributes('alt')).toBe(`${UsersMock.user.name}'s avatar`);
expect(trimText(assignee.find('.author').text())).toBe(UsersMock.user.name);
});
@ -102,15 +100,15 @@ describe('Assignee component', () => {
const first = collapsedChildren.at(0);
expect(first.findComponent(GlAvatar).attributes('src')).toBe(users[0].avatar_url);
expect(first.findComponent(GlAvatar).attributes('alt')).toBe(`${users[0].name}'s avatar`);
expect(first.find('.avatar').attributes('src')).toBe(users[0].avatar_url);
expect(first.find('.avatar').attributes('alt')).toBe(`${users[0].name}'s avatar`);
expect(trimText(first.find('.author').text())).toBe(users[0].name);
const second = collapsedChildren.at(1);
expect(second.findComponent(GlAvatar).attributes('src')).toBe(users[1].avatar_url);
expect(second.findComponent(GlAvatar).attributes('alt')).toBe(`${users[1].name}'s avatar`);
expect(second.find('.avatar').attributes('src')).toBe(users[1].avatar_url);
expect(second.find('.avatar').attributes('alt')).toBe(`${users[1].name}'s avatar`);
expect(trimText(second.find('.author').text())).toBe(users[1].name);
});
@ -128,8 +126,8 @@ describe('Assignee component', () => {
const first = collapsedChildren.at(0);
expect(first.findComponent(GlAvatar).attributes('src')).toBe(users[0].avatar_url);
expect(first.findComponent(GlAvatar).attributes('alt')).toBe(`${users[0].name}'s avatar`);
expect(first.find('.avatar').attributes('src')).toBe(users[0].avatar_url);
expect(first.find('.avatar').attributes('alt')).toBe(`${users[0].name}'s avatar`);
expect(trimText(first.find('.author').text())).toBe(users[0].name);

View File

@ -1,5 +1,4 @@
import { shallowMount } from '@vue/test-utils';
import { GlAvatar } from '@gitlab/ui';
import { nextTick } from 'vue';
import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue';
@ -38,7 +37,7 @@ describe('MrWidgetAuthor', () => {
});
it('renders image with avatar url', () => {
expect(wrapper.findComponent(GlAvatar).attributes('src')).toBe(
expect(wrapper.find('img').attributes('src')).toBe(
'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
);
});
@ -53,7 +52,7 @@ describe('MrWidgetAuthor', () => {
await nextTick();
expect(wrapper.findComponent(GlAvatar).props('src')).toBe('no_avatar.png');
expect(wrapper.find('img').attributes('src')).toBe('no_avatar.png');
});
it('renders author name', () => {

View File

@ -1,6 +1,6 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import { GlAvatar, GlDropdownItem } from '@gitlab/ui';
import { GlDropdownItem } from '@gitlab/ui';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@ -146,7 +146,7 @@ describe('Alert Details Sidebar Assignees', () => {
props: { alert: mockAlerts[1] },
});
expect(findAssigned().findComponent(GlAvatar).props('src')).toBe('/url');
expect(findAssigned().find('img').attributes('src')).toBe('/url');
expect(findAssigned().find('.dropdown-menu-user-full-name').text()).toBe('root');
expect(findAssigned().find('.dropdown-menu-user-username').text()).toBe('@root');
});

Some files were not shown because too many files have changed in this diff Show More