Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-07-18 18:10:24 +00:00
parent 93d0784e6d
commit 7488eeff6f
54 changed files with 1159 additions and 183 deletions

View File

@ -608,7 +608,7 @@ gem 'cvss-suite', '~> 3.0.1', require: 'cvss_suite'
gem 'arr-pm', '~> 0.0.12'
# Remote Development
gem 'devfile', '~> 0.0.19.pre.alpha1'
gem 'devfile', '~> 0.0.20.pre.alpha1'
# Apple plist parsing
gem 'CFPropertyList', '~> 3.0.0'

View File

@ -109,9 +109,9 @@
{"name":"deprecation_toolkit","version":"1.5.1","platform":"ruby","checksum":"a8a1ab1a19ae40ea12560b65010e099f3459ebde390b76621ef0c21c516a04ba"},
{"name":"derailed_benchmarks","version":"2.1.2","platform":"ruby","checksum":"eaadc6206ceeb5538ff8f5e04a0023d54ebdd95d04f33e8960fb95a5f189a14f"},
{"name":"descendants_tracker","version":"0.0.4","platform":"ruby","checksum":"e9c41dd4cfbb85829a9301ea7e7c48c2a03b26f09319db230e6479ccdc780897"},
{"name":"devfile","version":"0.0.19.pre.alpha1","platform":"arm64-darwin","checksum":"6087103d7e6c6226f2e9209d8619446afdb9bdaaf75cf7f6e06e929622465fe8"},
{"name":"devfile","version":"0.0.19.pre.alpha1","platform":"ruby","checksum":"d928c4529162c1f2a8434d8509d9d760d15dfb3c133669199de0d12ba3ec9ed8"},
{"name":"devfile","version":"0.0.19.pre.alpha1","platform":"x86_64-linux","checksum":"8c64e74eb470eedfd9a1754c40f82a6621a26735cdb22fc3d3dad4bc49445b37"},
{"name":"devfile","version":"0.0.20.pre.alpha1","platform":"arm64-darwin","checksum":"1dcd4a55482f04a0ec308d13fe41358bfbfd95ffe98678a471aa5e0e0c5dba98"},
{"name":"devfile","version":"0.0.20.pre.alpha1","platform":"ruby","checksum":"6a046be066c1b0392da1387249962d765d1f1a3a242e1ede0459b14a6d6fd760"},
{"name":"devfile","version":"0.0.20.pre.alpha1","platform":"x86_64-linux","checksum":"d0fa2a05ae7de71a17c6251e853459504ed6a51cb1f90a25ba9d0b7c9cb82074"},
{"name":"device_detector","version":"1.0.0","platform":"ruby","checksum":"b800fb3150b00c23e87b6768011808ac1771fffaae74c3238ebaf2b782947a7d"},
{"name":"devise","version":"4.8.1","platform":"ruby","checksum":"fdd48bbe79a89e7c1152236a70479842ede48bea4fa7f4f2d8da1f872559803e"},
{"name":"devise-two-factor","version":"4.0.2","platform":"ruby","checksum":"6548d2696ed090d27046f888f4fa7380f151e0f823902d46fd9b91e7d0cac511"},

View File

@ -418,7 +418,7 @@ GEM
thor (>= 0.19, < 2)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devfile (0.0.19.pre.alpha1)
devfile (0.0.20.pre.alpha1)
device_detector (1.0.0)
devise (4.8.1)
bcrypt (~> 3.0)
@ -1773,7 +1773,7 @@ DEPENDENCIES
declarative_policy (~> 1.1.0)
deprecation_toolkit (~> 1.5.1)
derailed_benchmarks
devfile (~> 0.0.19.pre.alpha1)
devfile (~> 0.0.20.pre.alpha1)
device_detector
devise (~> 4.8.1)
devise-pbkdf2-encryptable (~> 0.0.0)!

View File

@ -0,0 +1,33 @@
<script>
import { GlLabel } from '@gitlab/ui';
import { ABUSE_CATEGORIES } from '../constants';
export default {
name: 'AbuseCategory',
components: {
GlLabel,
},
props: {
category: {
type: String,
required: true,
},
},
computed: {
categoryObject() {
return ABUSE_CATEGORIES[this.category];
},
},
};
</script>
<template>
<gl-label
v-if="categoryObject"
size="sm"
:background-color="categoryObject.backgroundColor"
:title="categoryObject.title"
:target="null"
:class="`gl-text-${categoryObject.color}`"
/>
</template>

View File

