Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-07-31 15:10:14 +00:00
parent 2f2c8f84bf
commit ea937d0916
46 changed files with 885 additions and 389 deletions

View file

@ -72,11 +72,18 @@ export default {
GlPagination,
GlTabs,
GlTab,
PublishedCell: () => import('ee_component/incidents/components/published_cell.vue'),
},
directives: {
GlTooltip: GlTooltipDirective,
},
inject: ['projectPath', 'newIssuePath', 'incidentTemplateName', 'issuePath'],
inject: [
'projectPath',
'newIssuePath',
'incidentTemplateName',
'issuePath',
'publishedAvailable',
],
apollo: {
incidents: {
query: getIncidents,
@ -144,6 +151,20 @@ export default {
newIncidentPath() {
return mergeUrlParams({ issuable_template: this.incidentTemplateName }, this.newIssuePath);
},
availableFields() {
return this.publishedAvailable
? [
...this.$options.fields,
...[
{
key: 'published',
label: s__('IncidentManagement|Published'),
thClass: 'gl-pointer-events-none',
},
],
]
: this.$options.fields;
},
},
methods: {
onInputChange: debounce(function debounceSearch(input) {
@ -230,7 +251,7 @@ export default {
</h4>
<gl-table
:items="incidents.list || []"
:fields="$options.fields"
:fields="availableFields"
:show-empty="true"
:busy="loading"
stacked="md"
@ -245,7 +266,7 @@ export default {
<gl-icon
v-if="item.state === 'closed'"
name="issue-close"
class="gl-fill-blue-500"
class="gl-ml-1 gl-fill-blue-500"
data-testid="incident-closed"
/>
</div>
@ -285,6 +306,13 @@ export default {
</div>
</template>
<template v-if="publishedAvailable" #cell(published)="{ item }">
<published-cell
:status-page-published-incident="item.statusPagePublishedIncident"
:un-published="$options.i18n.unPublished"
/>
</template>
<template #table-busy>
<gl-loading-icon size="lg" color="dark" class="mt-3" />
</template>

View file

@ -6,6 +6,7 @@ export const I18N = {
unassigned: s__('IncidentManagement|Unassigned'),
createIncidentBtnLabel: s__('IncidentManagement|Create incident'),
searchPlaceholder: __('Search or filter results...'),
unPublished: s__('IncidentManagement|Unpublished'),
};
export const INCIDENT_STATE_TABS = [

View file

@ -37,6 +37,7 @@ query getIncidents(
webUrl
}
}
statusPagePublishedIncident
}
pageInfo {
hasNextPage

View file

@ -8,7 +8,13 @@ export default () => {
const selector = '#js-incidents';
const domEl = document.querySelector(selector);
const { projectPath, newIssuePath, incidentTemplateName, issuePath } = domEl.dataset;
const {
projectPath,
newIssuePath,
incidentTemplateName,
issuePath,
publishedAvailable,
} = domEl.dataset;
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
@ -21,6 +27,7 @@ export default () => {
incidentTemplateName,
newIssuePath,
issuePath,
publishedAvailable,
},
apolloProvider,
components: {

View file

@ -107,7 +107,7 @@ export default {
</script>
<template>
<gl-new-dropdown class="ref-selector" @shown="focusSearchBox">
<gl-new-dropdown v-bind="$attrs" class="ref-selector" @shown="focusSearchBox">
<template slot="button-content">
<span class="gl-flex-grow-1 gl-ml-2 gl-text-gray-600" data-testid="button-content">
<span v-if="selectedRef" class="gl-font-monospace">{{ selectedRef }}</span>

View file

@ -1,4 +1,5 @@
import Vue from 'vue';
import { escapeFileUrl } from '../lib/utils/url_utility';
import createRouter from './router';
import App from './components/app.vue';
import Breadcrumbs from './components/breadcrumbs.vue';
@ -109,7 +110,7 @@ export default function setupVueRepositoryList() {
return h(TreeActionLink, {
props: {
path: `${historyLink}/${
this.$route.params.path ? encodeURIComponent(this.$route.params.path) : ''
this.$route.params.path ? escapeFileUrl(this.$route.params.path) : ''
}`,
text: __('History'),
},

View file

@ -5,39 +5,88 @@ import axios from '~/lib/utils/axios_utils';
import { spriteIcon } from '~/lib/utils/common_utils';
import SidebarMediator from '~/sidebar/sidebar_mediator';
/**
* Creates the HTML template for each row of the mentions dropdown.
*
* @param original - An object from the array returned from the `autocomplete_sources/members` API
* @returns {string} - An HTML template
*/
function menuItemTemplate({ original }) {
const rectAvatarClass = original.type === 'Group' ? 'rect-avatar' : '';
const AutoComplete = {
Labels: 'labels',
Members: 'members',
};
const avatarClasses = `avatar avatar-inline center s26 ${rectAvatarClass}
gl-display-inline-flex! gl-align-items-center gl-justify-content-center`;
const avatarTag = original.avatar_url
? `<img
src="${original.avatar_url}"
alt="${original.username}'s avatar"
class="${avatarClasses}"/>`
: `<div class="${avatarClasses}">${original.username.charAt(0).toUpperCase()}</div>`;
const name = escape(original.name);
const count = original.count && !original.mentionsDisabled ? ` (${original.count})` : '';
const icon = original.mentionsDisabled
? spriteIcon('notifications-off', 's16 gl-vertical-align-middle gl-ml-3')
: '';
return `${avatarTag}
${original.username}
<small class="gl-text-small gl-font-weight-normal gl-reset-color">${name}${count}</small>
${icon}`;
function doesCurrentLineStartWith(searchString, fullText, selectionStart) {
const currentLineNumber = fullText.slice(0, selectionStart).split('\n').length;
const currentLine = fullText.split('\n')[currentLineNumber - 1];
return currentLine.startsWith(searchString);
}
const autoCompleteMap = {
[AutoComplete.Labels]: {
filterValues() {
const fullText = this.$slots.default?.[0]?.elm?.value;
const selectionStart = this.$slots.default?.[0]?.elm?.selectionStart;
if (doesCurrentLineStartWith('/label', fullText, selectionStart)) {
return this.labels.filter(label => !label.set);
}
if (doesCurrentLineStartWith('/unlabel', fullText, selectionStart)) {
return this.labels.filter(label => label.set);
}
return this.labels;
},
menuItemTemplate({ original }) {
return `
<span class="dropdown-label-box" style="background: ${escape(original.color)};"></span>
${escape(original.title)}`;
},
},
[AutoComplete.Members]: {
filterValues() {
const fullText = this.$slots.default?.[0]?.elm?.value;
const selectionStart = this.$slots.default?.[0]?.elm?.selectionStart;
if (!this.assignees) {
this.assignees =
SidebarMediator.singleton?.store?.assignees?.map(assignee => assignee.username) || [];
}
if (doesCurrentLineStartWith('/assign', fullText, selectionStart)) {
return this.members.filter(member => !this.assignees.includes(member.username));
}
if (doesCurrentLineStartWith('/unassign', fullText, selectionStart)) {
return this.members.filter(member => this.assignees.includes(member.username));
}
return this.members;
},
menuItemTemplate({ original }) {
const rectAvatarClass = original.type === 'Group' ? 'rect-avatar' : '';
const avatarClasses = `avatar avatar-inline center s26 ${rectAvatarClass}
gl-display-inline-flex! gl-align-items-center gl-justify-content-center`;
const avatarTag = original.avatar_url
? `<img
src="${original.avatar_url}"
alt="${original.username}'s avatar"
class="${avatarClasses}"/>`
: `<div class="${avatarClasses}">${original.username.charAt(0).toUpperCase()}</div>`;
const name = escape(original.name);
const count = original.count && !original.mentionsDisabled ? ` (${original.count})` : '';
const icon = original.mentionsDisabled
? spriteIcon('notifications-off', 's16 gl-vertical-align-middle gl-ml-3')
: '';
return `${avatarTag}
${original.username}
<small class="gl-text-small gl-font-weight-normal gl-reset-color">${name}${count}</small>
${icon}`;
},
},
};
export default {
name: 'GlMentions',
props: {
@ -47,67 +96,57 @@ export default {
default: () => gl.GfmAutoComplete?.dataSources || {},
},
},
data() {
return {
assignees: undefined,
members: undefined,
};
},
mounted() {
const NON_WORD_OR_INTEGER = /\W|^\d+$/;
this.tribute = new Tribute({
trigger: '@',
fillAttr: 'username',
lookup: value => value.name + value.username,
menuItemTemplate,
values: this.getMembers,
collection: [
{
trigger: '@',
fillAttr: 'username',
lookup: value => value.name + value.username,
menuItemTemplate: autoCompleteMap[AutoComplete.Members].menuItemTemplate,
values: this.getValues(AutoComplete.Members),
},
{
trigger: '~',
lookup: 'title',
menuItemTemplate: autoCompleteMap[AutoComplete.Labels].menuItemTemplate,
selectTemplate: ({ original }) =>
NON_WORD_OR_INTEGER.test(original.title)
? `~"${original.title}"`
: `~${original.title}`,
values: this.getValues(AutoComplete.Labels),
},
],
});
const input = this.$slots.default[0].elm;
const input = this.$slots.default?.[0]?.elm;
this.tribute.attach(input);
},
beforeDestroy() {
const input = this.$slots.default[0].elm;
const input = this.$slots.default?.[0]?.elm;
this.tribute.detach(input);
},
methods: {
/**
* Creates the list of users to show in the mentions dropdown.
*
* @param inputText - The text entered by the user in the mentions input field
* @param processValues - Callback function to set the list of users to show in the mentions dropdown
*/
getMembers(inputText, processValues) {
if (this.members) {
processValues(this.getFilteredMembers());
} else if (this.dataSources.members) {
axios
.get(this.dataSources.members)
.then(response => {
this.members = response.data;
processValues(this.getFilteredMembers());
})
.catch(() => {});
} else {
processValues([]);
}
},
getFilteredMembers() {
const fullText = this.$slots.default[0].elm.value;
if (!this.assignees) {
this.assignees =
SidebarMediator.singleton?.store?.assignees?.map(assignee => assignee.username) || [];
}
if (fullText.startsWith('/assign @')) {
return this.members.filter(member => !this.assignees.includes(member.username));
}
if (fullText.startsWith('/unassign @')) {
return this.members.filter(member => this.assignees.includes(member.username));
}
return this.members;
getValues(autoCompleteType) {
return (inputText, processValues) => {
if (this[autoCompleteType]) {
const filteredValues = autoCompleteMap[autoCompleteType].filterValues.call(this);
processValues(filteredValues);
} else if (this.dataSources[autoCompleteType]) {
axios
.get(this.dataSources[autoCompleteType])
.then(response => {
this[autoCompleteType] = response.data;
const filteredValues = autoCompleteMap[autoCompleteType].filterValues.call(this);
processValues(filteredValues);
})
.catch(() => {});
} else {
processValues([]);
}
};
},
},
render(createElement) {

View file

@ -171,7 +171,7 @@ export default {
mergeRequests: this.enableAutocomplete,
epics: this.enableAutocomplete,
milestones: this.enableAutocomplete,
labels: this.enableAutocomplete,
labels: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete,
snippets: this.enableAutocomplete,
});
},

View file

@ -98,4 +98,9 @@
@include gl-w-full;
}
}
// TODO: Abstract to `@gitlab/ui` utility set: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/921
.gl-fill-green-500 {
fill: $gray-500;
}
}

View file

@ -97,6 +97,9 @@ module Types
field :design_collection, Types::DesignManagement::DesignCollectionType, null: true,
description: 'Collection of design images associated with this issue'
field :status_page_published_incident, GraphQL::BOOLEAN_TYPE, null: true,
description: 'Indicates whether an issue is published to the status page'
end
end

View file

@ -10,3 +10,5 @@ module Projects::IncidentsHelper
}
end
end
Projects::IncidentsHelper.prepend_if_ee('EE::Projects::IncidentsHelper')

View file

@ -1,4 +1,4 @@
= link_to safe_params.merge(rss_url_options), class: 'btn btn-svg has-tooltip', data: { container: 'body' }, title: _('Subscribe to RSS feed') do
= link_to safe_params.merge(rss_url_options), class: 'btn btn-svg has-tooltip', data: { container: 'body', testid: 'rss-feed-link' }, title: _('Subscribe to RSS feed') do
= sprite_icon('rss', css_class: 'qa-rss-icon', size: 16)
= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do
= sprite_icon('calendar')

View file

@ -0,0 +1,5 @@
---
title: Remove updated_at column on audit_events table
merge_request: 35690
author:
type: other

View file

@ -0,0 +1,5 @@
---
title: Allow optional keyset pagination for branch list API
merge_request: 37524
author:
type: added

View file

@ -0,0 +1,5 @@
---
title: Fixes the history button link URL being encoded incorrectly
merge_request: 38392
author:
type: fixed

View file

@ -0,0 +1,119 @@
# frozen_string_literal: true
class RemoveUpdatedAtFromAuditEvents < ActiveRecord::Migration[6.0]
include Gitlab::Database::SchemaHelpers
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
SOURCE_TABLE_NAME = 'audit_events'
PARTITIONED_TABLE_NAME = 'audit_events_part_5fc467ac26'
TRIGGER_FUNCTION_NAME = 'table_sync_function_2be879775d'
def up
with_lock_retries do
remove_column SOURCE_TABLE_NAME, :updated_at
create_trigger_function(TRIGGER_FUNCTION_NAME, replace: true) do
<<~SQL
IF (TG_OP = 'DELETE') THEN
DELETE FROM #{PARTITIONED_TABLE_NAME} where id = OLD.id;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE #{PARTITIONED_TABLE_NAME}
SET author_id = NEW.author_id,
type = NEW.type,
entity_id = NEW.entity_id,
entity_type = NEW.entity_type,
details = NEW.details,
ip_address = NEW.ip_address,
author_name = NEW.author_name,
entity_path = NEW.entity_path,
target_details = NEW.target_details,
created_at = NEW.created_at
WHERE #{PARTITIONED_TABLE_NAME}.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO #{PARTITIONED_TABLE_NAME} (id,
author_id,
type,
entity_id,
entity_type,
details,
ip_address,
author_name,
entity_path,
target_details,
created_at)
VALUES (NEW.id,
NEW.author_id,
NEW.type,
NEW.entity_id,
NEW.entity_type,
NEW.details,
NEW.ip_address,
NEW.author_name,
NEW.entity_path,
NEW.target_details,
NEW.created_at);
END IF;
RETURN NULL;
SQL
end
remove_column PARTITIONED_TABLE_NAME, :updated_at
end
end
def down
with_lock_retries do
add_column SOURCE_TABLE_NAME, :updated_at, :datetime # rubocop:disable Migration/Datetime
add_column PARTITIONED_TABLE_NAME, :updated_at, :datetime # rubocop:disable Migration/Datetime
create_trigger_function(TRIGGER_FUNCTION_NAME, replace: true) do
<<~SQL
IF (TG_OP = 'DELETE') THEN
DELETE FROM #{PARTITIONED_TABLE_NAME} where id = OLD.id;
ELSIF (TG_OP = 'UPDATE') THEN
UPDATE #{PARTITIONED_TABLE_NAME}
SET author_id = NEW.author_id,
type = NEW.type,
entity_id = NEW.entity_id,
entity_type = NEW.entity_type,
details = NEW.details,
updated_at = NEW.updated_at,
ip_address = NEW.ip_address,
author_name = NEW.author_name,
entity_path = NEW.entity_path,
target_details = NEW.target_details,
created_at = NEW.created_at
WHERE #{PARTITIONED_TABLE_NAME}.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO #{PARTITIONED_TABLE_NAME} (id,
author_id,
type,
entity_id,
entity_type,
details,
updated_at,
ip_address,
author_name,
entity_path,
target_details,
created_at)
VALUES (NEW.id,
NEW.author_id,
NEW.type,
NEW.entity_id,
NEW.entity_type,
NEW.details,
NEW.updated_at,
NEW.ip_address,
NEW.author_name,
NEW.entity_path,
NEW.target_details,
NEW.created_at);
END IF;
RETURN NULL;
SQL
end
end
end
end

View file

@ -0,0 +1 @@
77601e653f7b4f2740db87a7b19b64bb73fffbe4ce59c0f68b0bb65599da0eb3

View file

@ -25,7 +25,6 @@ ELSIF (TG_OP = 'UPDATE') THEN
entity_id = NEW.entity_id,
entity_type = NEW.entity_type,
details = NEW.details,
updated_at = NEW.updated_at,
ip_address = NEW.ip_address,
author_name = NEW.author_name,
entity_path = NEW.entity_path,
@ -39,7 +38,6 @@ ELSIF (TG_OP = 'INSERT') THEN
entity_id,
entity_type,
details,
updated_at,
ip_address,
author_name,
entity_path,
@ -51,7 +49,6 @@ ELSIF (TG_OP = 'INSERT') THEN
NEW.entity_id,
NEW.entity_type,
NEW.details,
NEW.updated_at,
NEW.ip_address,
NEW.author_name,
NEW.entity_path,
@ -72,7 +69,6 @@ CREATE TABLE public.audit_events_part_5fc467ac26 (
entity_id integer NOT NULL,
entity_type character varying NOT NULL,
details text,
updated_at timestamp without time zone,
ip_address inet,
author_name text,
entity_path text,
@ -9470,7 +9466,6 @@ CREATE TABLE public.audit_events (
entity_type character varying NOT NULL,
details text,
created_at timestamp without time zone,
updated_at timestamp without time zone,
ip_address inet,
author_name text,
entity_path text,

View file

@ -52,7 +52,6 @@ The following metrics are available:
| `gitlab_sql_duration_seconds` | Histogram | 10.2 | SQL execution time, excluding `SCHEMA` operations and `BEGIN` / `COMMIT` | |
| `gitlab_ruby_threads_max_expected_threads` | Gauge | 13.3 | Maximum number of threads expected to be running and performing application work |
| `gitlab_ruby_threads_running_threads` | Gauge | 13.3 | Number of running Ruby threads by name |
| `gitlab_transaction_allocated_memory_bytes` | Histogram | 10.2 | Allocated memory for all transactions (`gitlab_transaction_*` metrics) | |
| `gitlab_transaction_cache_<key>_count_total` | Counter | 10.2 | Counter for total Rails cache calls (per key) | |
| `gitlab_transaction_cache_<key>_duration_total` | Counter | 10.2 | Counter for total time (seconds) spent in Rails cache calls (per key) | |
| `gitlab_transaction_cache_count_total` | Counter | 10.2 | Counter for total Rails cache calls (aggregate) | |

View file

@ -13,7 +13,7 @@ There are essentially three setups to choose from.
This setup is for when you have installed GitLab using the
[Omnibus GitLab **Enterprise Edition** (EE) package](https://about.gitlab.com/install/?version=ee).
All the tools that are needed like PostgreSQL, PgBouncer, Repmgr are bundled in
All the tools that are needed like PostgreSQL, PgBouncer, Patroni, and repmgr are bundled in
the package, so you can it to set up the whole PostgreSQL infrastructure (primary, replica).
[> Read how to set up PostgreSQL replication and failover using Omnibus GitLab](replication_and_failover.md)

View file

@ -29,6 +29,11 @@ You also need to take into consideration the underlying network topology, making
sure you have redundant connectivity between all Database and GitLab instances
to avoid the network becoming a single point of failure.
NOTE: **Note:**
As of GitLab 13.3, PostgreSQL 12 is shipped with Omnibus GitLab. Clustering for PostgreSQL 12 is only supported with
Patroni. See the [Patroni](#patroni) section for further details. The support for repmgr will not be extended beyond
PostgreSQL 11.
### Database node
Each database node runs three services:

View file

@ -1,36 +1,46 @@
---
type: reference, concepts
stage: Enablement
group: Distribution
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
# Reference architectures
<!-- TBD to be reviewed by Eric -->
You can set up GitLab on a single server or scale it up to serve many users.
This page details the recommended Reference Architectures that were built and verified by GitLab's Quality and Support teams.
This page details the recommended Reference Architectures that were built and
verified by GitLab's Quality and Support teams.
Below is a chart representing each architecture tier and the number of users they can handle. As your number of users grow with time, its recommended that you scale GitLab accordingly.
Below is a chart representing each architecture tier and the number of users
they can handle. As your number of users grow with time, its recommended that
you scale GitLab accordingly.
![Reference Architectures](img/reference-architectures.png)
<!-- Internal link: https://docs.google.com/spreadsheets/d/1obYP4fLKkVVDOljaI3-ozhmCiPtEeMblbBKkf2OADKs/edit#gid=1403207183 -->
Testing on these reference architectures were performed with [GitLab's Performance Tool](https://gitlab.com/gitlab-org/quality/performance)
at specific coded workloads, and the throughputs used for testing were calculated based on sample customer data.
After selecting the reference architecture that matches your scale, refer to
Testing on these reference architectures were performed with
[GitLab's Performance Tool](https://gitlab.com/gitlab-org/quality/performance)
at specific coded workloads, and the throughputs used for testing were
calculated based on sample customer data. After selecting the reference
architecture that matches your scale, refer to
[Configure GitLab to Scale](#configure-gitlab-to-scale) to see the components
involved, and how to configure them.
Each endpoint type is tested with the following number of requests per second (RPS) per 1000 users:
Each endpoint type is tested with the following number of requests per second (RPS)
per 1,000 users:
- API: 20 RPS
- Web: 2 RPS
- Git: 2 RPS
For GitLab instances with less than 2,000 users, it's recommended that you use the [default setup](#automated-backups-core-only)
by [installing GitLab](../../install/README.md) on a single machine to minimize maintenance and resource costs.
For GitLab instances with less than 2,000 users, it's recommended that you use
the [default setup](#automated-backups-core-only) by
[installing GitLab](../../install/README.md) on a single machine to minimize
maintenance and resource costs.
If your organization has more than 2,000 users, the recommendation is to scale GitLab's components to multiple
machine nodes. The machine nodes are grouped by component(s). The addition of these
nodes increases the performance and scalability of to your GitLab instance.
If your organization has more than 2,000 users, the recommendation is to scale
GitLab's components to multiple machine nodes. The machine nodes are grouped by
components. The addition of these nodes increases the performance and
scalability of to your GitLab instance.
When scaling GitLab, there are several factors to consider:
@ -39,12 +49,13 @@ When scaling GitLab, there are several factors to consider:
- The application nodes connects to a shared file server and PostgreSQL and Redis services on the backend.
NOTE: **Note:**
Depending on your workflow, the following recommended
reference architectures may need to be adapted accordingly. Your workload
is influenced by factors including how active your users are,
how much automation you use, mirroring, and repository/change size. Additionally the
displayed memory values are provided by [GCP machine types](https://cloud.google.com/compute/docs/machine-types).
For different cloud vendors, attempt to select options that best match the provided architecture.
Depending on your workflow, the following recommended reference architectures
may need to be adapted accordingly. Your workload is influenced by factors
including how active your users are, how much automation you use, mirroring,
and repository/change size. Additionally the displayed memory values are
provided by [GCP machine types](https://cloud.google.com/compute/docs/machine-types).
For different cloud vendors, attempt to select options that best match the
provided architecture.
## Available reference architectures
@ -60,14 +71,14 @@ The following reference architectures are available:
## Availability Components
GitLab comes with the following components for your use, listed from
least to most complex:
GitLab comes with the following components for your use, listed from least to
most complex:
1. [Automated backups](#automated-backups-core-only)
1. [Traffic load balancer](#traffic-load-balancer-starter-only)
1. [Zero downtime updates](#zero-downtime-updates-starter-only)
1. [Automated database failover](#automated-database-failover-premium-only)
1. [Instance level replication with GitLab Geo](#instance-level-replication-with-gitlab-geo-premium-only)
- [Automated backups](#automated-backups-core-only)
- [Traffic load balancer](#traffic-load-balancer-starter-only)
- [Zero downtime updates](#zero-downtime-updates-starter-only)
- [Automated database failover](#automated-database-failover-premium-only)
- [Instance level replication with GitLab Geo](#instance-level-replication-with-gitlab-geo-premium-only)
As you implement these components, begin with a single server and then do
backups. Only after completing the first server should you proceed to the next.
@ -144,7 +155,9 @@ that can also be promoted in case of disaster.
## Configure GitLab to scale
NOTE: **Note:**
From GitLab 13.0, using NFS for Git repositories is deprecated. In GitLab 14.0, support for NFS for Git repositories is scheduled to be removed. Upgrade to [Gitaly Cluster](../gitaly/praefect.md) as soon as possible.
From GitLab 13.0, using NFS for Git repositories is deprecated. In GitLab 14.0,
support for NFS for Git repositories is scheduled to be removed. Upgrade to
[Gitaly Cluster](../gitaly/praefect.md) as soon as possible.
The following components are the ones you need to configure in order to scale
GitLab. They are listed in the order you'll typically configure them if they are
@ -233,9 +246,3 @@ cluster with the Rails nodes broken down into a number of smaller Pods across th
as with time they may be adjusted higher or lower depending on the scale of your
environment's workload. If you're running the environment on a Cloud provider
you may need to refer to their documentation on how configure IOPS correctly.
1. The architectures were built and tested with the [Intel Xeon E5 v3 (Haswell)](https://cloud.google.com/compute/docs/cpu-platforms)
CPU platform on GCP. On different hardware you may find that adjustments, either lower
or higher, are required for your CPU or Node counts accordingly. For more information, a
[Sysbench](https://github.com/akopytov/sysbench) benchmark of the CPU can be found
[here](https://gitlab.com/gitlab-org/quality/performance/-/wikis/Reference-Architectures/GCP-CPU-Benchmarks).

View file

@ -4470,6 +4470,11 @@ type EpicIssue implements Noteable {
"""
state: IssueState!
"""
Indicates whether an issue is published to the status page
"""
statusPagePublishedIncident: Boolean
"""
Indicates the currently logged in user is subscribed to the issue
"""
@ -6103,6 +6108,11 @@ type Issue implements Noteable {
"""
state: IssueState!
"""
Indicates whether an issue is published to the status page
"""
statusPagePublishedIncident: Boolean
"""
Indicates the currently logged in user is subscribed to the issue
"""

View file

@ -12461,6 +12461,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "statusPagePublishedIncident",
"description": "Indicates whether an issue is published to the status page",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "subscribed",
"description": "Indicates the currently logged in user is subscribed to the issue",
@ -16817,6 +16831,20 @@
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "statusPagePublishedIncident",
"description": "Indicates whether an issue is published to the status page",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "subscribed",
"description": "Indicates the currently logged in user is subscribed to the issue",

View file

@ -750,6 +750,7 @@ Relationship between an epic and an issue
| `relationPath` | String | URI path of the epic-issue relation |
| `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) |
| `state` | IssueState! | State of the issue |
| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page |
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the issue |
| `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue |
| `timeEstimate` | Int! | Time estimate of the issue |
@ -916,6 +917,7 @@ Represents a Group Member
| `reference` | String! | Internal reference of the issue. Returned in shortened format by default |
| `relativePosition` | Int | Relative position of the issue (used for positioning in epic tree and issue boards) |
| `state` | IssueState! | State of the issue |
| `statusPagePublishedIncident` | Boolean | Indicates whether an issue is published to the status page |
| `subscribed` | Boolean! | Indicates the currently logged in user is subscribed to the issue |
| `taskCompletionStatus` | TaskCompletionStatus! | Task completion status of the issue |
| `timeEstimate` | Int! | Time estimate of the issue |

View file

@ -11,7 +11,7 @@ Available `RAILS_ENV`:
- `development` (this is your main GDK db).
- `test` (used for tests like RSpec).
## Nuke everything and start over
## Delete everything and start over
If you just want to delete everything and start over with an empty DB (approximately 1 minute):

View file

@ -57,7 +57,7 @@ bundle exec guard
When using spring and guard together, use `SPRING=1 bundle exec guard` instead to make use of spring.
Use [Factory Doctor](https://test-prof.evilmartians.io/#/factory_doctor.md) to find cases on un-necessary database manipulation, which can cause slow tests.
Use [Factory Doctor](https://test-prof.evilmartians.io/#/profilers/factory_doctor) to find cases on un-necessary database manipulation, which can cause slow tests.
```shell
# run test for path
@ -261,8 +261,8 @@ As much as possible, do not implement this using `before(:all)` or `before(:cont
you would need to manually clean up the data as those hooks run outside a database transaction.
Instead, this can be achieved by using
[`let_it_be`](https://test-prof.evilmartians.io/#/let_it_be) variables and the
[`before_all`](https://test-prof.evilmartians.io/#/before_all) hook
[`let_it_be`](https://test-prof.evilmartians.io/#/recipes/let_it_be) variables and the
[`before_all`](https://test-prof.evilmartians.io/#/recipes/before_all) hook
from the [`test-prof` gem](https://rubygems.org/gems/test-prof).
```ruby

View file

@ -67,12 +67,10 @@ For instance, consider the following workflow:
## Example configuration
CAUTION: **Caution:**
The job definition shown below is supported on GitLab 11.11 and later versions. It
also requires the GitLab Runner 11.5 or later. For earlier versions, use the
[previous job definitions](#previous-job-definitions).
This example shows how to run Code Quality on your code by using GitLab CI/CD and Docker.
It requires GitLab 11.11 or later, and GitLab Runner 11.5 or later. If you are using
GitLab 11.4 or ealier, you can view the deprecated job definitions in the
[documentation archive](https://docs.gitlab.com/12.10/ee/user/project/merge_requests/code_quality.html#previous-job-definitions).
First, you need GitLab Runner configured:
@ -132,105 +130,6 @@ definition they will be able to execute privileged Docker commands on the Runner
host. Having proper access control policies mitigates this attack vector by
allowing access only to trusted actors.
### Previous job definitions
CAUTION: **Caution:**
Before GitLab 11.5, Code Quality job and artifact had to be named specifically to
automatically extract report data and show it in the merge request widget. While these
old job definitions are still maintained they have been deprecated and are no longer supported on GitLab 12.0 or higher.
You're advised to update your `.gitlab-ci.yml` configuration to reflect that change.
For GitLab 11.5 and later, the job should look like:
```yaml
code_quality:
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
script:
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run
--env SOURCE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/ci-cd/codequality:$SP_VERSION" /code
artifacts:
reports:
codequality: gl-code-quality-report.json
```
In GitLab 12.6, Code Quality switched to the
[new versioning scheme](https://gitlab.com/gitlab-org/ci-cd/codequality#versioning-and-release-cycle).
It's highly recommended to include the Code Quality template as shown in the
[example configuration](#example-configuration), which uses the new versioning scheme.
If not using the template, the `SP_VERSION` variable can be hardcoded to use the
new image versions:
```yaml
code_quality:
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
SP_VERSION: 0.85.6
allow_failure: true
services:
- docker:stable-dind
script:
- docker run
--env SOURCE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/ci-cd/codequality:$SP_VERSION" /code
artifacts:
reports:
codequality: gl-code-quality-report.json
```
For GitLab 11.4 and earlier, the job should look like:
```yaml
code_quality:
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
allow_failure: true
services:
- docker:stable-dind
script:
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- docker run
--env SOURCE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/ci-cd/codequality:$SP_VERSION" /code
artifacts:
paths: [gl-code-quality-report.json]
```
Alternatively the job name could be `codeclimate` or `codequality` and the artifact
name could be `codeclimate.json`. These names have been deprecated with GitLab 11.0
and may be removed in the next major release, GitLab 12.0.
For GitLab 10.3 and earlier, the job should look like:
```yaml
codequality:
image: docker:latest
variables:
DOCKER_DRIVER: overlay
services:
- docker:dind
script:
- docker pull codeclimate/codeclimate:0.69.0
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 init
- docker run --env CODECLIMATE_CODE="$PWD" --volume "$PWD":/code --volume /var/run/docker.sock:/var/run/docker.sock --volume /tmp/cc:/tmp/cc codeclimate/codeclimate:0.69.0 analyze -f json > codeclimate.json || true
artifacts:
paths: [codeclimate.json]
```
## Configuring jobs using variables
The Code Quality job supports environment variables that users can set to

View file

@ -40,12 +40,8 @@ module API
repository = user_project.repository
if Feature.enabled?(:branch_list_keyset_pagination, user_project)
branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute(gitaly_pagination: true)
else
branches = BranchesFinder.new(repository, declared_params(include_missing: false)).execute
branches = paginate(::Kaminari.paginate_array(branches))
end
branches_finder = BranchesFinder.new(repository, declared_params(include_missing: false))
branches = Gitlab::Pagination::GitalyKeysetPager.new(self, user_project).paginate(branches_finder)
merged_branch_names = repository.merged_branch_names(branches.map(&:name))

View file

@ -14,7 +14,6 @@ module Gitlab
THREAD_KEY = :_gitlab_metrics_transaction
SMALL_BUCKETS = [0.1, 0.25, 0.5, 1.0, 2.5, 5.0].freeze
BIG_BUCKETS = [100, 1000, 10000, 100000, 1000000, 10000000].freeze
# The series to store events (e.g. Git pushes) in.
EVENT_SERIES = 'events'
@ -43,9 +42,6 @@ module Gitlab
@started_at = nil
@finished_at = nil
@memory_before = 0
@memory_after = 0
end
def duration
@ -56,20 +52,14 @@ module Gitlab
System.thread_cpu_duration(@thread_cputime_start)
end
def allocated_memory
@memory_after - @memory_before
end
def run
Thread.current[THREAD_KEY] = self
@memory_before = System.memory_usage_rss
@started_at = System.monotonic_time
@thread_cputime_start = System.thread_cpu_time
yield
ensure
@memory_after = System.memory_usage_rss
@finished_at = System.monotonic_time
observe(:gitlab_transaction_cputime_seconds, thread_cpu_duration) do
@ -78,9 +68,6 @@ module Gitlab
observe(:gitlab_transaction_duration_seconds, duration) do
buckets SMALL_BUCKETS
end
observe(:gitlab_transaction_allocated_memory_bytes, allocated_memory * 1024.0) do
buckets BIG_BUCKETS
end
Thread.current[THREAD_KEY] = nil
end

View file

@ -0,0 +1,54 @@
# frozen_string_literal: true
module Gitlab
module Pagination
class GitalyKeysetPager
attr_reader :request_context, :project
delegate :params, to: :request_context
def initialize(request_context, project)
@request_context = request_context
@project = project
end
# It is expected that the given finder will respond to `execute` method with `gitaly_pagination: true` option
# and supports pagination via gitaly.
def paginate(finder)
return paginate_via_gitaly(finder) if keyset_pagination_enabled?
branches = ::Kaminari.paginate_array(finder.execute)
Gitlab::Pagination::OffsetPagination
.new(request_context)
.paginate(branches)
end
private
def keyset_pagination_enabled?
Feature.enabled?(:branch_list_keyset_pagination, project) && params[:pagination] == 'keyset'
end
def paginate_via_gitaly(finder)
finder.execute(gitaly_pagination: true).tap do |records|
apply_headers(records)
end
end
def apply_headers(records)
if records.count == params[:per_page]
Gitlab::Pagination::Keyset::HeaderBuilder
.new(request_context)
.add_next_page_header(
query_params_for(records.last)
)
end
end
def query_params_for(record)
# NOTE: page_token is name for now, but it could be dynamic if we have other gitaly finders
# that is based on something other than name
{ page_token: record.name }
end
end
end
end

View file

@ -0,0 +1,45 @@
# frozen_string_literal: true
module Gitlab
module Pagination
module Keyset
class HeaderBuilder
attr_reader :request_context
delegate :params, :header, :request, to: :request_context
def initialize(request_context)
@request_context = request_context
end
def add_next_page_header(query_params)
link = next_page_link(page_href(query_params))
header('Links', link)
header('Link', link)
end
private
def next_page_link(href)
%(<#{href}>; rel="next")
end
def page_href(query_params)
base_request_uri.tap do |uri|
uri.query = updated_params(query_params).to_query
end.to_s
end
def base_request_uri
@base_request_uri ||= URI.parse(request.url).tap do |uri|
uri.host = Gitlab.config.gitlab.host
uri.port = Gitlab.config.gitlab.port
end
end
def updated_params(query_params)
params.merge(query_params)
end
end
end
end
end

View file

@ -24,9 +24,11 @@ module Gitlab
end
def apply_headers(next_page)
link = pagination_links(next_page)
request.header('Links', link)
request.header('Link', link)
Gitlab::Pagination::Keyset::HeaderBuilder
.new(request)
.add_next_page_header(
query_params_for(next_page)
)
end
private
@ -63,25 +65,8 @@ module Gitlab
end
end
def page_href(page)
base_request_uri.tap do |uri|
uri.query = query_params_for(page).to_query
end.to_s
end
def pagination_links(next_page)
%(<#{page_href(next_page)}>; rel="next")
end
def base_request_uri
@base_request_uri ||= URI.parse(request.request.url).tap do |uri|
uri.host = Gitlab.config.gitlab.host
uri.port = Gitlab.config.gitlab.port
end
end
def query_params_for(page)
request.params.merge(lower_bounds_params(page))
lower_bounds_params(page)
end
end
end

View file

@ -12743,12 +12743,21 @@ msgstr ""
msgid "IncidentManagement|Open"
msgstr ""
msgid "IncidentManagement|Published"
msgstr ""
msgid "IncidentManagement|Published to status page"
msgstr ""
msgid "IncidentManagement|There was an error displaying the incidents."
msgstr ""
msgid "IncidentManagement|Unassigned"
msgstr ""
msgid "IncidentManagement|Unpublished"
msgstr ""
msgid "IncidentSettings|Alert integration"
msgstr ""

View file

@ -43,7 +43,7 @@
"@babel/preset-env": "^7.10.1",
"@gitlab/at.js": "1.5.5",
"@gitlab/svgs": "1.156.0",
"@gitlab/ui": "17.39.0",
"@gitlab/ui": "17.40.0",
"@gitlab/visual-review-tools": "1.6.1",
"@rails/actioncable": "^6.0.3-1",
"@sentry/browser": "^5.10.2",

View file

@ -11,11 +11,7 @@ RSpec.describe 'Group issues page' do
let(:project_with_issues_disabled) { create(:project, :issues_disabled, group: group) }
let(:path) { issues_group_path(group) }
before do
stub_feature_flags(vue_issuables_list: false)
end
context 'with shared examples' do
context 'with shared examples', :js do
let(:issuable) { create(:issue, project: project, title: "this is my created issuable")}
include_examples 'project features apply to issuables', Issue
@ -30,19 +26,33 @@ RSpec.describe 'Group issues page' do
user_in_group
end
it_behaves_like "it has an RSS button with current_user's feed token"
it_behaves_like "an autodiscoverable RSS feed with current_user's feed token"
# Note: The one from rss_shared_example.rb uses a css pseudo-class `:has`
# which is VERY experimental and only supported in Nokogiri used by Capybara
# However,`:js` option forces Capybara to use Selenium that doesn't support`:has`
context "it has an RSS button with current_user's feed token" do
it "shows the RSS button with current_user's feed token" do
expect(find('[data-testid="rss-feed-link"]')['href']).to have_content(user.feed_token)
end
end
end
context 'when signed out' do
let(:user) { nil }
it_behaves_like "it has an RSS button without a feed token"
it_behaves_like "an autodiscoverable RSS feed without a feed token"
# Note: please see the above
context "it has an RSS button without a feed token" do
it "shows the RSS button without a feed token" do
expect(find('[data-testid="rss-feed-link"]')['href']).not_to have_content('feed_token')
end
end
end
end
context 'assignee', :js do
context 'assignee' do
let(:access_level) { ProjectFeature::ENABLED }
let(:user) { user_in_group }
let(:user2) { user_outside_group }
@ -56,7 +66,7 @@ RSpec.describe 'Group issues page' do
end
end
context 'issues list' do
context 'issues list', :js do
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_project) { create(:project, :public, group: subgroup)}
let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
@ -110,7 +120,7 @@ RSpec.describe 'Group issues page' do
end
end
context 'manual ordering' do
context 'manual ordering', :js do
let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
let!(:issue1) { create(:issue, project: project, title: 'Issue #1', relative_position: 1) }
@ -143,9 +153,11 @@ RSpec.describe 'Group issues page' do
end
end
it 'issues should be draggable and persist order', :js do
it 'issues should be draggable and persist order' do
visit issues_group_path(group, sort: 'relative_position')
wait_for_requests
drag_to(selector: '.manual-ordering',
from_index: 0,
to_index: 2)
@ -159,11 +171,13 @@ RSpec.describe 'Group issues page' do
check_issue_order
end
it 'issues should not be draggable when user is not logged in', :js do
it 'issues should not be draggable when user is not logged in' do
sign_out(user_in_group)
visit issues_group_path(group, sort: 'relative_position')
wait_for_requests
drag_to(selector: '.manual-ordering',
from_index: 0,
to_index: 2)
@ -187,7 +201,7 @@ RSpec.describe 'Group issues page' do
end
end
context 'issues pagination' do
context 'issues pagination', :js do
let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
let!(:issues) do
@ -204,7 +218,9 @@ RSpec.describe 'Group issues page' do
end
it 'first pagination item is active' do
expect(page).to have_css(".js-first-button a.page-link.active")
page.within('.gl-pagination') do
expect(find('.active')).to have_content('1')
end
end
end
end

View file

@ -487,7 +487,7 @@ RSpec.describe 'GFM autocomplete', :js do
wait_for_requests
find('.tribute-container .highlight').click
find('.tribute-container .highlight', visible: true).click
click_button 'Save changes'
@ -501,7 +501,7 @@ RSpec.describe 'GFM autocomplete', :js do
find('#note-body').native.send_keys('@')
end
expect(page).to have_selector('.tribute-container')
expect(page).to have_selector('.tribute-container', visible: true)
end
it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do
@ -511,20 +511,9 @@ RSpec.describe 'GFM autocomplete', :js do
wait_for_requests
expect(page).to have_selector('.tribute-container')
expect(page).to have_selector('.tribute-container', visible: true)
page.within '.tribute-container ul' do
expect(find('li').text).to have_content(user_xss.username)
end
end
it 'doesnt open autocomplete menu character is prefixed with text' do
page.within '.timeline-content-form' do
find('#note-body').native.send_keys('testing')
find('#note-body').native.send_keys('@')
end
expect(page).not_to have_selector('.tribute-container')
expect(find('.tribute-container ul', visible: true).text).to have_content(user_xss.username)
end
it 'selects the first item for assignee dropdowns' do
@ -532,11 +521,11 @@ RSpec.describe 'GFM autocomplete', :js do
find('#note-body').native.send_keys('@')
end
expect(page).to have_selector('.tribute-container')
expect(page).to have_selector('.tribute-container', visible: true)
wait_for_requests
expect(find('.tribute-container ul')).to have_selector('.highlight:first-of-type')
expect(find('.tribute-container ul', visible: true)).to have_selector('.highlight:first-of-type')
end
it 'includes items for assignee dropdowns with non-ASCII characters in name' do
@ -545,14 +534,26 @@ RSpec.describe 'GFM autocomplete', :js do
simulate_input('#note-body', "@#{user.name[0...8]}")
end
expect(page).to have_selector('.tribute-container')
expect(page).to have_selector('.tribute-container', visible: true)
wait_for_requests
expect(find('.tribute-container')).to have_content(user.name)
expect(find('.tribute-container ul', visible: true)).to have_content(user.name)
end
context 'if a selected value has special characters' do
it 'wraps the result in double quotes' do
note = find('#note-body')
page.within '.timeline-content-form' do
find('#note-body').native.send_keys('')
simulate_input('#note-body', "~#{label.title[0]}")
end
label_item = find('.tribute-container ul', text: label.title, visible: true)
expect_to_wrap(true, label_item, note, label.title)
end
it "shows dropdown after a new line" do
note = find('#note-body')
page.within '.timeline-content-form' do
@ -562,7 +563,7 @@ RSpec.describe 'GFM autocomplete', :js do
note.native.send_keys('@')
end
expect(page).to have_selector('.tribute-container')
expect(page).to have_selector('.tribute-container', visible: true)
end
it "does not show dropdown when preceded with a special character" do
@ -571,12 +572,21 @@ RSpec.describe 'GFM autocomplete', :js do
note.native.send_keys("@")
end
expect(page).to have_selector('.tribute-container')
expect(page).to have_selector('.tribute-container', visible: true)
page.within '.timeline-content-form' do
note.native.send_keys("@")
end
expect(page).not_to have_selector('.tribute-container')
end
it "does not throw an error if no labels exist" do
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('~')
end
expect(page).to have_selector('.tribute-container', visible: false)
end
@ -586,7 +596,7 @@ RSpec.describe 'GFM autocomplete', :js do
note.native.send_keys("@#{user.username[0]}")
end
user_item = find('.tribute-container li', text: user.username)
user_item = find('.tribute-container ul', text: user.username, visible: true)
expect_to_wrap(false, user_item, note, user.username)
end
@ -611,7 +621,7 @@ RSpec.describe 'GFM autocomplete', :js do
wait_for_requests
user_item = find('.tribute-container li', text: user.username)
user_item = find('.tribute-container ul', text: user.username, visible: true)
expect(user_item).to have_content(user.username)
end
end
@ -640,8 +650,99 @@ RSpec.describe 'GFM autocomplete', :js do
wait_for_requests
expect(find('.tribute-container ul')).not_to have_content(user.username)
expect(find('.tribute-container ul')).to have_content(unassigned_user.username)
expect(find('.tribute-container ul', visible: true)).not_to have_content(user.username)
expect(find('.tribute-container ul', visible: true)).to have_content(unassigned_user.username)
end
it 'lists users who are currently not assigned to the issue when using /assign on the second line' do
visit project_issue_path(project, issue_assignee)
note = find('#note-body')
page.within '.timeline-content-form' do
note.native.send_keys('/assign @user2')
note.native.send_keys(:enter)
note.native.send_keys('/assign @')
note.native.send_keys(:right)
end
wait_for_requests
expect(find('.tribute-container ul', visible: true)).not_to have_content(user.username)
expect(find('.tribute-container ul', visible: true)).to have_content(unassigned_user.username)
end
end
context 'labels' do
it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
label_xss_title = 'alert label &lt;img src=x onerror="alert(\'Hello xss\');" a'
create(:label, project: project, title: label_xss_title)
note = find('#note-body')
# It should show all the labels on "~".
type(note, '~')
wait_for_requests
expect(find('.tribute-container ul', visible: true).text).to have_content('alert label')
end
it 'allows colons when autocompleting scoped labels' do
create(:label, project: project, title: 'scoped:label')
note = find('#note-body')
type(note, '~scoped:')
wait_for_requests
expect(find('.tribute-container ul', visible: true).text).to have_content('scoped:label')
end
it 'allows colons when autocompleting scoped labels with double colons' do
create(:label, project: project, title: 'scoped::label')
note = find('#note-body')
type(note, '~scoped::')
wait_for_requests
expect(find('.tribute-container ul', visible: true).text).to have_content('scoped::label')
end
it 'autocompletes multi-word labels' do
create(:label, project: project, title: 'Accepting merge requests')
note = find('#note-body')
type(note, '~Acceptingmerge')
wait_for_requests
expect(find('.tribute-container ul', visible: true).text).to have_content('Accepting merge requests')
end
it 'only autocompletes the latest label' do
create(:label, project: project, title: 'documentation')
create(:label, project: project, title: 'feature')
note = find('#note-body')
type(note, '~documentation foo bar ~feat')
note.native.send_keys(:right)
wait_for_requests
expect(find('.tribute-container ul', visible: true).text).to have_content('feature')
expect(find('.tribute-container ul', visible: true).text).not_to have_content('documentation')
end
it 'does not autocomplete labels if no tilde is typed' do
create(:label, project: project, title: 'documentation')
note = find('#note-body')
type(note, 'document')
wait_for_requests
expect(page).not_to have_selector('.tribute-container')
end
end

View file

@ -5,10 +5,9 @@ require 'spec_helper'
RSpec.describe "Internal Project Snippets Access" do
include AccessMatchers
let(:project) { create(:project, :internal) }
let(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
let(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
let_it_be(:project) { create(:project, :internal) }
let_it_be(:internal_snippet) { create(:project_snippet, :internal, project: project, author: project.owner) }
let_it_be(:private_snippet) { create(:project_snippet, :private, project: project, author: project.owner) }
describe "GET /:project_path/snippets" do
subject { project_snippets_path(project) }

View file

@ -56,6 +56,7 @@ describe('Incidents List', () => {
newIssuePath,
incidentTemplateName,
issuePath: '/project/isssues',
publishedAvailable: true,
},
stubs: {
GlButton: true,

View file

@ -26,13 +26,14 @@ describe('Ref selector component', () => {
let tagsApiCallSpy;
let commitApiCallSpy;
const createComponent = (props = {}) => {
const createComponent = (props = {}, attrs = {}) => {
wrapper = mount(RefSelector, {
propsData: {
projectId,
value: '',
...props,
},
attrs,
listeners: {
// simulate a parent component v-model binding
input: selectedRef => {
@ -164,6 +165,20 @@ describe('Ref selector component', () => {
});
describe('post-initialization behavior', () => {
describe('when the parent component provides an `id` binding', () => {
const id = 'git-ref';
beforeEach(() => {
createComponent({}, { id });
return waitForRequests();
});
it('adds the provided ID to the GlNewDropdown instance', () => {
expect(wrapper.attributes().id).toBe(id);
});
});
describe('when a ref is pre-selected', () => {
const preselectedRef = fixtures.branches[0].name;

View file

@ -28,14 +28,6 @@ RSpec.describe Gitlab::Metrics::Transaction do
end
end
describe '#allocated_memory' do
it 'returns the allocated memory in bytes' do
transaction.run { 'a' * 32 }
expect(transaction.allocated_memory).to be_a_kind_of(Numeric)
end
end
describe '#run' do
it 'yields the supplied block' do
expect { |b| transaction.run(&b) }.to yield_control

View file

@ -48,16 +48,6 @@ RSpec.describe Gitlab::Metrics::WebTransaction do
end
end
describe '#allocated_memory' do
include_context 'transaction observe metrics'
it 'returns the allocated memory in bytes' do
transaction.run { 'a' * 32 }
expect(transaction.allocated_memory).to be_a_kind_of(Numeric)
end
end
describe '#run' do
include_context 'transaction observe metrics'

View file

@ -0,0 +1,106 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Pagination::GitalyKeysetPager do
let(:pager) { described_class.new(request_context, project) }
let_it_be(:project) { create(:project, :repository) }
let(:request_context) { double("request context") }
let(:finder) { double("branch finder") }
let(:custom_port) { 8080 }
let(:incoming_api_projects_url) { "#{Gitlab.config.gitlab.url}:#{custom_port}/api/v4/projects" }
before do
stub_config_setting(port: custom_port)
end
describe '.paginate' do
let(:base_query) { { per_page: 2 } }
let(:query) { base_query }
before do
allow(request_context).to receive(:params).and_return(query)
allow(request_context).to receive(:header)
end
shared_examples_for 'offset pagination' do
let(:paginated_array) { double 'paginated array' }
let(:branches) { [] }
it 'uses offset pagination' do
expect(finder).to receive(:execute).and_return(branches)
expect(Kaminari).to receive(:paginate_array).with(branches).and_return(paginated_array)
expect_next_instance_of(Gitlab::Pagination::OffsetPagination) do |offset_pagination|
expect(offset_pagination).to receive(:paginate).with(paginated_array)
end
pager.paginate(finder)
end
end
context 'with branch_list_keyset_pagination feature off' do
before do
stub_feature_flags(branch_list_keyset_pagination: false)
end
context 'without keyset pagination option' do
it_behaves_like 'offset pagination'
end
context 'with keyset pagination option' do
let(:query) { base_query.merge(pagination: 'keyset') }
it_behaves_like 'offset pagination'
end
end
context 'with branch_list_keyset_pagination feature on' do
before do
stub_feature_flags(branch_list_keyset_pagination: project)
end
context 'without keyset pagination option' do
it_behaves_like 'offset pagination'
end
context 'with keyset pagination option' do
let(:query) { base_query.merge(pagination: 'keyset') }
let(:fake_request) { double(url: "#{incoming_api_projects_url}?#{query.to_query}") }
before do
allow(request_context).to receive(:request).and_return(fake_request)
expect(finder).to receive(:execute).with(gitaly_pagination: true).and_return(branches)
end
context 'when next page could be available' do
let(:branch1) { double 'branch', name: 'branch1' }
let(:branch2) { double 'branch', name: 'branch2' }
let(:branches) { [branch1, branch2] }
let(:expected_next_page_link) { %Q(<#{incoming_api_projects_url}?#{query.merge(page_token: branch2.name).to_query}>; rel="next") }
it 'uses keyset pagination and adds link headers' do
expect(request_context).to receive(:header).with('Links', expected_next_page_link)
expect(request_context).to receive(:header).with('Link', expected_next_page_link)
pager.paginate(finder)
end
end
context 'when the current page is the last page' do
let(:branch1) { double 'branch', name: 'branch1' }
let(:branches) { [branch1] }
it 'uses keyset pagination without link headers' do
expect(request_context).not_to receive(:header).with('Links', anything)
expect(request_context).not_to receive(:header).with('Link', anything)
pager.paginate(finder)
end
end
end
end
end
end

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe AuditEventPartitioned do
let(:source_table) { AuditEvent }
let(:partitioned_table) { described_class }
it 'has the same columns as the source table' do
expect(partitioned_table.column_names).to match_array(source_table.column_names)
end
it 'inserts the same record as the one in the source table', :aggregate_failures do
expect { create(:audit_event) }.to change { partitioned_table.count }.by(1)
event_from_source_table = source_table.connection.select_one(
"SELECT * FROM #{source_table.table_name} ORDER BY created_at desc LIMIT 1"
)
event_from_partitioned_table = partitioned_table.connection.select_one(
"SELECT * FROM #{partitioned_table.table_name} ORDER BY created_at desc LIMIT 1"
)
expect(event_from_partitioned_table).to eq(event_from_source_table)
end
end

View file

@ -39,9 +39,11 @@ RSpec.describe API::Branches do
end
context 'with branch_list_keyset_pagination feature off' do
context 'with legacy pagination params' do
let(:base_params) { {} }
context 'with offset pagination params' do
it 'returns the repository branches' do
get api(route, current_user), params: { per_page: 100 }
get api(route, current_user), params: base_params.merge(per_page: 100)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/branches')
@ -53,7 +55,7 @@ RSpec.describe API::Branches do
it 'determines only a limited number of merged branch names' do
expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
get api(route, current_user), params: { per_page: 2 }
get api(route, current_user), params: base_params.merge(per_page: 2)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 2
@ -64,7 +66,7 @@ RSpec.describe API::Branches do
it 'merge status matches reality on paginated input' do
expected_first_branch_name = project.repository.branches_sorted_by('name')[20].name
get api(route, current_user), params: { per_page: 20, page: 2 }
get api(route, current_user), params: base_params.merge(per_page: 20, page: 2)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 20
@ -74,11 +76,11 @@ RSpec.describe API::Branches do
end
end
context 'with gitaly pagination params ' do
context 'with gitaly pagination params' do
it 'merge status matches reality on paginated input' do
expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
get api(route, current_user), params: base_params.merge(per_page: 20, page_token: 'feature')
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 20
@ -91,52 +93,58 @@ RSpec.describe API::Branches do
context 'with branch_list_keyset_pagination feature on' do
before do
stub_feature_flags(branch_list_keyset_pagination: true)
stub_feature_flags(branch_list_keyset_pagination: project)
end
context 'with gitaly pagination params ' do
it 'returns the repository branches' do
get api(route, current_user), params: { per_page: 100 }
context 'with keyset pagination option' do
let(:base_params) { { pagination: 'keyset' } }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/branches')
branch_names = json_response.map { |x| x['name'] }
expect(branch_names).to match_array(project.repository.branch_names)
context 'with gitaly pagination params ' do
it 'returns the repository branches' do
get api(route, current_user), params: base_params.merge(per_page: 100)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/branches')
expect(response.headers).not_to include('Link', 'Links')
branch_names = json_response.map { |x| x['name'] }
expect(branch_names).to match_array(project.repository.branch_names)
end
it 'determines only a limited number of merged branch names' do
expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
get api(route, current_user), params: base_params.merge(per_page: 2)
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers).to include('Link', 'Links')
expect(json_response.count).to eq 2
check_merge_status(json_response)
end
it 'merge status matches reality on paginated input' do
expected_first_branch_name = project.repository.branches_sorted_by('name').drop_while { |b| b.name <= 'feature' }.first.name
get api(route, current_user), params: base_params.merge(per_page: 20, page_token: 'feature')
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 20
expect(json_response.first['name']).to eq(expected_first_branch_name)
check_merge_status(json_response)
end
end
it 'determines only a limited number of merged branch names' do
expect(API::Entities::Branch).to receive(:represent).with(anything, has_up_to_merged_branch_names_count(2)).and_call_original
context 'with offset pagination params' do
it 'ignores legacy pagination params' do
expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
get api(route, current_user), params: base_params.merge(per_page: 20, page: 2)
get api(route, current_user), params: { per_page: 2 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq(expected_first_branch_name)
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 2
check_merge_status(json_response)
end
it 'merge status matches reality on paginated input' do
expected_first_branch_name = project.repository.branches_sorted_by('name').drop_while { |b| b.name <= 'feature' }.first.name
get api(route, current_user), params: { per_page: 20, page_token: 'feature' }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.count).to eq 20
expect(json_response.first['name']).to eq(expected_first_branch_name)
check_merge_status(json_response)
end
end
context 'with legacy pagination params' do
it 'ignores legacy pagination params' do
expected_first_branch_name = project.repository.branches_sorted_by('name').first.name
get api(route, current_user), params: { per_page: 20, page: 2 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq(expected_first_branch_name)
check_merge_status(json_response)
check_merge_status(json_response)
end
end
end
end

View file

@ -848,10 +848,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.156.0.tgz#2af56246b5d71000ec81abb1281e811a921cdfd1"
integrity sha512-+b670Sxkjo80Wb4GKMZQ+xvuwu9sVvql8aS9nzw63FLn84QyqXS+jMjvyDqPAW5kly6B1Eg4Kljq0YawJ0ySBg==
"@gitlab/ui@17.39.0":
version "17.39.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.39.0.tgz#04417471ac094323581482d354a33cdf0a21ec86"
integrity sha512-3KPrw+1cwF+ibY5zo01b6EsSOE2Kgflu7FGmrvJMvEgpK4w2shloGEts4vEJbPEGBpUzpjr3gQinNcoeIYu/JA==
"@gitlab/ui@17.40.0":
version "17.40.0"
resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-17.40.0.tgz#70c89a31d5e98382a9b30aaeac13caf924f38b6d"
integrity sha512-0Jf1TwE572cZBgWubCkD9F7YE4Vok+r4Jbtx9ORBxmLaxq1XKpGf/TAd3iMftRQ+pr4T011/z0rJYLqde1mUgw==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.3.0"