Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-07-12 18:09:09 +00:00
parent d9b3f39aca
commit dd1c093e28
51 changed files with 1010 additions and 454 deletions

View File

@ -66,6 +66,7 @@
"Grafana",
"Gzip",
"Helm",
"HipChat",
"ID",
"Ingress",
"jasmine-jquery",
@ -130,6 +131,7 @@
"Ubuntu",
"Ultra Auth",
"Unicorn",
"unicorn-worker-killer",
"URL",
"WebdriverIO",
"YAML",

View File

@ -5,14 +5,14 @@ import highlight from '~/lib/utils/highlight';
import { truncateNamespace } from '~/lib/utils/text_utility';
import { mapVuexModuleState } from '~/lib/utils/vuex_module_mappers';
import Tracking from '~/tracking';
import Identicon from '~/vue_shared/components/identicon.vue';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
const trackingMixin = Tracking.mixin();
export default {
components: {
Identicon,
GlButton,
ProjectAvatar,
},
mixins: [trackingMixin],
inject: ['vuexModule'],
@ -64,19 +64,12 @@ export default {
class="gl-text-left gl-justify-content-start!"
@click="track('click_link', { label: `${dropdownType}_dropdown_frequent_items_list_item` })"
>
<div
ref="frequentItemsItemAvatarContainer"
class="frequent-items-item-avatar-container avatar-container rect-avatar s32"
>
<img v-if="avatarUrl" ref="frequentItemsItemAvatar" :src="avatarUrl" class="avatar s32" />
<identicon
v-else
:entity-id="itemId"
:entity-name="itemName"
size-class="s32"
class="rect-avatar"
/>
</div>
<project-avatar
class="gl-float-left gl-mr-3"
:project-avatar-url="avatarUrl"
:project-name="itemName"
aria-hidden="true"
/>
<div ref="frequentItemsItemMetadataContainer" class="frequent-items-item-metadata-container">
<div
ref="frequentItemsItemTitle"

View File

@ -1,4 +1,6 @@
<script>
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF } from '../../constants';
import LogLine from './line.vue';
import LogLineHeader from './line_header.vue';
@ -7,7 +9,9 @@ export default {
components: {
LogLine,
LogLineHeader,
CollapsibleLogSection: () => import('./collapsible_section.vue'),
},
mixins: [glFeatureFlagsMixin()],
props: {
section: {
type: Object,
@ -22,6 +26,9 @@ export default {
badgeDuration() {
return this.section.line && this.section.line.section_duration;
},
infinitelyCollapsibleSectionsFlag() {
return this.glFeatures?.[INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF];
},
},
methods: {
handleOnClickCollapsibleLine(section) {
@ -40,12 +47,26 @@ export default {
@toggleLine="handleOnClickCollapsibleLine(section)"
/>
<template v-if="!section.isClosed">
<log-line
v-for="line in section.lines"
:key="line.offset"
:line="line"
:path="traceEndpoint"
/>
<template v-if="infinitelyCollapsibleSectionsFlag">
<template v-for="line in section.lines">
<collapsible-log-section
v-if="line.isHeader"
:key="line.line.offset"
:section="line"
:trace-endpoint="traceEndpoint"
@onClickCollapsibleLine="handleOnClickCollapsibleLine"
/>
<log-line v-else :key="line.offset" :line="line" :path="traceEndpoint" />
</template>
</template>
<template v-else>
<log-line
v-for="line in section.lines"
:key="line.offset"
:line="line"
:path="traceEndpoint"
/>
</template>
</template>
</div>
</template>

View File

@ -1,4 +1,6 @@
<script>
import { INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF } from '../../constants';
export default {
functional: true,
props: {
@ -14,7 +16,9 @@ export default {
render(h, { props }) {
const { lineNumber, path } = props;
const parsedLineNumber = lineNumber + 1;
const parsedLineNumber = gon.features?.[INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF]
? lineNumber
: lineNumber + 1;
const lineId = `L${parsedLineNumber}`;
const lineHref = `${path}#${lineId}`;

View File

@ -24,3 +24,5 @@ export const JOB_RETRY_FORWARD_DEPLOYMENT_MODAL = {
};
export const SUCCESS_STATUS = 'SUCCESS';
export const INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF = 'infinitelyCollapsibleSections';

View File

@ -1,6 +1,7 @@
import Vue from 'vue';
import { INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF } from '../constants';
import * as types from './mutation_types';
import { logLinesParser, updateIncrementalTrace } from './utils';
import { logLinesParser, logLinesParserLegacy, updateIncrementalTrace } from './utils';
export default {
[types.SET_JOB_ENDPOINT](state, endpoint) {
@ -20,12 +21,26 @@ export default {
},
[types.RECEIVE_TRACE_SUCCESS](state, log = {}) {
const infinitelyCollapsibleSectionsFlag =
gon.features?.[INFINITELY_NESTED_COLLAPSIBLE_SECTIONS_FF];
if (log.state) {
state.traceState = log.state;
}
if (log.append) {
state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
if (infinitelyCollapsibleSectionsFlag) {
if (log.lines) {
const parsedResult = logLinesParser(
log.lines,
state.auxiliaryPartialTraceHelpers,
state.trace,
);
state.trace = parsedResult.parsedLines;
state.auxiliaryPartialTraceHelpers = parsedResult.auxiliaryPartialTraceHelpers;
}
} else {
state.trace = log.lines ? updateIncrementalTrace(log.lines, state.trace) : state.trace;
}
state.traceSize += log.size;
} else {
@ -33,7 +48,14 @@ export default {
// the trace response will not have a defined
// html or size. We keep the old value otherwise these
// will be set to `null`
state.trace = log.lines ? logLinesParser(log.lines) : state.trace;
if (infinitelyCollapsibleSectionsFlag) {
const parsedResult = logLinesParser(log.lines);
state.trace = parsedResult.parsedLines;
state.auxiliaryPartialTraceHelpers = parsedResult.auxiliaryPartialTraceHelpers;
} else {
state.trace = log.lines ? logLinesParserLegacy(log.lines) : state.trace;
}
state.traceSize = log.size || state.traceSize;
}

View File

@ -30,4 +30,7 @@ export default () => ({
selectedStage: '',
stages: [],
jobs: [],
// to parse partial logs
auxiliaryPartialTraceHelpers: {},
});

View File

@ -104,7 +104,7 @@ export const getIncrementalLineNumber = (acc) => {
* @param Array accumulator
* @returns Array parsed log lines
*/
export const logLinesParser = (lines = [], accumulator = []) =>
export const logLinesParserLegacy = (lines = [], accumulator = []) =>
lines.reduce(
(acc, line, index) => {
const lineNumber = accumulator.length > 0 ? getIncrementalLineNumber(acc) : index;
@ -131,6 +131,77 @@ export const logLinesParser = (lines = [], accumulator = []) =>
[...accumulator],
);
export const logLinesParser = (lines = [], previousTraceState = {}, prevParsedLines = []) => {
let currentLine = previousTraceState?.prevLineCount ?? 0;
let currentHeader = previousTraceState?.currentHeader;
let isPreviousLineHeader = previousTraceState?.isPreviousLineHeader ?? false;
const parsedLines = prevParsedLines.length > 0 ? prevParsedLines : [];
const sectionsQueue = previousTraceState?.sectionsQueue ?? [];
for (let i = 0; i < lines.length; i += 1) {
const line = lines[i];
// First run we can use the current index, later runs we have to retrieve the last number of lines
currentLine = previousTraceState?.prevLineCount ? currentLine + 1 : i + 1;
if (line.section_header && !isPreviousLineHeader) {
// If there's no previous line header that means we're at the root of the log
isPreviousLineHeader = true;
parsedLines.push(parseHeaderLine(line, currentLine));
currentHeader = { index: parsedLines.length - 1 };
} else if (line.section_header && isPreviousLineHeader) {
// If there's a current section, we can't push to the parsedLines array
sectionsQueue.push(currentHeader);
currentHeader = parseHeaderLine(line, currentLine); // Let's parse the incoming header line
} else if (line.section && !line.section_duration) {
// We're inside a collapsible section and want to parse a standard line
if (currentHeader?.index) {
// If the current section header is only an index, add the line as part of the lines
// array of the current collapsible section
parsedLines[currentHeader.index].lines.push(parseLine(line, currentLine));
} else {
// Otherwise add it to the innermost collapsible section lines array
currentHeader.lines.push(parseLine(line, currentLine));
}
} else if (line.section && line.section_duration) {
// NOTE: This marks the end of a section_header
const previousSection = sectionsQueue.pop();
// Add the duration to section header
// If at the root, just push the end to the current parsedLine,
// otherwise, push it to the previous sections queue
if (currentHeader?.index) {
parsedLines[currentHeader.index].line.section_duration = line.section_duration;
isPreviousLineHeader = false;
currentHeader = null;
} else {
currentHeader.line.section_duration = line.section_duration;
if (previousSection && previousSection?.index) {
// Is the previous section on root?
parsedLines[previousSection.index].lines.push(currentHeader);
} else if (previousSection && !previousSection?.index) {
previousSection.lines.push(currentHeader);
}
currentHeader = previousSection;
}
} else {
parsedLines.push(parseLine(line, currentLine));
}
}
return {
parsedLines,
auxiliaryPartialTraceHelpers: {
isPreviousLineHeader,
currentHeader,
sectionsQueue,
prevLineCount: lines.length,
},
};
};
/**
* Finds the repeated offset, removes the old one
*
@ -177,5 +248,5 @@ export const findOffsetAndRemove = (newLog = [], oldParsed = []) => {
export const updateIncrementalTrace = (newLog = [], oldParsed = []) => {
const parsedLog = findOffsetAndRemove(newLog, oldParsed);
return logLinesParser(newLog, parsedLog);
return logLinesParserLegacy(newLog, parsedLog);
};

View File

@ -1,3 +1,3 @@
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
initForm();

View File

@ -1,3 +1,3 @@
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
initForm();

View File

@ -1,3 +1,3 @@
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
initForm();

View File

@ -1,3 +1,3 @@
import initForm from '../shared/init_form';
document.addEventListener('DOMContentLoaded', initForm);
initForm();

View File

@ -1,6 +1,6 @@
<script>
import { cloneDeep } from 'lodash';
import { __, s__ } from '~/locale';
import { formatNumber, sprintf, __, s__ } from '~/locale';
import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants';
import FilteredSearch from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue';
import BaseToken from '~/vue_shared/components/filtered_search_bar/tokens/base_token.vue';
@ -58,6 +58,10 @@ export default {
type: String,
required: true,
},
activeRunnersCount: {
type: Number,
required: true,
},
},
data() {
// filtered_search_bar_root.vue may mutate the inital
@ -119,6 +123,11 @@ export default {
},
];
},
activeRunnersMessage() {
return sprintf(__('Runners currently online: %{active_runners_count}'), {
active_runners_count: formatNumber(this.activeRunnersCount),
});
},
},
methods: {
onFilter(filters) {
@ -144,16 +153,20 @@ export default {
};
</script>
<template>
<filtered-search
v-bind="$attrs"
:namespace="namespace"
recent-searches-storage-key="runners-search"
:sort-options="$options.sortOptions"
:initial-filter-value="initialFilterValue"
:initial-sort-by="initialSortBy"
:tokens="searchTokens"
:search-input-placeholder="__('Search or filter results...')"
@onFilter="onFilter"
@onSort="onSort"
/>
<div>
<filtered-search
v-bind="$attrs"
:namespace="namespace"
recent-searches-storage-key="runners-search"
:sort-options="$options.sortOptions"
:initial-filter-value="initialFilterValue"
:initial-sort-by="initialSortBy"
:tokens="searchTokens"
:search-input-placeholder="__('Search or filter results...')"
data-testid="runners-filtered-search"
@onFilter="onFilter"
@onSort="onSort"
/>
<div class="gl-text-right" data-testid="active-runners-message">{{ activeRunnersMessage }}</div>
</div>
</template>

View File

@ -1,7 +1,7 @@
<script>
import { GlTable, GlTooltipDirective, GlSkeletonLoader } from '@gitlab/ui';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { formatNumber, sprintf, __, s__ } from '~/locale';
import { formatNumber, __, s__ } from '~/locale';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import { RUNNER_JOB_COUNT_LIMIT } from '../constants';
import RunnerActionsCell from './cells/runner_actions_cell.vue';
@ -52,17 +52,6 @@ export default {
type: Array,
required: true,
},
activeRunnersCount: {
type: Number,
required: true,
},
},
computed: {
activeRunnersMessage() {
return sprintf(__('Runners currently online: %{active_runners_count}'), {
active_runners_count: formatNumber(this.activeRunnersCount),
});
},
},
methods: {
formatProjectCount(projectCount) {
@ -101,12 +90,12 @@ export default {
</script>
<template>
<div>
<div class="gl-text-right" data-testid="active-runners-message">{{ activeRunnersMessage }}</div>
<gl-table
:busy="loading"
:items="runners"
:fields="$options.fields"
:tbody-tr-attr="runnerTrAttr"
data-testid="runner-list"
stacked="md"
fixed
>

View File

@ -12,7 +12,8 @@ export const initRunnerList = (selector = '#js-runner-list') => {
return null;
}
// TODO `activeRunnersCount` should be implemented using a GraphQL API.
// TODO `activeRunnersCount` should be implemented using a GraphQL API
// https://gitlab.com/gitlab-org/gitlab/-/issues/333806
const { activeRunnersCount, registrationToken, runnerInstallHelpPage } = el.dataset;
const apolloProvider = new VueApollo({

View File

@ -116,17 +116,17 @@ export default {
</div>
</div>
<runner-filtered-search-bar v-model="search" namespace="admin_runners" />
<runner-filtered-search-bar
v-model="search"
namespace="admin_runners"
:active-runners-count="activeRunnersCount"
/>
<div v-if="noRunnersFound" class="gl-text-center gl-p-5">
{{ __('No runners found') }}
</div>
<template v-else>
<runner-list
:runners="runners.items"
:loading="runnersLoading"
:active-runners-count="activeRunnersCount"
/>
<runner-list :runners="runners.items" :loading="runnersLoading" />
<runner-pagination v-model="search.pagination" :page-info="runners.pageInfo" />
</template>
</div>

View File

@ -933,13 +933,9 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
}
.frequent-items-list-item-container {
.frequent-items-item-avatar-container,
.frequent-items-item-metadata-container {
flex-shrink: 0;
}
.frequent-items-item-metadata-container {
display: flex;
flex-shrink: 0;
flex-direction: column;
justify-content: center;
}
@ -951,12 +947,6 @@ header.header-content .dropdown-menu.frequent-items-dropdown-menu {
white-space: nowrap;
}
&:hover {
.frequent-items-item-avatar-container .avatar {
border-color: $gray-50;
}
}
.frequent-items-item-title {
font-size: $gl-font-size;
font-weight: 400;

View File

@ -17,6 +17,10 @@ class Projects::JobsController < Projects::ApplicationController
before_action :verify_proxy_request!, only: :proxy_websocket_authorize
before_action :push_jobs_table_vue, only: [:index]
before_action do
push_frontend_feature_flag(:infinitely_collapsible_sections, @project, default_enabled: :yaml)
end
layout 'project'
feature_category :continuous_integration

View File

@ -46,7 +46,7 @@ module RepositoryCheck
true
rescue Gitlab::Git::Repository::GitError => e
Gitlab::RepositoryCheckLogger.error(e.message)
Gitlab::RepositoryCheckLogger.error("#{repository.full_path}: #{e.message}")
false
end

View File

@ -0,0 +1,8 @@
---
name: infinitely_collapsible_sections
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65496
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335297
milestone: '14.1'
type: development
group: group::pipeline execution
default_enabled: false

View File

@ -102,6 +102,7 @@ exceptions:
- NFS
- NGINX
- NOTE
- NPM
- NTP
- ONLY
- OSS

View File

@ -247,6 +247,7 @@ Helm
Heroku
Herokuish
Hexo
HipChat
hostname
hostnames
hotfix
@ -295,6 +296,7 @@ keytab
keytabs
Kibana
Kinesis
Klar
Knative
Kramdown
Kroki

View File

@ -492,7 +492,7 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab
response. They have `rel` set to `prev`, `next`, `first`, or `last` and contain
the relevant URL. Be sure to use these links instead of generating your own URLs.
For GitLab.com users, [some pagination headers may not be returned](../user/gitlab_com/index.md#pagination-response-headers).
For GitLab SaaS users, [some pagination headers may not be returned](../user/gitlab_com/index.md#pagination-response-headers).
In the following cURL example, we limit the output to three items per page
(`per_page=3`) and we request the second page (`page=2`) of [comments](notes.md)
@ -836,7 +836,7 @@ languages. For a complete list, visit the [GitLab website](https://about.gitlab.
For administrator documentation on rate limit settings, see
[Rate limits](../security/rate_limits.md). To find the settings that are
specifically used by GitLab.com, see
[GitLab.com-specific rate limits](../user/gitlab_com/index.md#gitlabcom-specific-rate-limits).
[GitLab SaaS-specific rate limits](../user/gitlab_com/index.md#gitlab-saas-specific-rate-limits).
## Content type

View File

@ -25,9 +25,9 @@ things manually.
As with every job, you need to create a valid `.gitlab-ci.yml` describing the
build environment.
Let's first specify the PHP image that is used for the job process
(you can read more about what an image means in the runner's lingo reading
about [Using Docker images](../docker/using_docker_images.md#what-is-an-image)).
Let's first specify the PHP image that is used for the job process.
(You can read more about what an image means in the runner's lingo reading
about [Using Docker images](../docker/using_docker_images.md#what-is-an-image).)
Start by adding the image to your `.gitlab-ci.yml`:

View File

@ -164,8 +164,15 @@ In most cases, these pages are standalone.
### Tutorials
A tutorial is an end-to-end walkthrough of a complex workflow or scenario.
It might include tasks across a variety of GitLab features, tools, and processes.
It does not cover core conceptual information.
In general, you might consider using a tutorial when:
- The workflow requires a number of sequential steps where each step consists
of sub-steps.
- The steps cover a variety of GitLab features or third-party tools.
Tutorials are learning aids that complement our core documentation.
They do not introduce new features.
Always use the primary [topic types](#documentation-topic-types) to document new features.
Tutorials should be in this format:

View File

@ -230,7 +230,7 @@ in your local development environment.
#### File size limits
Files uploaded to the GitLab Package Registry are [limited by format](../administration/instance_limits.md#package-registry-limits).
On GitLab.com, these are typically set to 5GB to help prevent timeout issues and abuse.
On GitLab SaaS, these are typically set to 5GB to help prevent timeout issues and abuse.
When a new package type is added to the `Packages::Package` model, a size limit must be added
similar to [this example](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52639/diffs#382f879fb09b0212e3cedd99e6c46e2083867216),
@ -238,10 +238,10 @@ or the [related test](https://gitlab.com/gitlab-org/gitlab/-/blob/fe4ba437667813
must be updated if file size limits do not apply. The only reason a size limit does not apply is if
the package format does not upload and store package files.
#### Rate Limits on GitLab.com
#### Rate Limits on GitLab SaaS
Package manager clients can make rapid requests that exceed the
[GitLab.com standard API rate limits](../user/gitlab_com/index.md#gitlabcom-specific-rate-limits).
[GitLab SaaS standard API rate limits](../user/gitlab_com/index.md#gitlab-saas-specific-rate-limits).
This results in a `429 Too Many Requests` error.
We have opened a set of paths to allow higher rate limits. Unless it is not possible,

View File

@ -9,7 +9,7 @@ type: reference, howto
NOTE:
For GitLab.com, please see
[GitLab.com-specific rate limits](../user/gitlab_com/index.md#gitlabcom-specific-rate-limits).
[GitLab SaaS-specific rate limits](../user/gitlab_com/index.md#gitlab-saas-specific-rate-limits).
Rate limiting is a common technique used to improve the security and durability
of a web application.

View File

@ -4,7 +4,7 @@ group: Database
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/#assignments
---
# Batched Background Migrations **(FREE SELF)**
# Batched background migrations **(FREE SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51332) in GitLab 13.11.
> - [Deployed behind a feature flag](../../../user/feature_flags.md), disabled by default.
@ -21,16 +21,19 @@ are created by GitLab developers and run automatically on upgrade. However, such
limited in scope to help with migrating some `integer` database columns to `bigint`. This is needed to
prevent integer overflow for some tables.
All migrations must be finished before upgrading GitLab. To check the status of the existing
migrations, execute this command:
## Check the status of background migrations **(FREE SELF)**
```ruby
Gitlab::Database::BackgroundMigration::BatchedMigration.pluck(:id, :table_name, :status)
```
All migrations must have a `Finished` status before updating GitLab. To check the status of the existing
migrations:
## Enable or disable Batched Background Migrations **(FREE SELF)**
1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Monitoring > Background Migrations**.
Batched Background Migrations is under development but ready for production use.
![queued batched background migrations table](img/batched_background_migrations_queued_v14_0.png)
## Enable or disable batched background migrations **(FREE SELF)**
Batched background migrations are under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to disable it.
@ -63,7 +66,7 @@ To maximize throughput of batched background migrations (in terms of the number
## Enable or disable automatic batch size optimization **(FREE SELF)**
Automatic batch size optimization for Batched Background Migrations is under development but ready for production use.
Automatic batch size optimization for batched background migrations is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to disable it.
@ -79,3 +82,27 @@ To disable it:
```ruby
Feature.disable(:optimize_batched_migrations)
```
## Troubleshooting
### Database migrations failing because of batched background migration not finished
When updating to GitLab 14.2 or later there might be a database migration failing with a message like:
```plaintext
StandardError: An error has occurred, all later migrations canceled:
Expected batched background migration for the given configuration to be marked as 'finished', but it is 'active':
{:job_class_name=>"CopyColumnUsingBackgroundMigrationJob", :table_name=>"push_event_payloads", :column_name=>"event_id", :job_arguments=>[["event_id"], ["event_id_convert_to_bigint"]]}
```
To fix this error:
1. Update to either 14.0.3 or 14.1.
1. [Check the status](#check-the-status-of-background-migrations) of the batched background migration from the error message, and make sure it is listed as finished. If it is still active, either wait until it is done, or finalize it manually using the command suggested in the error, for example:
```shell
sudo gitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,push_event_payloads,event_id,'[["event_id"]\, ["event_id_convert_to_bigint"]]']
```
1. You can now update to GitLab 14.2 or higher.

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View File

@ -20,7 +20,7 @@ In a comment, you can enter [Markdown](../markdown.md) and use [quick actions](.
You can [suggest code changes](../project/merge_requests/reviews/suggestions.md) in your commit diff comment,
which the user can accept through the user interface.
## Where you can create comments
## Places you can add comments
You can create comments in places like:
@ -34,6 +34,141 @@ You can create comments in places like:
Each object can have as many as 5,000 comments.
## Add a comment to a merge request diff
You can add comments to a merge request diff. These comments
persist, even when you:
- Force-push after a rebase.
- Amend a commit.
To add a commit diff comment:
1. To select a specific commit, on the merge request, select the **Commits** tab, select the commit
message. To view the latest commit, select the **Changes** tab.
1. By the line you want to comment on, hover over the line number and select **{comment}**.
You can select multiple lines by dragging the **{comment}** icon.
1. Type your comment and select **Start a review** or **Add comment now**.
The comment is displayed on the merge request's **Discussions** tab.
The comment is not displayed on your project's **Repository > Commits** page.
NOTE:
When your comment contains a reference to a commit included in the merge request,
it's automatically converted to a link in the context of the current merge request.
For example, `28719b171a056960dfdc0012b625d0b47b123196` becomes
`https://gitlab.example.com/example-group/example-project/-/merge_requests/12345/diffs?commit_id=28719b171a056960dfdc0012b625d0b47b123196`.
## Add a comment to a commit
You can add comments and threads to a particular commit.
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Repository > Commits**.
1. Below the commits, in the **Comment** field, enter a comment.
1. Select **Comment** or select the down arrow (**{chevron-down}**) to select **Start thread**.
WARNING:
Threads created this way are lost if the commit ID changes after a
force push.
## Add a comment to an image
In merge requests and commit detail views, you can add a comment to an image.
This comment can also be a thread.
1. Hover your mouse over the image.
1. Select the location where you want to comment.
An icon is displayed on the image and a comment field is displayed.
![Start image thread](img/start_image_discussion.gif)
## Reply to a comment by sending email
If you have ["reply by email"](../../administration/reply_by_email.md) configured,
you can reply to comments by sending an email.
- When you reply to a standard comment, another standard comment is created.
- When you reply to a threaded comment, it creates a reply in the thread.
You can use [Markdown](../markdown.md) and [quick actions](../project/quick_actions.md) in your email replies.
## Who can edit comments
You can edit your own comment at any time.
Anyone with the [Maintainer role](../permissions.md) or
higher can also edit a comment made by someone else.
## Prevent comments by locking an issue
You can prevent public comments in an issue or merge request.
When you do, only project members can add and edit comments.
Prerequisite:
- In merge requests, you must have at least the Developer role.
- In issues, you must have at least the Reporter role.
1. On the right sidebar, next to **Lock issue** or **Lock merge request**, select **Edit**.
1. On the confirmation dialog, select **Lock**.
Notes are added to the page details.
If an issue or merge request is locked and closed, you cannot reopen it.
## Mark a comment as confidential
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207473) in GitLab 13.9.
> - [Deployed behind a feature flag](../feature_flags.md), disabled by default.
> - Disabled on GitLab.com.
> - Not recommended for production use.
> - To use in GitLab self-managed instances, ask a GitLab administrator to enable it. **(FREE SELF)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
You can make a comment confidential, so that it is visible only to project members
who have at least the Reporter role.
1. Below the comment, select the **Make this comment confidential** checkbox.
1. Select **Comment**.
![Confidential comments](img/confidential_comments_v13_9.png)
## Show only comments
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/26723) in GitLab 11.5.
For issues and merge requests with many comments, you can filter the page to show comments only.
1. Open a merge request's **Discussion** tab, or epic or issue's **Overview** tab.
1. On the right side of the page, select from the filter:
- **Show all activity**: Display all user comments and system notes
(issue updates, mentions from other issues, changes to the description, and so on).
- **Show comments only**: Display only user comments.
- **Show history only**: Display only activity notes.
![Notes filters dropdown options](img/index_notes_filters.png)
GitLab saves your preference, so it persists when you visit the same page again
from any device you're logged into.
## Assign an issue to the commenting user
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/191455) in GitLab 13.1.
You can assign an issue to a user who made a comment.
1. In the comment, select the **More Actions** menu.
1. Select **Assign to commenting user**.
![Assign to commenting user](img/quickly_assign_commenter_v13_1.png)
Select the button again to unassign the commenter.
## Create a thread by replying to a standard comment
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/30299) in GitLab 11.9.
@ -78,23 +213,6 @@ A threaded comment is created.
![Thread comment](img/discussion_comment.png)
## Reply to a comment by sending email
If you have ["reply by email"](../../administration/reply_by_email.md) configured,
you can reply to comments by sending an email.
- When you reply to a standard comment, another standard comment is created.
- When you reply to a threaded comment, it creates a reply in the thread.
You can use [Markdown](../markdown.md) and [quick actions](../project/quick_actions.md) in your email replies.
## Who can edit comments
You can edit your own comment at any time.
Anyone with the [Maintainer role](../permissions.md) or
higher can also edit a comment made by someone else.
## Resolve a thread
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/5022) in GitLab 8.11.
@ -172,133 +290,6 @@ with a new push.
Threads are now resolved if a push makes a diff section outdated.
Threads on lines that don't change and top-level resolvable threads are not resolved.
## Commit threads in the context of a merge request
For reviewers with commit-based workflow, it may be useful to add threads to
specific commit diffs in the context of a merge request. These threads
persist through a commit ID change when:
- force-pushing after a rebase
- amending a commit
To create a commit diff thread:
1. Navigate to the merge request **Commits** tab. A list of commits that
constitute the merge request are shown.
![Merge request commits tab](img/merge_request_commits_tab.png)
1. Navigate to a specific commit, select the **Changes** tab (where you
are only be presented diffs from the selected commit), and leave a comment.
![Commit diff discussion in merge request context](img/commit_comment_mr_context.png)
1. Any threads created this way are shown in the merge request's
**Discussions** tab and are resolvable.
![Merge request Discussions tab](img/commit_comment_mr_discussions_tab.png)
Threads created this way only appear in the original merge request
and not when navigating to that commit under your project's
**Repository > Commits** page.
NOTE:
When a link of a commit reference is found in a thread inside a merge
request, it is automatically converted to a link in the context of the
current merge request.
## Add a comment to a commit
You can add comments and threads to a particular commit.
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select **Repository > Commits**.
1. Below the commits, in the **Comment** field, enter a comment.
1. Select **Comment** or select the down arrow (**{chevron-down}**) to select **Start thread**.
WARNING:
Threads created this way are lost if the commit ID changes after a
force push.
## Add a comment to an image
In merge requests and commit detail views, you can add a comment to an image.
This comment can also be a thread.
1. Hover your mouse over the image.
1. Select the location where you want to comment.
An icon is displayed on the image and a comment field is displayed.
![Start image thread](img/start_image_discussion.gif)
## Prevent comments by locking an issue
You can prevent public comments in an issue or merge request.
When you do, only project members can add and edit comments.
Prerequisite:
- In merge requests, you must have at least the Developer role.
- In issues, you must have at least the Reporter role.
1. On the right sidebar, next to **Lock issue** or **Lock merge request**, select **Edit**.
1. On the confirmation dialog, select **Lock**.
Notes are added to the page details.
If an issue or merge request is locked and closed, you cannot reopen it.
## Mark a comment as confidential
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207473) in GitLab 13.9.
> - [Deployed behind a feature flag](../feature_flags.md), disabled by default.
> - Disabled on GitLab.com.
> - Not recommended for production use.
> - To use in GitLab self-managed instances, ask a GitLab administrator to enable it. **(FREE SELF)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
You can make a comment confidential, so that it is visible only to project members
who have at least the Reporter role.
1. Below the comment, select the **Make this comment confidential** checkbox.
1. Select **Comment**.
![Confidential comments](img/confidential_comments_v13_9.png)
## Show only comments
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/26723) in GitLab 11.5.
For issues and merge requests with many comments, you can filter the page to show comments only.
1. Open a merge request's **Discussion** tab, or epic or issue's **Overview** tab.
1. On the right side of the page, select from the filter:
- **Show all activity**: Display all user comments and system notes
(issue updates, mentions from other issues, changes to the description, and so on).
- **Show comments only**: Display only user comments.
- **Show history only**: Display only activity notes.
![Notes filters dropdown options](img/index_notes_filters.png)
GitLab saves your preference, so it persists when you visit the same page again
from any device you're logged into.
## Assign an issue to the commenting user
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/191455) in GitLab 13.1.
You can assign an issue to a user who made a comment.
1. In the comment, select the **More Actions** menu.
1. Select **Assign to commenting user**.
![Assign to commenting user](img/quickly_assign_commenter_v13_1.png)
Select the button again to unassign the commenter.
## Enable or disable confidential comments **(FREE SELF)**
Confidential comments are under development and not ready for production use. The feature is

View File

@ -4,15 +4,15 @@ group: unassigned
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/#assignments
---
# GitLab.com settings **(FREE SAAS)**
# GitLab SaaS settings **(FREE SAAS)**
This page contains information about the settings that are used on
[GitLab.com](https://about.gitlab.com/pricing/).
[GitLab SaaS](https://about.gitlab.com/pricing/).
## SSH host keys fingerprints
Below are the fingerprints for GitLab.com's SSH host keys. The first time you
connect to a GitLab.com repository, one of these keys is displayed in the output.
Below are the fingerprints for GitLab SaaS's SSH host keys. The first time you
connect to a GitLab SaaS repository, one of these keys is displayed in the output.
| Algorithm | MD5 (deprecated) | SHA256 |
|------------------|------------------|---------|
@ -34,14 +34,14 @@ gitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAA
## Mail configuration
GitLab.com sends emails from the `mg.gitlab.com` domain by using [Mailgun](https://www.mailgun.com/),
GitLab SaaS sends emails from the `mg.gitlab.com` domain by using [Mailgun](https://www.mailgun.com/),
and has its own dedicated IP address (`192.237.158.143`).
The IP address for `mg.gitlab.com` is subject to change at any time.
### Service Desk custom mailbox
On GitLab.com there's a mailbox configured for Service Desk with the email adress:
On GitLab SaaS, there's a mailbox configured for Service Desk with the email address:
`contact-project+%{key}@incoming.gitlab.com`. To use this mailbox, configure the
[custom suffix](../project/service_desk.md#configuring-a-custom-email-address-suffix) in project
settings.
@ -50,7 +50,7 @@ settings.
[See our backup strategy](https://about.gitlab.com/handbook/engineering/infrastructure/production/#backups).
To back up an entire project on GitLab.com, you can export it either:
To back up an entire project on GitLab SaaS, you can export it either:
- [Through the UI](../project/settings/import_export.md).
- [Through the API](../../api/project_import_export.md#schedule-an-export). You
@ -69,7 +69,7 @@ are included when cloning.
## Alternative SSH port
GitLab.com can be reached by using a [different SSH port](https://about.gitlab.com/blog/2016/02/18/gitlab-dot-com-now-supports-an-alternate-git-plus-ssh-port/) for `git+ssh`.
GitLab SaaS can be reached by using a [different SSH port](https://about.gitlab.com/blog/2016/02/18/gitlab-dot-com-now-supports-an-alternate-git-plus-ssh-port/) for `git+ssh`.
| Setting | Value |
|------------|---------------------|
@ -91,7 +91,7 @@ Host gitlab.com
Below are the settings for [GitLab Pages](https://about.gitlab.com/stages-devops-lifecycle/pages/).
| Setting | GitLab.com | Default |
| Setting | GitLab SaaS | Default |
|---------------------------|------------------------|------------------------|
| Domain name | `gitlab.io` | - |
| IP address | `35.185.44.232` | - |
@ -108,9 +108,9 @@ Below are the current settings regarding [GitLab CI/CD](../../ci/index.md).
Any settings or feature limits not listed here are using the defaults listed in
the related documentation.
| Setting | GitLab.com | Default |
|-------------------------------------|------------|---------|
| Artifacts maximum size (compressed) | 1 GB | 100 MB |
| Setting | GitLab SaaS | Default |
|-------------------------------------|-------------|---------|
| Artifacts maximum size (compressed) | 1 GB | 100 MB |
| Artifacts [expiry time](../../ci/yaml/index.md#artifactsexpire_in) | From June 22, 2020, deleted after 30 days unless otherwise specified (artifacts created before that date have no expiry). | deleted after 30 days unless otherwise specified |
| Scheduled Pipeline Cron | `*/5 * * * *` | `3-59/10 * * * *` |
| [Max jobs in active pipelines](../../administration/instance_limits.md#number-of-jobs-in-active-pipelines) | `500` for Free tier, unlimited otherwise | Unlimited |
@ -122,17 +122,17 @@ the related documentation.
## Account and limit settings
GitLab.com has the following [account limits](../admin_area/settings/account_and_limit_settings.md)
GitLab SaaS has the following [account limits](../admin_area/settings/account_and_limit_settings.md)
enabled. If a setting is not listed, it is set to the default value.
If you are near or over the repository size limit, you can either
[reduce your repository size with Git](../project/repository/reducing_the_repo_size_using_git.md) or [purchase additional storage](https://about.gitlab.com/pricing/licensing-faq/#can-i-buy-more-storage).
| Setting | GitLab.com | Default |
|-------------------------------|------------|---------|
| Setting | GitLab SaaS | Default |
|-------------------------------|-------------|---------|
| [Repository size including LFS](../admin_area/settings/account_and_limit_settings.md#repository-size-limit) | 10 GB | Unlimited |
| Maximum import size | 5 GB | Unlimited ([Modified](https://gitlab.com/gitlab-org/gitlab/-/issues/251106) from 50MB to unlimited in GitLab 13.8. |
| Maximum attachment size | 10 MB | 10 MB |
| Maximum import size | 5 GB | Unlimited ([Modified](https://gitlab.com/gitlab-org/gitlab/-/issues/251106) from 50MB to unlimited in GitLab 13.8.) |
| Maximum attachment size | 10 MB | 10 MB |
NOTE:
`git push` and GitLab project imports are limited to 5 GB per request through
@ -141,11 +141,11 @@ this limit.
## IP range
GitLab.com uses the IP ranges `34.74.90.64/28` and `34.74.226.0/24` for traffic from its Web/API
GitLab SaaS uses the IP ranges `34.74.90.64/28` and `34.74.226.0/24` for traffic from its Web/API
fleet. This whole range is solely allocated to GitLab. You can expect connections from webhooks or repository mirroring to come
from those IPs and allow them.
GitLab.com is fronted by Cloudflare. For incoming connections to GitLab.com you might need to allow CIDR blocks of Cloudflare ([IPv4](https://www.cloudflare.com/ips-v4) and [IPv6](https://www.cloudflare.com/ips-v6)).
GitLab SaaS is fronted by Cloudflare. For incoming connections to GitLab SaaS, you might need to allow CIDR blocks of Cloudflare ([IPv4](https://www.cloudflare.com/ips-v4) and [IPv6](https://www.cloudflare.com/ips-v6)).
For outgoing connections from CI/CD runners, we are not providing static IP
addresses. All GitLab runners are deployed into Google Cloud Platform (GCP). Any
@ -156,7 +156,7 @@ IP-based firewall can be configured by looking up all
Add these hostnames when you configure allow-lists in local HTTP(S) proxies,
or other web-blocking software that governs end-user computers. Pages on
GitLab.com load content from these hostnames:
GitLab SaaS load content from these hostnames:
- `gitlab.com`
- `*.gitlab.com`
@ -171,32 +171,32 @@ also load certain page content directly from common public CDN hostnames.
The following limits apply for [Webhooks](../project/integrations/webhooks.md):
| Setting | GitLab.com | Default |
|----------------------|------------|---------|
| Setting | GitLab SaaS | Default |
|----------------------|-------------|---------|
| [Webhook rate limit](../../administration/instance_limits.md#webhook-rate-limit) | `120` calls per minute for GitLab Free, unlimited for GitLab Premium and GitLab Ultimate | Unlimited |
| [Number of webhooks](../../administration/instance_limits.md#number-of-webhooks) | `100` per project, `50` per group | `100` per project, `50` per group |
| Maximum payload size | 25 MB | 25 MB |
| Maximum payload size | 25 MB | 25 MB |
## Shared runners
GitLab has shared runners on GitLab.com that you can use to run your CI jobs.
GitLab has shared runners on GitLab SaaS that you can use to run your CI jobs.
For more information, see [choosing a runner](../../ci/runners/index.md).
## Sidekiq
GitLab.com runs [Sidekiq](https://sidekiq.org) with arguments `--timeout=4 --concurrency=4`
GitLab SaaS runs [Sidekiq](https://sidekiq.org) with arguments `--timeout=4 --concurrency=4`
and the following environment variables:
| Setting | GitLab.com | Default |
|----------------------------------------|------------|-----------|
| `SIDEKIQ_DAEMON_MEMORY_KILLER` | - | `1` |
| `SIDEKIQ_MEMORY_KILLER_MAX_RSS` | `2000000` | `2000000` |
| `SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS` | - | - |
| `SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL` | - | `3` |
| `SIDEKIQ_MEMORY_KILLER_GRACE_TIME` | - | `900` |
| `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT` | - | `30` |
| `SIDEKIQ_LOG_ARGUMENTS` | `1` | `1` |
| Setting | GitLab SaaS | Default |
|----------------------------------------|-------------|-----------|
| `SIDEKIQ_DAEMON_MEMORY_KILLER` | - | `1` |
| `SIDEKIQ_MEMORY_KILLER_MAX_RSS` | `2000000` | `2000000` |
| `SIDEKIQ_MEMORY_KILLER_HARD_LIMIT_RSS` | - | - |
| `SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL` | - | `3` |
| `SIDEKIQ_MEMORY_KILLER_GRACE_TIME` | - | `900` |
| `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT` | - | `30` |
| `SIDEKIQ_LOG_ARGUMENTS` | `1` | `1` |
NOTE:
The `SIDEKIQ_MEMORY_KILLER_MAX_RSS` setting is `16000000` on Sidekiq import
@ -204,14 +204,14 @@ nodes and Sidekiq export nodes.
## PostgreSQL
GitLab.com being a fairly large installation of GitLab means we have changed
GitLab SaaS being a fairly large installation of GitLab means we have changed
various PostgreSQL settings to better suit our needs. For example, we use
streaming replication and servers in hot-standby mode to balance queries across
different database servers.
The list of GitLab.com specific settings (and their defaults) is as follows:
The list of GitLab SaaS specific settings (and their defaults) is as follows:
| Setting | GitLab.com | Default |
| Setting | GitLab SaaS | Default |
|:--------------------------------------|:--------------------------------------------------------------------|:--------------------------------------|
| `archive_command` | `/usr/bin/envdir /etc/wal-e.d/env /opt/wal-e/bin/wal-e wal-push %p` | empty |
| `archive_mode` | on | off |
@ -249,9 +249,9 @@ for `shared_buffers` is quite high, and we are
## Puma
GitLab.com uses the default of 60 seconds for [Puma request timeouts](https://docs.gitlab.com/omnibus/settings/puma.html#worker-timeout).
GitLab SaaS uses the default of 60 seconds for [Puma request timeouts](https://docs.gitlab.com/omnibus/settings/puma.html#worker-timeout).
## GitLab.com-specific rate limits
## GitLab SaaS-specific rate limits
NOTE:
See [Rate limits](../../security/rate_limits.md) for administrator
@ -262,7 +262,7 @@ code. The client should wait before attempting the request again. There
are also informational headers with this response detailed in [rate
limiting responses](#rate-limiting-responses).
The following table describes the rate limits for GitLab.com, both before and
The following table describes the rate limits for GitLab SaaS, both before and
after the limits change in January, 2021:
| Rate limit | Before 2021-01-18 | From 2021-01-18 | From 2021-02-12 |
@ -289,7 +289,7 @@ For information on rate limiting responses, see:
### Protected paths throttle
GitLab.com responds with HTTP status code `429` to POST requests at protected
GitLab SaaS responds with HTTP status code `429` to POST requests at protected
paths that exceed 10 requests per **minute** per IP address.
See the source below for which paths are protected. This includes user creation,
@ -302,20 +302,20 @@ See [Protected Paths](../admin_area/settings/protected_paths.md) for more detail
### IP blocks
IP blocks can occur when GitLab.com receives unusual traffic from a single
IP blocks can occur when GitLab SaaS receives unusual traffic from a single
IP address that the system views as potentially malicious. This can be based on
rate limit settings. After the unusual traffic ceases, the IP address is
automatically released depending on the type of block, as described in a
following section.
If you receive a `403 Forbidden` error for all requests to GitLab.com,
If you receive a `403 Forbidden` error for all requests to GitLab SaaS,
check for any automated processes that may be triggering a block. For
assistance, contact [GitLab Support](https://support.gitlab.com/hc/en-us)
with details, such as the affected IP address.
#### Git and container registry failed authentication ban
GitLab.com responds with HTTP status code `403` for 1 hour, if 30 failed
GitLab SaaS responds with HTTP status code `403` for 1 hour, if 30 failed
authentication requests were received in a 3-minute period from a single IP address.
This applies only to Git requests and container registry (`/jwt/auth`) requests
@ -343,7 +343,7 @@ doesn't return the following headers:
If created before GitLab 12.2 (July 2019), these items have the
[Internal visibility](../../public_access/public_access.md#internal-projects)
setting [disabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/12388):
setting [disabled on GitLab SaaS](https://gitlab.com/gitlab-org/gitlab/-/issues/12388):
- Projects
- Groups
@ -351,7 +351,7 @@ setting [disabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/1
### SSH maximum number of connections
GitLab.com defines the maximum number of concurrent, unauthenticated SSH
GitLab SaaS defines the maximum number of concurrent, unauthenticated SSH
connections by using the [MaxStartups setting](http://man.openbsd.org/sshd_config.5#MaxStartups).
If more than the maximum number of allowed connections occur concurrently, they
are dropped and users get
@ -367,9 +367,9 @@ for details.
See [non-configurable limits](../../security/rate_limits.md#non-configurable-limits)
for information on rate limits that are not configurable, and therefore also
used on GitLab.com.
used on GitLab SaaS.
## GitLab.com Logging
## GitLab SaaS logging
We use [Fluentd](https://gitlab.com/gitlab-com/runbooks/tree/master/logging/doc#fluentd)
to parse our logs. Fluentd sends our logs to
@ -384,20 +384,20 @@ You can view more information in our runbooks such as:
- Our [current log retention policies](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#retention)
- A [diagram of our logging infrastructure](https://gitlab.com/gitlab-com/runbooks/-/tree/master/docs/logging#logging-infrastructure-overview)
### Job Logs
### Job logs
By default, GitLab does not expire job logs. Job logs are retained indefinitely,
and can't be configured on GitLab.com to expire. You can erase job logs
and can't be configured on GitLab SaaS to expire. You can erase job logs
[manually with the Jobs API](../../api/jobs.md#erase-a-job) or by
[deleting a pipeline](../../ci/pipelines/index.md#delete-a-pipeline).
## GitLab.com at scale
## GitLab SaaS at scale
In addition to the GitLab Enterprise Edition Omnibus install, GitLab.com uses
In addition to the GitLab Enterprise Edition Omnibus install, GitLab SaaS uses
the following applications and settings to achieve scale. All settings are
publicly available at [chef cookbooks](https://gitlab.com/gitlab-cookbooks).
### Elastic Cluster
### Elastic cluster
We use Elasticsearch and Kibana for part of our monitoring solution:

View File

@ -188,7 +188,7 @@ Use the switches to enable or disable the following features:
| **Issues** | ✓ | Activates the GitLab issues tracker |
| **Repository** | ✓ | Enables [repository](../repository/) functionality |
| **Merge Requests** | ✓ | Enables [merge request](../merge_requests/) functionality; also see [Merge request settings](#merge-request-settings) |
| **Forks** | ✓ | Enables [forking](../working_with_projects.md#fork-a-project) functionality |
| **Forks** | ✓ | Enables [forking](../repository/forking_workflow.md) functionality |
| **Pipelines** | ✓ | Enables [CI/CD](../../../ci/index.md) functionality |
| **Container Registry** | | Activates a [registry](../../packages/container_registry/) for your Docker images |
| **Git Large File Storage** | | Enables the use of [large files](../../../topics/git/lfs/index.md#git-large-file-storage-lfs) |

View File

@ -3,9 +3,10 @@
module Backup
# Backup and restores repositories using gitaly-backup
class GitalyBackup
def initialize(progress, parallel: nil)
def initialize(progress, parallel: nil, parallel_storage: nil)
@progress = progress
@parallel = parallel
@parallel_storage = parallel_storage
end
def start(type)
@ -22,6 +23,7 @@ module Backup
args = []
args += ['-parallel', @parallel.to_s] if type == :create && @parallel
args += ['-parallel-storage', @parallel_storage.to_s] if type == :create && @parallel_storage
@read_io, @write_io = IO.pipe
@pid = Process.spawn(bin_path, command, '-path', backup_repos_path, *args, in: @read_io, out: @progress)

View File

@ -1110,11 +1110,16 @@ module Gitlab
Gitlab::AppLogger.warn "Could not find batched background migration for the given configuration: #{configuration}"
elsif !migration.finished?
raise "Expected batched background migration for the given configuration to be marked as 'finished', " \
"but it is '#{migration.status}': #{configuration}" \
"but it is '#{migration.status}':" \
"\t#{configuration}" \
"\n\n" \
"Finalize it manualy by running" \
"\n\n" \
"\tgitlab-rake gitlab:background_migrations:finalize[#{job_class_name},#{table_name},#{column_name},'#{job_arguments.inspect.gsub(',', '\,')}']"
"\tsudo gitlab-rake gitlab:background_migrations:finalize[#{job_class_name},#{table_name},#{column_name},'#{job_arguments.inspect.gsub(',', '\,')}']" \
"\n\n" \
"For more information, check the documentation" \
"\n\n" \
"\thttps://docs.gitlab.com/ee/user/admin_area/monitoring/background_migrations.html#database-migrations-failing-because-of-batched-background-migration-not-finished"
end
end

View File

@ -299,7 +299,8 @@ namespace :gitlab do
def repository_backup_strategy
if Feature.enabled?(:gitaly_backup)
max_concurrency = ENV['GITLAB_BACKUP_MAX_CONCURRENCY'].presence
Backup::GitalyBackup.new(progress, parallel: max_concurrency)
max_storage_concurrency = ENV['GITLAB_BACKUP_MAX_STORAGE_CONCURRENCY'].presence
Backup::GitalyBackup.new(progress, parallel: max_concurrency, parallel_storage: max_storage_concurrency)
else
Backup::GitalyRpcBackup.new(progress)
end

View File

@ -4,8 +4,6 @@ require 'spec_helper'
RSpec.describe "Admin Runners" do
include StubENV
include FilteredSearchHelpers
include SortingHelper
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
@ -14,31 +12,68 @@ RSpec.describe "Admin Runners" do
gitlab_enable_admin_mode_sign_in(admin)
end
describe "Runners page" do
let(:pipeline) { create(:ci_pipeline) }
before do
stub_feature_flags(runner_list_view_vue_ui: false)
end
describe "Runners page", :js do
let_it_be(:user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:namespace) { create(:namespace) }
let_it_be(:project) { create(:project, namespace: namespace, creator: user) }
context "when there are runners" do
it 'has all necessary texts' do
runner = create(:ci_runner, contacted_at: Time.now)
create(:ci_build, pipeline: pipeline, runner_id: runner.id)
create(:ci_runner, :instance, contacted_at: Time.now)
visit admin_runners_path
expect(page).to have_text "Set up a shared runner manually"
expect(page).to have_text "Runners currently online: 1"
end
describe 'search', :js do
it 'with an instance runner shows an instance badge and no project count' do
runner = create(:ci_runner, :instance)
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'shared'
expect(page).to have_text 'n/a'
end
end
it 'with a group runner shows a group badge and no project count' do
runner = create(:ci_runner, :group, groups: [group])
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'group'
expect(page).to have_text 'n/a'
end
end
it 'with a project runner shows a project badge and project count' do
runner = create(:ci_runner, :project, projects: [project])
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'specific'
expect(page).to have_text '1'
end
end
describe 'search' do
before do
create(:ci_runner, description: 'runner-foo')
create(:ci_runner, description: 'runner-bar')
create(:ci_runner, :instance, description: 'runner-foo')
create(:ci_runner, :instance, description: 'runner-bar')
visit admin_runners_path
end
it 'shows runners' do
expect(page).to have_content("runner-foo")
expect(page).to have_content("runner-bar")
end
it 'shows correct runner when description matches' do
input_filtered_search_keys('runner-foo')
@ -53,28 +88,29 @@ RSpec.describe "Admin Runners" do
end
end
describe 'filter by status', :js do
describe 'filter by status' do
it 'shows correct runner when status matches' do
create(:ci_runner, description: 'runner-active', active: true)
create(:ci_runner, description: 'runner-paused', active: false)
create(:ci_runner, :instance, description: 'runner-active', active: true)
create(:ci_runner, :instance, description: 'runner-paused', active: false)
visit admin_runners_path
expect(page).to have_content 'runner-active'
expect(page).to have_content 'runner-paused'
input_filtered_search_keys('status:=active')
input_filtered_search_filter_is_only('Status', 'Active')
expect(page).to have_content 'runner-active'
expect(page).not_to have_content 'runner-paused'
end
it 'shows no runner when status does not match' do
create(:ci_runner, :online, description: 'runner-active', active: true)
create(:ci_runner, :online, description: 'runner-paused', active: false)
create(:ci_runner, :instance, description: 'runner-active', active: true)
create(:ci_runner, :instance, description: 'runner-paused', active: false)
visit admin_runners_path
input_filtered_search_keys('status:=offline')
input_filtered_search_filter_is_only('Status', 'Online')
expect(page).not_to have_content 'runner-active'
expect(page).not_to have_content 'runner-paused'
@ -83,46 +119,48 @@ RSpec.describe "Admin Runners" do
end
it 'shows correct runner when status is selected and search term is entered' do
create(:ci_runner, description: 'runner-a-1', active: true)
create(:ci_runner, description: 'runner-a-2', active: false)
create(:ci_runner, description: 'runner-b-1', active: true)
create(:ci_runner, :instance, description: 'runner-a-1', active: true)
create(:ci_runner, :instance, description: 'runner-a-2', active: false)
create(:ci_runner, :instance, description: 'runner-b-1', active: true)
visit admin_runners_path
input_filtered_search_keys('status:=active')
input_filtered_search_filter_is_only('Status', 'Active')
expect(page).to have_content 'runner-a-1'
expect(page).to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
input_filtered_search_keys('status:=active runner-a')
input_filtered_search_keys('runner-a')
expect(page).to have_content 'runner-a-1'
expect(page).not_to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
end
end
describe 'filter by type', :js do
it 'shows correct runner when type matches' do
create :ci_runner, :project, description: 'runner-project'
create :ci_runner, :group, description: 'runner-group'
describe 'filter by type' do
before do
create(:ci_runner, :project, description: 'runner-project', projects: [project])
create(:ci_runner, :group, description: 'runner-group', groups: [group])
end
it 'shows correct runner when type matches' do
visit admin_runners_path
expect(page).to have_content 'runner-project'
expect(page).to have_content 'runner-group'
input_filtered_search_keys('type:=project_type')
input_filtered_search_filter_is_only('Type', 'project')
expect(page).to have_content 'runner-project'
expect(page).not_to have_content 'runner-group'
end
it 'shows no runner when type does not match' do
create :ci_runner, :project, description: 'runner-project'
create :ci_runner, :group, description: 'runner-group'
visit admin_runners_path
input_filtered_search_keys('type:=instance_type')
input_filtered_search_filter_is_only('Type', 'instance')
expect(page).not_to have_content 'runner-project'
expect(page).not_to have_content 'runner-group'
@ -131,95 +169,93 @@ RSpec.describe "Admin Runners" do
end
it 'shows correct runner when type is selected and search term is entered' do
create :ci_runner, :project, description: 'runner-a-1'
create :ci_runner, :instance, description: 'runner-a-2'
create :ci_runner, :project, description: 'runner-b-1'
create(:ci_runner, :project, description: 'runner-2-project', projects: [project])
visit admin_runners_path
input_filtered_search_keys('type:=project_type')
expect(page).to have_content 'runner-a-1'
expect(page).to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
input_filtered_search_filter_is_only('Type', 'project')
input_filtered_search_keys('type:=project_type runner-a')
expect(page).to have_content 'runner-a-1'
expect(page).not_to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
expect(page).to have_content 'runner-project'
expect(page).to have_content 'runner-2-project'
expect(page).not_to have_content 'runner-group'
input_filtered_search_keys('runner-project')
expect(page).to have_content 'runner-project'
expect(page).not_to have_content 'runner-2-project'
expect(page).not_to have_content 'runner-group'
end
end
describe 'filter by tag', :js do
it 'shows correct runner when tag matches' do
create :ci_runner, description: 'runner-blue', tag_list: ['blue']
create :ci_runner, description: 'runner-red', tag_list: ['red']
describe 'filter by tag' do
before do
create(:ci_runner, :instance, description: 'runner-blue', tag_list: ['blue'])
create(:ci_runner, :instance, description: 'runner-red', tag_list: ['red'])
end
it 'shows correct runner when tag matches' do
visit admin_runners_path
expect(page).to have_content 'runner-blue'
expect(page).to have_content 'runner-red'
input_filtered_search_keys('tag:=blue')
input_filtered_search_filter_is_only('Tags', 'blue')
expect(page).to have_content 'runner-blue'
expect(page).not_to have_content 'runner-red'
end
it 'shows no runner when tag does not match' do
create :ci_runner, description: 'runner-blue', tag_list: ['blue']
create :ci_runner, description: 'runner-red', tag_list: ['blue']
visit admin_runners_path
input_filtered_search_keys('tag:=red')
input_filtered_search_filter_is_only('Tags', 'green')
expect(page).not_to have_content 'runner-blue'
expect(page).not_to have_content 'runner-blue'
expect(page).to have_text 'No runners found'
end
it 'shows correct runner when tag is selected and search term is entered' do
create :ci_runner, description: 'runner-a-1', tag_list: ['blue']
create :ci_runner, description: 'runner-a-2', tag_list: ['red']
create :ci_runner, description: 'runner-b-1', tag_list: ['blue']
create(:ci_runner, :instance, description: 'runner-2-blue', tag_list: ['blue'])
visit admin_runners_path
input_filtered_search_keys('tag:=blue')
input_filtered_search_filter_is_only('Tags', 'blue')
expect(page).to have_content 'runner-a-1'
expect(page).to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
expect(page).to have_content 'runner-blue'
expect(page).to have_content 'runner-2-blue'
expect(page).not_to have_content 'runner-red'
input_filtered_search_keys('tag:=blue runner-a')
input_filtered_search_keys('runner-2-blue')
expect(page).to have_content 'runner-a-1'
expect(page).not_to have_content 'runner-b-1'
expect(page).not_to have_content 'runner-a-2'
expect(page).to have_content 'runner-2-blue'
expect(page).not_to have_content 'runner-blue'
expect(page).not_to have_content 'runner-red'
end
end
it 'sorts by last contact date', :js do
create(:ci_runner, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37')
create(:ci_runner, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37')
it 'sorts by last contact date' do
create(:ci_runner, :instance, description: 'runner-1', created_at: '2018-07-12 15:37', contacted_at: '2018-07-12 15:37')
create(:ci_runner, :instance, description: 'runner-2', created_at: '2018-07-12 16:37', contacted_at: '2018-07-12 16:37')
visit admin_runners_path
within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(2)' do
within '[data-testid="runner-list"] tbody tr:nth-child(1)' do
expect(page).to have_content 'runner-2'
end
within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(3)' do
within '[data-testid="runner-list"] tbody tr:nth-child(2)' do
expect(page).to have_content 'runner-1'
end
sorting_by 'Last Contact'
click_on 'Created date' # Open "sort by" dropdown
click_on 'Last contact'
click_on 'Sort direction: Descending'
within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(2)' do
within '[data-testid="runner-list"] tbody tr:nth-child(1)' do
expect(page).to have_content 'runner-1'
end
within '[data-testid="runners-table"] .gl-responsive-table-row:nth-child(3)' do
within '[data-testid="runner-list"] tbody tr:nth-child(2)' do
expect(page).to have_content 'runner-2'
end
end
@ -237,47 +273,6 @@ RSpec.describe "Admin Runners" do
end
end
context 'group runner' do
let(:group) { create(:group) }
let!(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'shows the label and does not show the project count' do
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'group'
expect(page).to have_text 'n/a'
end
end
end
context 'shared runner' do
it 'shows the label and does not show the project count' do
runner = create(:ci_runner, :instance)
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'shared'
expect(page).to have_text 'n/a'
end
end
end
context 'specific runner' do
it 'shows the label and the project count' do
project = create(:project)
runner = create(:ci_runner, :project, projects: [project])
visit admin_runners_path
within "[data-testid='runner-row-#{runner.id}']" do
expect(page).to have_selector '.badge', text: 'specific'
expect(page).to have_text '1'
end
end
end
describe 'runners registration token' do
let!(:token) { Gitlab::CurrentSettings.runners_registration_token }
@ -286,17 +281,23 @@ RSpec.describe "Admin Runners" do
end
it 'has a registration token' do
expect(page.find('[data-testid="registration_token"]')).to have_content(token)
click_on 'Click to reveal'
expect(page.find('[data-testid="registration-token"]')).to have_content(token)
end
describe 'reset registration token' do
let(:page_token) { find('[data-testid="registration_token"]').text }
let(:page_token) { find('[data-testid="registration-token"]').text }
before do
click_button 'Reset registration token'
page.accept_alert
wait_for_requests
end
it 'changes registration token' do
click_on 'Click to reveal'
expect(page_token).not_to eq token
end
end
@ -409,4 +410,43 @@ RSpec.describe "Admin Runners" do
end
end
end
private
def search_bar_selector
'[data-testid="runners-filtered-search"]'
end
# The filters must be clicked first to be able to receive events
# See: https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1493
def focus_filtered_search
page.within(search_bar_selector) do
page.find('.gl-filtered-search-term-token').click
end
end
def input_filtered_search_keys(search_term)
focus_filtered_search
page.within(search_bar_selector) do
page.find('input').send_keys(search_term)
click_on 'Search'
end
end
def input_filtered_search_filter_is_only(filter, value)
focus_filtered_search
page.within(search_bar_selector) do
click_on filter
# For OPERATOR_IS_ONLY, clicking the filter
# immediately preselects "=" operator
page.find('input').send_keys(value)
page.find('input').send_keys(:enter)
click_on 'Search'
end
end
end

View File

@ -5,6 +5,7 @@ import { trimText } from 'helpers/text_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import frequentItemsListItemComponent from '~/frequent_items/components/frequent_items_list_item.vue';
import { createStore } from '~/frequent_items/store';
import ProjectAvatar from '~/vue_shared/components/project_avatar.vue';
import { mockProject } from '../mock_data';
const localVue = createLocalVue();
@ -16,12 +17,12 @@ describe('FrequentItemsListItemComponent', () => {
let store;
const findTitle = () => wrapper.find({ ref: 'frequentItemsItemTitle' });
const findAvatar = () => wrapper.find({ ref: 'frequentItemsItemAvatar' });
const findAvatar = () => wrapper.findComponent(ProjectAvatar);
const findAllTitles = () => wrapper.findAll({ ref: 'frequentItemsItemTitle' });
const findNamespace = () => wrapper.find({ ref: 'frequentItemsItemNamespace' });
const findAllButtons = () => wrapper.findAllComponents(GlButton);
const findAllNamespace = () => wrapper.findAll({ ref: 'frequentItemsItemNamespace' });
const findAvatarContainer = () => wrapper.findAll({ ref: 'frequentItemsItemAvatarContainer' });
const findAllAvatars = () => wrapper.findAllComponents(ProjectAvatar);
const findAllMetadataContainers = () =>
wrapper.findAll({ ref: 'frequentItemsItemMetadataContainer' });
@ -92,16 +93,8 @@ describe('FrequentItemsListItemComponent', () => {
createComponent();
});
it('should render avatar if avatarUrl is present', () => {
wrapper.setProps({ avatarUrl: 'path/to/avatar.png' });
return wrapper.vm.$nextTick(() => {
expect(findAvatar().exists()).toBe(true);
});
});
it('should not render avatar if avatarUrl is not present', () => {
expect(findAvatar().exists()).toBe(false);
it('renders avatar', () => {
expect(findAvatar().exists()).toBe(true);
});
it('renders root element with the right classes', () => {
@ -111,7 +104,7 @@ describe('FrequentItemsListItemComponent', () => {
it.each`
name | selector | expected
${'button'} | ${findAllButtons} | ${1}
${'avatar container'} | ${findAvatarContainer} | ${1}
${'avatar container'} | ${findAllAvatars} | ${1}
${'metadata container'} | ${findAllMetadataContainers} | ${1}
${'title'} | ${findAllTitles} | ${1}
${'namespace'} | ${findAllNamespace} | ${1}

View File

@ -24,6 +24,7 @@ describe('Job App', () => {
let store;
let wrapper;
let mock;
let origGon;
const initSettings = {
endpoint: `${TEST_HOST}jobs/123.json`,
@ -85,11 +86,17 @@ describe('Job App', () => {
beforeEach(() => {
mock = new MockAdapter(axios);
store = createStore();
origGon = window.gon;
window.gon = { features: { infinitelyCollapsibleSections: false } }; // NOTE: All of this passes with the feature flag
});
afterEach(() => {
wrapper.destroy();
mock.restore();
window.gon = origGon;
});
describe('while loading', () => {

View File

@ -4,6 +4,7 @@ import { collapsibleSectionClosed, collapsibleSectionOpened } from './mock_data'
describe('Job Log Collapsible Section', () => {
let wrapper;
let origGon;
const traceEndpoint = 'jobs/335';
@ -18,8 +19,16 @@ describe('Job Log Collapsible Section', () => {
});
};
beforeEach(() => {
origGon = window.gon;
window.gon = { features: { infinitelyCollapsibleSections: false } }; // NOTE: This also works with true
});
afterEach(() => {
wrapper.destroy();
window.gon = origGon;
});
describe('with closed section', () => {

View File

@ -1,7 +1,7 @@
import { mount, createLocalVue } from '@vue/test-utils';
import Vuex from 'vuex';
import Log from '~/jobs/components/log/log.vue';
import { logLinesParser } from '~/jobs/store/utils';
import { logLinesParserLegacy, logLinesParser } from '~/jobs/store/utils';
import { jobLog } from './mock_data';
describe('Job Log', () => {
@ -9,6 +9,7 @@ describe('Job Log', () => {
let actions;
let state;
let store;
let origGon;
const localVue = createLocalVue();
localVue.use(Vuex);
@ -25,8 +26,12 @@ describe('Job Log', () => {
toggleCollapsibleLine: () => {},
};
origGon = window.gon;
window.gon = { features: { infinitelyCollapsibleSections: false } };
state = {
trace: logLinesParser(jobLog),
trace: logLinesParserLegacy(jobLog),
traceEndpoint: 'jobs/id',
};
@ -40,6 +45,88 @@ describe('Job Log', () => {
afterEach(() => {
wrapper.destroy();
window.gon = origGon;
});
const findCollapsibleLine = () => wrapper.find('.collapsible-line');
describe('line numbers', () => {
it('renders a line number for each open line', () => {
expect(wrapper.find('#L1').text()).toBe('1');
expect(wrapper.find('#L2').text()).toBe('2');
expect(wrapper.find('#L3').text()).toBe('3');
});
it('links to the provided path and correct line number', () => {
expect(wrapper.find('#L1').attributes('href')).toBe(`${state.traceEndpoint}#L1`);
});
});
describe('collapsible sections', () => {
it('renders a clickable header section', () => {
expect(findCollapsibleLine().attributes('role')).toBe('button');
});
it('renders an icon with the open state', () => {
expect(findCollapsibleLine().find('[data-testid="angle-down-icon"]').exists()).toBe(true);
});
describe('on click header section', () => {
it('calls toggleCollapsibleLine', () => {
jest.spyOn(wrapper.vm, 'toggleCollapsibleLine');
findCollapsibleLine().trigger('click');
expect(wrapper.vm.toggleCollapsibleLine).toHaveBeenCalled();
});
});
});
});
describe('Job Log, infinitelyCollapsibleSections feature flag enabled', () => {
let wrapper;
let actions;
let state;
let store;
let origGon;
const localVue = createLocalVue();
localVue.use(Vuex);
const createComponent = () => {
wrapper = mount(Log, {
localVue,
store,
});
};
beforeEach(() => {
actions = {
toggleCollapsibleLine: () => {},
};
origGon = window.gon;
window.gon = { features: { infinitelyCollapsibleSections: true } };
state = {
trace: logLinesParser(jobLog).parsedLines,
traceEndpoint: 'jobs/id',
};
store = new Vuex.Store({
actions,
state,
});
createComponent();
});
afterEach(() => {
wrapper.destroy();
window.gon = origGon;
});
const findCollapsibleLine = () => wrapper.find('.collapsible-line');

View File

@ -58,6 +58,71 @@ export const utilsMockData = [
},
];
export const multipleCollapsibleSectionsMockData = [
{
offset: 1001,
content: [{ text: ' on docker-auto-scale-com 8a6210b8' }],
},
{
offset: 1002,
content: [
{
text: 'Executing "step_script" stage of the job script',
},
],
section: 'step-script',
section_header: true,
},
{
offset: 1003,
content: [{ text: 'sleep 60' }],
section: 'step-script',
},
{
offset: 1004,
content: [
{
text:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lorem dolor, congue ac condimentum vitae',
},
],
section: 'step-script',
},
{
offset: 1005,
content: [{ text: 'executing...' }],
section: 'step-script',
},
{
offset: 1006,
content: [{ text: '1st collapsible section' }],
section: 'collapsible-1',
section_header: true,
},
{
offset: 1007,
content: [
{
text:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lorem dolor, congue ac condimentum vitae',
},
],
section: 'collapsible-1',
},
{
offset: 1008,
content: [],
section: 'collapsible-1',
section_duration: '01:00',
},
{
offset: 1009,
content: [],
section: 'step-script',
section_duration: '10:00',
},
];
export const originalTrace = [
{
offset: 1,

View File

@ -4,12 +4,21 @@ import state from '~/jobs/store/state';
describe('Jobs Store Mutations', () => {
let stateCopy;
let origGon;
const html =
'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png<br>I';
beforeEach(() => {
stateCopy = state();
origGon = window.gon;
window.gon = { features: { infinitelyCollapsibleSections: false } };
});
afterEach(() => {
window.gon = origGon;
});
describe('SET_JOB_ENDPOINT', () => {
@ -267,3 +276,88 @@ describe('Jobs Store Mutations', () => {
});
});
});
describe('Job Store mutations, feature flag ON', () => {
let stateCopy;
let origGon;
const html =
'I, [2018-08-17T22:57:45.707325 #1841] INFO -- : Writing /builds/ab89e95b0fa0b9272ea0c797b76908f24d36992630e9325273a4ce3.png<br>I';
beforeEach(() => {
stateCopy = state();
origGon = window.gon;
window.gon = { features: { infinitelyCollapsibleSections: true } };
});
afterEach(() => {
window.gon = origGon;
});
describe('RECEIVE_TRACE_SUCCESS', () => {
describe('with new job log', () => {
describe('log.lines', () => {
describe('when append is true', () => {
it('sets the parsed log ', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: true,
size: 511846,
complete: true,
lines: [
{
offset: 1,
content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
},
],
});
expect(stateCopy.trace).toEqual([
{
offset: 1,
content: [{ text: 'Running with gitlab-runner 11.12.1 (5a147c92)' }],
lineNumber: 1,
},
]);
});
});
describe('when lines are defined', () => {
it('sets the parsed log ', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: false,
size: 511846,
complete: true,
lines: [
{ offset: 0, content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }] },
],
});
expect(stateCopy.trace).toEqual([
{
offset: 0,
content: [{ text: 'Running with gitlab-runner 11.11.1 (5a147c92)' }],
lineNumber: 1,
},
]);
});
});
describe('when lines are null', () => {
it('sets the default value', () => {
mutations[types.RECEIVE_TRACE_SUCCESS](stateCopy, {
append: true,
html,
size: 511846,
complete: false,
lines: null,
});
expect(stateCopy.trace).toEqual([]);
});
});
});
});
});
});

View File

@ -1,5 +1,6 @@
import {
logLinesParser,
logLinesParserLegacy,
updateIncrementalTrace,
parseHeaderLine,
parseLine,
@ -17,6 +18,7 @@ import {
headerTraceIncremental,
collapsibleTrace,
collapsibleTraceIncremental,
multipleCollapsibleSectionsMockData,
} from '../components/log/mock_data';
describe('Jobs Store Utils', () => {
@ -175,11 +177,11 @@ describe('Jobs Store Utils', () => {
expect(isCollapsibleSection()).toEqual(false);
});
});
describe('logLinesParser', () => {
describe('logLinesParserLegacy', () => {
let result;
beforeEach(() => {
result = logLinesParser(utilsMockData);
result = logLinesParserLegacy(utilsMockData);
});
describe('regular line', () => {
@ -216,6 +218,87 @@ describe('Jobs Store Utils', () => {
});
});
describe('logLinesParser', () => {
let result;
beforeEach(() => {
result = logLinesParser(utilsMockData);
});
describe('regular line', () => {
it('adds a lineNumber property with correct index', () => {
expect(result.parsedLines[0].lineNumber).toEqual(1);
expect(result.parsedLines[1].line.lineNumber).toEqual(2);
});
});
describe('collapsible section', () => {
it('adds a `isClosed` property', () => {
expect(result.parsedLines[1].isClosed).toEqual(false);
});
it('adds a `isHeader` property', () => {
expect(result.parsedLines[1].isHeader).toEqual(true);
});
it('creates a lines array property with the content of the collapsible section', () => {
expect(result.parsedLines[1].lines.length).toEqual(2);
expect(result.parsedLines[1].lines[0].content).toEqual(utilsMockData[2].content);
expect(result.parsedLines[1].lines[1].content).toEqual(utilsMockData[3].content);
});
});
describe('section duration', () => {
it('adds the section information to the header section', () => {
expect(result.parsedLines[1].line.section_duration).toEqual(
utilsMockData[4].section_duration,
);
});
it('does not add section duration as a line', () => {
expect(result.parsedLines[1].lines.includes(utilsMockData[4])).toEqual(false);
});
});
describe('multiple collapsible sections', () => {
beforeEach(() => {
result = logLinesParser(multipleCollapsibleSectionsMockData);
});
it('should contain a section inside another section', () => {
const innerSection = [
{
isClosed: false,
isHeader: true,
line: {
content: [{ text: '1st collapsible section' }],
lineNumber: 6,
offset: 1006,
section: 'collapsible-1',
section_duration: '01:00',
section_header: true,
},
lines: [
{
content: [
{
text:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam lorem dolor, congue ac condimentum vitae',
},
],
lineNumber: 7,
offset: 1007,
section: 'collapsible-1',
},
],
},
];
expect(result.parsedLines[1].lines).toEqual(expect.arrayContaining(innerSection));
});
});
});
describe('findOffsetAndRemove', () => {
describe('when last item is header', () => {
const existingLog = [
@ -391,7 +474,7 @@ describe('Jobs Store Utils', () => {
describe('updateIncrementalTrace', () => {
describe('without repeated section', () => {
it('concats and parses both arrays', () => {
const oldLog = logLinesParser(originalTrace);
const oldLog = logLinesParserLegacy(originalTrace);
const result = updateIncrementalTrace(regularIncremental, oldLog);
expect(result).toEqual([
@ -419,7 +502,7 @@ describe('Jobs Store Utils', () => {
describe('with regular line repeated offset', () => {
it('updates the last line and formats with the incremental part', () => {
const oldLog = logLinesParser(originalTrace);
const oldLog = logLinesParserLegacy(originalTrace);
const result = updateIncrementalTrace(regularIncrementalRepeated, oldLog);
expect(result).toEqual([
@ -438,7 +521,7 @@ describe('Jobs Store Utils', () => {
describe('with header line repeated', () => {
it('updates the header line and formats with the incremental part', () => {
const oldLog = logLinesParser(headerTrace);
const oldLog = logLinesParserLegacy(headerTrace);
const result = updateIncrementalTrace(headerTraceIncremental, oldLog);
expect(result).toEqual([
@ -464,7 +547,7 @@ describe('Jobs Store Utils', () => {
describe('with collapsible line repeated', () => {
it('updates the collapsible line and formats with the incremental part', () => {
const oldLog = logLinesParser(collapsibleTrace);
const oldLog = logLinesParserLegacy(collapsibleTrace);
const result = updateIncrementalTrace(collapsibleTraceIncremental, oldLog);
expect(result).toEqual([

View File

@ -13,6 +13,7 @@ describe('RunnerList', () => {
const findFilteredSearch = () => wrapper.findComponent(FilteredSearch);
const findGlFilteredSearch = () => wrapper.findComponent(GlFilteredSearch);
const findSortOptions = () => wrapper.findAllComponents(GlDropdownItem);
const findActiveRunnersMessage = () => wrapper.findByTestId('active-runners-message');
const mockDefaultSort = 'CREATED_DESC';
const mockOtherSort = 'CONTACTED_DESC';
@ -20,6 +21,7 @@ describe('RunnerList', () => {
{ type: PARAM_KEY_STATUS, value: { data: 'ACTIVE', operator: '=' } },
{ type: 'filtered-search-term', value: { data: '' } },
];
const mockActiveRunnersCount = 2;
const createComponent = ({ props = {}, options = {} } = {}) => {
wrapper = extendedWrapper(
@ -30,6 +32,7 @@ describe('RunnerList', () => {
filters: [],
sort: mockDefaultSort,
},
activeRunnersCount: mockActiveRunnersCount,
...props,
},
stubs: {
@ -55,6 +58,18 @@ describe('RunnerList', () => {
expect(findFilteredSearch().props('namespace')).toBe('runners');
});
it('Displays an active runner count', () => {
expect(findActiveRunnersMessage().text()).toBe(
`Runners currently online: ${mockActiveRunnersCount}`,
);
});
it('Displays a large active runner count', () => {
createComponent({ props: { activeRunnersCount: 2000 } });
expect(findActiveRunnersMessage().text()).toBe('Runners currently online: 2,000');
});
it('sets sorting options', () => {
const SORT_OPTIONS_COUNT = 2;

View File

@ -12,7 +12,6 @@ const mockActiveRunnersCount = mockRunners.length;
describe('RunnerList', () => {
let wrapper;
const findActiveRunnersMessage = () => wrapper.findByTestId('active-runners-message');
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findTable = () => wrapper.findComponent(GlTable);
const findHeaders = () => wrapper.findAll('th');
@ -40,18 +39,6 @@ describe('RunnerList', () => {
wrapper.destroy();
});
it('Displays active runner count', () => {
expect(findActiveRunnersMessage().text()).toBe(
`Runners currently online: ${mockActiveRunnersCount}`,
);
});
it('Displays a large active runner count', () => {
createComponent({ props: { activeRunnersCount: 2000 } });
expect(findActiveRunnersMessage().text()).toBe('Runners currently online: 2,000');
});
it('Displays headers', () => {
const headerLabels = findHeaders().wrappers.map((w) => w.text());

View File

@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Backup::GitalyBackup do
let(:parallel) { nil }
let(:parallel_storage) { nil }
let(:progress) do
Tempfile.new('progress').tap do |progress|
progress.unlink
@ -14,7 +15,7 @@ RSpec.describe Backup::GitalyBackup do
progress.close
end
subject { described_class.new(progress, parallel: parallel) }
subject { described_class.new(progress, parallel: parallel, parallel_storage: parallel_storage) }
context 'unknown' do
it 'fails to start unknown' do
@ -59,6 +60,17 @@ RSpec.describe Backup::GitalyBackup do
end
end
context 'parallel_storage option set' do
let(:parallel_storage) { 3 }
it 'passes parallel option through' do
expect(Process).to receive(:spawn).with(anything, 'create', '-path', anything, '-parallel-storage', '3', { in: anything, out: progress }).and_call_original
subject.start(:create)
subject.wait
end
end
it 'raises when the exit code not zero' do
expect(subject).to receive(:bin_path).and_return(Gitlab::Utils.which('false'))

View File

@ -2063,11 +2063,16 @@ RSpec.describe Gitlab::Database::MigrationHelpers do
create(:batched_background_migration, configuration.merge(status: :active))
expect { ensure_batched_background_migration_is_finished }
.to raise_error "Expected batched background migration for the given configuration to be marked as 'finished', but it is 'active': #{configuration}" \
.to raise_error "Expected batched background migration for the given configuration to be marked as 'finished', but it is 'active':" \
"\t#{configuration}" \
"\n\n" \
"Finalize it manualy by running" \
"\n\n" \
"\tgitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,events,id,'[[\"id\"]\\, [\"id_convert_to_bigint\"]]']"
"\tsudo gitlab-rake gitlab:background_migrations:finalize[CopyColumnUsingBackgroundMigrationJob,events,id,'[[\"id\"]\\, [\"id_convert_to_bigint\"]]']" \
"\n\n" \
"For more information, check the documentation" \
"\n\n" \
"\thttps://docs.gitlab.com/ee/user/admin_area/monitoring/background_migrations.html#database-migrations-failing-because-of-batched-background-migration-not-finished"
end
it 'does not raise error when migration exists and is marked as finished' do

View File

@ -407,7 +407,7 @@ RSpec.describe 'gitlab:app namespace rake task', :delete do
.with(max_concurrency: 5, max_storage_concurrency: 2)
.and_call_original
end
expect(::Backup::GitalyBackup).to receive(:new).with(anything, parallel: 5).and_call_original
expect(::Backup::GitalyBackup).to receive(:new).with(anything, parallel: 5, parallel_storage: 2).and_call_original
expect { run_rake_task('gitlab:backup:create') }.to output.to_stdout_from_any_process
end