Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-11-02 09:09:49 +00:00
parent e55fd66eae
commit 157c4d9279
52 changed files with 368 additions and 280 deletions

View File

@ -2,10 +2,10 @@ import { __ } from '~/locale';
export const INTRO_COOKIE_KEY = 'dev_ops_report_intro_callout_dismissed';
export const INTRO_BANNER_TITLE = __('Introducing Your DevOps Report');
export const INTRO_BANNER_TITLE = __('Introducing Your DevOps Reports');
export const INTRO_BANNER_BODY = __(
'Your DevOps Report gives an overview of how you are using GitLab from a feature perspective. Use it to view how you compare with other organizations.',
'Your DevOps Reports give an overview of how you are using GitLab from a feature perspective. Use them to view how you compare with other organizations, and how your teams compare against each other.',
);
export const INTRO_BANNER_ACTION_TEXT = __('Read more');

View File

@ -1,4 +1,5 @@
import { sortBy, cloneDeep } from 'lodash';
import { isGid } from '~/graphql_shared/utils';
import { ListType, MilestoneIDs } from './constants';
export function getMilestone() {
@ -95,6 +96,9 @@ export function fullMilestoneId(id) {
}
export function fullLabelId(label) {
if (isGid(label.id)) {
return label.id;
}
if (label.project_id && label.project_id !== null) {
return `gid://gitlab/ProjectLabel/${label.id}`;
}

View File

@ -54,6 +54,9 @@ export default {
allowLabelEdit: {
default: false,
},
labelsFilterBasePath: {
default: '',
},
},
inheritAttrs: false,
computed: {
@ -90,6 +93,11 @@ export default {
labelType() {
return this.isGroupBoard ? LabelType.group : LabelType.project;
},
labelsFilterPath() {
return this.isGroupBoard
? this.labelsFilterBasePath.replace(':project_path', this.projectPathForActiveIssue)
: this.labelsFilterBasePath;
},
},
methods: {
...mapActions([
@ -212,7 +220,7 @@ export default {
:footer-create-label-title="createLabelTitle"
:footer-manage-label-title="manageLabelTitle"
:labels-create-title="createLabelTitle"
:labels-filter-base-path="projectPathForActiveIssue"
:labels-filter-base-path="labelsFilterPath"
:attr-workspace-path="attrWorkspacePath"
workspace-type="project"
:issuable-type="issuableType"

View File

@ -57,39 +57,16 @@ export default {
type: Boolean,
required: true,
},
labelsPath: {
type: String,
required: true,
},
labelsWebUrl: {
type: String,
required: true,
},
scopedIssueBoardFeatureEnabled: {
type: Boolean,
required: false,
default: false,
},
projectId: {
type: Number,
required: false,
default: 0,
},
groupId: {
type: Number,
required: false,
default: 0,
},
weights: {
type: Array,
required: false,
default: () => [],
},
enableScopedLabels: {
type: Boolean,
required: false,
default: false,
},
currentBoard: {
type: Object,
required: true,
@ -288,16 +265,7 @@ export default {
this.board.iteration_id = iterationId;
},
setBoardLabels(labels) {
labels.forEach((label) => {
if (label.set && !this.board.labels.find((l) => l.id === label.id)) {
this.board.labels.push({
...label,
textColor: label.text_color,
});
} else if (!label.set) {
this.board.labels = this.board.labels.filter((selected) => selected.id !== label.id);
}
});
this.board.labels = labels;
},
setAssignee(assigneeId) {
this.$set(this.board, 'assignee', {
@ -371,11 +339,6 @@ export default {
:collapse-scope="isNewForm"
:board="board"
:can-admin-board="canAdminBoard"
:labels-path="labelsPath"
:labels-web-url="labelsWebUrl"
:enable-scoped-labels="enableScopedLabels"
:project-id="projectId"
:group-id="groupId"
:weights="weights"
@set-iteration="setIteration"
@set-board-labels="setBoardLabels"

View File

@ -64,22 +64,6 @@ export default {
type: Boolean,
required: true,
},
labelsPath: {
type: String,
required: true,
},
labelsWebUrl: {
type: String,
required: true,
},
projectId: {
type: Number,
required: true,
},
groupId: {
type: Number,
required: true,
},
scopedIssueBoardFeatureEnabled: {
type: Boolean,
required: true,
@ -88,11 +72,6 @@ export default {
type: Array,
required: true,
},
enabledScopedLabels: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
@ -354,14 +333,9 @@ export default {
<board-form
v-if="currentPage"
:labels-path="labelsPath"
:labels-web-url="labelsWebUrl"
:project-id="projectId"
:group-id="groupId"
:can-admin-board="canAdminBoard"
:scoped-issue-board-feature-enabled="scopedIssueBoardFeatureEnabled"
:weights="weights"
:enable-scoped-labels="enabledScopedLabels"
:current-board="currentBoard"
:current-page="currentPage"
@cancel="cancel"

View File

@ -142,5 +142,7 @@ export default () => {
fullPath: $boardApp.dataset.fullPath,
rootPath: $boardApp.dataset.boardsEndpoint,
recentBoardsEndpoint: $boardApp.dataset.recentBoardsEndpoint,
allowScopedLabels: $boardApp.dataset.scopedLabels,
labelsManagePath: $boardApp.dataset.labelsManagePath,
});
};

View File

@ -13,6 +13,7 @@ const apolloProvider = new VueApollo({
export default (params = {}) => {
const boardsSwitcherElement = document.getElementById('js-multiple-boards-switcher');
const { dataset } = boardsSwitcherElement;
return new Vue({
el: boardsSwitcherElement,
components: {
@ -24,18 +25,17 @@ export default (params = {}) => {
fullPath: params.fullPath,
rootPath: params.rootPath,
recentBoardsEndpoint: params.recentBoardsEndpoint,
allowScopedLabels: params.allowScopedLabels,
labelsManagePath: params.labelsManagePath,
allowLabelCreate: parseBoolean(dataset.canAdminBoard),
},
data() {
const { dataset } = boardsSwitcherElement;
const boardsSelectorProps = {
...dataset,
currentBoard: JSON.parse(dataset.currentBoard),
hasMissingBoards: parseBoolean(dataset.hasMissingBoards),
canAdminBoard: parseBoolean(dataset.canAdminBoard),
multipleIssueBoardsAvailable: parseBoolean(dataset.multipleIssueBoardsAvailable),
projectId: dataset.projectId ? Number(dataset.projectId) : 0,
groupId: Number(dataset.groupId),
scopedIssueBoardFeatureEnabled: parseBoolean(dataset.scopedIssueBoardFeatureEnabled),
weights: JSON.parse(dataset.weights),
};

View File

@ -3,6 +3,7 @@
query usersSearch($search: String!, $fullPath: ID!) {
workspace: group(fullPath: $fullPath) {
id
users: groupMembers(search: $search, relations: [DIRECT, INHERITED]) {
nodes {
user {

View File

@ -1,5 +1,6 @@
query searchProjectMembers($fullPath: ID!, $search: String) {
project(fullPath: $fullPath) {
id
projectMembers(search: $search) {
nodes {
user {

View File

@ -37,6 +37,7 @@ import epicLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widge
import updateEpicLabelsMutation from '~/vue_shared/components/sidebar/labels_select_widget/graphql/epic_update_labels.mutation.graphql';
import groupLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/group_labels.query.graphql';
import issueLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/issue_labels.query.graphql';
import mergeRequestLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/merge_request_labels.query.graphql';
import projectLabelsQuery from '~/vue_shared/components/sidebar/labels_select_widget/graphql/project_labels.query.graphql';
import getAlertAssignees from '~/vue_shared/components/sidebar/queries/get_alert_assignees.query.graphql';
import getIssueAssignees from '~/vue_shared/components/sidebar/queries/get_issue_assignees.query.graphql';
@ -128,6 +129,7 @@ export const issuableLabelsQueries = {
mutationName: 'updateIssue',
},
[IssuableType.MergeRequest]: {
issuableQuery: mergeRequestLabelsQuery,
mutation: updateMergeRequestLabelsMutation,
mutationName: 'mergeRequestSetLabels',
},

View File

@ -260,6 +260,10 @@ export function mountSidebarLabels() {
variant: DropdownVariant.Sidebar,
canUpdate: parseBoolean(el.dataset.canEdit),
isClassicSidebar: true,
issuableType:
isInIssuePage() || isInIncidentPage() || isInDesignPage()
? IssuableType.Issue
: IssuableType.MergeRequest,
},
render: (createElement) => createElement(SidebarLabels),
});

View File

@ -2,6 +2,7 @@ mutation mergeRequestSetLabels($input: MergeRequestSetLabelsInput!) {
mergeRequestSetLabels(input: $input) {
errors
mergeRequest {
id
labels {
nodes {
color

View File

@ -45,7 +45,7 @@ export default {
default: false,
},
selected: {
type: Object,
type: [Object, Array],
required: false,
default: () => {},
},
@ -54,6 +54,11 @@ export default {
required: false,
default: '',
},
allowMultiselect: {
type: Boolean,
required: false,
default: false,
},
},
computed: {
isSearchEmpty() {
@ -66,8 +71,14 @@ export default {
methods: {
selectOption(option) {
this.$emit('set-option', option || null);
if (!this.allowMultiselect) {
this.$refs.dropdown.hide();
}
},
isSelected(option) {
if (Array.isArray(this.selected)) {
return this.selected.some((label) => label.title === option.title);
}
return (
this.selected &&
((option.name && this.selected.name === option.name) ||
@ -78,7 +89,7 @@ export default {
this.$refs.dropdown.show();
},
setFocus() {
this.$refs.search.focusInput();
this.$refs.search?.focusInput();
},
setSearchTerm(search) {
this.$emit('set-search', search);
@ -108,56 +119,60 @@ export default {
@shown="setFocus"
>
<template #header>
<gl-search-box-by-type
ref="search"
:value="searchTerm"
:placeholder="searchText"
class="js-dropdown-input-field"
@input="setSearchTerm"
/>
<slot name="header">
<gl-search-box-by-type
ref="search"
:value="searchTerm"
:placeholder="searchText"
class="js-dropdown-input-field"
@input="setSearchTerm"
/>
</slot>
</template>
<gl-dropdown-form class="gl-relative gl-min-h-7">
<gl-loading-icon
v-if="isLoading"
size="md"
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
/>
<template v-else>
<template v-if="isSearchEmpty && presetOptions.length > 0">
<slot name="default">
<gl-dropdown-form class="gl-relative gl-min-h-7">
<gl-loading-icon
v-if="isLoading"
size="md"
class="gl-absolute gl-left-0 gl-top-0 gl-right-0"
/>
<template v-else>
<template v-if="isSearchEmpty && presetOptions.length > 0">
<gl-dropdown-item
v-for="option in presetOptions"
:key="option.id"
:is-checked="isSelected(option)"
:is-check-centered="true"
:is-check-item="true"
@click.native.capture.stop="selectOption(option)"
>
<slot name="preset-item" :item="option">
{{ option.title }}
</slot>
</gl-dropdown-item>
<gl-dropdown-divider />
</template>
<gl-dropdown-item
v-for="option in presetOptions"
v-for="option in options"
:key="option.id"
:is-checked="isSelected(option)"
:is-check-centered="true"
:is-check-item="true"
@click="selectOption(option)"
:avatar-url="avatarUrl(option)"
:secondary-text="secondaryText(option)"
data-testid="unselected-option"
@click.native.capture.stop="selectOption(option)"
>
<slot name="preset-item" :item="option">
<slot name="item" :item="option">
{{ option.title }}
</slot>
</gl-dropdown-item>
<gl-dropdown-divider />
<gl-dropdown-item v-if="noOptionsFound" class="gl-pl-6!">
{{ $options.i18n.noMatchingResults }}
</gl-dropdown-item>
</template>
<gl-dropdown-item
v-for="option in options"
:key="option.id"
:is-checked="isSelected(option)"
:is-check-centered="true"
:is-check-item="true"
:avatar-url="avatarUrl(option)"
:secondary-text="secondaryText(option)"
data-testid="unselected-option"
@click="selectOption(option)"
>
<slot name="item" :item="option">
{{ option.title }}
</slot>
</gl-dropdown-item>
<gl-dropdown-item v-if="noOptionsFound" class="gl-pl-6!">
{{ $options.i18n.noMatchingResults }}
</gl-dropdown-item>
</template>
</gl-dropdown-form>
</gl-dropdown-form>
</slot>
<template #footer>
<slot name="footer"></slot>
</template>

View File

@ -53,10 +53,6 @@ export default {
type: String,
required: true,
},
issuableType: {
type: String,
required: true,
},
isVisible: {
type: Boolean,
required: false,
@ -209,7 +205,6 @@ export default {
v-model="localSelectedLabels"
:search-key="searchKey"
:allow-multiselect="allowMultiselect"
:issuable-type="issuableType"
:full-path="fullPath"
:workspace-type="workspaceType"
:attr-workspace-path="attrWorkspacePath"

View File

@ -32,11 +32,6 @@ export default {
type: String,
required: true,
},
issuableType: {
type: String,
required: false,
default: undefined,
},
workspaceType: {
type: String,
required: true,

View File

@ -23,10 +23,6 @@ export default {
type: Boolean,
required: true,
},
issuableType: {
type: String,
required: true,
},
localSelectedLabels: {
type: Array,
required: true,
@ -119,13 +115,7 @@ export default {
({ id }) => id !== getIdFromGraphQLId(label.id) && id !== label.id,
);
} else {
labels = [
...this.localSelectedLabels,
{
...label,
id: getIdFromGraphQLId(label.id),
},
];
labels = [...this.localSelectedLabels, label];
}
this.$emit('input', labels);
},

View File

@ -39,7 +39,7 @@ export default {
},
methods: {
focusInput() {
this.$refs.searchInput.focusInput();
this.$refs.searchInput?.focusInput();
},
},
};

View File

@ -1,7 +1,6 @@
<script>
import { GlLabel } from '@gitlab/ui';
import { sortBy } from 'lodash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { isScopedLabel } from '~/lib/utils/common_utils';
export default {
@ -47,7 +46,7 @@ export default {
return this.allowScopedLabels && isScopedLabel(label);
},
removeLabel(labelId) {
this.$emit('onLabelRemove', getIdFromGraphQLId(labelId));
this.$emit('onLabelRemove', labelId);
},
},
};

View File

@ -2,7 +2,8 @@
query groupLabels($fullPath: ID!, $searchTerm: String) {
workspace: group(fullPath: $fullPath) {
labels(searchTerm: $searchTerm, onlyGroupLabels: true) {
id
labels(searchTerm: $searchTerm, onlyGroupLabels: true, includeAncestorGroups: true) {
nodes {
...Label
}

View File

@ -0,0 +1,14 @@
#import "~/graphql_shared/fragments/label.fragment.graphql"
query mergeRequestLabels($fullPath: ID!, $iid: String!) {
workspace: project(fullPath: $fullPath) {
issuable: mergeRequest(iid: $iid) {
id
labels {
nodes {
...Label
}
}
}
}
}

View File

@ -2,6 +2,7 @@
query projectLabels($fullPath: ID!, $searchTerm: String) {
workspace: project(fullPath: $fullPath) {
id
labels(searchTerm: $searchTerm, includeAncestorGroups: true) {
nodes {
...Label

View File

@ -207,7 +207,7 @@ export default {
return {
iid: currentIid,
groupPath: this.fullPath,
addLabelIds: labelIds,
addLabelIds: labelIds.map((id) => getIdFromGraphQLId(id)),
removeLabelIds: this.issuableLabelIds
.filter((id) => !labelIds.includes(id))
.map((id) => getIdFromGraphQLId(id)),
@ -232,8 +232,8 @@ export default {
}
this.$emit('updateSelectedLabels', {
id: data[mutationName]?.[this.issuableType].id,
labels: data[mutationName]?.[this.issuableType].labels?.nodes,
id: data[mutationName]?.[this.issuableType]?.id,
labels: data[mutationName]?.[this.issuableType]?.labels?.nodes,
});
})
.catch((error) =>
@ -268,7 +268,7 @@ export default {
case IssuableType.Epic:
return {
iid: this.iid,
removeLabelIds: [labelId],
removeLabelIds: [getIdFromGraphQLId(labelId)],
groupPath: this.fullPath,
};
default:
@ -341,7 +341,6 @@ export default {
:labels-create-title="labelsCreateTitle"
:selected-labels="issuableLabels"
:variant="variant"
:issuable-type="issuableType"
:is-visible="edit"
:full-path="fullPath"
:workspace-type="workspaceType"
@ -364,7 +363,6 @@ export default {
:labels-create-title="labelsCreateTitle"
:selected-labels="issuableLabels"
:variant="variant"
:issuable-type="issuableType"
:full-path="fullPath"
:workspace-type="workspaceType"
:attr-workspace-path="attrWorkspacePath"

View File

@ -470,6 +470,10 @@
.labels-select-wrapper.is-embedded .labels-select-wrapper.is-embedded {
width: auto;
}
.show.dropdown .dropdown-menu {
@include gl-w-full;
}
}
.board-header-collapsed-info-icon:hover {

View File

@ -42,6 +42,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:restructured_mr_widget, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_changes_fluid_layout, project, default_enabled: :yaml)
push_frontend_feature_flag(:mr_attention_requests, project, default_enabled: :yaml)
push_frontend_feature_flag(:labels_widget, project, default_enabled: :yaml)
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)

View File

@ -30,7 +30,7 @@ module Autocomplete
class NamespacesOnly < self
def routables
return Namespace.all if current_user.admin?
return Namespace.without_project_namespaces if current_user.admin?
current_user.namespaces
end

View File

@ -43,7 +43,7 @@ module BoardsHelper
def build_issue_link_base
if board.group_board?
"#{group_path(@board.group)}/:project_path/issues"
"/:project_path/-/issues"
else
project_issues_path(@project)
end

View File

@ -41,9 +41,11 @@ module LoadedInGroupList
namespaces = Namespace.arel_table
children = namespaces.alias('children')
# TODO 6473: remove the filtering of the Namespaces::ProjectNamespace see https://gitlab.com/groups/gitlab-org/-/epics/6473
namespaces.project(Arel.star.count.as('preloaded_subgroup_count'))
.from(children)
.where(children[:parent_id].eq(namespaces[:id]))
.where(children[:type].is_distinct_from(Namespaces::ProjectNamespace.sti_name))
end
def member_count_sql

View File

@ -1,4 +1,4 @@
- page_title _('DevOps Report')
- page_title _('DevOps Reports')
- add_page_specific_style 'page_bundles/dev_ops_report'
.container

View File

@ -66,9 +66,9 @@
= _('Analytics')
%li.divider.fly-out-top-item
= nav_link(controller: :dev_ops_report) do
= link_to admin_dev_ops_report_path, title: _('DevOps Report') do
= link_to admin_dev_ops_report_path, title: _('DevOps Reports') do
%span
= _('DevOps Report')
= _('DevOps Reports')
= nav_link(controller: :usage_trends) do
= link_to admin_usage_trends_path, title: _('Usage Trends') do
%span

View File

@ -9,9 +9,5 @@
has_missing_boards: (!multiple_boards_available? && current_board_parent.boards.size > 1).to_s,
can_admin_board: can?(current_user, :admin_issue_board, parent).to_s,
multiple_issue_boards_available: parent.multiple_issue_boards_available?.to_s,
labels_path: labels_filter_path_with_defaults(only_group_labels: true, include_descendant_groups: true),
labels_web_url: parent.is_a?(Project) ? project_labels_path(@project) : group_labels_path(@group),
project_id: @project&.id,
group_id: @group&.id,
scoped_issue_board_feature_enabled: Gitlab.ee? && parent.feature_available?(:scoped_issue_board) ? 'true' : 'false',
weights: weights.to_json } }

View File

@ -131,12 +131,13 @@ and we recommend you use:
The following table lists basic ports that must be open between the **primary** and **secondary** sites for Geo.
| **Primary** site | **Secondary** site | Protocol |
|:-----------------|:-------------------|:-------------|
| 80 | 80 | HTTP |
| 443 | 443 | TCP or HTTPS |
| 22 | 22 | TCP |
| 5432 | | PostgreSQL |
| Source site | Source port | Destination site | Destination port | Protocol |
|-------------|-------------|------------------|------------------|-------------|
| Primary | Any | Secondary | 80 | TCP (HTTP) |
| Primary | Any | Secondary | 443 | TCP (HTTPS) |
| Secondary | Any | Primary | 80 | TCP (HTTP) |
| Secondary | Any | Primary | 443 | TCP (HTTPS) |
| Secondary | Any | Primary | 5432 | TCP |
See the full list of ports used by GitLab in [Package defaults](../package_information/defaults.md)

View File

@ -25,3 +25,5 @@ To enable the Puma exporter:
for the changes to take effect.
Prometheus begins collecting performance data from the Puma exporter exposed at `localhost:8083`.
For more information on using Puma with GitLab, see [Puma](../../operations/puma.md).

View File

@ -76,7 +76,13 @@ Mattermost. See the [full table for a complete list](#storage-specific-configura
However, backups can be configured with [server side encryption](../raketasks/backup_restore.md#s3-encrypted-buckets) separately.
Enabling consolidated object storage enables object storage for all object
types. If you want to use local storage for specific object types, you can
types. If not all buckets are specified, `sudo gitlab-ctl reconfigure` may fail with the error like:
```plaintext
Object storage for <object type> must have a bucket specified
```
If you want to use local storage for specific object types, you can
[selectively disable object storages](#selectively-disabling-object-storage).
Most types of objects, such as CI artifacts, LFS files, upload

View File

@ -224,3 +224,8 @@ in Puma when using the Linux package, and which ones have no corresponding count
| `unicorn['exporter_enabled']` | `puma['exporter_enabled']` |
| `unicorn['exporter_address']` | `puma['exporter_address']` |
| `unicorn['exporter_port']` | `puma['exporter_port']` |
## Puma exporter
You can use the Puma exporter to measure various Puma metrics. For more information, see
[Puma exporter](../monitoring/prometheus/puma_exporter.md).

View File

@ -168,7 +168,7 @@ their color is `#428BCA`.
`<Category Name>` is the category name as it is in the single source of truth for categories at
<https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml>.
For instance, the "DevOps Report" category is represented by the
For instance, the "DevOps Reports" category is represented by the
~"Category:DevOps Reports" label in the `gitlab-org` group since its
`devops_reports.name` value is "DevOps Reports".

View File

@ -55,7 +55,7 @@ We use the following terminology to describe the Service Ping components:
- The main purpose of Service Ping is to build a better GitLab. Data about how GitLab is used is collected to better understand feature/stage adoption and usage, which helps us understand how GitLab is adding value and helps our team better understand the reasons why people use GitLab and with this knowledge we're able to make better product decisions.
- As a benefit of having Service Ping active, GitLab lets you analyze the users' activities over time of your GitLab installation.
- As a benefit of having Service Ping active, GitLab provides you with The DevOps Report,which gives you an overview of your entire instance's adoption of Concurrent DevOps from planning to monitoring.
- As a benefit of having Service Ping active, GitLab provides you with [DevOps Score](../../user/admin_area/analytics/dev_ops_report.md#devops-score), which gives you an overview of your entire instance's adoption of Concurrent DevOps from planning to monitoring.
- You get better, more proactive support. (assuming that our TAMs and support organization used the data to deliver more value)
- You get insight and advice into how to get the most value out of your investment in GitLab. Wouldn't you want to know that a number of features or values are not being adopted in your organization?
- You get a report that illustrates how you compare against other similar organizations (anonymized), with specific advice and recommendations on how to improve your DevOps processes.
@ -186,7 +186,7 @@ sequenceDiagram
S3 Bucket->>Snowflake DW: Import data
Snowflake DW->>Snowflake DW: Transform data using dbt
Snowflake DW->>Sisense Dashboards: Data available for querying
Versions Application->>GitLab Instance: DevOps Report (Conversational Development Index)
Versions Application->>GitLab Instance: DevOps Score (Conversational Development Index)
```
## How Service Ping works

View File

@ -4,21 +4,21 @@ group: Optimize
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
---
# DevOps Report **(FREE SELF)**
# DevOps Reports **(FREE SELF)**
> [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/20976) from Conversational Development Index in GitLab 12.6.
The DevOps Report gives you an overview of your entire instance's adoption of
DevOps Reports give you an overview of your entire instance's adoption of
[Concurrent DevOps](https://about.gitlab.com/topics/concurrent-devops/)
from planning to monitoring.
To see DevOps Report:
To see DevOps Reports:
1. On the top bar, select **Menu > Admin**.
1. On the left sidebar, select **Analytics > DevOps Report**.
1. On the left sidebar, select **Analytics > DevOps Reports**.
## DevOps Score
> [Renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/20976) from Conversational Development Index in GitLab 12.6.
NOTE:
To see the DevOps score, you must activate your GitLab instance's [Service Ping](../settings/usage_statistics.md#service-ping). This is because DevOps Score is a comparative tool, so your score data must be centrally processed by GitLab Inc. first.
@ -72,4 +72,4 @@ DevOps Adoption allows you to:
- Identify specific groups that are lagging in their adoption of GitLab, so you can help them along in their DevOps journey.
- Find the groups that have adopted certain features, and can provide guidance to other groups on how to use those features.
![DevOps Report](img/admin_devops_adoption_v14_2.png)
![DevOps Adoption](img/admin_devops_adoption_v14_2.png)

View File

@ -15,5 +15,5 @@ Administrators have access to instance-wide analytics:
There are several kinds of statistics:
- [DevOps Report](dev_ops_report.md): Provides an overview of your entire instance's feature usage.
- [DevOps Reports](dev_ops_report.md): Provides an overview of your entire instance's feature usage.
- [Usage Trends](usage_trends.md): Shows how much data your instance contains, and how that is changing.

View File

@ -48,7 +48,7 @@ With DevOps Adoption you can:
- Identify specific subgroups that are lagging in their adoption of GitLab, so you can help them along in their DevOps journey.
- Find the subgroups that have adopted certain features, and can provide guidance to other subgroups on how to use those features.
![DevOps Report](img/group_devops_adoption_v14_2.png)
![DevOps Adoption](img/group_devops_adoption_v14_2.png)
Feature adoption is based on usage in the previous calendar month. Data is updated on the first day
of each month. If the monthly update fails, it tries again daily until successful.

View File

@ -16,15 +16,13 @@ Use a `.gitlab-ci.yml` template when you have an existing project that you want
Your GitLab repository should contain files specific to an SSG, or plain HTML. After you complete
these steps, you may have to do additional configuration for the Pages site to generate properly.
1. On the left sidebar, select **Project information**.
1. Click **Set up CI/CD**. If this button is not available, CI/CD is already configured for your project.
You may want to browse the `.gitlab-ci.yml` files [in these projects instead](https://gitlab.com/pages).
1. From the **Apply a template** list, choose a template for the SSG you're using.
You can also choose plain HTML. If you don't find a corresponding template, you can view the
[GitLab Pages group of sample projects](https://gitlab.com/pages).
These projects contain `.gitlab-ci.yml` files that you can modify for your needs.
You can also [learn how to write your own `.gitlab-ci.yml` file for GitLab Pages](pages_from_scratch.md).
1. Save and commit the `.gitlab-ci.yml` file.
1. On the top bar, select **Menu > Projects** and find your project.
1. On the left sidebar, select the project's name.
1. From the **Add** (**{plus}**) dropdown, select **New file**.
1. From the **Select a template type** dropdown, select `.gitlab-ci.yml`.
1. From the **Apply a template** dropdown, in the **Pages** section, select the name of your SSG.
1. In the **Commit message** box, type the commit message.
1. Select **Commit changes**.
If everything is configured correctly, the site can take approximately 30 minutes to deploy.

View File

@ -5491,6 +5491,9 @@ msgstr ""
msgid "BoardScope|An error occurred while getting milestones, please try again."
msgstr ""
msgid "BoardScope|An error occurred while searching for labels, please try again."
msgstr ""
msgid "BoardScope|An error occurred while searching for users, please try again."
msgstr ""
@ -5500,12 +5503,21 @@ msgstr ""
msgid "BoardScope|Any assignee"
msgstr ""
msgid "BoardScope|Any label"
msgstr ""
msgid "BoardScope|Assignee"
msgstr ""
msgid "BoardScope|Choose labels"
msgstr ""
msgid "BoardScope|Edit"
msgstr ""
msgid "BoardScope|Labels"
msgstr ""
msgid "BoardScope|Milestone"
msgstr ""
@ -5518,6 +5530,9 @@ msgstr ""
msgid "BoardScope|Select assignee"
msgstr ""
msgid "BoardScope|Select labels"
msgstr ""
msgid "BoardScope|Select milestone"
msgstr ""
@ -11765,7 +11780,7 @@ msgstr ""
msgid "DevOps Adoption"
msgstr ""
msgid "DevOps Report"
msgid "DevOps Reports"
msgstr ""
msgid "DevOps adoption"
@ -18629,7 +18644,7 @@ msgstr ""
msgid "Interval Pattern"
msgstr ""
msgid "Introducing Your DevOps Report"
msgid "Introducing Your DevOps Reports"
msgstr ""
msgid "Invalid Insights config file detected"
@ -39603,7 +39618,7 @@ msgstr ""
msgid "Your CSV import for project"
msgstr ""
msgid "Your DevOps Report gives an overview of how you are using GitLab from a feature perspective. Use it to view how you compare with other organizations."
msgid "Your DevOps Reports give an overview of how you are using GitLab from a feature perspective. Use them to view how you compare with other organizations, and how your teams compare against each other."
msgstr ""
msgid "Your GPG keys (%{count})"

View File

@ -0,0 +1,57 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Autocomplete::RoutesFinder do
describe '#execute' do
let_it_be(:user) { create(:user, username: 'user_path') }
let_it_be(:admin) { create(:admin) }
let_it_be(:group) { create(:group, path: 'path1') }
let_it_be(:group2) { create(:group, path: 'path2') }
let_it_be(:group3) { create(:group, path: 'not-matching') }
let_it_be(:project) { create(:project, path: 'path3', namespace: user.namespace) }
let_it_be(:project2) { create(:project, path: 'path4') }
let_it_be(:project_namespace) { create(:project_namespace, parent: group, path: 'path5') }
let(:current_user) { user }
let(:search) { 'path' }
before do
group.add_owner(user)
end
context 'for NamespacesOnly' do
subject { Autocomplete::RoutesFinder::NamespacesOnly.new(current_user, search: search).execute }
let(:user_route) { Route.find_by_path(user.username) }
it 'finds only user namespace and groups matching the search excluding project namespaces' do
is_expected.to match_array([group.route, user_route])
end
context 'when user is admin' do
let(:current_user) { admin }
it 'finds all namespaces matching the search excluding project namespaces' do
is_expected.to match_array([group.route, group2.route, user_route])
end
end
end
context 'for ProjectsOnly' do
subject { Autocomplete::RoutesFinder::ProjectsOnly.new(current_user, search: 'path').execute }
it 'finds only matching projects the user has access to' do
is_expected.to match_array([project.route])
end
context 'when user is admin' do
let(:current_user) { admin }
it 'finds all projects matching the search' do
is_expected.to match_array([project.route, project2.route])
end
end
end
end
end

View File

@ -1,7 +1,6 @@
import { GlModal } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import BoardForm from '~/boards/components/board_form.vue';
@ -31,8 +30,6 @@ const currentBoard = {
const defaultProps = {
canAdminBoard: false,
labelsPath: `${TEST_HOST}/labels/path`,
labelsWebUrl: `${TEST_HOST}/-/labels`,
currentBoard,
currentPage: '',
};

View File

@ -78,10 +78,6 @@ describe('BoardsSelector', () => {
hasMissingBoards: false,
canAdminBoard: true,
multipleIssueBoardsAvailable: true,
labelsPath: `${TEST_HOST}/labels/path`,
labelsWebUrl: `${TEST_HOST}/labels`,
projectId: 42,
groupId: 19,
scopedIssueBoardFeatureEnabled: true,
weights: [],
},

View File

@ -12,6 +12,7 @@ export const boardObj = {
id: 1,
name: 'test',
milestone_id: null,
labels: [],
};
export const listObj = {
@ -609,3 +610,43 @@ export const mockTokens = (fetchLabels, fetchAuthors, fetchMilestones) => [
unique: true,
},
];
export const mockLabel1 = {
id: 'gid://gitlab/GroupLabel/121',
title: 'To Do',
color: '#F0AD4E',
textColor: '#FFFFFF',
description: null,
};
export const mockLabel2 = {
id: 'gid://gitlab/GroupLabel/122',
title: 'Doing',
color: '#F0AD4E',
textColor: '#FFFFFF',
description: null,
};
export const mockProjectLabelsResponse = {
data: {
workspace: {
id: 'gid://gitlab/Project/1',
labels: {
nodes: [mockLabel1, mockLabel2],
},
},
__typename: 'Project',
},
};
export const mockGroupLabelsResponse = {
data: {
workspace: {
id: 'gid://gitlab/Group/1',
labels: {
nodes: [mockLabel1, mockLabel2],
},
},
__typename: 'Group',
},
};

View File

@ -34,6 +34,7 @@ describe('DropdownWidget component', () => {
// invokes `show` method of BDropdown used inside GlDropdown.
// Context: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/54895#note_524281679
jest.spyOn(wrapper.vm, 'showDropdown').mockImplementation();
jest.spyOn(findDropdown().vm, 'hide').mockImplementation();
};
beforeEach(() => {
@ -67,10 +68,7 @@ describe('DropdownWidget component', () => {
});
it('emits set-option event when clicking on an option', async () => {
wrapper
.findAll('[data-testid="unselected-option"]')
.at(1)
.vm.$emit('click', new Event('click'));
wrapper.findAll('[data-testid="unselected-option"]').at(1).trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.emitted('set-option')).toEqual([[wrapper.props().options[1]]]);

View File

@ -10,7 +10,6 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import { IssuableType } from '~/issue_show/constants';
import { DEFAULT_DEBOUNCE_AND_THROTTLE_MS } from '~/lib/utils/constants';
import { DropdownVariant } from '~/vue_shared/components/sidebar/labels_select_widget/constants';
import DropdownContentsLabelsView from '~/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue';
@ -57,7 +56,6 @@ describe('DropdownContentsLabelsView', () => {
propsData: {
...initialState,
localSelectedLabels,
issuableType: IssuableType.Issue,
searchKey,
labelCreateType: 'project',
workspaceType: 'project',

View File

@ -39,7 +39,6 @@ describe('DropdownContent', () => {
footerManageLabelTitle: 'manage',
dropdownButtonText: 'Labels',
variant: 'sidebar',
issuableType: 'issue',
fullPath: 'test',
workspaceType: 'project',
labelCreateType: 'project',

View File

@ -92,6 +92,7 @@ export const createLabelSuccessfulResponse = {
export const workspaceLabelsQueryResponse = {
data: {
workspace: {
id: 'gid://gitlab/Project/126',
labels: {
nodes: [
{

View File

@ -23,14 +23,14 @@ RSpec.describe BoardsHelper do
it 'returns correct path for base group' do
assign(:board, group_board)
expect(helper.build_issue_link_base).to eq('/base/:project_path/issues')
expect(helper.build_issue_link_base).to eq('/:project_path/-/issues')
end
it 'returns correct path for subgroup' do
subgroup = create(:group, parent: base_group, path: 'sub')
assign(:board, create(:board, group: subgroup))
expect(helper.build_issue_link_base).to eq('/base/sub/:project_path/issues')
expect(helper.build_issue_link_base).to eq('/:project_path/-/issues')
end
end
end
@ -149,7 +149,7 @@ RSpec.describe BoardsHelper do
end
it 'returns correct path for base group' do
expect(helper.build_issue_link_base).to eq("/#{base_group.full_path}/:project_path/issues")
expect(helper.build_issue_link_base).to eq("/:project_path/-/issues")
end
it 'returns required label endpoints' do

View File

@ -3,49 +3,67 @@
require 'spec_helper'
RSpec.describe LoadedInGroupList do
let(:parent) { create(:group) }
let_it_be(:parent) { create(:group) }
let_it_be(:group) { create(:group, parent: parent) }
let_it_be(:project) { create(:project, namespace: parent) }
subject(:found_group) { Group.with_selects_for_list.find_by(id: parent.id) }
let(:archived_parameter) { nil }
before do
parent.add_developer(create(:user))
end
subject(:found_group) { Group.with_selects_for_list(archived: archived_parameter).find_by(id: parent.id) }
describe '.with_selects_for_list' do
it 'includes the preloaded counts for groups' do
create(:group, parent: parent)
create(:project, namespace: parent)
parent.add_developer(create(:user))
found_group = Group.with_selects_for_list.find_by(id: parent.id)
expect(found_group.preloaded_project_count).to eq(1)
expect(found_group.preloaded_subgroup_count).to eq(1)
expect(found_group.preloaded_member_count).to eq(1)
end
context 'with project namespaces' do
let_it_be(:group1) { create(:group, parent: parent) }
let_it_be(:group2) { create(:group, parent: parent) }
let_it_be(:project_namespace) { create(:project_namespace, project: project) }
it 'does not include project_namespaces in the count of subgroups' do
expect(found_group.preloaded_subgroup_count).to eq(3)
expect(parent.subgroup_count).to eq(3)
end
end
context 'with archived projects' do
let_it_be(:archived_project) { create(:project, namespace: parent, archived: true) }
let(:archived_parameter) { true }
it 'counts including archived projects when `true` is passed' do
create(:project, namespace: parent, archived: true)
create(:project, namespace: parent)
found_group = Group.with_selects_for_list(archived: 'true').find_by(id: parent.id)
expect(found_group.preloaded_project_count).to eq(2)
end
it 'counts only archived projects when `only` is passed' do
create_list(:project, 2, namespace: parent, archived: true)
create(:project, namespace: parent)
context 'when not counting archived projects' do
let(:archived_parameter) { false }
found_group = Group.with_selects_for_list(archived: 'only').find_by(id: parent.id)
it 'counts projects without archived ones' do
expect(found_group.preloaded_project_count).to eq(1)
end
end
expect(found_group.preloaded_project_count).to eq(2)
context 'with archived only' do
let_it_be(:archived_project2) { create(:project, namespace: parent, archived: true) }
let(:archived_parameter) { 'only' }
it 'counts only archived projects when `only` is passed' do
expect(found_group.preloaded_project_count).to eq(2)
end
end
end
end
describe '#children_count' do
it 'counts groups and projects' do
create(:group, parent: parent)
create(:project, namespace: parent)
expect(found_group.children_count).to eq(2)
end
end

View File

@ -6,7 +6,7 @@ RSpec.describe 'Database::PreventCrossDatabaseModification' do
let_it_be(:pipeline, refind: true) { create(:ci_pipeline) }
let_it_be(:project, refind: true) { create(:project) }
shared_examples 'succeessful examples' do
shared_examples 'successful examples' do
context 'outside transaction' do
it { expect { run_queries }.not_to raise_error }
end
@ -36,7 +36,7 @@ RSpec.describe 'Database::PreventCrossDatabaseModification' do
project.reload
end
include_examples 'succeessful examples'
include_examples 'successful examples'
end
context 'when only CI data is modified' do
@ -45,7 +45,7 @@ RSpec.describe 'Database::PreventCrossDatabaseModification' do
project.reload
end
include_examples 'succeessful examples'
include_examples 'successful examples'
end
context 'when other data is modified' do
@ -54,38 +54,42 @@ RSpec.describe 'Database::PreventCrossDatabaseModification' do
project.touch
end
include_examples 'succeessful examples'
include_examples 'successful examples'
end
describe 'with_cross_database_modification_prevented block' do
it 'raises error when CI and other data is modified' do
expect do
with_cross_database_modification_prevented do
Project.transaction do
project.touch
pipeline.touch
end
end
end.to raise_error /Cross-database data modification/
context 'when both CI and other data is modified' do
def run_queries
project.touch
pipeline.touch
end
it 'raises an error when an undefined gitlab_schema table is modified with another table' do
expect do
with_cross_database_modification_prevented do
Project.transaction do
context 'outside transaction' do
it { expect { run_queries }.not_to raise_error }
end
context 'when data modification happens in a transaction' do
it 'raises error' do
Project.transaction do
expect { run_queries }.to raise_error /Cross-database data modification/
end
end
context 'when data modification happens in nested transactions' do
it 'raises error' do
Project.transaction(requires_new: true) do
project.touch
project.connection.execute('UPDATE foo_bars_undefined_table SET a=1 WHERE id = -1')
Project.transaction(requires_new: true) do
expect { pipeline.touch }.to raise_error /Cross-database data modification/
end
end
end
end.to raise_error /Cross-database data modification.*The gitlab_schema was undefined/
end
end
end
context 'when running tests with prevent_cross_database_modification', :prevent_cross_database_modification do
context 'when both CI and other data is modified' do
context 'when executing a SELECT FOR UPDATE query' do
def run_queries
project.touch
pipeline.touch
pipeline.lock!
end
context 'outside transaction' do
@ -99,41 +103,11 @@ RSpec.describe 'Database::PreventCrossDatabaseModification' do
end
end
context 'when data modification happens in nested transactions' do
it 'raises error' do
Project.transaction(requires_new: true) do
project.touch
Project.transaction(requires_new: true) do
expect { pipeline.touch }.to raise_error /Cross-database data modification/
end
end
end
end
end
context 'when the modification is inside a factory save! call' do
let(:runner) { create(:ci_runner, :project, projects: [build(:project)]) }
context 'when executing a SELECT FOR UPDATE query' do
def run_queries
project.touch
pipeline.lock!
end
context 'outside transaction' do
it { expect { run_queries }.not_to raise_error }
end
context 'when data modification happens in a transaction' do
it 'raises error' do
Project.transaction do
expect { run_queries }.to raise_error /Cross-database data modification/
end
end
context 'when the modification is inside a factory save! call' do
let(:runner) { create(:ci_runner, :project, projects: [build(:project)]) }
it 'does not raise an error' do
runner
end
it 'does not raise an error' do
runner
end
end
end
@ -145,7 +119,7 @@ RSpec.describe 'Database::PreventCrossDatabaseModification' do
project.save!
end
include_examples 'succeessful examples'
include_examples 'successful examples'
end
describe '#allow_cross_database_modification_within_transaction' do
@ -171,4 +145,15 @@ RSpec.describe 'Database::PreventCrossDatabaseModification' do
end
end
end
context 'when some table with a defined schema and another table with undefined gitlab_schema is modified' do
it 'raises an error including including message about undefined schema' do
expect do
Project.transaction do
project.touch
project.connection.execute('UPDATE foo_bars_undefined_table SET a=1 WHERE id = -1')
end
end.to raise_error /Cross-database data modification.*The gitlab_schema was undefined/
end
end
end