@ -5,12 +5,14 @@ import { queryToObject } from '~/lib/utils/url_utility';
import { s__, __, sprintf } from '~/locale';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { SORT_UPDATED_AT } from '../constants';
import AbuseCategory from './abuse_category.vue';
export default {
name: 'AbuseReportRow',
components: {
GlLink,
ListItem,
AbuseCategory,
},
props: {
report: {
@ -44,13 +46,24 @@ export default {
<template>
<list-item data-testid="abuse-report-row">
<template #left-primary>
<gl-link :href="report.reportPath" class="gl-font-weight-normal gl-mb-2" data-testid="title">
<gl-link
:href="report.reportPath"
class="gl-font-weight-normal gl-pt-4 gl-text-gray-900"
data-testid="abuse-report-title"
>
{{ title }}
</gl-link>
</template>
<template #left-secondary>
<abuse-category
:category="report.category"
class="gl-mt-2 gl-mb-3"
data-testid="abuse-report-category"
/>
</template>
<template #right-secondary>
<div data-testid="abuse-report-date">{{ displayDate }}</div>
<div class="gl-mt-7" data-testid="abuse-report-date">{{ displayDate }}</div>
</template>
</list-item>
</template>

View File

@ -5,7 +5,7 @@ import {
OPERATORS_IS,
TOKEN_TITLE_STATUS,
} from '~/vue_shared/components/filtered_search_bar/constants';
import { __ } from '~/locale';
import { s__, __ } from '~/locale';
const STATUS_OPTIONS = [
{ value: 'closed', title: __('Closed') },
@ -78,3 +78,46 @@ export const FILTERED_SEARCH_TOKENS = [
FILTERED_SEARCH_TOKEN_REPORTER,
FILTERED_SEARCH_TOKEN_STATUS,
];
export const ABUSE_CATEGORIES = {
spam: {
backgroundColor: '#f5d9a8',
color: 'orange-700',
title: s__('AbuseReport|Spam'),
},
offensive: {
backgroundColor: '#e1d8f9',
color: 'purple-700',
title: s__('AbuseReport|Offensive or Abusive'),
},
phishing: {
backgroundColor: '#7c7ccc',
color: 'indigo-800',
title: s__('AbuseReport|Phishing'),
},
crypto: {
backgroundColor: '#fdd4cd',
color: 'red-700',
title: s__('AbuseReport|Crypto Mining'),
},
credentials: {
backgroundColor: '#cbe2f9',
color: 'blue-700',
title: s__('AbuseReport|Personal information or credentials'),
},
copyright: {
backgroundColor: '#c3e6cd',
color: 'green-700',
title: s__('AbuseReport|Copyright or trademark violation'),
},
malware: {
backgroundColor: '#fdd4cd',
color: 'red-700',
title: s__('AbuseReport|Malware'),
},
other: {
backgroundColor: '#dcdcde',
color: 'gray-700',
title: s__('AbuseReport|Other'),
},
};

View File

@ -6,7 +6,7 @@ import errorQuery from '~/boards/graphql/client/error.query.graphql';
import getIssueStateQuery from '~/issues/show/queries/get_issue_state.query.graphql';
import createDefaultClient from '~/lib/graphql';
import typeDefs from '~/work_items/graphql/typedefs.graphql';
import { WIDGET_TYPE_NOTES } from '~/work_items/constants';
import { WIDGET_TYPE_NOTES, WIDGET_TYPE_AWARD_EMOJI } from '~/work_items/constants';
import activeBoardItemQuery from 'ee_else_ce/boards/graphql/client/active_board_item.query.graphql';
export const config = {
@ -36,6 +36,15 @@ export const config = {
},
},
},
WorkItemWidgetAwardEmoji: {
fields: {
// If we add any key args, the awardEmoji field becomes awardEmoji({"first":10}) and
// kills any possibility to handle it on the widget level without hardcoding a string.
awardEmoji: {
keyArgs: false,
},
},
},
WorkItemWidgetProgress: {
fields: {
progress: {
@ -68,10 +77,30 @@ export const config = {
const incomingWidget = incoming.find(
(w) => w.type && w.type === existingWidget.type,
);
// We don't want to override existing notes with empty widget on work item updates
if (incomingWidget?.type === WIDGET_TYPE_NOTES && !context.variables.pageSize) {
// We don't want to override existing notes or award emojis with empty widget on work item updates
if (
(incomingWidget?.type === WIDGET_TYPE_NOTES ||
incomingWidget?.type === WIDGET_TYPE_AWARD_EMOJI) &&
!context.variables.pageSize
) {
return existingWidget;
}
// we want to concat next page of awardEmoji to the existing ones
if (incomingWidget?.type === WIDGET_TYPE_AWARD_EMOJI && context.variables.after) {
// concatPagination won't work because we were placing new widget here so we have to do this manually
return {
...incomingWidget,
awardEmoji: {
...incomingWidget.awardEmoji,
nodes: [
...existingWidget.awardEmoji.nodes,
...incomingWidget.awardEmoji.nodes,
],
},
};
}
// we want to concat next page of discussions to the existing ones
if (incomingWidget?.type === WIDGET_TYPE_NOTES && context.variables.after) {
// concatPagination won't work because we were placing new widget here so we have to do this manually

View File

@ -340,7 +340,7 @@ export default {
class="assign-myself"
data-testid="assign-self"
@click.stop="assignToCurrentUser"
>{{ __('Assign myself') }}</gl-button
>{{ __('Assign yourself') }}</gl-button
>
</div>
</template>

View File

@ -7,9 +7,15 @@ import AwardsList from '~/vue_shared/components/awards_list.vue';
import { isLoggedIn } from '~/lib/utils/common_utils';
import { TYPENAME_USER } from '~/graphql_shared/constants';
import workItemAwardEmojiQuery from '../graphql/award_emoji.query.graphql';
import updateAwardEmojiMutation from '../graphql/update_award_emoji.mutation.graphql';
import workItemByIidQuery from '../graphql/work_item_by_iid.query.graphql';
import { EMOJI_THUMBSDOWN, EMOJI_THUMBSUP, WIDGET_TYPE_AWARD_EMOJI } from '../constants';
import {
EMOJI_THUMBSDOWN,
EMOJI_THUMBSUP,
WIDGET_TYPE_AWARD_EMOJI,
DEFAULT_PAGE_SIZE_EMOJIS,
I18N_WORK_ITEM_FETCH_AWARD_EMOJI_ERROR,
} from '../constants';
export default {
defaultAwards: [EMOJI_THUMBSUP, EMOJI_THUMBSDOWN],
@ -26,16 +32,17 @@ export default {
type: String,
required: true,
},
awardEmoji: {
type: Object,
required: true,
},
workItemIid: {
type: String,
required: false,
default: null,
},
},
data() {
return {
isLoading: false,
};
},
computed: {
currentUserId() {
return window.gon.current_user_id;
@ -47,6 +54,10 @@ export default {
* Parse and convert award emoji list to a format that AwardsList can understand
*/
awards() {
if (!this.awardEmoji) {
return [];
}
return this.awardEmoji.nodes.map((emoji) => ({
name: emoji.name,
user: {
@ -55,16 +66,56 @@ export default {
},
}));
},
pageInfo() {
return this.awardEmoji?.pageInfo;
},
hasNextPage() {
return this.pageInfo?.hasNextPage;
},
},
apollo: {
awardEmoji: {
query: workItemAwardEmojiQuery,
variables() {
return {
iid: this.workItemIid,
fullPath: this.workItemFullpath,
after: this.after,
pageSize: DEFAULT_PAGE_SIZE_EMOJIS,
};
},
update(data) {
const widgets = data.workspace?.workItems?.nodes[0].widgets;
return widgets?.find((widget) => widget.type === WIDGET_TYPE_AWARD_EMOJI).awardEmoji || {};
},
skip() {
return !this.workItemIid;
},
result() {
if (this.hasNextPage) {
this.fetchAwardEmojis();
} else {
this.isLoading = false;
}
},
error() {
this.$emit('error', I18N_WORK_ITEM_FETCH_AWARD_EMOJI_ERROR);
},
},
},
methods: {
getAwards() {
return this.awardEmoji.nodes.map((emoji) => ({
name: emoji.name,
user: {
id: getIdFromGraphQLId(emoji.user.id),
name: emoji.user.name,
},
}));
async fetchAwardEmojis() {
this.isLoading = true;
try {
await this.$apollo.queries.awardEmoji.fetchMore({
variables: {
pageSize: DEFAULT_PAGE_SIZE_EMOJIS,
after: this.pageInfo?.endCursor,
},
});
} catch (error) {
this.$emit('error', I18N_WORK_ITEM_FETCH_AWARD_EMOJI_ERROR);
}
},
isEmojiPresentForCurrentUser(name) {
return (
@ -108,8 +159,12 @@ export default {
},
updateWorkItemAwardEmojiWidgetCache({ cache, name, toggledOn }) {
const query = {
query: workItemByIidQuery,
variables: { fullPath: this.workItemFullpath, iid: this.workItemIid },
query: workItemAwardEmojiQuery,
variables: {
fullPath: this.workItemFullpath,
iid: this.workItemIid,
pageSize: DEFAULT_PAGE_SIZE_EMOJIS,
},
};
const sourceData = cache.readQuery(query);
@ -117,7 +172,6 @@ export default {
const newData = produce(sourceData, (draftState) => {
const { widgets } = draftState.workspace.workItems.nodes[0];
const widgetAwardEmoji = widgets.find((widget) => widget.type === WIDGET_TYPE_AWARD_EMOJI);
widgetAwardEmoji.awardEmoji.nodes = this.getAwardEmojiNodes(name, toggledOn);
});
@ -175,7 +229,7 @@ export default {
</script>
<template>
<div class="gl-mt-3">
<div v-if="!isLoading" class="gl-mt-3">
<awards-list
data-testid="work-item-award-list"
:awards="awards"

View File

@ -79,6 +79,10 @@ export const I18N_WORK_ITEM_FETCH_ITERATIONS_ERROR = s__(
'WorkItem|Something went wrong when fetching iterations. Please try again.',
);
export const I18N_WORK_ITEM_FETCH_AWARD_EMOJI_ERROR = s__(
'WorkItem|Something went wrong while fetching work item award emojis. Please try again.',
);
export const I18N_WORK_ITEM_CREATE_BUTTON_LABEL = s__('WorkItem|Create %{workItemType}');
export const I18N_WORK_ITEM_ADD_BUTTON_LABEL = s__('WorkItem|Add %{workItemType}');
export const I18N_WORK_ITEM_ADD_MULTIPLE_BUTTON_LABEL = s__('WorkItem|Add %{workItemType}s');
@ -192,6 +196,7 @@ export const FORM_TYPES = {
export const DEFAULT_PAGE_SIZE_ASSIGNEES = 10;
export const DEFAULT_PAGE_SIZE_NOTES = 30;
export const DEFAULT_PAGE_SIZE_EMOJIS = 100;
export const WORK_ITEM_NOTES_SORT_ORDER_KEY = 'sort_direction_work_item';

View File

@ -0,0 +1,27 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
#import "~/work_items/graphql/award_emoji.fragment.graphql"
query workItemAwardEmojis($fullPath: ID!, $iid: String, $after: String, $pageSize: Int) {
workspace: project(fullPath: $fullPath) {
id
workItems(iid: $iid) {
nodes {
id
iid
widgets {
... on WorkItemWidgetAwardEmoji {
type
awardEmoji(first: $pageSize, after: $after) {
pageInfo {
...PageInfo
}
nodes {
...AwardEmojiFragment
}
}
}
}
}
}
}
}

View File

@ -1,7 +1,6 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
#import "~/graphql_shared/fragments/user.fragment.graphql"
#import "~/work_items/graphql/milestone.fragment.graphql"
#import "~/work_items/graphql/award_emoji.fragment.graphql"
fragment WorkItemMetadataWidgets on WorkItemWidget {
... on WorkItemWidgetDescription {
@ -52,10 +51,5 @@ fragment WorkItemMetadataWidgets on WorkItemWidget {
}
... on WorkItemWidgetAwardEmoji {
type
awardEmoji {
nodes {
...AwardEmojiFragment
}
}
}
}

View File

@ -1,7 +1,6 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
#import "~/graphql_shared/fragments/user.fragment.graphql"
#import "~/work_items/graphql/milestone.fragment.graphql"
#import "~/work_items/graphql/award_emoji.fragment.graphql"
#import "ee_else_ce/work_items/graphql/work_item_metadata_widgets.fragment.graphql"
fragment WorkItemWidgets on WorkItemWidget {
@ -100,10 +99,5 @@ fragment WorkItemWidgets on WorkItemWidget {
}
... on WorkItemWidgetAwardEmoji {
type
awardEmoji {
nodes {
...AwardEmojiFragment
}
}
}
}

View File

@ -18,10 +18,8 @@
%p
= s_('SlackIntegration|You must do this step only once.')
%p
= link_to slack_app_manifest_share_admin_application_settings_path, class: 'btn btn-default gl-button' do
= image_tag 'illustrations/slack_logo.svg', class: 'gl-w-9! gl-h-9! gl-my-n4! gl-ml-n4 gl-mr-n2!'
%strong.gl-button-text
= s_("SlackIntegration|Create Slack app")
= render Pajamas::ButtonComponent.new(href: slack_app_manifest_share_admin_application_settings_path) do
= s_("SlackIntegration|Create Slack app")
%hr
%h5
= s_('SlackIntegration|Step 2: Configure the app settings')

View File

@ -1,16 +1,6 @@
- breadcrumb_title _("Compare revisions")
- page_title _("Compare revisions")
- breadcrumb_title s_("CompareRevisions|Compare revisions")
%h1.page-title.gl-font-size-h-display
= _("Compare Git revisions")
%div
- example_branch = capture do
%code.ref-name= @project.default_branch_or_main
- example_sha = capture do
%code.ref-name 4eedf23
= html_escape(_("To see what's changed or create a merge request, choose a branch or tag (like %{branch}), or enter a commit (like %{sha}).")) % { branch: example_branch.html_safe, sha: example_sha.html_safe }
%br
= html_escape(_("Changes are shown as if the %{b_open}source%{b_close} revision was being merged into the %{b_open}target%{b_close} revision.")) % { b_open: '<b>'.html_safe, b_close: '</b>'.html_safe }
- page_title _("CompareRevisions|Compare revisions")
.prepend-top-20
#js-compare-selector{ data: project_compare_selector_data(@project, @merge_request, @compare_params) }

View File

@ -1,4 +1,4 @@
- add_to_breadcrumbs _("Compare revisions"), project_compare_index_path(@project)
- add_to_breadcrumbs s_("CompareRevisions|Compare revisions"), project_compare_index_path(@project)
- page_title "#{params[:from]} to #{params[:to]}"
.sub-header-block.gl-border-b-0.gl-mb-0.gl-pt-4
@ -20,13 +20,13 @@
= render Pajamas::CardComponent.new(card_options: { class: "gl-bg-gray-50 gl-mb-5 gl-border-none gl-text-center" }) do |c|
- c.with_body do
%h4
= s_("CompareBranches|There isn't anything to compare.")
= s_("CompareRevisions|There isn't anything to compare.")
%p.gl-mb-4.gl-line-height-24
- if params[:to] == params[:from]
- source_branch = capture do
%span.ref-name= params[:from]
- target_branch = capture do
%span.ref-name= params[:to]
= (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe
= (s_("CompareRevisions|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe
- else
= _("You'll need to use different branch names to get a valid comparison.")

View File

@ -0,0 +1,8 @@
---
name: global_file_size_check
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/125956
rollout_issue_url:
milestone: '16.2'
type: development
group: group::source code
default_enabled: false

View File

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/410429
milestone: '16.0'
type: development
group: group::environments
default_enabled: false
default_enabled: true

View File

@ -0,0 +1,6 @@
---
migration_job_name: BackfillUuidConversionColumnInVulnerabilityOccurrences
description: backfill values for `uuid_convert_string_to_uuid` column in vulnerability_occurrences table
feature_category: vulnerability_management
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124986
milestone: 16.2

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class QueueBackfillUuidConversionColumnInVulnerabilityOccurrences < Gitlab::Database::Migration[2.1]
MIGRATION = "BackfillUuidConversionColumnInVulnerabilityOccurrences"
DELAY_INTERVAL = 2.minutes
BATCH_SIZE = 10_000
SUB_BATCH_SIZE = 1000
restrict_gitlab_migration gitlab_schema: :gitlab_main
def up
queue_batched_background_migration(
MIGRATION,
:vulnerability_occurrences,
:id,
job_interval: DELAY_INTERVAL,
batch_size: BATCH_SIZE,
sub_batch_size: SUB_BATCH_SIZE
)
end
def down
delete_batched_background_migration(MIGRATION, :vulnerability_occurrences, :id, [])
end
end

View File

@ -0,0 +1 @@
4e169ac2e0dd9df9f3f8ebf6041189ccdde1d683d9e64ced6743abdcc22eea7b

View File

@ -196,7 +196,7 @@ You must be an administrator to manually add emails to users:
## User cohorts
The [Cohorts](../user/admin_area/user_cohorts.md) tab displays the monthly cohorts of new users and their activities over time.
The [Cohorts](user_cohorts.md) tab displays the monthly cohorts of new users and their activities over time.
## Prevent a user from creating groups

View File

@ -69,7 +69,7 @@ to activate it in the GitLab instance. You can also select **Sign-in page**,
to review the saved appearance settings:
NOTE:
You can add also add a [customized hcelp message](../user/admin_area/settings/help_page.md) below the sign in message or add [a Sign in text message](settings/sign_in_restrictions.md#sign-in-information).
You can add also add a [customized hcelp message](settings/help_page.md) below the sign in message or add [a Sign in text message](settings/sign_in_restrictions.md#sign-in-information).
## Progressive Web App

View File

@ -20,7 +20,7 @@ and secure supply chain best practices:
| Feature | Instances | Groups | Projects | Description |
|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Credentials inventory](../user/admin_area/credentials_inventory.md) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Keep track of the credentials used by all of the users in a GitLab instance. |
| [Credentials inventory](credentials_inventory.md) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Keep track of the credentials used by all of the users in a GitLab instance. |
| [Granular user roles<br/>and flexible permissions](../user/permissions.md) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Manage access and permissions with five different user roles and settings for external users. Set permissions according to people's role, rather than either read or write access to a repository. Don't share the source code with people that only need access to the issue tracker. |
| [Merge request approvals](../user/project/merge_requests/approvals/index.md) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Configure approvals required for merge requests. |
| [Push rules](../user/project/repository/push_rules.md) | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Control pushes to your repositories. |
@ -65,8 +65,8 @@ These features can also help with compliance requirements:
| Feature | Instances | Groups | Projects | Description |
|:------------------------------------------------------------------------------------------------------------------------------------|:-----------------------|:-----------------------|:-----------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Email all users of a project,<br/>group, or entire server](../user/admin_area/email_from_gitlab.md) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Email groups of users based on project or group membership, or email everyone using the GitLab instance. These emails are great for scheduled maintenance or upgrades. |
| [Enforce ToS acceptance](../user/admin_area/settings/terms.md) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Enforce your users accepting new terms of service by blocking GitLab traffic. |
| [Email all users of a project,<br/>group, or entire server](email_from_gitlab.md) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Email groups of users based on project or group membership, or email everyone using the GitLab instance. These emails are great for scheduled maintenance or upgrades. |
| [Enforce ToS acceptance](settings/terms.md) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Enforce your users accepting new terms of service by blocking GitLab traffic. |
| [External Status Checks](../user/project/merge_requests/status_checks.md) | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Interface with third-party systems you already use during development to ensure you remain compliant. |
| [Generate reports on permission<br/>levels of users](../administration/admin_area.md#user-permission-export) | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Generate a report listing all users' access permissions for groups and projects in the instance. |
| [License compliance](../user/compliance/license_compliance/index.md) | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Search dependencies for their licenses. This lets you determine if the licenses of your project's dependencies are compatible with your project's license. |

View File

@ -35,7 +35,7 @@ Customize and configure your self-managed GitLab installation.
- [Agent server for Kubernetes](../administration/clusters/kas.md)
- [Server hooks](../administration/server_hooks.md)
- [Terraform state](../administration/terraform_state.md)
- [Terraform limits](../user/admin_area/settings/terraform_limits.md)
- [Terraform limits](settings/terraform_limits.md)
- [Packages](../administration/packages/index.md)
- [Web terminals](../administration/integration/terminal.md)
- [Wikis](../administration/wikis/index.md)

View File

@ -173,9 +173,9 @@ documentation URL requests as needed. For example, if your GitLab version is
14.5:
- The GitLab documentation URL becomes `http://0.0.0.0:4000/14.5/`.
- The link in GitLab displays as `<instance_url>/help/user/admin_area/settings/help_page#destination-requirements`.
- The link in GitLab displays as `<instance_url>/help/administration/settings/help_page#destination-requirements`.
- When you select the link, you are redirected to
`http://0.0.0.0:4000/14.5/ee/user/admin_area/settings/help_page/#destination-requirements`.
`http://0.0.0.0:4000/14.5/ee/administration/settings/help_page/#destination-requirements`.
To test the setting, in GitLab, select a **Learn more** link. For example:

View File

@ -85,7 +85,7 @@ While this isn't an exhaustive list, following these steps gives you a solid sta
- Set up [email notification for unknown sign-ins](settings/sign_in_restrictions.md#email-notification-for-unknown-sign-ins).
- Configure [user and IP rate limits](https://about.gitlab.com/blog/2020/05/20/gitlab-instance-security-best-practices/#user-and-ip-rate-limits).
- Limit [webhooks local access](https://about.gitlab.com/blog/2020/05/20/gitlab-instance-security-best-practices/#webhooks).
- Set [rate limits for protected paths](../user/admin_area/settings/protected_paths.md).
- Set [rate limits for protected paths](settings/protected_paths.md).
- Sign up for [Security Alerts](https://about.gitlab.com/company/preference-center/) from the Communication Preference Center.
- Keep track of security best practices on our [blog page](https://about.gitlab.com/blog/2020/05/20/gitlab-instance-security-best-practices/).
@ -236,10 +236,10 @@ Rate limits also improve the security of your application.
You can make changes to your default rate limits from the Admin Area. For more information about configuration, see the [Admin Area page](../security/rate_limits.md#configurable-limits).
- Define [issues rate limits](../user/admin_area/settings/rate_limit_on_issues_creation.md) to set a maximum number of issue creation requests per minute, per user.
- Enforce [user and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md) for unauthenticated web requests.
- Review the [rate limit on raw endpoints](../user/admin_area/settings/rate_limits_on_raw_endpoints.md). The default setting is 300 requests per minute for raw file access.
- Review the [import/export rate limits](../user/admin_area/settings/import_export_rate_limits.md) of the six active defaults.
- Define [issues rate limits](settings/rate_limit_on_issues_creation.md) to set a maximum number of issue creation requests per minute, per user.
- Enforce [user and IP rate limits](settings/user_and_ip_rate_limits.md) for unauthenticated web requests.
- Review the [rate limit on raw endpoints](settings/rate_limits_on_raw_endpoints.md). The default setting is 300 requests per minute for raw file access.
- Review the [import/export rate limits](settings/import_export_rate_limits.md) of the six active defaults.
For more information about API and rate limits, see our [API page](../api/rest/index.md).

View File

@ -12,7 +12,7 @@ Refer to the information below when troubleshooting Gitaly and Gitaly Cluster.
The following sections provide possible solutions to Gitaly errors.
See also [Gitaly timeout](../../user/admin_area/settings/gitaly_timeouts.md) settings,
See also [Gitaly timeout](../settings/gitaly_timeouts.md) settings,
and our advice on [parsing the `gitaly/current` file](../logs/log_parsing.md#parsing-gitalycurrent).
### Check versions when using standalone Gitaly servers

View File

@ -21,7 +21,7 @@ Read more about [configuring rate limits](../security/rate_limits.md).
This setting limits the request rate to the issue creation endpoint.
Read more about [issue creation rate limits](../user/admin_area/settings/rate_limit_on_issues_creation.md).
Read more about [issue creation rate limits](settings/rate_limit_on_issues_creation.md).
- **Default rate limit**: Disabled by default.
@ -29,7 +29,7 @@ Read more about [issue creation rate limits](../user/admin_area/settings/rate_li
This setting limits the request rate per user or IP.
Read more about [User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md).
Read more about [User and IP rate limits](settings/user_and_ip_rate_limits.md).
- **Default rate limit**: Disabled by default.
@ -37,7 +37,7 @@ Read more about [User and IP rate limits](../user/admin_area/settings/user_and_i
This setting limits the request rate per endpoint.
Read more about [raw endpoint rate limits](../user/admin_area/settings/rate_limits_on_raw_endpoints.md).
Read more about [raw endpoint rate limits](settings/rate_limits_on_raw_endpoints.md).
- **Default rate limit**: 300 requests per project, per commit and per file path.
@ -59,14 +59,14 @@ GitLab rate limits the following paths by default:
'/admin/session'
```
Read more about [protected path rate limits](../user/admin_area/settings/protected_paths.md).
Read more about [protected path rate limits](settings/protected_paths.md).
- **Default rate limit**: After 10 requests, the client must wait 60 seconds before trying again.
### Package Registry
This setting limits the request rate on the Packages API per user or IP. For more information, see
[Package Registry Rate Limits](../user/admin_area/settings/package_registry_rate_limits.md).
[Package Registry Rate Limits](settings/package_registry_rate_limits.md).
- **Default rate limit**: Disabled by default.
@ -86,7 +86,7 @@ requests per user. For more information, read
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75918) in GitLab 14.6. [Feature flag `files_api_throttling`](https://gitlab.com/gitlab-org/gitlab/-/issues/338903) removed.
This setting limits the request rate on the Packages API per user or IP address. For more information, read
[Files API rate limits](../user/admin_area/settings/files_api_rate_limits.md).
[Files API rate limits](settings/files_api_rate_limits.md).
- **Default rate limit**: Disabled by default.
@ -95,7 +95,7 @@ This setting limits the request rate on the Packages API per user or IP address.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68645) in GitLab 14.4.
This setting limits the request rate on deprecated API endpoints per user or IP address. For more information, read
[Deprecated API rate limits](../user/admin_area/settings/deprecated_api_rate_limits.md).
[Deprecated API rate limits](settings/deprecated_api_rate_limits.md).
- **Default rate limit**: Disabled by default.
@ -112,7 +112,7 @@ This setting limits the import/export actions for groups and projects.
| Group Export | 6 |
| Group Export Download | 1 |
Read more about [import/export rate limits](../user/admin_area/settings/import_export_rate_limits.md).
Read more about [import/export rate limits](settings/import_export_rate_limits.md).
### Member Invitations
@ -171,7 +171,7 @@ This endpoint has been requested too many times. Try again later.
This setting limits the request rate to the pipeline creation endpoints.
Read more about [pipeline creation rate limits](../user/admin_area/settings/rate_limit_on_pipelines_creation.md).
Read more about [pipeline creation rate limits](settings/rate_limit_on_pipelines_creation.md).
## Gitaly concurrency limit
@ -800,7 +800,7 @@ Plan.default.actual_limits.update!(dotenv_size: 5.kilobytes)
This setting limits the number of inbound alert payloads over a period of time.
Read more about [incident management rate limits](../user/admin_area/settings/rate_limit_on_pipelines_creation.md).
Read more about [incident management rate limits](settings/rate_limit_on_pipelines_creation.md).
### Prometheus Alert JSON payloads
@ -945,7 +945,7 @@ More information can be found in these documentations:
Total number of changes (branches or tags) in a single push to determine whether
individual push events or a bulk push event are created.
More information can be found in the [Push event activities limit and bulk push events documentation](../user/admin_area/settings/push_event_activities_limit.md).
More information can be found in the [Push event activities limit and bulk push events documentation](settings/push_event_activities_limit.md).
## Package Registry Limits

View File

@ -754,8 +754,8 @@ This file is located at:
This log records:
- Requests over the [Rate Limit](../../user/admin_area/settings/rate_limits_on_raw_endpoints.md) on raw endpoints.
- [Protected paths](../../user/admin_area/settings/protected_paths.md) abusive requests.
- Requests over the [Rate Limit](../settings/rate_limits_on_raw_endpoints.md) on raw endpoints.
- [Protected paths](../settings/protected_paths.md) abusive requests.
- In GitLab versions [12.3](https://gitlab.com/gitlab-org/gitlab/-/issues/29239) and later,
user ID and username, if available.

View File

@ -55,7 +55,7 @@ guides you through the process.
When downloading packages as dependencies in downstream projects, many requests are made through the
Packages API. You may therefore reach enforced user and IP rate limits. To address this issue, you
can define specific rate limits for the Packages API. For more details, see [Package Registry Rate Limits](../../user/admin_area/settings/package_registry_rate_limits.md).
can define specific rate limits for the Packages API. For more details, see [Package Registry Rate Limits](../settings/package_registry_rate_limits.md).
## Enable or disable the Package Registry

View File

@ -659,7 +659,7 @@ persistence classes.
| `shared_state` | Store session-related and other persistent data. |
| `actioncable` | Pub/Sub queue backend for ActionCable. |
| `trace_chunks` | Store [CI trace chunks](../job_logs.md#enable-or-disable-incremental-logging) data. |
| `rate_limiting` | Store [rate limiting](../../user/admin_area/settings/user_and_ip_rate_limits.md) state. |
| `rate_limiting` | Store [rate limiting](../settings/user_and_ip_rate_limits.md) state. |
| `sessions` | Store [sessions](../../../ee/development/session.md#gitlabsession). |
| `repository_cache` | Store cache data specific to repositories. |

View File

@ -51,4 +51,4 @@ To override the general user and IP rate limits for requests to deprecated API e
## Related topics
- [Rate limits](../../security/rate_limits.md)
- [User and IP rate limits](../../user/admin_area/settings/user_and_ip_rate_limits.md)
- [User and IP rate limits](../settings/user_and_ip_rate_limits.md)

View File

@ -70,7 +70,7 @@ verify:
- apk add --update cosign docker
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- cosign verify "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" --certificate-identity "https://gitlab.com/my-group/my-project@refs/heads/main" --certificate-oidc-issuer "https://gitlab.com"
- cosign verify "$CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA" --certificate-identity "https://gitlab.com/my-group/my-project//path/to/.gitlab-ci.yml@refs/heads/main" --certificate-oidc-issuer "https://gitlab.com"
```
## Use Sigstore and npm to generate keyless provenance
@ -306,5 +306,5 @@ verify_artifact:
before_script:
- apk add --update cosign
script:
- cosign verify-blob artifact.txt --bundle cosign.bundle --certificate-identity "https://gitlab.com/my-group/my-project@refs/heads/main" --certificate-oidc-issuer "https://gitlab.com"
- cosign verify-blob artifact.txt --bundle cosign.bundle --certificate-identity "https://gitlab.com/my-group/my-project//path/to/.gitlab-ci.yml@refs/heads/main" --certificate-oidc-issuer "https://gitlab.com"
```

View File

@ -77,7 +77,11 @@ Now that our values have been defined, we can base our goals on these values and
- Improve our pipelines speed
- Build a better set of shared components with documentation
## Browser Support
### Frontend onboarding course
The [Frontend onboarding course](onboarding_course/index.md) provides a 6-week structured curriculum to learn how to contribute to the GitLab frontend.
### Browser Support
For supported browsers, see our [requirements](../../install/requirements.md#supported-web-browsers).

View File

@ -0,0 +1,64 @@
---
stage: Manage
group: Foundations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Frontend onboarding course
Welcome to the Frontend Onboarding Course at GitLab!
In this course, we walk you through your first professional frontend development experience, helping you gain real world skills and learn how to contribute to a large-scale codebase effectively.
Throughout the course, we'll follow a structured approach.
Each lesson focuses on solving a specific problem on GitLab, giving you hands-on experience.
You'll learn theory that you can immediately put into practice.
By working on real-world GitLab issues, you'll encounter challenges and learn how to navigate the codebase effectively while at the same time improving GitLab the product.
We believe in an interactive learning experience.
You'll have the opportunity to ask questions and seek help from the GitLab community.
We appreciate your contributions and are here to support your learning while at the same time making GitLab better.
Our teaching style prioritizes practical learning.
Lessons include an introduction to the problem, theory, live coding walkthroughs, and similar issues for you to tackle.
As you progress, the complexity of the tasks increase, helping you grow your skills.
Join us on this journey of front-end development at GitLab. Say hello in [the Discord community](https://discord.gg/gitlab) and let's learn and improve together.
## Lessons
- [Lesson 1](lesson_1.md)
## Structure and timings
The course is run over 6 weeks, with a required time commitment of 5-10 hours per week.
The course is free of charge, but we do ask for a commitment to complete the curriculum (including 10 merged merge requests).
After completing the course, you receive a certificate and GitLab achievement.
Each week consists of the following sessions:
- 1-hour relaxed discussion-style lesson with explanation of how GitLab frontend works. Each week features a different guest and includes an AMA portion.
- 2-hour live coding lesson with a practical task for participants to complete.
- 2 x 2-hour dedicated “Office Hours” sessions where participants can work on the task assigned in the lesson with GitLab frontend engineers. (2 sessions in different timezones as this will require participants to join synchronously)
A fortnightly 1-on-1 mentoring sessions are also available to each participant.
There are 10 places available on the course.
The date will be set after the course material has been prepared.
Please complete the [Frontend Onboarding Course Application Form](https://forms.gle/39Rs4w4ZxQuByhE4A) to apply.
You may also participate in the course informally at your own pace, without the benefit of the synchronous office hours or mentoring session.
GitLab team members are happy to support you regardless.
## Curriculum summary
### Lesson 1
- What is a development environment?
- What is the GDK?
- Installing the GDK.
- GDK tips and tricks.
- Using GitPod to run the GDK.
- Navigating the GitLab codebase.
- Writing a good merge request.

View File

@ -0,0 +1,183 @@
---
stage: manage
group: foundations
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Lesson 1
<div class="video-fallback">
See the video: <a href="https://www.youtube.com/watch?v=k4C3-FKvZyI">Lesson 1 intro</a>.
</div>
<figure class="video-container">
<iframe src="https://www.youtube-nocookie.com/embed/k4C3-FKvZyI" frameborder="0" allowfullscreen> </iframe>
</figure>
In this lesson you tackle the smallest of problems - a one-character text change. To do so, we have to learn:
- How to set up a GitLab Development Environment.
- How to navigate the GitLab code base.
- How to create a merge request in the GitLab project.
After we have learned these 3 things, a GitLab team member will do a live coding demo.
In the demo, they'll use each of the things learned by completing one of these small issues, so that you can complete an issue by yourself.
There is a list of issues that are very similar to the one we'll be live coding [here in the "Linked items" section](https://gitlab.com/gitlab-org/gitlab/-/issues/389920), it would be worth commenting on one of these now to get yourself assigned to one so that you can follow along.
## What is the GDK?
<div class="video-fallback">
See the video: <a href="https://www.youtube.com/watch?v=qXGXshfo934">What is the GDK</a>?
</div>
<figure class="video-container">
<iframe src="https://www.youtube-nocookie.com/embed/qXGXshfo934" frameborder="0" allowfullscreen> </iframe>
</figure>
The GDK (GitLab Development Kit) is a local instance of GitLab that allows developers to run and test GitLab on their own computers.
Unlike frontend only applications, the GDK runs the entire GitLab application, including the back-end services, APIs, and a local database.
This allows developers to make changes, test them in real-time, and validate their modifications.
Tips for using the GDK:
- Troubleshooting documentation: When encountering issues with the GDK, refer to the troubleshooting documentation in the [GDK repository](https://gitlab.com/gitlab-org/gitlab-development-kit/-/tree/main/doc/troubleshooting).
These resources provide useful commands and tips to help resolve common problems.
- Using the Rails console: The Rails console is an essential tool for interacting with your local instance of GitLab.
You can access it by running `gdk rails c` and use it to enable or disable feature flags, perform backend operations, and more.
- Stay updated: Regularly update your GDK by running `gdk update`.
This command fetches the latest branch of the GitLab project, as well as the latest branch of the GDK and its dependencies.
Keeping your GDK up to date helps ensure you will be working with the latest version of GitLab and make sure you have the latest bug fixes.
Remember, if you need further assistance or have specific questions, you can reach out to the GitLab community through our [Discord](https://discord.gg/gitlab) or [other available support channels](https://about.gitlab.com/community/contribute/).
## Installing and using the GDK locally
<div class="video-fallback">
See the video: <a href="https://www.youtube.com/watch?v=fcOyjuCizmY">Installing the GDK</a>.
</div>
<figure class="video-container">
<iframe src="https://www.youtube-nocookie.com/embed/fcOyjuCizmY" frameborder="0" allowfullscreen> </iframe>
</figure>
For the latest installation instructions, refer to the [GitLab Development Kit documentation](https://gitlab.com/gitlab-org/gitlab-development-kit#installation).
Here's a step-by-step summary:
1. Prerequisites:
- 16 GB RAM. If you have less, consider [using Gitpod](#using-gitpod-instead-of-running-the-gdk-locally)
- Ensure that Git is installed on your machine.
- Install a code editor, such as Visual Studio Code.
- [Create an account](https://gitlab.com/users/sign_up) or [sign in](https://gitlab.com/users/sign_in) on GitLab.com and join the [community members group](https://gitlab.com/gitlab-community/meta#request-access-to-community-forks).
1. Installation:
- Choose a directory to install the GitLab Development Kit (GDK).
- Open your terminal and navigate to the chosen directory.
- Download and run the installation script from the terminal:
```shell
curl "https://gitlab.com/gitlab-org/gitlab-development-kit/-/raw/main/support/install" | bash
```
- Only run scripts from trusted sources to ensure your safety.
- The installation process may take around 20 minutes or more.
1. Choosing the repository:
- Instead of cloning the main GitLab repository, use the community fork recommended for wider community members.
- Follow the instructions provided to install the community fork.
1. GDK structure:
- After the installation, the GDK directory is created.
- Inside the GDK directory, you'll find the GitLab project folder.
1. Working with the GDK:
- GDK offers lots of commands you can use to interact with your installation. To run those commands you must be inside the GDK or GitLab folder.
- To start the GDK, run the command `gdk start` in your terminal.
- You can explore available commands and options by running `gdk help` in the terminal.
Remember to consult the documentation or seek community support if you have any further questions or issues.
## Using Gitpod instead of running the GDK locally
<div class="video-fallback">
See the video: <a href="https://www.youtube.com/watch?v=RI2kM5_oii4">Using Gitpod with GitLab</a>.
</div>
<figure class="video-container">
<iframe src="https://www.youtube-nocookie.com/embed/RI2kM5_oii4" frameborder="0" allowfullscreen> </iframe>
</figure>
Gitpod is a service that allows you to run a virtual machine, specifically the GitLab Development Kit (GDK), on the Gitpod server instead of running it on your own machine.
It provides a web-based Integrated Development Environment (IDE) where you can edit code and see the GDK in action.
Gitpod is useful for quickly getting a GDK environment up and running, for making small merge requests without installing the GDK locally, or for running GDK on a machine that may not have enough resources.
To use Gitpod:
1. Go to the [GitLab community fork website](https://gitlab.com/gitlab-community/gitlab), select **Edit**, then select **Gitpod**.
1. Configure your settings, such as the editor (VS Code desktop or browser) and the context (usually the `main` or `master` branch).
1. Select **Open** to create your Gitpod workspace. This process may take up to 20 minutes. The GitLab Development Kit (GDK) will be installed in the Gitpod workspace. This installation is faster than downloading and installing the full GDK locally.
After the workspace is created, you'll find your chosen IDE running in your browser. You can also connect it to your desktop IDE if preferred.
Treat Gitpod just like you would use VS Code locally. Create branches, make code changes, commit them, and push them back to the community fork.
Other tips:
- Remember to push your code regularly to avoid the workspace timing out. Idle workspaces are eventually destroyed.
- Customize your Gitpod workspace settings if needed, such as making your instance of GitLab frontend publicly available.
- If you run out of minutes, contact the support team on the Discord server.
- Troubleshoot issues by using commands like `gdk start` and `gdk status` in the Gitpod workspace as you would if it was running locally.
By following these steps, you can leverage Gitpod to efficiently develop with the GitLab Development Kit without the need for local installation.
## Navigating the GitLab codebase
<div class="video-fallback">
See the video: <a href="https://www.youtube.com/watch?v=Wc5u879_0Aw">How to navigate the GitLab codebase</a>.
</div>
<figure class="video-container">
<iframe src="https://www.youtube-nocookie.com/embed/Wc5u879_0Aw" frameborder="0" allowfullscreen> </iframe>
</figure>
Understanding how to navigate the GitLab codebase is essential for contributors.
Navigating the codebase and locating specific files can be challenging but crucial for making changes and addressing issues effectively.
Here we'll explore a step-by-step process for finding files and finding where they are rendered in GitLab.
If you already know the file you are going to work on and now you want to find where it is rendered:
1. Start by gathering clues to understand the files purpose. Look for relevant information within the file itself, such as keywords or specific content that might indicate its context.
1. You can also examine the file path (or folder structure) to gain insights into where the file might be rendered.
A lot of routing in GitLab is very similar to the folder structure.
1. If you can work out which feature (or one of the features) that this component is used in, you can then leverage the GitLab user documentation to find out how to navigate to the feature page.
1. Follow the component hierarchy, do a global search for the file name to identify the parent component that renders the component.
Continue to follow the hierarchy of components to trace back to a feature you recognize or can search for in the GitLab user docs.
1. You can use `git blame` with an extension like GitLens to find a recent MR where this file was changed.
Most MRs have a "How to validate" section that you can follow, if the MR doesn't have one, look for the previous change and until you find one that have validation steps.
If you know which page you need to fix and you want to find the file path, here are some things you can try:
- Look for content that is unique and doesnt contain variables so that you can search for the translation variable.
- Try using Vue Dev Tools to find the component name.
- Look for unique identifiers like a `data-testid`,`id` or a unique looking CSS class in the HTML of the component and then search globally the codebase for those identifying strings.
## Writing a good merge request
<div class="video-fallback">
See the video: <a href="https://www.youtube.com/watch?v=H5zozDNIn98">How to write a good MR</a>.
</div>
<figure class="video-container">
<iframe src="https://www.youtube-nocookie.com/embed/H5zozDNIn98" frameborder="0" allowfullscreen> </iframe>
</figure>
When writing a merge request there are some important things to be aware of:
- Your MR will become a permanent part of the documentation of the GitLab project.
It may be used in the future to help people understand why some code works the way it does and why it doesn't use an alternative solution.
- At least 2 other engineers are going to review your code. For the sake of efficiency (much like the code itself you have written) it is best to take a little while longer to get your MR right so that it is quicker and easier for others to read.
- The MRs that you create on GitLab are available to the public. This means you can add a link to MRs you are particularly proud of to your portfolio page when looking for a job.
- Since an MR is a technical document, you should try to implement a technical writing style.
If you dont know what that is, here is a highly recommended short course from [Google on Technical writing](https://developers.google.com/tech-writing/one).
If you are also contributing to the documentation at GitLab, there is a [Technical Writing Fundamentals course available here from GitLab](https://about.gitlab.com/handbook/product/ux/technical-writing/fundamentals/).
## Live coding
<div class="video-fallback">
See the video: <a href="https://www.youtube.com/watch?v=BJCCwc1Czt4">Lesson 1 code walkthrough</a>.
</div>
<figure class="video-container">
<iframe src="https://www.youtube-nocookie.com/embed/BJCCwc1Czt4" frameborder="0" allowfullscreen> </iframe>
</figure>
Now it is your turn to complete your first MR, there is a list of issues that are very similar to the one we just finished that need completing [here in the "Linked items" section](https://gitlab.com/gitlab-org/gitlab/-/issues/389920). Thanks for contributing! (if there are none left, let us know on [Discord](https://discord.gg/gitlab) or [other available support channels](https://about.gitlab.com/community/contribute/) and we'll find more for you)

View File

@ -411,7 +411,7 @@ Color written inside backticks is followed by a color "chip":
### Equations and Formulas (STEM)
If you need to include Science, Technology, Engineering and Math (STEM)
If you need to include Science, Technology, Engineering, and Math (STEM)
expressions, set the `stem` attribute in the document's header to `latexmath`.
Equations and formulas are rendered using [KaTeX](https://katex.org/):

View File

@ -125,6 +125,29 @@ The `cube_analytics` data type connects to the Cube instance defined when [produ
All filters and queries are sent to the Cube instance and the returned data is processed by the
product analytics data source to be rendered by the appropriate visualizations.
### Filling missing data
- Introduced in GitLab 16.3 behind the [feature flag](../../administration/feature_flags.md) named `product_analytics_dashboards`. Disabled by default.
When [exporting data](#raw-data-export) or [viewing dashboards](../analytics/analytics_dashboards.md#view-project-dashboards),
if there is no data for a given day, the missing data is autofilled with `0`.
This approach has the following benefits:
- The visualization's day axis matches the selected date range, removing ambiguity about missing data.
- Data exports have rows for the entire date range, making data analysis easier.
However, this approach also has the following limitations:
- The `day` [granularity](https://cube.dev/docs/product/apis-integrations/rest-api/query-format) must be used.
All other granularities are not supported at this time.
- It only fills a date range defined by the [`inDateRange`](https://cube.dev/docs/product/apis-integrations/rest-api/query-format#indaterange) filter.
- The date selector in the UI already uses this filter.
- The filling of data ignores the query-defined limit. If you set a limit of 10 data points over 20 days, it
returns 20 data points, with the missing data filled by `0`.
[Issue 417231](https://gitlab.com/gitlab-org/gitlab/-/issues/417231) proposes a solution to this limitation.
## Funnel analysis
Use funnel analysis to understand the flow of users through your application, and where

View File

@ -291,7 +291,7 @@ To confirm that your account is enabled, go to [https://gitlab.com/api/v4/ml/ai-
#### Code Suggestions not displayed in VS Code or GitLab WebIDE
Check all steps above first.
Check all the steps in [Code Suggestions aren't displayed](#code-suggestions-arent-displayed) first.
If you are a self-managed user, ensure that Code Suggestions for the [GitLab WebIDE](../../project/web_ide/index.md) are enabled. The same settings apply to VS Code as local IDE.
@ -303,7 +303,7 @@ If you are a self-managed user, ensure that Code Suggestions for the [GitLab Web
If the settings are enabled, but Code Suggestions are still not displayed, try the following steps:
1. Enable the `Debug` checkbox in the GitLab Workflow **Extension Settings**.
1. Open the extension log in **View > Output** and change the dropdown to **GitLab Workflow** as log filter. The command palette command is `GitLab: Show Extension Logs`.
1. Open the extension log in **View > Output** and change the dropdown list to **GitLab Workflow** as the log filter. The command palette command is `GitLab: Show Extension Logs`.
1. Disable and re-enable the **Enable code completion (Beta)** checkbox.
1. Verify that the debug log contains similar output:
@ -313,6 +313,21 @@ If the settings are enabled, but Code Suggestions are still not displayed, try t
2023-07-14T17:29:01:802 [debug]: AI Assist: Using server: https://codesuggestions.gitlab.com/v2/completions
```
#### Code Suggestions not displayed in Microsoft Visual Studio
Check all the steps in [Code Suggestions aren't displayed](#code-suggestions-arent-displayed) first.
1. Ensure you have properly [set up the extension](https://gitlab.com/gitlab-org/editor-extensions/gitlab-visual-studio-extension#setup).
1. From the **Tools > Options** menu, find the **GitLab** option. Ensure **Log Level** is set to **Debug**.
1. Open the extension log in **View > Output** and change the dropdown list to **GitLab Extension** as the log filter.
1. Verify that the debug log contains similar output:
```shell
14:48:21:344 GitlabProposalSource.GetCodeSuggestionAsync
14:48:21:344 LsClient.SendTextDocumentCompletionAsync("GitLab.Extension.Test\TestData.cs", 34, 0)
14:48:21:346 LS(55096): time="2023-07-17T14:48:21-05:00" level=info msg="update context"
```
### Authentication troubleshooting
If the above steps do not solve your issue, the problem may be related to the recent changes in authentication,

View File

@ -0,0 +1,21 @@
# frozen_string_literal: true
module Gitlab
module BackgroundMigration
# This batched background migration will backfill values for `uuid_convert_string_to_uuid` column in
# vulnerability_occurrences table to allow us to migrate the column type from `varchar(36)` to `uuid`
class BackfillUuidConversionColumnInVulnerabilityOccurrences < BatchedMigrationJob
operation_name :backfill_uuid_conversion_column_in_vulnerability_occurrences
scope_to ->(relation) do
relation.where("uuid_convert_string_to_uuid = '00000000-0000-0000-0000-000000000000'::uuid")
end
feature_category :vulnerability_management
def perform
each_sub_batch do |sub_batch|
sub_batch.update_all("uuid_convert_string_to_uuid = uuid::uuid")
end
end
end
end
end

View File

@ -117,6 +117,7 @@ module Gitlab
def bulk_access_checks!
Gitlab::Checks::LfsCheck.new(self).validate!
Gitlab::Checks::GlobalFileSizeCheck.new(self).validate!
end
def blank_rev?(rev)

View File

@ -0,0 +1,29 @@
# frozen_string_literal: true
module Gitlab
module Checks
class GlobalFileSizeCheck < BaseBulkChecker
MAX_FILE_SIZE_MB = 100
LOG_MESSAGE = 'Checking for blobs over the file size limit'
def validate!
return unless Feature.enabled?(:global_file_size_check, project)
Gitlab::AppJsonLogger.info(LOG_MESSAGE)
logger.log_timed(LOG_MESSAGE) do
Gitlab::Checks::FileSizeCheck::AllowExistingOversizedBlobs.new(
project: project,
changes: changes,
file_size_limit_megabytes: MAX_FILE_SIZE_MB
).find
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/393535
# - set limit per plan tier
# - raise an error if large blobs are found
end
true
end
end
end
end

View File

@ -2279,9 +2279,15 @@ msgstr ""
msgid "AbuseReport|Confirmed violation of a copyright or a trademark"
msgstr ""
msgid "AbuseReport|Copyright or trademark violation"
msgstr ""
msgid "AbuseReport|Credit card"
msgstr ""
msgid "AbuseReport|Crypto Mining"
msgstr ""
msgid "AbuseReport|Delete user"
msgstr ""
@ -2309,6 +2315,9 @@ msgstr ""
msgid "AbuseReport|Last login"
msgstr ""
msgid "AbuseReport|Malware"
msgstr ""
msgid "AbuseReport|Member since"
msgstr ""
@ -2321,6 +2330,18 @@ msgstr ""
msgid "AbuseReport|Normal location"
msgstr ""
msgid "AbuseReport|Offensive or Abusive"
msgstr ""
msgid "AbuseReport|Other"
msgstr ""
msgid "AbuseReport|Personal information or credentials"
msgstr ""
msgid "AbuseReport|Phishing"
msgstr ""
msgid "AbuseReport|Phone"
msgstr ""
@ -2360,6 +2381,9 @@ msgstr ""
msgid "AbuseReport|Something else"
msgstr ""
msgid "AbuseReport|Spam"
msgstr ""
msgid "AbuseReport|Tier"
msgstr ""
@ -6371,9 +6395,6 @@ msgstr ""
msgid "Assign labels"
msgstr ""
msgid "Assign myself"
msgstr ""
msgid "Assign reviewer"
msgstr ""
@ -6395,6 +6416,9 @@ msgstr ""
msgid "Assign to me"
msgstr ""
msgid "Assign yourself"
msgstr ""
msgid "Assigned"
msgstr ""
@ -9243,9 +9267,6 @@ msgstr ""
msgid "Changes"
msgstr ""
msgid "Changes are shown as if the %{b_open}source%{b_close} revision was being merged into the %{b_open}target%{b_close} revision."
msgstr ""
msgid "Changes saved."
msgstr ""
@ -11536,9 +11557,6 @@ msgstr ""
msgid "Compare %{oldCommitId}...%{newCommitId}"
msgstr ""
msgid "Compare Git revisions"
msgstr ""
msgid "Compare GitLab editions"
msgstr ""
@ -11566,10 +11584,7 @@ msgstr ""
msgid "Compare with previous version"
msgstr ""
msgid "CompareBranches|%{source_branch} and %{target_branch} are the same."
msgstr ""
msgid "CompareBranches|There isn't anything to compare."
msgid "CompareRevisions|%{source_branch} and %{target_branch} are the same."
msgstr ""
msgid "CompareRevisions|Branches"
@ -11620,6 +11635,9 @@ msgstr ""
msgid "CompareRevisions|Tags"
msgstr ""
msgid "CompareRevisions|There isn't anything to compare."
msgstr ""
msgid "CompareRevisions|There was an error while loading the branch/tag list. Please try again."
msgstr ""
@ -48118,9 +48136,6 @@ msgstr ""
msgid "To see this project's operational details, contact an owner of group %{groupName} to upgrade the plan. You can also remove the project from the dashboard."
msgstr ""
msgid "To see what's changed or create a merge request, choose a branch or tag (like %{branch}), or enter a commit (like %{sha})."
msgstr ""
msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
msgstr ""
@ -52297,6 +52312,9 @@ msgstr ""
msgid "WorkItem|Something went wrong while fetching milestones. Please try again."
msgstr ""
msgid "WorkItem|Something went wrong while fetching work item award emojis. Please try again."
msgstr ""
msgid "WorkItem|Something went wrong while promoting the %{workItemType}. Please try again."
msgstr ""

View File

@ -0,0 +1,43 @@
import { GlLabel } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import AbuseCategory from '~/admin/abuse_reports/components/abuse_category.vue';
import { ABUSE_CATEGORIES } from '~/admin/abuse_reports/constants';
import { mockAbuseReports } from '../mock_data';
describe('AbuseCategory', () => {
let wrapper;
const mockAbuseReport = mockAbuseReports[0];
const category = ABUSE_CATEGORIES[mockAbuseReport.category];
const findLabel = () => wrapper.findComponent(GlLabel);
const createComponent = (props = {}) => {
wrapper = shallowMountExtended(AbuseCategory, {
propsData: {
category: mockAbuseReport.category,
...props,
},
});
};
beforeEach(() => {
createComponent();
});
it('renders a label', () => {
expect(findLabel().exists()).toBe(true);
});
it('renders the label with the right background color for the category', () => {
expect(findLabel().props()).toMatchObject({
backgroundColor: category.backgroundColor,
title: category.title,
target: null,
});
});
it('renders the label with the right text color for the category', () => {
expect(findLabel().attributes('class')).toBe(`gl-text-${category.color}`);
});
});

View File

@ -1,6 +1,7 @@
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import AbuseReportRow from '~/admin/abuse_reports/components/abuse_report_row.vue';
import AbuseCategory from '~/admin/abuse_reports/components/abuse_category.vue';
import ListItem from '~/vue_shared/components/registry/list_item.vue';
import { getTimeago } from '~/lib/utils/datetime_utility';
import { SORT_UPDATED_AT } from '~/admin/abuse_reports/constants';
@ -11,7 +12,8 @@ describe('AbuseReportRow', () => {
const mockAbuseReport = mockAbuseReports[0];
const findListItem = () => wrapper.findComponent(ListItem);
const findTitle = () => wrapper.findByTestId('title');
const findAbuseCategory = () => wrapper.findComponent(AbuseCategory);
const findAbuseReportTitle = () => wrapper.findByTestId('abuse-report-title');
const findDisplayedDate = () => wrapper.findByTestId('abuse-report-date');
const createComponent = (props = {}) => {
@ -35,13 +37,13 @@ describe('AbuseReportRow', () => {
const { reporter, reportedUser, category, reportPath } = mockAbuseReport;
it('displays correctly formatted title', () => {
expect(findTitle().text()).toMatchInterpolatedText(
expect(findAbuseReportTitle().text()).toMatchInterpolatedText(
`${reportedUser.name} reported for ${category} by ${reporter.name}`,
);
});
it('links to the details page', () => {
expect(findTitle().attributes('href')).toEqual(reportPath);
expect(findAbuseReportTitle().attributes('href')).toEqual(reportPath);
});
describe('when the reportedUser is missing', () => {
@ -50,7 +52,7 @@ describe('AbuseReportRow', () => {
});
it('displays correctly formatted title', () => {
expect(findTitle().text()).toMatchInterpolatedText(
expect(findAbuseReportTitle().text()).toMatchInterpolatedText(
`Deleted user reported for ${category} by ${reporter.name}`,
);
});
@ -62,7 +64,7 @@ describe('AbuseReportRow', () => {
});
it('displays correctly formatted title', () => {
expect(findTitle().text()).toMatchInterpolatedText(
expect(findAbuseReportTitle().text()).toMatchInterpolatedText(
`${reportedUser.name} reported for ${category} by Deleted user`,
);
});
@ -88,4 +90,8 @@ describe('AbuseReportRow', () => {
});
});
});
it('renders abuse category', () => {
expect(findAbuseCategory().exists()).toBe(true);
});
});

View File

@ -274,14 +274,14 @@ describe('WorkItemAssignees component', () => {
});
describe('when assigning to current user', () => {
it('does not show `Assign myself` button if current user is loading', () => {
it('does not show `Assign yourself` button if current user is loading', () => {
createComponent();
findTokenSelector().trigger('mouseover');
expect(findAssignSelfButton().exists()).toBe(false);
});
it('does not show `Assign myself` button if work item has assignees', async () => {
it('does not show `Assign yourself` button if work item has assignees', async () => {
createComponent();
await waitForPromises();
findTokenSelector().trigger('mouseover');
@ -289,7 +289,7 @@ describe('WorkItemAssignees component', () => {
expect(findAssignSelfButton().exists()).toBe(false);
});
it('does now show `Assign myself` button if user is not logged in', async () => {
it('does now show `Assign yourself` button if user is not logged in', async () => {
createComponent({ currentUserQueryHandler: noCurrentUserQueryHandler, assignees: [] });
await waitForPromises();
findTokenSelector().trigger('mouseover');
@ -304,7 +304,7 @@ describe('WorkItemAssignees component', () => {
return waitForPromises();
});
it('renders `Assign myself` button', () => {
it('renders `Assign yourself` button', () => {
findTokenSelector().trigger('mouseover');
expect(findAssignSelfButton().exists()).toBe(true);
});

View File

@ -1,4 +1,4 @@
import Vue from 'vue';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
@ -9,36 +9,67 @@ import { isLoggedIn } from '~/lib/utils/common_utils';
import AwardList from '~/vue_shared/components/awards_list.vue';
import WorkItemAwardEmoji from '~/work_items/components/work_item_award_emoji.vue';
import updateAwardEmojiMutation from '~/work_items/graphql/update_award_emoji.mutation.graphql';
import workItemByIidQuery from '~/work_items/graphql/work_item_by_iid.query.graphql';
import { EMOJI_THUMBSUP, EMOJI_THUMBSDOWN } from '~/work_items/constants';
import workItemAwardEmojiQuery from '~/work_items/graphql/award_emoji.query.graphql';
import {
EMOJI_THUMBSUP,
EMOJI_THUMBSDOWN,
DEFAULT_PAGE_SIZE_EMOJIS,
I18N_WORK_ITEM_FETCH_AWARD_EMOJI_ERROR,
} from '~/work_items/constants';
import {
workItemByIidResponseFactory,
mockAwardsWidget,
mockAwardEmojiThumbsUp,
getAwardEmojiResponse,
mockMoreThanDefaultAwardEmojisWidget,
} from '../mock_data';
jest.mock('~/lib/utils/common_utils');
jest.mock('~/work_items/constants', () => ({
...jest.requireActual('~/work_items/constants'),
DEFAULT_PAGE_SIZE_EMOJIS: 5,
}));
Vue.use(VueApollo);
describe('WorkItemAwardEmoji component', () => {
let wrapper;
let mockApolloProvider;
const errorMessage = 'Failed to update the award';
const mutationErrorMessage = 'Failed to update the award';
const workItemQueryResponse = workItemByIidResponseFactory();
const workItemQueryAddAwardEmojiResponse = workItemByIidResponseFactory({
awardEmoji: { ...mockAwardsWidget, nodes: [mockAwardEmojiThumbsUp] },
});
const workItemQueryRemoveAwardEmojiResponse = workItemByIidResponseFactory({
awardEmoji: { ...mockAwardsWidget, nodes: [] },
});
const mockWorkItem = workItemQueryResponse.data.workspace.workItems.nodes[0];
const awardEmojiQuerySuccessHandler = jest.fn().mockResolvedValue(workItemQueryResponse);
const awardEmojiQueryEmptyHandler = jest.fn().mockResolvedValue(
workItemByIidResponseFactory({
awardEmoji: {
...mockAwardsWidget,
nodes: [],
},
}),
);
const awardEmojiQueryThumbsUpHandler = jest.fn().mockResolvedValue(
workItemByIidResponseFactory({
awardEmoji: {
...mockAwardsWidget,
nodes: [mockAwardEmojiThumbsUp],
},
}),
);
const awardEmojiQueryFailureHandler = jest
.fn()
.mockRejectedValue(new Error(I18N_WORK_ITEM_FETCH_AWARD_EMOJI_ERROR));
const awardEmojiAddSuccessHandler = jest.fn().mockResolvedValue(getAwardEmojiResponse(true));
const awardEmojiRemoveSuccessHandler = jest.fn().mockResolvedValue(getAwardEmojiResponse(false));
const awardEmojiUpdateFailureHandler = jest.fn().mockRejectedValue(new Error(errorMessage));
const mockWorkItem = workItemQueryResponse.data.workspace.workItems.nodes[0];
const mockAwardEmojiDifferentUserThumbsUp = {
const awardEmojiUpdateFailureHandler = jest
.fn()
.mockRejectedValue(new Error(mutationErrorMessage));
const mockAwardEmojiDifferentUser = {
name: 'thumbsup',
__typename: 'AwardEmoji',
user: {
@ -49,35 +80,37 @@ describe('WorkItemAwardEmoji component', () => {
};
const createComponent = ({
awardMutationHandler = awardEmojiAddSuccessHandler,
workItem = mockWorkItem,
awardEmojiQueryHandler = awardEmojiQuerySuccessHandler,
awardEmojiMutationHandler = awardEmojiAddSuccessHandler,
workItemIid = '1',
awardEmoji = { ...mockAwardsWidget, nodes: [] },
} = {}) => {
mockApolloProvider = createMockApollo([[updateAwardEmojiMutation, awardMutationHandler]]);
mockApolloProvider.clients.defaultClient.writeQuery({
query: workItemByIidQuery,
variables: { fullPath: workItem.project.fullPath, iid: workItemIid },
data: {
...workItemQueryResponse.data,
workspace: {
__typename: 'Project',
id: 'gid://gitlab/Project/1',
workItems: {
nodes: [workItem],
mockApolloProvider = createMockApollo(
[
[workItemAwardEmojiQuery, awardEmojiQueryHandler],
[updateAwardEmojiMutation, awardEmojiMutationHandler],
],
{},
{
typePolicies: {
WorkItemWidgetAwardEmoji: {
fields: {
// If we add any key args, the awardEmoji field becomes awardEmoji({"first":10}) and
// kills any possibility to handle it on the widget level without hardcoding a string.
awardEmoji: {
keyArgs: false,
},
},
},
},
},
});
);
wrapper = shallowMount(WorkItemAwardEmoji, {
isLoggedIn: isLoggedIn(),
apolloProvider: mockApolloProvider,
propsData: {
workItemId: workItem.id,
workItemFullpath: workItem.project.fullPath,
awardEmoji,
workItemId: 'gid://gitlab/WorkItem/1',
workItemFullpath: 'test-project-path',
workItemIid,
},
});
@ -85,17 +118,23 @@ describe('WorkItemAwardEmoji component', () => {
const findAwardsList = () => wrapper.findComponent(AwardList);
beforeEach(() => {
beforeEach(async () => {
isLoggedIn.mockReturnValue(true);
window.gon = {
current_user_id: 5,
current_user_fullname: 'Dave Smith',
};
createComponent();
await createComponent();
});
it('renders the award-list component with default props', () => {
it('renders the award-list component with default props', async () => {
createComponent({
awardEmojiQueryHandler: awardEmojiQueryEmptyHandler,
});
await waitForPromises();
expect(findAwardsList().exists()).toBe(true);
expect(findAwardsList().props()).toEqual({
boundary: '',
@ -108,8 +147,6 @@ describe('WorkItemAwardEmoji component', () => {
});
it('renders awards-list component with awards present', () => {
createComponent({ awardEmoji: mockAwardsWidget });
expect(findAwardsList().props('awards')).toEqual([
{
name: EMOJI_THUMBSUP,
@ -128,13 +165,32 @@ describe('WorkItemAwardEmoji component', () => {
]);
});
it('renders awards list given by multiple users', () => {
it('emits error when there is an error while fetching award emojis', async () => {
createComponent({
awardEmojiQueryHandler: awardEmojiQueryFailureHandler,
});
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[I18N_WORK_ITEM_FETCH_AWARD_EMOJI_ERROR]]);
});
it('renders awards list given by multiple users', async () => {
const mockWorkItemAwardEmojiDifferentUser = workItemByIidResponseFactory({
awardEmoji: {
...mockAwardsWidget,
nodes: [mockAwardEmojiThumbsUp, mockAwardEmojiDifferentUserThumbsUp],
nodes: [mockAwardEmojiThumbsUp, mockAwardEmojiDifferentUser],
},
});
const awardEmojiWithDifferentUsersQueryHandler = jest
.fn()
.mockResolvedValue(mockWorkItemAwardEmojiDifferentUser);
createComponent({
awardEmojiQueryHandler: awardEmojiWithDifferentUsersQueryHandler,
});
await waitForPromises();
expect(findAwardsList().props('awards')).toEqual([
{
@ -155,21 +211,19 @@ describe('WorkItemAwardEmoji component', () => {
});
it.each`
expectedAssertion | awardEmojiMutationHandler | mockAwardEmojiNodes | workItem
${'added'} | ${awardEmojiAddSuccessHandler} | ${[]} | ${workItemQueryRemoveAwardEmojiResponse.data.workspace.workItems.nodes[0]}
${'removed'} | ${awardEmojiRemoveSuccessHandler} | ${[mockAwardEmojiThumbsUp]} | ${workItemQueryAddAwardEmojiResponse.data.workspace.workItems.nodes[0]}
expectedAssertion | awardEmojiMutationHandler | awardEmojiQueryHandler
${'added'} | ${awardEmojiAddSuccessHandler} | ${awardEmojiQueryEmptyHandler}
${'removed'} | ${awardEmojiRemoveSuccessHandler} | ${awardEmojiQueryThumbsUpHandler}
`(
'calls mutation when an award emoji is $expectedAssertion',
({ awardEmojiMutationHandler, mockAwardEmojiNodes, workItem }) => {
async ({ awardEmojiMutationHandler, awardEmojiQueryHandler }) => {
createComponent({
awardMutationHandler: awardEmojiMutationHandler,
awardEmoji: {
...mockAwardsWidget,
nodes: mockAwardEmojiNodes,
},
workItem,
awardEmojiMutationHandler,
awardEmojiQueryHandler,
});
await waitForPromises();
findAwardsList().vm.$emit('award', EMOJI_THUMBSUP);
expect(awardEmojiMutationHandler).toHaveBeenCalledWith({
@ -183,21 +237,24 @@ describe('WorkItemAwardEmoji component', () => {
it('emits error when the update mutation fails', async () => {
createComponent({
awardMutationHandler: awardEmojiUpdateFailureHandler,
awardEmojiMutationHandler: awardEmojiUpdateFailureHandler,
awardEmojiQueryHandler: awardEmojiQueryEmptyHandler,
});
await waitForPromises();
findAwardsList().vm.$emit('award', EMOJI_THUMBSUP);
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[errorMessage]]);
expect(wrapper.emitted('error')).toEqual([[mutationErrorMessage]]);
});
describe('when user is not logged in', () => {
beforeEach(() => {
beforeEach(async () => {
isLoggedIn.mockReturnValue(false);
createComponent();
await createComponent();
});
it('renders the component with required props and canAwardEmoji false', () => {
@ -213,15 +270,13 @@ describe('WorkItemAwardEmoji component', () => {
};
});
it('calls mutation succesfully and adds the award emoji with proper user details', () => {
it('calls mutation succesfully and adds the award emoji with proper user details', async () => {
createComponent({
awardMutationHandler: awardEmojiAddSuccessHandler,
awardEmoji: {
...mockAwardsWidget,
nodes: [mockAwardEmojiThumbsUp],
},
awardEmojiMutationHandler: awardEmojiAddSuccessHandler,
});
await waitForPromises();
findAwardsList().vm.$emit('award', EMOJI_THUMBSUP);
expect(awardEmojiAddSuccessHandler).toHaveBeenCalledWith({
@ -232,4 +287,62 @@ describe('WorkItemAwardEmoji component', () => {
});
});
});
describe('pagination', () => {
describe('when there is no next page', () => {
const awardEmojiQuerySingleItemHandler = jest.fn().mockResolvedValue(
workItemByIidResponseFactory({
awardEmoji: {
...mockAwardsWidget,
nodes: [mockAwardEmojiThumbsUp],
},
}),
);
it('fetch more award emojis should not be called', async () => {
createComponent({ awardEmojiQueryHandler: awardEmojiQuerySingleItemHandler });
await waitForPromises();
expect(awardEmojiQuerySingleItemHandler).toHaveBeenCalledWith({
fullPath: 'test-project-path',
iid: '1',
pageSize: DEFAULT_PAGE_SIZE_EMOJIS,
after: undefined,
});
expect(awardEmojiQuerySingleItemHandler).toHaveBeenCalledTimes(1);
});
});
describe('when there is next page', () => {
const awardEmojisQueryMoreThanDefaultHandler = jest.fn().mockResolvedValueOnce(
workItemByIidResponseFactory({
awardEmoji: mockMoreThanDefaultAwardEmojisWidget,
}),
);
it('fetch more award emojis should be called', async () => {
createComponent({
awardEmojiQueryHandler: awardEmojisQueryMoreThanDefaultHandler,
});
await waitForPromises();
expect(awardEmojisQueryMoreThanDefaultHandler).toHaveBeenCalledWith({
fullPath: 'test-project-path',
iid: '1',
pageSize: DEFAULT_PAGE_SIZE_EMOJIS,
after: 'endCursor',
});
await nextTick();
expect(awardEmojisQueryMoreThanDefaultHandler).toHaveBeenCalledWith({
fullPath: 'test-project-path',
iid: '1',
pageSize: DEFAULT_PAGE_SIZE_EMOJIS,
after: mockMoreThanDefaultAwardEmojisWidget.pageInfo.endCursor,
});
expect(awardEmojisQueryMoreThanDefaultHandler).toHaveBeenCalledTimes(2);
});
});
});
});

View File

@ -68,6 +68,38 @@ export const mockAwardEmojiThumbsDown = {
export const mockAwardsWidget = {
nodes: [mockAwardEmojiThumbsUp, mockAwardEmojiThumbsDown],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
__typename: 'PageInfo',
},
__typename: 'AwardEmojiConnection',
};
export const mockMoreThanDefaultAwardEmojisWidget = {
nodes: [
mockAwardEmojiThumbsUp,
mockAwardEmojiThumbsDown,
{ ...mockAwardEmojiThumbsUp, name: 'one' },
{ ...mockAwardEmojiThumbsUp, name: 'two' },
{ ...mockAwardEmojiThumbsUp, name: 'three' },
{ ...mockAwardEmojiThumbsUp, name: 'four' },
{ ...mockAwardEmojiThumbsUp, name: 'five' },
{ ...mockAwardEmojiThumbsUp, name: 'six' },
{ ...mockAwardEmojiThumbsUp, name: 'seven' },
{ ...mockAwardEmojiThumbsUp, name: 'eight' },
{ ...mockAwardEmojiThumbsUp, name: 'nine' },
{ ...mockAwardEmojiThumbsUp, name: 'ten' },
],
pageInfo: {
hasNextPage: true,
hasPreviousPage: false,
startCursor: null,
endCursor: 'endCursor',
__typename: 'PageInfo',
},
__typename: 'AwardEmojiConnection',
};

View File

@ -53,6 +53,7 @@ describe('Work items router', () => {
WorkItemIteration: true,
WorkItemHealthStatus: true,
WorkItemNotes: true,
WorkItemAwardEmoji: true,
},
});
};

View File

@ -0,0 +1,133 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::BackgroundMigration::BackfillUuidConversionColumnInVulnerabilityOccurrences, schema: 20230629095819, feature_category: :vulnerability_management do # rubocop:disable Layout/LineLength
let(:namespaces) { table(:namespaces) }
let(:projects) { table(:projects) }
let(:users) { table(:users) }
let(:members) { table(:members) }
let(:vulnerability_identifiers) { table(:vulnerability_identifiers) }
let(:vulnerability_scanners) { table(:vulnerability_scanners) }
let(:vulnerability_findings) { table(:vulnerability_occurrences) }
let!(:user) { create_user(email: "test1@example.com", username: "test1") }
let!(:namespace) { namespaces.create!(name: "test-1", path: "test-1", owner_id: user.id) }
let!(:project) do
projects.create!(
id: 9999, namespace_id: namespace.id,
project_namespace_id: namespace.id,
creator_id: user.id
)
end
let!(:membership) do
members.create!(access_level: 50, source_id: project.id, source_type: "Project", user_id: user.id, state: 0,
notification_level: 3, type: "ProjectMember", member_namespace_id: namespace.id)
end
let(:scanner) { create_scanner(project) }
let(:null_uuid) { '00000000-0000-0000-0000-000000000000' }
let(:migration_attrs) do
{
start_id: finding_with_no_converted_uuid_1.id,
end_id: finding_with_converted_uuid.id,
batch_table: :vulnerability_occurrences,
batch_column: :id,
sub_batch_size: 100,
pause_ms: 0,
connection: ApplicationRecord.connection
}
end
describe "#perform" do
subject(:background_migration) { described_class.new(**migration_attrs).perform }
before do
# We have to disable it in tests because it's triggered BEFORE INSERT OR UPDATE ON vulnerability_occurrences
# so it's hard to create or update the uuid_convert_string_to_uuid with null UUID value
# In reality the UPDATE query in the batched background migration could be SET id = id which would
# trigger UUID update trigger but we can't exactly do that and expect readable tests
ApplicationRecord.connection.execute("ALTER TABLE vulnerability_occurrences DISABLE TRIGGER trigger_1a857e8db6cd")
end
let(:finding_with_no_converted_uuid_1) do
create_finding(project, scanner, uuid_convert_string_to_uuid: null_uuid)
end
let(:finding_with_converted_uuid) do
uuid = SecureRandom.uuid
create_finding(project, scanner, uuid: uuid, uuid_convert_string_to_uuid: uuid)
end
after do
ApplicationRecord.connection.execute("ALTER TABLE vulnerability_occurrences ENABLE TRIGGER trigger_1a857e8db6cd")
end
it "backfills the uuid_convert_string_to_uuid column" do
expect { background_migration }.to change { finding_with_no_converted_uuid_1.reload.uuid_convert_string_to_uuid }
.from(null_uuid).to(finding_with_no_converted_uuid_1.uuid)
end
it "doesn't change the UUID for exisiting records" do
expect { background_migration }.not_to change { finding_with_converted_uuid.uuid_convert_string_to_uuid }
end
end
private
def create_scanner(project, overrides = {})
attrs = {
project_id: project.id,
external_id: "test_vulnerability_scanner",
name: "Test Vulnerabilities::Scanner"
}.merge(overrides)
vulnerability_scanners.create!(attrs)
end
def create_identifier(project, overrides = {})
attrs = {
project_id: project.id,
external_id: "CVE-2018-1234",
external_type: "CVE",
name: "CVE-2018-1234",
fingerprint: SecureRandom.hex(20)
}.merge(overrides)
vulnerability_identifiers.create!(attrs)
end
def create_finding(project, scanner, overrides = {})
attrs = {
project_id: project.id,
scanner_id: scanner.id,
severity: 5, # medium
confidence: 2, # unknown,
report_type: 99, # generic
primary_identifier_id: create_identifier(project).id,
project_fingerprint: SecureRandom.hex(20),
location_fingerprint: SecureRandom.hex(20),
uuid: SecureRandom.uuid,
name: "CVE-2018-1234",
raw_metadata: "{}",
metadata_version: "test:1.0"
}.merge(overrides)
vulnerability_findings.create!(attrs)
end
def create_user(overrides = {})
attrs = {
email: "test@example.com",
notification_email: "test@example.com",
name: "test",
username: "test",
state: "active",
projects_limit: 10
}.merge(overrides)
users.create!(attrs)
end
end

View File

@ -24,6 +24,14 @@ RSpec.describe Gitlab::Checks::ChangesAccess, feature_category: :source_code_man
subject.validate!
end
it 'calls file size check' do
expect_next_instance_of(Gitlab::Checks::GlobalFileSizeCheck) do |instance|
expect(instance).to receive(:validate!)
end
subject.validate!
end
end
context 'when time limit was reached' do

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Checks::GlobalFileSizeCheck, feature_category: :source_code_management do
include_context 'changes access checks context'
describe '#validate!' do
context 'when global_file_size_check is disabled' do
before do
stub_feature_flags(global_file_size_check: false)
end
it 'does not log' do
expect(subject).not_to receive(:log_timed)
expect(Gitlab::AppJsonLogger).not_to receive(:info)
expect(Gitlab::Checks::FileSizeCheck::AllowExistingOversizedBlobs).not_to receive(:new)
subject.validate!
end
end
it 'checks for file sizes' do
expect_next_instance_of(Gitlab::Checks::FileSizeCheck::AllowExistingOversizedBlobs,
project: project,
changes: changes,
file_size_limit_megabytes: 100
) do |check|
expect(check).to receive(:find).and_call_original
end
expect(subject.logger).to receive(:log_timed).with('Checking for blobs over the file size limit')
.and_call_original
expect(Gitlab::AppJsonLogger).to receive(:info).with('Checking for blobs over the file size limit')
subject.validate!
end
end
end

View File

@ -0,0 +1,26 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe QueueBackfillUuidConversionColumnInVulnerabilityOccurrences, feature_category: :vulnerability_management do
let!(:batched_migration) { described_class::MIGRATION }
it 'schedules a new batched migration' do
reversible_migration do |migration|
migration.before -> {
expect(batched_migration).not_to have_scheduled_batched_migration
}
migration.after -> {
expect(batched_migration).to have_scheduled_batched_migration(
table_name: :vulnerability_occurrences,
column_name: :id,
interval: described_class::DELAY_INTERVAL,
batch_size: described_class::BATCH_SIZE,
sub_batch_size: described_class::SUB_BATCH_SIZE
)
}
end
end
end