Commit d6b2313f authored by Imre Farkas's avatar Imre Farkas

Merge branch 'cat-readd-logging-for-ints-273663' into 'master'

Fix logging handling for API integer params

See merge request gitlab-org/gitlab!46551
parents 3c433e59 23515563
......@@ -16,7 +16,7 @@ inherit_mode:
- Include
AllCops:
TargetRubyVersion: 2.6
TargetRubyVersion: 2.7
TargetRailsVersion: 6.0
Exclude:
- 'vendor/**/*'
......
5783f980c3c83022dd5a0173186fba4158948062
a2aab16ef40f2c1a30e457b82b0da7bc9633db22
import Vue from 'vue';
import DevopsAdoptionApp from './components/devops_adoption_app.vue';
export default () => {
const el = document.querySelector('.js-devops-adoption');
if (!el) return false;
const { emptyStateSvgPath } = el.dataset;
return new Vue({
el,
provide: {
emptyStateSvgPath,
},
render(h) {
return h(DevopsAdoptionApp);
},
});
};
// EE-specific feature. Find the implementation in the `ee/`-folder
export default () => {};
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
export default {
components: {
GlLink,
GlSprintf,
},
props: {
message: {
type: String,
required: true,
},
link: {
type: String,
required: true,
},
},
};
</script>
<template>
<span class="gl-text-gray-500">
<gl-sprintf :message="message">
<template #link="{ content }">
<gl-link class="gl-display-inline-block" :href="link" target="_blank">{{
content
}}</gl-link>
</template>
</gl-sprintf>
</span>
</template>
......@@ -81,7 +81,6 @@ export default {
<div class="incident-management-list">
<h5 class="gl-font-lg">{{ $options.i18n.title }}</h5>
<gl-table
:empty-text="$options.i18n.emptyState"
:items="integrations"
:fields="$options.fields"
:busy="loading"
......@@ -115,6 +114,14 @@ export default {
<template #table-busy>
<gl-loading-icon size="lg" color="dark" class="mt-3" />
</template>
<template #empty>
<div
class="gl-border-t-solid gl-border-b-solid gl-border-1 gl-border gl-border-gray-100 mt-n3"
>
<p class="gl-text-gray-400 gl-py-3 gl-my-3">{{ $options.i18n.emptyState }}</p>
</div>
</template>
</gl-table>
</div>
</template>
......@@ -56,7 +56,7 @@ export default {
data() {
return {
loading: false,
selectedIntegration: integrationTypes[1].value,
selectedIntegration: integrationTypes[0].value,
options: integrationTypes,
active: false,
authKey: '',
......@@ -88,34 +88,34 @@ export default {
];
},
isPrometheus() {
return this.selectedIntegration === 'prometheus';
return this.selectedIntegration === 'PROMETHEUS';
},
isOpsgenie() {
return this.selectedIntegration === 'opsgenie';
return this.selectedIntegration === 'OPSGENIE';
},
selectedIntegrationType() {
switch (this.selectedIntegration) {
case 'generic': {
case 'HTTP': {
return {
url: this.generic.url,
authKey: this.generic.authorizationKey,
activated: this.generic.activated,
authKey: this.generic.authKey,
active: this.generic.active,
resetKey: this.resetKey.bind(this),
};
}
case 'prometheus': {
case 'PROMETHEUS': {
return {
url: this.prometheus.prometheusUrl,
authKey: this.prometheus.authorizationKey,
activated: this.prometheus.activated,
resetKey: this.resetKey.bind(this, 'prometheus'),
url: this.prometheus.url,
authKey: this.prometheus.authKey,
active: this.prometheus.active,
resetKey: this.resetKey.bind(this, 'PROMETHEUS'),
targetUrl: this.prometheus.prometheusApiUrl,
};
}
case 'opsgenie': {
case 'OPSGENIE': {
return {
targetUrl: this.opsgenie.opsgenieMvcTargetUrl,
activated: this.opsgenie.activated,
active: this.opsgenie.active,
};
}
default: {
......@@ -161,16 +161,12 @@ export default {
},
},
mounted() {
if (
this.prometheus.activated ||
this.generic.activated ||
!this.opsgenie.opsgenieMvcIsAvailable
) {
if (this.prometheus.active || this.generic.active || !this.opsgenie.opsgenieMvcIsAvailable) {
this.removeOpsGenieOption();
} else if (this.opsgenie.activated) {
} else if (this.opsgenie.active) {
this.setOpsgenieAsDefault();
}
this.active = this.selectedIntegrationType.activated;
this.active = this.selectedIntegrationType.active;
this.authKey = this.selectedIntegrationType.authKey ?? '';
},
methods: {
......@@ -183,19 +179,19 @@ export default {
},
setOpsgenieAsDefault() {
this.options = this.options.map(el => {
if (el.value !== 'opsgenie') {
if (el.value !== 'OPSGENIE') {
return { ...el, disabled: true };
}
return { ...el, disabled: false };
});
this.selectedIntegration = this.options.find(({ value }) => value === 'opsgenie').value;
this.selectedIntegration = this.options.find(({ value }) => value === 'OPSGENIE').value;
if (this.targetUrl === null) {
this.targetUrl = this.selectedIntegrationType.targetUrl;
}
},
removeOpsGenieOption() {
this.options = this.options.map(el => {
if (el.value !== 'opsgenie') {
if (el.value !== 'OPSGENIE') {
return { ...el, disabled: false };
}
return { ...el, disabled: true };
......@@ -204,7 +200,7 @@ export default {
resetFormValues() {
this.testAlert.json = null;
this.targetUrl = this.selectedIntegrationType.targetUrl;
this.active = this.selectedIntegrationType.activated;
this.active = this.selectedIntegrationType.active;
},
dismissFeedback() {
this.serverError = null;
......@@ -212,7 +208,7 @@ export default {
this.isFeedbackDismissed = false;
},
resetKey(key) {
const fn = key === 'prometheus' ? this.resetPrometheusKey() : this.resetGenericKey();
const fn = key === 'PROMETHEUS' ? this.resetPrometheusKey() : this.resetGenericKey();
return fn
.then(({ data: { token } }) => {
......@@ -242,9 +238,10 @@ export default {
},
toggleActivated(value) {
this.loading = true;
const path = this.isOpsgenie ? this.opsgenie.formPath : this.generic.formPath;
return service
.updateGenericActive({
endpoint: this[this.selectedIntegration].formPath,
endpoint: path,
params: this.isOpsgenie
? { service: { opsgenie_mvc_target_url: this.targetUrl, opsgenie_mvc_enabled: value } }
: { service: { active: value } },
......@@ -345,7 +342,7 @@ export default {
if (this.canSaveForm) {
this.canSaveForm = false;
this.active = this.selectedIntegrationType.activated;
this.active = this.selectedIntegrationType.active;
}
},
},
......@@ -402,9 +399,9 @@ export default {
</gl-sprintf>
</span>
</gl-form-group>
<gl-form-group :label="$options.i18n.activeLabel" label-for="activated">
<gl-form-group :label="$options.i18n.activeLabel" label-for="active">
<toggle-button
id="activated"
id="active"
:disabled-input="loading"
:is-loading="loading"
:value="active"
......
<script>
import produce from 'immer';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { fetchPolicies } from '~/lib/graphql';
import createFlash, { FLASH_TYPES } from '~/flash';
import getIntegrationsQuery from '../graphql/queries/get_integrations.query.graphql';
import createHttpIntegrationMutation from '../graphql/mutations/create_http_integration.mutation.graphql';
import createPrometheusIntegrationMutation from '../graphql/mutations/create_prometheus_integration.mutation.graphql';
import IntegrationsList from './alerts_integrations_list.vue';
import SettingsFormOld from './alerts_settings_form_old.vue';
import SettingsFormNew from './alerts_settings_form_new.vue';
import { typeSet } from '../constants';
export default {
typeSet,
i18n: {
changesSaved: s__(
'AlertsIntegrations|The integration has been successfully saved. Alerts from this new integration should now appear on your alerts list.',
),
},
components: {
IntegrationsList,
SettingsFormOld,
......@@ -49,6 +60,7 @@ export default {
data() {
return {
errored: false,
isUpdating: false,
integrations: {},
};
},
......@@ -61,16 +73,85 @@ export default {
{
name: s__('AlertSettings|HTTP endpoint'),
type: s__('AlertsIntegrations|HTTP endpoint'),
active: this.generic.activated,
active: this.generic.active,
},
{
name: s__('AlertSettings|External Prometheus'),
type: s__('AlertsIntegrations|Prometheus'),
active: this.prometheus.activated,
active: this.prometheus.active,
},
];
},
},
methods: {
onCreateNewIntegration({ type, variables }) {
this.isUpdating = true;
this.$apollo
.mutate({
mutation:
type === this.$options.typeSet.http
? createHttpIntegrationMutation
: createPrometheusIntegrationMutation,
variables: {
...variables,
projectPath: this.projectPath,
},
update: this.updateIntegrations,
})
.then(({ data: { httpIntegrationCreate, prometheusIntegrationCreate } = {} } = {}) => {
const error = httpIntegrationCreate?.errors[0] || prometheusIntegrationCreate?.errors[0];
if (error) {
return createFlash({ message: error });
}
return createFlash({
message: this.$options.i18n.changesSaved,
type: FLASH_TYPES.SUCCESS,
});
})
.catch(err => {
this.errored = true;
createFlash({ message: err });
})
.finally(() => {
this.isUpdating = false;
});
},
updateIntegrations(
store,
{
data: { httpIntegrationCreate, prometheusIntegrationCreate },
},
) {
const integration =
httpIntegrationCreate?.integration || prometheusIntegrationCreate?.integration;
if (!integration) {
return;
}
const sourceData = store.readQuery({
query: getIntegrationsQuery,
variables: {
projectPath: this.projectPath,
},
});
const data = produce(sourceData, draftData => {
// eslint-disable-next-line no-param-reassign
draftData.project.alertManagementIntegrations.nodes = [
integration,
...draftData.project.alertManagementIntegrations.nodes,
];
});
store.writeQuery({
query: getIntegrationsQuery,
variables: {
projectPath: this.projectPath,
},
data,
});
},
},
};
</script>
......@@ -80,7 +161,11 @@ export default {
:integrations="glFeatures.httpIntegrationsList ? integrations.list : intergrationsOptionsOld"
:loading="loading"
/>
<settings-form-new v-if="glFeatures.httpIntegrationsList" />
<settings-form-new
v-if="glFeatures.httpIntegrationsList"
:loading="loading"
@on-create-new-integration="onCreateNewIntegration"
/>
<settings-form-old v-else />
</div>
</template>
import { s__ } from '~/locale';
// TODO: Remove this as part of the form old removal
export const i18n = {
usageSection: s__(
'AlertSettings|You must provide this URL and authorization key to authorize an external service to send alerts to GitLab. You can provide this URL and key to multiple services. After configuring an external service, alerts from your service will display on the GitLab %{linkStart}Alerts%{linkEnd} page.',
......@@ -39,13 +40,23 @@ export const i18n = {
integration: s__('AlertSettings|Integration'),
};
// TODO: Delete as part of old form removal in 13.6
export const integrationTypes = [
{ value: 'HTTP', text: s__('AlertSettings|HTTP Endpoint') },
{ value: 'PROMETHEUS', text: s__('AlertSettings|External Prometheus') },
{ value: 'OPSGENIE', text: s__('AlertSettings|Opsgenie') },
];
export const integrationTypesNew = [
{ value: '', text: s__('AlertSettings|Select integration type') },
{ value: 'generic', text: s__('AlertSettings|HTTP Endpoint') },
{ value: 'prometheus', text: s__('AlertSettings|External Prometheus') },
{ value: 'opsgenie', text: s__('AlertSettings|Opsgenie') },
...integrationTypes,
];
export const typeSet = {
http: 'HTTP',
prometheus: 'PROMETHEUS',
};
export const JSON_VALIDATE_DELAY = 250;
export const targetPrometheusUrlPlaceholder = 'http://prometheus.example.com/';
......
#import "../fragments/integration_item.fragment.graphql"
mutation createHttpIntegration($projectPath: ID!, $name: String!, $active: Boolean!) {
httpIntegrationCreate(input: { projectPath: $projectPath, name: $name, active: $active }) {
errors
integration {
...IntegrationItem
}
}
}
#import "../fragments/integration_item.fragment.graphql"
mutation createPrometheusIntegration($projectPath: ID!, $apiUrl: String!, $active: Boolean!) {
prometheusIntegrationCreate(
input: { projectPath: $projectPath, apiUrl: $apiUrl, active: $active }
) {
errors
integration {
...IntegrationItem
}
}
}
......@@ -48,9 +48,9 @@ export default el => {
el,
provide: {
prometheus: {
activated: parseBoolean(prometheusActivated),
prometheusUrl,
authorizationKey: prometheusAuthorizationKey,
active: parseBoolean(prometheusActivated),
url: prometheusUrl,
authKey: prometheusAuthorizationKey,
prometheusFormPath,
prometheusResetKeyPath,
prometheusApiUrl,
......@@ -58,14 +58,14 @@ export default el => {
generic: {
alertsSetupUrl,
alertsUsageUrl,
activated: parseBoolean(activatedStr),
active: parseBoolean(activatedStr),
formPath,
authorizationKey,
authKey: authorizationKey,
url,
},
opsgenie: {
formPath: opsgenieMvcFormPath,
activated: parseBoolean(opsgenieMvcEnabled),
active: parseBoolean(opsgenieMvcEnabled),
opsgenieMvcTargetUrl,
opsgenieMvcIsAvailable: parseBoolean(opsgenieMvcAvailable),
},
......
......@@ -70,6 +70,7 @@ const Api = {
featureFlagUserLists: '/api/:version/projects/:id/feature_flags_user_lists',
featureFlagUserList: '/api/:version/projects/:id/feature_flags_user_lists/:list_iid',
billableGroupMembersPath: '/api/:version/groups/:id/billable_members',
containerRegistryDetailsPath: '/api/:version/registry/repositories/:id/',
group(groupId, callback = () => {}) {
const url = Api.buildUrl(Api.groupPath).replace(':id', groupId);
......@@ -106,6 +107,11 @@ const Api = {
return axios.delete(url);
},
containerRegistryDetails(registryId, options = {}) {
const url = Api.buildUrl(this.containerRegistryDetailsPath).replace(':id', registryId);
return axios.get(url, options);
},
groupMembers(id, options) {
const url = Api.buildUrl(this.groupMembersPath).replace(':id', encodeURIComponent(id));
......
......@@ -12,11 +12,19 @@ import { getLocationHash } from '../lib/utils/url_utility';
$(() => {
function toggleContainer(container, toggleState) {
const $container = $(container);
$container
.find('.js-toggle-button .fa-chevron-up, .js-toggle-button .fa-chevron-down')
.toggleClass('fa-chevron-up', toggleState)
.toggleClass('fa-chevron-down', toggleState !== undefined ? !toggleState : undefined);
const isExpanded = $container.data('is-expanded');
const $collapseIcon = $container.find('.js-sidebar-collapse');
const $expandIcon = $container.find('.js-sidebar-expand');
if (isExpanded && !toggleState) {
$container.data('is-expanded', false);
$collapseIcon.addClass('hidden');
$expandIcon.removeClass('hidden');
} else {
$container.data('is-expanded', true);
$expandIcon.addClass('hidden');
$collapseIcon.removeClass('hidden');
}
$container.find('.js-toggle-content').toggle(toggleState);
}
......
......@@ -626,7 +626,7 @@ export function switchToFullDiffFromRenamedFile({ commit, dispatch, state }, { d
.then(({ data }) => {
const lines = data.map((line, index) =>
prepareLineForRenamedFile({
diffViewType: state.diffViewType,
diffViewType: window.gon?.features?.unifiedDiffLines ? 'inline' : state.diffViewType,
line,
diffFile,
index,
......@@ -638,6 +638,7 @@ export function switchToFullDiffFromRenamedFile({ commit, dispatch, state }, { d
viewer: {
...diffFile.alternate_viewer,
automaticallyCollapsed: false,
manuallyCollapsed: false,
},
});
commit(types.SET_CURRENT_VIEW_DIFF_FILE_LINES, { filePath: diffFile.file_path, lines });
......
......@@ -378,8 +378,13 @@ export default {
},
[types.SET_CURRENT_VIEW_DIFF_FILE_LINES](state, { filePath, lines }) {
const file = state.diffFiles.find(f => f.file_path === filePath);
const currentDiffLinesKey =
state.diffViewType === 'inline' ? 'highlighted_diff_lines' : 'parallel_diff_lines';
let currentDiffLinesKey;
if (window.gon?.features?.unifiedDiffLines || state.diffViewType === 'inline') {
currentDiffLinesKey = 'highlighted_diff_lines';
} else {
currentDiffLinesKey = 'parallel_diff_lines';
}
file[currentDiffLinesKey] = lines;
},
......
......@@ -27,7 +27,7 @@ export default {
rolloutUserListLabel: s__('FeatureFlag|User List'),
rolloutUserListDescription: s__('FeatureFlag|Select a user list'),
rolloutUserListNoListError: s__('FeatureFlag|There are no configured user lists'),
defaultDropdownText: s__('FeatureFlags|Select a user list'),
defaultDropdownText: s__('FeatureFlags|No user list selected'),
},
computed: {
...mapGetters(['hasUserLists', 'isLoading', 'hasError', 'userListOptions']),
......@@ -36,7 +36,7 @@ export default {
return this.strategy?.userList?.id ?? '';
},
dropdownText() {
return this.strategy?.userList?.name ?? this.$options.defaultDropdownText;
return this.strategy?.userList?.name ?? this.$options.translations.defaultDropdownText;
},
},
mounted() {
......
......@@ -116,7 +116,7 @@ export default {
<gl-dropdown
v-if="displayFilters"
id="discussion-filter-dropdown"
class="gl-mr-3 full-width-mobile discussion-filter-container js-discussion-filter-container qa-discussion-filter"
class="gl-mr-3 full-width-mobile discussion-filter-container js-discussion-filter-container"
data-qa-selector="discussion_filter_dropdown"
:text="currentFilter.title"
>
......
......@@ -65,8 +65,8 @@ export default {
};
},
computed: {
toggleChevronClass() {
return this.expanded ? 'fa-chevron-up' : 'fa-chevron-down';
toggleChevronIconName() {
return this.expanded ? 'chevron-up' : 'chevron-down';
},
noteTimestampLink() {
return this.noteId ? `#note_${this.noteId}` : undefined;
......@@ -133,7 +133,7 @@ export default {
type="button"
@click="handleToggle"
>
<i ref="chevronIcon" :class="toggleChevronClass" class="fa" aria-hidden="true"></i>
<gl-icon ref="chevronIcon" :name="toggleChevronIconName" aria-hidden="true" />
{{ __('Toggle thread') }}
</button>
</div>
......
import initDevopAdoption from 'ee_else_ce/admin/dev_ops_report/devops_adoption';
import initDevOpsScoreEmptyState from '~/admin/dev_ops_report/devops_score_empty_state';
import initDevopAdoption from '~/admin/dev_ops_report/devops_adoption';
initDevOpsScoreEmptyState();
initDevopAdoption();
......@@ -15,6 +15,10 @@ export const DELETE_TAGS_SUCCESS_MESSAGE = s__(
'ContainerRegistry|Tags successfully marked for deletion.',
);
export const FETCH_IMAGE_DETAILS_ERROR_MESSAGE = s__(
'ContainerRegistry|Something went wrong while fetching the image details.',
);
export const TAGS_LIST_TITLE = s__('ContainerRegistry|Image tags');
export const DIGEST_LABEL = s__('ContainerRegistry|Digest: %{imageId}');
export const CREATED_AT_LABEL = s__('ContainerRegistry|Published %{timeInfo}');
......
import axios from '~/lib/utils/axios_utils';
import { deprecatedCreateFlash as createFlash } from '~/flash';
import Api from '~/api';
import * as types from './mutation_types';
import {
FETCH_IMAGES_LIST_ERROR_MESSAGE,
DEFAULT_PAGE,
DEFAULT_PAGE_SIZE,
FETCH_TAGS_LIST_ERROR_MESSAGE,
FETCH_IMAGE_DETAILS_ERROR_MESSAGE,
} from '../constants/index';
import { decodeAndParse } from '../utils';
......@@ -61,6 +63,19 @@ export const requestTagsList = ({ commit, dispatch }, { pagination = {}, params
});
};
export const requestImageDetailsAndTagsList = ({ dispatch, commit }, id) => {
commit(types.SET_MAIN_LOADING, true);
return Api.containerRegistryDetails(id)
.then(({ data }) => {
commit(types.SET_IMAGE_DETAILS, data);
dispatch('requestTagsList');
})
.catch(() => {
createFlash(FETCH_IMAGE_DETAILS_ERROR_MESSAGE);
commit(types.SET_MAIN_LOADING, false);
});
};
export const requestDeleteTag = ({ commit, dispatch, state }, { tag, params }) => {
commit(types.SET_MAIN_LOADING, true);
return axios
......
......@@ -7,3 +7,4 @@ export const SET_MAIN_LOADING = 'SET_MAIN_LOADING';
export const SET_TAGS_PAGINATION = 'SET_TAGS_PAGINATION';
export const SET_TAGS_LIST_SUCCESS = 'SET_TAGS_LIST_SUCCESS';
export const SET_SHOW_GARBAGE_COLLECTION_TIP = 'SET_SHOW_GARBAGE_COLLECTION_TIP';
export const SET_IMAGE_DETAILS = 'SET_IMAGE_DETAILS';
......@@ -47,4 +47,8 @@ export default {
const normalizedHeaders = normalizeHeaders(headers);
state.tagsPagination = parseIntPagination(normalizedHeaders);
},
[types.SET_IMAGE_DETAILS](state, details) {
state.imageDetails = details;
},
};
......@@ -3,6 +3,7 @@ export default () => ({
showGarbageCollectionTip: false,
config: {},
images: [],
imageDetails: {},
tags: [],
pagination: {},
tagsPagination: {},
......
export const decodeAndParse = param => JSON.parse(window.atob(param));
// eslint-disable-next-line @gitlab/require-i18n-strings
export const pathGenerator = (imageDetails, ending = 'tags?format=json') => {
// this method is a temporary workaround, to be removed with graphql implementation
// https://gitlab.com/gitlab-org/gitlab/-/issues/276432
const basePath = imageDetails.path.replace(`/${imageDetails.name}`, '');
return `/${basePath}/registry/repository/${imageDetails.id}/${ending}`;
};
......@@ -109,10 +109,6 @@
content: '\f0da';
}
.fa-chevron-up::before {
content: '\f077';
}
.fa-exclamation-circle::before {
content: '\f06a';
}
......
......@@ -51,7 +51,7 @@ class Projects::IssuesController < Projects::ApplicationController
real_time_feature_flag = :real_time_issue_sidebar
real_time_enabled = Gitlab::ActionCable::Config.in_app? || Feature.enabled?(real_time_feature_flag, @project)
gon.push({ features: { real_time_feature_flag.to_s.camelize(:lower) => real_time_enabled } }, true)
push_to_gon_features(real_time_feature_flag, real_time_enabled)
record_experiment_user(:invite_members_version_a)
record_experiment_user(:invite_members_version_b)
......
......@@ -32,7 +32,7 @@ module FinderWithCrossProjectAccess
end
override :execute
def execute(*args)
def execute(*args, **kwargs)
check = Gitlab::CrossProjectAccess.find_check(self)
original = -> { super }
......
......@@ -30,7 +30,7 @@ module OperationsHelper
'alerts_setup_url' => help_page_path('operations/incident_management/alert_integrations.md', anchor: 'generic-http-endpoint'),
'alerts_usage_url' => project_alert_management_index_path(@project),
'disabled' => disabled.to_s,
'project_path' => project_path(@project)
'project_path' => @project.full_path
}
end
......
......@@ -4,17 +4,7 @@
.container
.gl-mt-3
- if Feature.enabled?(:devops_adoption)
%h2
= _('DevOps Report')
%ul.nav-links.nav-tabs.nav.js-devops-tabs{ role: 'tablist' }
= render 'tab', active: true, title: _('DevOps Score'), target: '#devops_score_pane'
= render 'tab', active: false, title: _('Adoption'), target: '#devops_adoption_pane'
.tab-content
.tab-pane.active#devops_score_pane
= render 'report'
.tab-pane#devops_adoption_pane
.js-devops-adoption{ data: { empty_state_svg_path: image_path('illustrations/monitoring/getting_started.svg') } }
= render_if_exists 'admin/dev_ops_report/devops_tabs'
- else
= render 'report'
......@@ -2,17 +2,15 @@
%li.note.note-discussion.timeline-entry.unstyled-comments
.timeline-entry-inner
.timeline-content
.discussion.js-toggle-container{ data: { discussion_id: discussion.id } }
.discussion.js-toggle-container{ data: { discussion_id: discussion.id, is_expanded: expanded.to_s } }
.discussion-header
.timeline-icon
= link_to user_path(discussion.author) do
= image_tag avatar_icon_for_user(discussion.author), class: "avatar s40"
.discussion-actions
%button.note-action-button.discussion-toggle-button.js-toggle-button{ type: "button", class: ("js-toggle-lazy-diff" unless expanded) }
- if expanded
= icon("chevron-up")
- else
= icon("chevron-down")
= sprite_icon('chevron-up', css_class: "js-sidebar-collapse #{'hidden' unless expanded}")
= sprite_icon('chevron-down', css_class: "js-sidebar-expand #{'hidden' if expanded}")
= _('Toggle thread')
= link_to_member(@project, discussion.author, avatar: false)
......
---
title: Add EC2 to AutoDevOps template
merge_request: 45651
author:
type: changed
---
title: Replace fa-chevron-up with GitLab SVG icon
merge_request: 46118
author:
type: changed
---
title: Remove all records from `security_findings` table
merge_request: 44312
author:
type: fixed
---
title: Remove feedback alert from on-demand scans form
merge_request: 45217
author:
type: changed
---
title: Assign new incoming diff lines for renamed files to the correct view type
merge_request: 46823
author:
type: fixed
---
title: Fix example responses for Group Issue Board creation API in the docs
merge_request: 46760
author: Takuya Noguchi
type: fixed
---
title: Fix compliance framework database migration on CE instances
merge_request: 46761
author:
type: fixed
---
title: Show "No user list selected" in feature flags
merge_request: 46790
author:
type: fixed
---
title: Add total projects imported usage ping
merge_request: 46541
author:
type: added
---
title: Fix 'File name too long' error happening during Project Export when exporting
project uploads
merge_request: 46674
author:
type: fixed
---
name: incident_sla
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43648
rollout_issue_url:
group: group::health
type: licensed
default_enabled: true
......@@ -14,10 +14,8 @@ if Gitlab.ee? && Gitlab.dev_or_test_env?
# being unique to licensed names. These feature flags should be reworked to
# be "development" with explicit check
IGNORED_FEATURE_FLAGS = %i[
ci_secrets_management
feature_flags_related_issues
group_wikis
incident_sla
swimlanes
minimal_access_role
].to_set
......
......@@ -11,7 +11,8 @@ ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, r
env: req.env['rack.attack.match_type'],
remote_ip: req.ip,
request_method: req.request_method,
path: req.fullpath
path: req.fullpath,
matched: req.env['rack.attack.matched']
}
throttles_with_user_information = [
......@@ -25,9 +26,8 @@ ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, finish, r
user_id = req.env['rack.attack.match_discriminator']
user = User.find_by(id: user_id)
rack_attack_info[:throttle_type] = req.env['rack.attack.matched']
rack_attack_info[:user_id] = user_id
rack_attack_info[:username] = user.username unless user.nil?
rack_attack_info['meta.user'] = user.username unless user.nil?
end
Gitlab::AuthLogger.error(rack_attack_info)
......
......@@ -52,8 +52,6 @@ class MigrateComplianceFrameworkEnumToDatabaseFrameworkRecord < ActiveRecord::Mi
end
def up
return unless Gitlab.ee?
TmpComplianceFramework.reset_column_information
TmpProjectSettings.reset_column_information
......
# frozen_string_literal: true
class TruncateSecurityFindingsTable < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
return unless Gitlab.dev_env_or_com?
with_lock_retries do
connection.execute('TRUNCATE security_findings RESTART IDENTITY')
end
end
def down
# no-op
end
end
55ffd18d5f55ee0fd51a31d50cf2d51595740c72ca23d5134d93e2da3fc186ff
\ No newline at end of file
......@@ -17,7 +17,7 @@ This integration works with most LDAP-compliant directory servers, including:
- Open LDAP
- 389 Server
Users added through LDAP take a [licensed seat](../../../subscriptions/self_managed/index.md#choose-the-number-of-users).
Users added through LDAP take a [licensed seat](../../../subscriptions/self_managed/index.md#billable-users).
GitLab Enterprise Editions (EE) include enhanced integration,
including group membership syncing as well as multiple LDAP servers support.
......
......@@ -36,8 +36,8 @@ error, it's very important that you [**provide feedback**](https://gitlab.com/gi
as possible so we can improve or fix it while behind a flag. When you upgrade
GitLab to an earlier version, the feature flag status may change.
NOTE: **Note:**
Mind that features deployed behind feature flags may not be ready for
CAUTION: **Caution:**
Features deployed behind feature flags may not be ready for
production use. However, disabling features behind flags that were deployed
enabled by default may also present a risk. If they're enabled, we recommend
you leave them as-is.
......
......@@ -43,6 +43,45 @@ To change the location where the job logs will be stored, follow the steps below
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the
changes to take effect.
Alternatively, if you have existing job logs you can follow
these steps to move the logs to a new location without losing any data.
1. Pause continuous integration data processing by updating this setting in `/etc/gitlab/gitlab.rb`.
Jobs in progress are not affected, based on how [data flow](#data-flow) works.
```ruby
sidekiq['experimental_queue_selector'] = true
sidekiq['queue_groups'] = [
"feature_category!=continuous_integration"
]
```
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the
changes to take effect.
1. Set the new storage location in `/etc/gitlab/gitlab.rb`:
```ruby
gitlab_ci['builds_directory'] = '/mnt/to/gitlab-ci/builds'
```
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the
changes to take effect.
1. Use `rsync` to move job logs from the current location to the new location:
```shell
sudo rsync -avzh --remove-source-files --ignore-existing --progress /var/opt/gitlab/gitlab-ci/builds/ /mnt/to/gitlab-ci/builds`
```
Use `--ignore-existing` so you don't override new job logs with older versions of the same log.
1. Unpause continuous integration data processing by editing `/etc/gitlab/gitlab.rb` and removing the `sidekiq` setting you updated earlier.
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the
changes to take effect.
1. Remove the old job logs storage location:
```shell
sudo rm -rf /var/opt/gitlab/gitlab-ci/builds`
```
**In installations from source:**
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
......
......@@ -13,7 +13,6 @@ The [GitLab exporter](https://gitlab.com/gitlab-org/gitlab-exporter) enables you
measure various GitLab metrics pulled from Redis and the database in Omnibus GitLab
instances.
NOTE: **Note:**
For installations from source you must install and configure it yourself.
To enable the GitLab exporter in an Omnibus GitLab instance:
......
......@@ -13,7 +13,6 @@ To enable the GitLab Prometheus metrics:
1. Find the **Metrics - Prometheus** section, and click **Enable Prometheus Metrics**.
1. [Restart GitLab](../../restart_gitlab.md#omnibus-gitlab-restart) for the changes to take effect.
NOTE: **Note:**
For installations from source you must configure it yourself.
## Collecting the metrics
......
......@@ -31,7 +31,6 @@ dashboard tool like [Grafana](https://grafana.com).
## Configuring Prometheus
NOTE: **Note:**
For installations from source, you must install and configure it yourself.
Prometheus and its exporters are on by default, starting with GitLab 9.0.
......@@ -54,7 +53,7 @@ To disable Prometheus and all of its exporters, as well as any added in the futu
### Changing the port and address Prometheus listens on
NOTE: **Note:**
CAUTION: **Caution:**
The following change was added in [Omnibus GitLab 8.17](https://gitlab.com/gitlab-org/omnibus-gitlab/-/merge_requests/1261). Although possible,
it's not recommended to change the port Prometheus listens
on, as this might affect or conflict with other services running on the GitLab
......@@ -178,7 +177,6 @@ The next step is to tell all the other nodes where the monitoring node is:
1. Save the file and [reconfigure GitLab](../../restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to
take effect.
NOTE: **Note:**
After monitoring using Service Discovery is enabled with `consul['monitoring_service_discovery'] = true`,
ensure that `prometheus['scrape_configs']` is not set in `/etc/gitlab/gitlab.rb`. Setting both
`consul['monitoring_service_discovery'] = true` and `prometheus['scrape_configs']` in `/etc/gitlab/gitlab.rb`
......@@ -186,7 +184,7 @@ will result in errors.
### Using an external Prometheus server
NOTE: **Note:**
CAUTION: **Caution:**
Prometheus and most exporters don't support authentication. We don't recommend exposing them outside the local network.
A few configuration changes are required to allow GitLab to be monitored by an external Prometheus server. External servers are recommended for [GitLab deployments with multiple nodes](../../reference_architectures/index.md).
......
......@@ -9,7 +9,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
The [node exporter](https://github.com/prometheus/node_exporter) enables you to measure
various machine resources such as memory, disk and CPU utilization.
NOTE: **Note:**
For installations from source you must install and configure it yourself.
To enable the node exporter:
......
......@@ -11,7 +11,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
The [PgBouncer exporter](https://github.com/prometheus-community/pgbouncer_exporter) enables
you to measure various [PgBouncer](https://www.pgbouncer.org/) metrics.
NOTE: **Note:**
For installations from source you must install and configure it yourself.
To enable the PgBouncer exporter:
......
......@@ -8,7 +8,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
The [PostgreSQL Server Exporter](https://github.com/wrouesnel/postgres_exporter) allows you to export various PostgreSQL metrics.
NOTE: **Note:**
For installations from source you must install and configure it yourself.
To enable the PostgreSQL Server Exporter:
......
......@@ -10,7 +10,6 @@ The [Redis exporter](https://github.com/oliver006/redis_exporter) enables you to
various [Redis](https://redis.io) metrics. For more information on what is exported,
[read the upstream documentation](https://github.com/oliver006/redis_exporter/blob/master/README.md#whats-exported).
NOTE: **Note:**
For installations from source you must install and configure it yourself.
To enable the Redis exporter:
......
......@@ -116,7 +116,7 @@ See the section on [ETag mismatch errors](#etag-mismatch) for more details.
gitlab_rails['object_store']['objects']['terraform_state']['bucket'] = '<terraform-state>'
```
NOTE: For GitLab 9.4 or later, if you're using AWS IAM profiles, be sure to omit the
For GitLab 9.4 or later, if you're using AWS IAM profiles, be sure to omit the
AWS access key and secret access key/value pairs. For example:
```ruby
......@@ -263,9 +263,9 @@ Here are the valid connection parameters for GCS:
| `google_json_key_location` | The JSON key path | `/path/to/gcp-project-12345-abcde.json` |
| `google_application_default` | Set to `true` to use [Google Cloud Application Default Credentials](https://cloud.google.com/docs/authentication/production#automatically) to locate service account credentials. |
NOTE: **Note:**
The service account must have permission to access the bucket.
[See more](https://cloud.google.com/storage/docs/authentication)
The service account must have permission to access the bucket. Learn more
in Google's
[Cloud Storage authentication documentation](https://cloud.google.com/storage/docs/authentication).
##### Google example (consolidated form)
......
......@@ -50,7 +50,6 @@ To configure the pseudonymizer, you need to:
}
```
NOTE: **Note:**
If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs.
```ruby
......
......@@ -21,7 +21,6 @@ Automatic resolution is not yet implemented. If you have values that
cannot be decrypted, you can follow steps to reset them, see our
docs on what to do [when the secrets file is lost](../../raketasks/backup_restore.md#when-the-secrets-file-is-lost).
NOTE: **Note:**
This can take a very long time, depending on the size of your
database, as it checks all rows in all tables.
......
......@@ -130,7 +130,6 @@ sudo gitlab-rake gitlab:check
bundle exec rake gitlab:check RAILS_ENV=production
```
NOTE: **Note:**
Use `SANITIZE=true` for `gitlab:check` if you want to omit project names from the output.
Example output:
......
......@@ -74,7 +74,7 @@ To have a summary and then a list of projects and their attachments using hashed
## Migrate to hashed storage
NOTE: **Note:**
DANGER: **Deprecated:**
In GitLab 13.0, [hashed storage](../repository_storage_types.md#hashed-storage)
is enabled by default and the legacy storage is deprecated.
Support for legacy storage will be removed in GitLab 14.0. If you're on GitLab
......@@ -115,7 +115,6 @@ If you find it necessary, you can run this migration script again to schedule mi
Any error or warning will be logged in Sidekiq's log file.
NOTE: **Note:**
If [Geo](../geo/index.md) is enabled, each project that is successfully migrated
generates an event to replicate the changes on any **secondary** nodes.
......@@ -124,7 +123,7 @@ commands below that helps you inspect projects and attachments in both legacy an
## Rollback from hashed storage to legacy storage
NOTE: **Deprecated:**
DANGER: **Deprecated:**
In GitLab 13.0, [hashed storage](../repository_storage_types.md#hashed-storage)
is enabled by default and the legacy storage is deprecated.
Support for legacy storage will be removed in GitLab 14.0. If you're on GitLab
......
......@@ -16,11 +16,10 @@ There is a Rake task for migrating uploads between different storage types.
After [configuring the object storage](../../uploads.md#using-object-storage) for GitLab's
uploads, use this task to migrate existing uploads from the local storage to the remote storage.
Read more about using [object storage with GitLab](../../object_storage.md).
NOTE: **Note:**
All of the processing will be done in a background worker and requires **no downtime**.
Read more about using [object storage with GitLab](../../object_storage.md).
### All-in-one Rake task
GitLab provides a wrapper Rake task that migrates all uploaded files (for example avatars, logos,
......@@ -99,7 +98,6 @@ gitlab-rake "gitlab:uploads:migrate[DesignManagement::DesignV432x230Uploader, De
**Source Installation**
NOTE: **Note:**
Use `RAILS_ENV=production` for every task.
```shell
......
......@@ -91,9 +91,10 @@ The instructions make the assumption that you will be using the email address `i
quit
```
_**Note:** The `.` is a literal period on its own line._
NOTE: **Note:**
The `.` is a literal period on its own line.
_**Note:** If you receive an error after entering `rcpt to: incoming@localhost`
If you receive an error after entering `rcpt to: incoming@localhost`
then your Postfix `my_network` configuration is not correct. The error will
say 'Temporary lookup failure'. See
[Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._
......@@ -164,11 +165,11 @@ Courier, which we will install later to add IMAP authentication, requires mailbo
q
```
_**Note:** If `mail` returns an error `Maildir: Is a directory` then your
If `mail` returns an error `Maildir: Is a directory` then your
version of `mail` doesn't support Maildir style mailboxes. Install
`heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then,
try the above steps again, substituting `heirloom-mailx` for the `mail`
command._
command.
1. Sign out of the `incoming` account, and go back to being `root`:
......@@ -271,7 +272,8 @@ Courier, which we will install later to add IMAP authentication, requires mailbo
quit
```
(Note: The `.` is a literal period on its own line)
NOTE: **Note:**
The `.` is a literal period on its own line.
1. Check if the `incoming` user received the email:
......
......@@ -9,8 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Notification emails sent by GitLab can be signed with S/MIME for improved
security.
NOTE: **Note:**
Please be aware that S/MIME certificates and TLS/SSL certificates are not the
Be aware that S/MIME certificates and TLS/SSL certificates are not the
same and are used for different purposes: TLS creates a secure channel, whereas
S/MIME signs and/or encrypts the message itself
......@@ -27,7 +26,7 @@ files must be provided:
Optionally, you can also provide a bundle of CA certs (PEM-encoded) to be
included on each signature. This will typically be an intermediate CA.
NOTE: **Note:**
CAUTION: **Caution:**
Be mindful of the access levels for your private keys and visibility to
third parties.
......@@ -45,7 +44,6 @@ third parties.
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
NOTE: **Note:**
The key needs to be readable by the GitLab system user (`git` by default).
**For installations from source:**
......@@ -69,7 +67,6 @@ The key needs to be readable by the GitLab system user (`git` by default).
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
NOTE: **Note:**
The key needs to be readable by the GitLab system user (`git` by default).
### How to convert S/MIME PKCS#12 / PFX format to PEM encoding
......
......@@ -21,7 +21,7 @@ To see all available time zones, run `bundle exec rake time:zones:all`.
For Omnibus installations, run `gitlab-rake time:zones:all`.
NOTE: **Note:**
Currently, this Rake task does not list timezones in TZInfo format required by Omnibus GitLab during a reconfigure: [#27209](https://gitlab.com/gitlab-org/gitlab/-/issues/27209).
This Rake task does not list timezones in TZInfo format required by Omnibus GitLab during a reconfigure: [#27209](https://gitlab.com/gitlab-org/gitlab/-/issues/27209).
## Changing time zone in Omnibus installations
......
......@@ -308,11 +308,11 @@ pp p.statistics # compare with earlier values
### Recreate
A Projects Wiki can be recreated by
NOTE: **Note:**
CAUTION: **Caution:**
This is a destructive operation, the Wiki will be empty.
A Projects Wiki can be recreated by this command:
```ruby
p = Project.find_by_full_path('<username-or-group>/<project-name>') ### enter your projects path
......
......@@ -70,8 +70,7 @@ and they will assist you with any issues you are having.
kubectl logs <pod-name> --previous
```
NOTE: **Note:**
No logs are kept in the containers/pods themselves, everything is written to stdout.
No logs are kept in the containers/pods themselves. Everything is written to stdout.
This is the principle of Kubernetes, read [Twelve-factor app](https://12factor.net/)
for details.
......
......@@ -18,7 +18,6 @@ If you are administering GitLab you are expected to know these commands for your
of choice. If you are a GitLab Support Engineer, consider this a cross-reference to
translate `yum` -> `apt-get` and the like.
Note: **Note:**
Most of the commands below have not been labeled as to which distribution they work
on. Contributions are welcome to help add them.
......
......@@ -385,7 +385,7 @@ User.find_by(username: 'root')
User.find_by_any_email('user@example.com')
```
Note: `find_by_any_email` is a custom method added by GitLab developers rather
The `find_by_any_email` method is a custom method added by GitLab developers rather
than a Rails-provided default method.
**Get a collection of admin users:**
......@@ -394,7 +394,7 @@ than a Rails-provided default method.
User.admins
```
Note: `admins` is a [scope convenience method](https://guides.rubyonrails.org/active_record_querying.html#scopes)
`admins` is a [scope convenience method](https://guides.rubyonrails.org/active_record_querying.html#scopes)
which does `where(admin: true)` under the hood.
**Get a project by its path:**
......@@ -403,7 +403,7 @@ which does `where(admin: true)` under the hood.
Project.find_by_full_path('group/subgroup/project')
```
Note: `find_by_full_path` is a custom method added by GitLab developers rather
`find_by_full_path` is a custom method added by GitLab developers rather
than a Rails-provided default method.
**Get a project's issue or merge request by its numeric ID:**
......@@ -414,7 +414,7 @@ project.issues.find_by(iid: 42)
project.merge_requests.find_by(iid: 42)
```
Note: `iid` means "internal ID" and is how we keep issue and merge request IDs
`iid` means "internal ID" and is how we keep issue and merge request IDs
scoped to each GitLab project.
**Get a group by its path:**
......@@ -454,7 +454,7 @@ Ci::Pipeline.find(4151)
Ci::Build.find(66124)
```
Note: The pipeline and job #ID numbers increment globally across your GitLab
The pipeline and job ID numbers increment globally across your GitLab
instance, so there's no need to use an internal ID attribute to look them up,
unlike with issues or merge requests.
......
......@@ -148,4 +148,4 @@ It may take a little while to respond.
```
NOTE: **Note:**
These are Omnibus settings. If an external database, such as a customer's PostgreSQL installation or Amazon RDS is being used, these values don't get set, and would have to be set externally.
These are Omnibus GitLab settings. If an external database, such as a customer's PostgreSQL installation or Amazon RDS is being used, these values don't get set, and would have to be set externally.
......@@ -13,12 +13,10 @@ may be filling up. Users will notice when this happens because new branches
may not show up and merge requests may not be updated. The following are some
troubleshooting steps that will help you diagnose the bottleneck.
NOTE: **Note:**
GitLab administrators/users should consider working through these
debug steps with GitLab Support so the backtraces can be analyzed by our team.
It may reveal a bug or necessary improvement in GitLab.
NOTE: **Note:**
In any of the backtraces, be wary of suspecting cases where every
thread appears to be waiting in the database, Redis, or waiting to acquire
a mutex. This **may** mean there's contention in the database, for example,
......@@ -133,7 +131,6 @@ corresponding Ruby code where this is happening.
`gdb` can be another effective tool for debugging Sidekiq. It gives you a little
more interactive way to look at each thread and see what's causing problems.
NOTE: **Note:**
Attaching to a process with `gdb` will suspends the normal operation
of the process (Sidekiq will not process jobs while `gdb` is attached).
......@@ -284,15 +281,15 @@ end
### Remove Sidekiq jobs for given parameters (destructive)
The general method to kill jobs conditionally is the following:
The general method to kill jobs conditionally is the following command, which
will remove jobs that are queued but not started. Running jobs will not be killed.
```ruby
queue = Sidekiq::Queue.new('<queue name>')
queue.each { |job| job.delete if <condition>}
```
NOTE: **Note:**
This will remove jobs that are queued but not started, running jobs will not be killed. Have a look at the section below for cancelling running jobs.
Have a look at the section below for cancelling running jobs.
In the method above, `<queue-name>` is the name of the queue that contains the job(s) you want to delete and `<condition>` will decide which jobs get deleted.
......@@ -300,7 +297,6 @@ Commonly, `<condition>` references the job arguments, which depend on the type o
For example, `repository_import` has `project_id` as the job argument, while `update_merge_requests` has `project_id, user_id, oldrev, newrev, ref`.
NOTE: **Note:**
Arguments need to be referenced by their sequence ID using `job.args[<id>]` because `job.args` is a list of all arguments provided to the Sidekiq job.
Here are some examples:
......
......@@ -631,7 +631,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| `sha` | string | yes | The commit SHA
| `ref` | string | no | The name of a repository branch or tag or, if not given, the default branch
| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
| `name` | string | no | Filter by [job name](../ci/yaml/README.md#introduction), e.g., `bundler:audit`
| `name` | string | no | Filter by [job name](../ci/yaml/README.md#job-keywords), e.g., `bundler:audit`
| `all` | boolean | no | Return all statuses, not only the latest ones
```shell
......
......@@ -1439,6 +1439,11 @@ type BoardEpic implements CurrentUserTodos & Noteable {
"""
iids: [ID!]
"""
Include epics from descendant groups
"""
includeDescendantGroups: Boolean = true
"""
Filter epics by labels
"""
......@@ -6592,6 +6597,11 @@ type Epic implements CurrentUserTodos & Noteable {
"""
iids: [ID!]
"""
Include epics from descendant groups
"""
includeDescendantGroups: Boolean = true
"""
Filter epics by labels
"""
......@@ -8166,6 +8176,11 @@ type Group {
"""
iids: [ID!]
"""
Include epics from descendant groups
"""
includeDescendantGroups: Boolean = true
"""
Filter epics by labels
"""
......@@ -8249,6 +8264,11 @@ type Group {
"""
iids: [ID!]
"""
Include epics from descendant groups
"""
includeDescendantGroups: Boolean = true
"""
Filter epics by labels
"""
......
......@@ -3867,6 +3867,16 @@
},
"defaultValue": null
},
{
"name": "includeDescendantGroups",
"description": "Include epics from descendant groups",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "true"
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......@@ -18288,6 +18298,16 @@
},
"defaultValue": null
},
{
"name": "includeDescendantGroups",
"description": "Include epics from descendant groups",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "true"
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......@@ -22575,6 +22595,16 @@
"ofType": null
},
"defaultValue": null
},
{
"name": "includeDescendantGroups",
"description": "Include epics from descendant groups",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "true"
}
],
"type": {
......@@ -22725,6 +22755,16 @@
},
"defaultValue": null
},
{
"name": "includeDescendantGroups",
"description": "Include epics from descendant groups",
"type": {
"kind": "SCALAR",
"name": "Boolean",
"ofType": null
},
"defaultValue": "true"
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......@@ -265,44 +265,17 @@ Example response:
{
"id": 1,
"name": "newboard",
"project": null,
"lists" : [],
"group": {
"id": 5,
"name": "Documentcloud",
"web_url": "http://example.com/groups/documentcloud"
},
"milestone": {
"id": 12
"title": "10.0"
},
"lists" : [
{
"id" : 1,
"label" : {
"name" : "Testing",
"color" : "#F0AD4E",
"description" : null
},
"position" : 1
},
{
"id" : 2,
"label" : {
"name" : "Ready",
"color" : "#FF0000",
"description" : null
},
"position" : 2
},
{
"id" : 3,
"label" : {
"name" : "Production",
"color" : "#FF5F00",
"description" : null
},
"position" : 3
}
]
"milestone": null,
"assignee" : null,
"labels" : [],
"weight" : null
}
```
......
......@@ -282,6 +282,32 @@ When running your project pipeline at this point:
on the related JSON object's content. The deployment job finishes whenever the deployment to EC2
is done or has failed.
#### Custom build job for Auto DevOps
To leverage [Auto DevOps](../../topics/autodevops/index.md) for your project when deploying to
AWS EC2, you must specify a job for the `build` stage.
To do so, you must reference the `Auto-DevOps.gitlab-ci.yml` template and include a job named
`build_artifact` in your `.gitlab-ci.yml` file. For example:
```yaml
# .gitlab-ci.yml
include:
- template: Auto-DevOps.gitlab-ci.yml
variables:
- AUTO_DEVOPS_PLATFORM_TARGET: EC2
build_artifact:
stage: build
script:
- <your build script goes here>
artifacts:
paths:
- <built artifact>
```
### Deploy to Amazon EKS
- [How to deploy your application to a GitLab-managed Amazon EKS cluster with Auto DevOps](https://about.gitlab.com/blog/2020/05/05/deploying-application-eks/)
......
......@@ -34,8 +34,7 @@ currently being deployed or has been deployed on your servers.
It's important to know that:
- Environments are like tags for your CI jobs, describing where code gets deployed.
- Deployments are created when [jobs](../yaml/README.md#introduction) deploy versions of code to environments,
so every environment can have one or more deployments.
- Deployments are created when [GitLab CI/CD](../yaml/README.md) is used to deploy versions of code to environments.
GitLab:
......
......@@ -140,7 +140,7 @@ new browser window interacting with your app as you specified.
Which brings us to the exciting part: how do we run this in GitLab CI/CD? There are two things we
need to do for this:
1. Set up [CI/CD jobs](../../yaml/README.md#introduction) that actually have a browser available.
1. Set up [CI/CD jobs](../../yaml/README.md) that actually have a browser available.
1. Update our WebdriverIO configuration to use those browsers to visit the review apps.
For the scope of this article, we've defined an additional [CI/CD stage](../../yaml/README.md#stages)
......
......@@ -17,6 +17,14 @@ Out-of-the-box management systems can decrease hours spent on maintaining toolch
Watch our ["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
webcast to learn about continuous methods and how GitLab’s built-in CI can help you simplify and scale software development.
> For some additional information about GitLab CI/CD:
>
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Watch the [CI/CD Ease of configuration](https://www.youtube.com/embed/opdLqwz6tcE) video.
> - Watch the [Making the case for CI/CD in your organization](https://about.gitlab.com/compare/github-actions-alternative/)
> webcast to learn the benefits of CI/CD and how to measure the results of CI/CD automation.
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Learn how [Verizon reduced rebuilds](https://about.gitlab.com/blog/2019/02/14/verizon-customer-story/)
> from 30 days to under 8 hours with GitLab.
## Introduction to CI/CD methodologies
The continuous methodologies of software development are based on
......
......@@ -27,7 +27,7 @@ CircleCI's `config.yml` configuration file defines scripts, jobs, and workflows
### Jobs
In CircleCI, jobs are a collection of steps to perform a specific task. In GitLab, [jobs](../yaml/README.md#introduction) are also a fundamental element in the configuration file. The `checkout` keyword is not necessary in GitLab CI/CD as the repository is automatically fetched.
In CircleCI, jobs are a collection of steps to perform a specific task. In GitLab, [jobs](../pipelines/index.md#about-jobs) are also a fundamental element in the configuration file. The `checkout` keyword is not necessary in GitLab CI/CD as the repository is automatically fetched.
CircleCI example job definition:
......
......@@ -68,7 +68,7 @@ Pipelines can be configured in many different ways:
Pipelines and their component jobs and stages are defined in the CI/CD pipeline configuration file for each project.
- Jobs are the [basic configuration](../yaml/README.md#introduction) component.
- Jobs are the [basic configuration](#about-jobs) component.
- Stages are defined by using the [`stages`](../yaml/README.md#stages) keyword.
For a list of configuration options in the CI pipeline file, see the [GitLab CI/CD Pipeline Configuration Reference](../yaml/README.md).
......@@ -287,7 +287,36 @@ preserving deployment keys and other credentials from being unintentionally
accessed. In order to ensure that jobs intended to be executed on protected
runners do not use regular runners, they must be tagged accordingly.
## View jobs in a pipeline
## About jobs
Pipeline configuration begins with jobs. Jobs are the most fundamental element of a `.gitlab-ci.yml` file.
Jobs are:
- Defined with constraints stating under what conditions they should be executed.
- Top-level elements with an arbitrary name and must contain at least the [`script`](../yaml/README.md#script) clause.
- Not limited in how many can be defined.
For example:
```yaml
job1:
script: "execute-script-for-job1"
job2:
script: "execute-script-for-job2"
```
The above example is the simplest possible CI/CD configuration with two separate
jobs, where each of the jobs executes a different command.
Of course a command can execute code directly (`./configure;make;make install`)
or run a script (`test.sh`) in the repository.
Jobs are picked up by [runners](../runners/README.md) and executed within the
environment of the runner. What is important is that each job is run
independently from each other.
### View jobs in a pipeline
When you access a pipeline, you can see the related jobs for that pipeline.
......
......@@ -13,7 +13,12 @@ GitLab offers a [continuous integration](https://about.gitlab.com/stages-devops-
- Add a [`.gitlab-ci.yml` file](#creating-a-gitlab-ciyml-file) to your repository's root directory.
- Ensure your project is configured to use a [runner](#configuring-a-runner).
The `.gitlab-ci.yml` file tells the runner what to do. A simple pipeline commonly has
The `.gitlab-ci.yml` file defines the structure and order of the pipelines, and determines:
- What to execute using [GitLab Runner](https://docs.gitlab.com/runner/).
- What decisions to make when specific conditions are encountered. For example, when a process succeeds or fails.
A simple pipeline commonly has
three [stages](../yaml/README.md#stages):
- `build`
......
......@@ -225,7 +225,7 @@ should disable **Pipelines must succeed** so you can accept merge requests.
Pipeline configuration warnings are shown when you:
- [Validate configuration with the CI Lint tool](yaml/README.md#validate-the-gitlab-ciyml).
- [Validate configuration with the CI Lint tool](yaml/README.md).
- [Manually run a pipeline](pipelines/index.md#run-a-pipeline-manually).
### "Job may allow multiple pipelines to run for a single action" warning
......
......@@ -7,92 +7,16 @@ type: reference
# GitLab CI/CD pipeline configuration reference
GitLab CI/CD [pipelines](../pipelines/index.md) are configured using a YAML file called `.gitlab-ci.yml` within each project.
This document lists the configuration options for your GitLab `.gitlab-ci.yml` file.
The `.gitlab-ci.yml` file defines the structure and order of the pipelines and determines:
- What to execute using [GitLab Runner](https://docs.gitlab.com/runner/).
- What decisions to make when specific conditions are encountered. For example, when a process succeeds or fails.
This topic covers CI/CD pipeline configuration. For other CI/CD configuration information, see:
- [GitLab CI/CD Variables](../variables/README.md), for configuring the environment the pipelines run in.
- [GitLab Runner advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html), for configuring GitLab Runner.
We have complete examples of configuring pipelines:
- For a quick introduction to GitLab CI/CD, follow our [quick start guide](../quick_start/README.md).
- For a quick introduction to GitLab CI/CD, follow the [quick start guide](../quick_start/README.md).
- For a collection of examples, see [GitLab CI/CD Examples](../examples/README.md).
- To see a large `.gitlab-ci.yml` file used in an enterprise, see the [`.gitlab-ci.yml` file for `gitlab`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab-ci.yml).
> For some additional information about GitLab CI/CD:
>
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Watch the [CI/CD Ease of configuration](https://www.youtube.com/embed/opdLqwz6tcE) video.
> - Watch the [Making the case for CI/CD in your organization](https://about.gitlab.com/compare/github-actions-alternative/)
> webcast to learn the benefits of CI/CD and how to measure the results of CI/CD automation.
> - <i class="fa fa-youtube-play youtube" aria-hidden="true"></i>&nbsp;Learn how [Verizon reduced rebuilds](https://about.gitlab.com/blog/2019/02/14/verizon-customer-story/)
> from 30 days to under 8 hours with GitLab.
If you have a [mirrored repository that GitLab pulls from](../../user/project/repository/repository_mirroring.md#pulling-from-a-remote-repository),
you may need to enable pipeline triggering. Go to your project's **Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
## Introduction
Pipeline configuration begins with jobs. Jobs are the most fundamental element of a `.gitlab-ci.yml` file.
- To view a large `.gitlab-ci.yml` file used in an enterprise, see the [`.gitlab-ci.yml` file for `gitlab`](https://gitlab.com/gitlab-org/gitlab/blob/master/.gitlab-ci.yml).
Jobs are:
- Defined with constraints stating under what conditions they should be executed.
- Top-level elements with an arbitrary name and must contain at least the [`script`](#script) clause.
- Not limited in how many can be defined.
For example:
```yaml
job1:
script: "execute-script-for-job1"
job2:
script: "execute-script-for-job2"
```
The above example is the simplest possible CI/CD configuration with two separate
jobs, where each of the jobs executes a different command.
Of course a command can execute code directly (`./configure;make;make install`)
or run a script (`test.sh`) in the repository.
Jobs are picked up by [runners](../runners/README.md) and executed within the
environment of the runner. What is important is that each job is run
independently from each other.
### Validate the `.gitlab-ci.yml`
Each instance of GitLab CI/CD has an embedded debug tool called Lint, which validates the
content of your `.gitlab-ci.yml` files. You can find the Lint under the page `ci/lint` of your
While you are authoring your `.gitlab-ci.yml` file, you can validate it
by using the [CI Lint](../lint.md) tool.
project namespace. For example, `https://gitlab.example.com/gitlab-org/project-123/-/ci/lint`.
### Unavailable names for jobs
Each job must have a unique name, but there are a few **reserved `keywords` that
can't be used as job names**:
- `image`
- `services`
- `stages`
- `types`
- `before_script`
- `after_script`
- `variables`
- `cache`
- `include`
### Using reserved keywords
If you get validation error when using specific values (for example, `true` or `false`), try to:
- Quote them.
- Change them to a different form. For example, `/bin/true`.
## Job keywords
A job is defined as a list of keywords that define the job's behavior.
......@@ -130,10 +54,32 @@ The following table lists available keywords for jobs:
| [`variables`](#variables) | Define job variables on a job level. |
| [`when`](#when) | When to run job. Also available: `when:manual` and `when:delayed`. |
### Unavailable names for jobs
Each job must have a unique name, but there are a few **reserved `keywords` that
can't be used as job names**:
- `image`
- `services`
- `stages`
- `types`
- `before_script`
- `after_script`
- `variables`
- `cache`
- `include`
## Global keywords
Some keywords must be defined at a global level, affecting all jobs in the pipeline.
### Using reserved keywords
If you get validation error when using specific values (for example, `true` or `false`), try to:
- Quote them.
- Change them to a different form. For example, `/bin/true`.
### Global defaults
Some keywords can be set globally as the default for all jobs using the
......
......@@ -133,6 +133,7 @@ from:
- [Approval Rules](approval_rules.md)
- [Feature categorization](feature_categorization/index.md)
- [Wikis development guide](wikis.md)
- [Newlines style guide](newlines_styleguide.md)
## Performance guides
......
......@@ -59,10 +59,8 @@ TIP: **Tip:**
## Bundling a service with GitLab
NOTE: **Note:**
Code shipped with GitLab needs to use a license approved by the Legal team. See the list of [existing approved licenses](https://about.gitlab.com/handbook/engineering/open-source/#using-open-source-libraries).
NOTE: **Note:**
Notify the [Distribution team](https://about.gitlab.com/handbook/engineering/development/enablement/distribution/) when adding a new dependency that must be compiled. We must be able to compile the dependency on all supported platforms.
New services to be bundled with GitLab need to be available in the following environments.
......
......@@ -132,8 +132,23 @@ Non-nullable fields should only be used when a field is required, very unlikely
to become optional in the future, and very easy to calculate. An example would
be `id` fields.
A non-nullable GraphQL schema field is an object type followed by the exclamation point (bang) `!`. Here's an example from the `gitlab_schema.graphql` file:
```graphql
id: ProjectID!
```
Here's an example of a non-nullable GraphQL array:
```graphql
errors: [String!]!
```
Further reading:
- [GraphQL Best Practices Guide](https://graphql.org/learn/best-practices/#nullability).
- GraphQL documentation on [Object types and fields](https://graphql.org/learn/schema/#object-types-and-fields).
- [GraphQL Best Practices Guide](https://graphql.org/learn/best-practices/#nullability)
- [Using nullability in GraphQL](https://www.apollographql.com/blog/using-nullability-in-graphql-2254f84c4ed7)
......@@ -689,7 +704,7 @@ end
```
Fields can also be authorized against multiple abilities, in which case
all of ability checks must pass. **Note:** This requires explicitly
all of ability checks must pass. This requires explicitly
passing a block to `field`:
```ruby
......@@ -702,7 +717,6 @@ module Types
end
```
NOTE: **Note:**
If the field's type already [has a particular
authorization](#type-authorization) then there is no need to add that
same authorization to the field.
......
......@@ -33,7 +33,6 @@ It's recommended to create two separate migration script files.
add_column(:plan_limits, :project_hooks, :integer, default: 100, null: false)
```
NOTE: **Note:**
Plan limits entries set to `0` mean that limits are not enabled. You should
use this setting only in special and documented circumstances.
......@@ -64,7 +63,6 @@ It's recommended to create two separate migration script files.
end
```
NOTE: **Note:**
Some plans exist only on GitLab.com. This will be a no-op for plans
that do not exist.
......@@ -103,7 +101,6 @@ can be used to validate that a model does not exceed the limits. It ensures
that the count of the records for the current model does not exceed the defined
limit.
NOTE: **Note:**
You must specify the limit scope of the object being validated
and the limit name if it's different from the pluralized model name.
......@@ -152,5 +149,4 @@ GitLab.com:
- `silver` - Namespaces and projects with a Silver subscription
- `gold` - Namespaces and projects with a Gold subscription
NOTE: **Note:**
The test environment doesn't have any plans.
The `test` environment doesn't have any plans.
......@@ -20,7 +20,7 @@ feature to work.
NOTE: **Note:**
This is a living document and should be updated accordingly when parts
of the codebase touched in this document changed/removed or when new components
of the codebase touched in this document are changed or removed, or when new components
are added.
## Data Model
......
......@@ -687,7 +687,6 @@ Sidekiq is a Ruby background job processor that pulls jobs from the Redis queue
#### Puma
NOTE: **Note:**
Starting with GitLab 13.0, Puma is the default web server and Unicorn has been
disabled by default.
......@@ -705,7 +704,6 @@ disabled by default.
#### Unicorn
NOTE: **Note:**
Starting with GitLab 13.0, Puma is the default web server and Unicorn has been
disabled by default.
......@@ -1021,9 +1019,9 @@ PostgreSQL:
GitLab has configuration files located in `/home/git/gitlab/config/*`. Commonly referenced
configuration files include:
- `gitlab.yml` - GitLab configuration
- `puma.rb` - Puma web server settings
- `database.yml` - Database connection settings
- `gitlab.yml`: GitLab configuration
- `puma.rb`: Puma web server settings
- `database.yml`: Database connection settings
GitLab Shell has a configuration file at `/home/git/gitlab-shell/config.yml`.
......@@ -1039,9 +1037,12 @@ bundle exec rake gitlab:env:info RAILS_ENV=production
bundle exec rake gitlab:check RAILS_ENV=production
```
Note: It is recommended to log into the `git` user using `sudo -i -u git` or `sudo su - git`. While
the `sudo` commands provided by GitLab work in Ubuntu they do not always work in RHEL.
It's recommended to sign in to the `git` user using either `sudo -i -u git` or
`sudo su - git`. Although the `sudo` commands provided by GitLab work in Ubuntu,
they don't always work in RHEL.
## GitLab.com
We've also detailed [our architecture of GitLab.com](https://about.gitlab.com/handbook/engineering/infrastructure/production/architecture/) but this is probably over the top unless you have millions of users.
The [GitLab.com architecture](https://about.gitlab.com/handbook/engineering/infrastructure/production/architecture/)
is detailed for your reference, but this architecture is only useful if you have
millions of users.
......@@ -125,7 +125,6 @@ the `--ee` option:
bin/changelog --ee 'Hey DZ, I added a feature to GitLab!'
```
NOTE: **Note:**
All entries in the `CHANGELOG.md` file apply to all editions of GitLab.
Changelog updates are based on a common [GitLab codebase](https://gitlab.com/gitlab-org/gitlab/),
and are mirrored without proprietary code to [GitLab FOSS](https://gitlab.com/gitlab-org/gitlab-foss/) (also known as GitLab Community Edition).
......
......@@ -50,8 +50,7 @@ each endpoint can be set to `true`. This will run the chaos process in a Sidekiq
To simulate a memory leak in your application, use the `/-/chaos/leakmem` endpoint.
NOTE: **Note:**
The memory is not retained after the request finishes. Once the request has completed, the Ruby garbage collector will attempt to recover the memory.
The memory is not retained after the request finishes. After the request has completed, the Ruby garbage collector will attempt to recover the memory.
```plaintext
GET /-/chaos/leakmem
......@@ -145,8 +144,8 @@ curl http://localhost:3000/-/chaos/sleep?duration_s=60&token=secret
This endpoint will simulate the unexpected death of a worker process using a `kill` signal.
NOTE: **Note:**
Since this endpoint uses the `KILL` signal, the worker is not given a chance to cleanup or shutdown.
Because this endpoint uses the `KILL` signal, the worker isn't given an
opportunity to cleanup or shutdown.
```plaintext
GET /-/chaos/kill
......
......@@ -390,8 +390,7 @@ When ready to merge:
- When you set the MR to "Merge When Pipeline Succeeds", you should take over
subsequent revisions for anything that would be spotted after that.
NOTE: **Note:**
Thanks to "Pipeline for Merged Results", authors won't have to rebase their
Thanks to **Pipeline for Merged Results**, authors won't have to rebase their
branch as frequently anymore (only when there are conflicts) since the Merge
Results Pipeline will already incorporate the latest changes from `master`.
This results in faster review/merge cycles since maintainers don't have to ask
......
......@@ -99,7 +99,6 @@ Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCol
No more files will be rendered at all if 5 megabytes have already been rendered.
NOTE: **Note:**
All collection limit parameters are currently sent and applied on Gitaly. That is, once the limit is surpassed,
Gitaly will only return the safe amount of data to be persisted on `merge_request_diff_files`.
......@@ -114,7 +113,6 @@ That is, it's equivalent to 10kb if the maximum allowed value is 100kb.
The diff will still be persisted and expandable if the patch size doesn't
surpass `ApplicationSettings#diff_max_patch_bytes`.
NOTE: **Note:**
Although this nomenclature (Collapsing) is also used on Gitaly, this limit is only used on GitLab (hardcoded - not sent to Gitaly).
Gitaly will only return `Diff.Collapsed` (RPC) when surpassing collection limits.
......@@ -129,7 +127,6 @@ Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines
File diff will be suppressed (technically different from collapsed, but behaves the same, and is expandable) if it has more than 5000 lines.
NOTE: **Note:**
This limit is currently hardcoded and only applied on GitLab.
## Viewers
......
......@@ -186,7 +186,7 @@ variables for all GitLab processes, including Workhorse, Gitaly, Rails, and Side
### 3. Start the GitLab application
Once the `GITLAB_TRACING` environment variable is exported to all GitLab services, start the
After the `GITLAB_TRACING` environment variable is exported to all GitLab services, start the
application.
When `GITLAB_TRACING` is configured properly, the application will log this on startup:
......
......@@ -30,7 +30,6 @@ See how to document them below, according to the state of the flag:
- [Features that can be enabled or disabled for a single project](#features-enabled-by-project).
- [Features with the feature flag removed](#features-with-flag-removed).
NOTE: **Note:**
The [`**(CORE ONLY)**`](styleguide.md#product-badges) badge or equivalent for
the feature's tier should be added to the line and heading that refers to
enabling/disabling feature flags as Admin access is required to do so,
......
......@@ -70,7 +70,6 @@ With these groups in mind, the following are general rules for where new items s
- Other documentation belongs at the top-level, but care must be taken to not create an enormously
long top-level navigation, which defeats the purpose of it.
NOTE: **Note:**
Making all documentation and navigation items adhere to these principles is being progressively
rolled out.
......@@ -117,7 +116,6 @@ for clarity.
To see the improvements planned, check the
[global nav epic](https://gitlab.com/groups/gitlab-com/-/epics/21).
NOTE: **Note:**
**Do not** [add items](#adding-new-items) to the global nav without
the consent of one of the technical writers.
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment