Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-04-18 09:15:50 +00:00
parent 20ab32fe10
commit 5d7b3c05e5
79 changed files with 701 additions and 440 deletions

View File

@ -63,6 +63,7 @@ stages:
QA_INTERCEPT_REQUESTS: "true"
GITLAB_LICENSE_MODE: test
GITLAB_QA_ADMIN_ACCESS_TOKEN: $QA_ADMIN_ACCESS_TOKEN
GITLAB_QA_OPTS: $EXTRA_GITLAB_QA_OPTS
# todo: remove in 16.1 milestone when not needed for backwards compatibility anymore
EE_LICENSE: $QA_EE_LICENSE
GITHUB_ACCESS_TOKEN: $QA_GITHUB_ACCESS_TOKEN
@ -233,28 +234,6 @@ _quarantine:
variables:
QA_RSPEC_TAGS: --tag quarantine
# Temporary test job to support the effort of migrating to Super Sidebar
# https://gitlab.com/groups/gitlab-org/-/epics/9044
_super-sidebar-nav:
extends:
- .qa
- .parallel
variables:
QA_SCENARIO: Test::Instance::Image
QA_KNAPSACK_REPORT_NAME: ee-instance
QA_TESTS: ""
QA_SUPER_SIDEBAR_ENABLED: "true"
QA_ALLURE_RESULTS_DIRECTORY: tmp/allure-results-super-sidebar
QA_EXPORT_TEST_METRICS: "false"
QA_DISABLE_RSPEC_RETRY: "true"
GITLAB_QA_OPTS: --set-feature-flags super_sidebar_nav=enabled
RSPEC_REPORT_OPTS: "--format documentation"
SKIP_REPORT_IN_ISSUES: "true"
allow_failure: true
rules:
- if: $CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_PATH == "gitlab-org/gitlab" && $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
- !reference [.rules:test:manual, rules]
# ------------------------------------------
# FF changes
# ------------------------------------------
@ -325,7 +304,7 @@ decomposition-single-db-selective:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
GITLAB_QA_OPTS: --omnibus-config decomposition_single_db
GITLAB_QA_OPTS: --omnibus-config decomposition_single_db $EXTRA_GITLAB_QA_OPTS
rules:
- !reference [.rules:test:qa-selective, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
@ -342,7 +321,7 @@ decomposition-multiple-db-selective:
variables:
QA_SCENARIO: Test::Instance::Image
GITLAB_ALLOW_SEPARATE_CI_DATABASE: "true"
GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db
GITLAB_QA_OPTS: --omnibus-config decomposition_multiple_db $EXTRA_GITLAB_QA_OPTS
rules:
- !reference [.rules:test:qa-selective, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
@ -359,7 +338,7 @@ object-storage-selective:
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag object_storage
GITLAB_QA_OPTS: --omnibus-config object_storage
GITLAB_QA_OPTS: --omnibus-config object_storage $EXTRA_GITLAB_QA_OPTS
rules:
- !reference [.rules:test:qa-selective, rules]
- if: $QA_SUITES =~ /Test::Instance::ObjectStorage/
@ -377,7 +356,7 @@ object-storage-aws-selective:
AWS_S3_BUCKET_NAME: $QA_AWS_S3_BUCKET_NAME
AWS_S3_KEY_ID: $QA_AWS_S3_KEY_ID
AWS_S3_REGION: $QA_AWS_S3_REGION
GITLAB_QA_OPTS: --omnibus-config object_storage_aws
GITLAB_QA_OPTS: --omnibus-config object_storage_aws $EXTRA_GITLAB_QA_OPTS
object-storage-aws:
extends: object-storage-aws-selective
parallel: 2
@ -391,7 +370,7 @@ object-storage-gcs-selective:
GOOGLE_PROJECT: $QA_GOOGLE_PROJECT
GOOGLE_JSON_KEY: $QA_GOOGLE_JSON_KEY
GOOGLE_CLIENT_EMAIL: $QA_GOOGLE_CLIENT_EMAIL
GITLAB_QA_OPTS: --omnibus-config object_storage_gcs
GITLAB_QA_OPTS: --omnibus-config object_storage_gcs $EXTRA_GITLAB_QA_OPTS
object-storage-gcs:
extends: object-storage-gcs-selective
parallel: 2
@ -403,7 +382,7 @@ packages-selective:
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag packages
GITLAB_QA_OPTS: --omnibus-config packages
GITLAB_QA_OPTS: --omnibus-config packages $EXTRA_GITLAB_QA_OPTS
rules:
- !reference [.rules:test:qa-selective, rules]
- if: $QA_SUITES =~ /Test::Instance::Packages/
@ -650,7 +629,7 @@ registry-object-storage-tls:
QA_SCENARIO: Test::Integration::RegistryTLS
QA_RSPEC_TAGS: ""
GITLAB_TLS_CERTIFICATE: $QA_GITLAB_TLS_CERTIFICATE
GITLAB_QA_OPTS: --omnibus-config registry_object_storage
GITLAB_QA_OPTS: --omnibus-config registry_object_storage $EXTRA_GITLAB_QA_OPTS
importers:
extends: .qa
@ -671,27 +650,10 @@ e2e-test-report:
- .rules:report:allure-report
stage: report
variables:
ALLURE_JOB_NAME: e2e-package-and-test
GITLAB_AUTH_TOKEN: $PROJECT_TOKEN_FOR_CI_SCRIPTS_API_USAGE
ALLURE_PROJECT_PATH: $CI_PROJECT_PATH
ALLURE_MERGE_REQUEST_IID: $CI_MERGE_REQUEST_IID
# Temporary separate test report for super-sidebar test job
# TODO: remove once super-sidebar is on by default and enabled in tests
# https://gitlab.com/groups/gitlab-org/-/epics/9044
e2e-test-report-super-sidebar:
extends:
- .generate-allure-report-base
stage: report
needs:
- _super-sidebar-nav
variables:
ALLURE_JOB_NAME: e2e-super-sidebar
ALLURE_RESULTS_GLOB: gitlab-qa-run-*/**/allure-results-super-sidebar
rules:
- if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH
- !reference [.rules:test:manual, rules]
upload-knapsack-report:
extends:
- .generate-knapsack-report-base

View File

@ -70,6 +70,7 @@ e2e:package-and-test-ee:
RUN_WITH_BUNDLE: "true" # instructs pipeline to install and run gitlab-qa gem via bundler
QA_PATH: qa # sets the optional path for bundler to run from
QA_RUN_TYPE: e2e-package-and-test
ALLURE_JOB_NAME: e2e-package-and-test
PIPELINE_NAME: E2E Omnibus GitLab EE
inherit:
variables:
@ -100,12 +101,22 @@ e2e:package-and-test-ce:
- e2e-test-pipeline-generate
variables:
FOSS_ONLY: "1"
QA_RUN_TYPE: e2e-package-and-test-ce
ALLURE_JOB_NAME: e2e-package-and-test-ce
RELEASE: ${REGISTRY_HOST}/${REGISTRY_GROUP}/build/omnibus-gitlab-mirror/gitlab-ce:${CI_COMMIT_SHA}
GITLAB_QA_IMAGE: ${CI_REGISTRY_IMAGE}/gitlab-ce-qa:${CI_COMMIT_SHA}
QA_RUN_TYPE: e2e-package-and-test-ce
ALLURE_JOB_NAME: e2e-package-and-test-ce
PIPELINE_NAME: E2E Omnibus GitLab CE
e2e:package-and-test-super-sidebar:
extends: e2e:package-and-test-ee
when: manual
variables:
QA_SUPER_SIDEBAR_ENABLED: "true"
EXTRA_GITLAB_QA_OPTS: --set-feature-flags super_sidebar_nav=enabled
QA_RUN_TYPE: e2e-package-and-test-super-sidebar
ALLURE_JOB_NAME: e2e-package-and-test-super-sidebar
PIPELINE_NAME: E2E Omnibus Super Sidebar
e2e:test-on-gdk:
extends:
- .qa:rules:e2e:test-on-gdk

View File

@ -1 +1 @@
28c0539251cf00a675b78e987ff30b7741425653
24badf7502c1864dc59e4f19bbebe53a1d36b638

View File

@ -460,9 +460,7 @@ export default {
data-testid="aws-guidance-tip"
@dismiss="dismissTip"
>
<div
class="gl-display-flex gl-flex-direction-row gl-flex-wrap gl-md-flex-wrap-nowrap gl-gap-3"
>
<div class="gl-display-flex gl-flex-direction-row gl-flex-wrap gl-md-flex-nowrap gl-gap-3">
<div>
<p>
<gl-sprintf :message="$options.i18n.awsTipMessage">

View File

@ -302,7 +302,7 @@ export default {
<div :class="newSubgroup && 'row gl-mb-3'">
<gl-form-group v-if="newSubgroup" class="col-sm-6 gl-pr-0" :label="inputLabels.subgroupPath">
<div class="input-group gl-flex-wrap-nowrap">
<div class="input-group gl-flex-nowrap">
<gl-button-group class="gl-w-full">
<gl-button class="js-group-namespace-button gl-text-truncate gl-flex-grow-0!" label>
{{ basePath }}

View File

@ -20,7 +20,7 @@ export default {
</script>
<template>
<div class="d-flex-center gl-flex-wrap-nowrap text-nowrap js-ide-status-mr">
<div class="d-flex-center gl-flex-nowrap text-nowrap js-ide-status-mr">
<gl-icon name="merge-request" />
<span class="ml-1 d-none d-sm-block">{{ s__('WebIDE|Merge request') }}</span>
<gl-link class="ml-1" :href="url">{{ text }}</gl-link>

View File

@ -31,7 +31,7 @@ export default {
<template>
<dropdown-button class="gl-w-full!">
<span class="row gl-flex-wrap-nowrap">
<span class="row gl-flex-nowrap">
<span class="col-auto flex-fill text-truncate">
<gl-icon :size="16" :aria-label="__('Current Branch')" name="branch" /> {{ branchLabel }}
</span>

View File

@ -180,7 +180,7 @@ export default {
</div>
</td>
<td data-testid="fullPath" data-qa-selector="project_path_content">
<div class="gl-display-flex gl-sm-flex-wrap-wrap">
<div class="gl-display-flex gl-sm-flex-wrap">
<template v-if="repo.importSource.target">{{ repo.importSource.target }}</template>
<template v-else-if="isImportNotStarted || isSelectedForReimport">
<div class="gl-display-flex gl-align-items-stretch gl-w-full">

View File

@ -105,7 +105,7 @@ export default {
class="item-body gl-display-flex gl-align-items-center gl-gap-3 gl-mx-n2"
>
<div
class="item-contents gl-display-flex gl-align-items-center gl-flex-wrap-wrap gl-flex-grow-1 gl-gap-2 gl-px-3 gl-py-2 py-xl-0 flex-xl-nowrap gl-min-h-7"
class="item-contents gl-display-flex gl-align-items-center gl-flex-wrap gl-flex-grow-1 gl-gap-2 gl-px-3 gl-py-2 py-xl-0 flex-xl-nowrap gl-min-h-7"
>
<!-- Title area: Status icon (XL) and title -->
<div class="item-title gl-display-flex gl-gap-3 gl-min-w-0">
@ -146,7 +146,7 @@ export default {
>
<!-- Path area: status icon (<XL), path, issue # -->
<div
class="item-path-area item-path-id gl-display-flex gl-align-items-center gl-flex-wrap-wrap gl-gap-3"
class="item-path-area item-path-id gl-display-flex gl-align-items-center gl-flex-wrap gl-gap-3"
>
<gl-tooltip :target="() => $refs.iconElement">
<span v-safe-html="stateTitle"></span>
@ -164,7 +164,7 @@ export default {
<!-- Attributes area: CI, epic count, weight, milestone -->
<!-- They have a different order on large screen sizes -->
<div
class="item-attributes-area gl-display-flex gl-align-items-center gl-flex-wrap-wrap gl-gap-3"
class="item-attributes-area gl-display-flex gl-align-items-center gl-flex-wrap gl-gap-3"
>
<span v-if="hasPipeline" class="mr-ci-status order-md-last">
<a :href="pipelineStatus.details_path">

View File

@ -68,7 +68,7 @@ export default {
<div class="card card-slim gl-mt-5 gl-mb-0 gl-bg-gray-10">
<div class="card-header gl-px-5 gl-py-4 gl-bg-white">
<div
class="card-title gl-relative gl-display-flex gl-flex-wrap-wrap gl-align-items-center gl-line-height-20 gl-font-weight-bold gl-m-0"
class="card-title gl-relative gl-display-flex gl-flex-wrap gl-align-items-center gl-line-height-20 gl-font-weight-bold gl-m-0"
>
<gl-link
class="anchor gl-absolute gl-text-decoration-none"

View File

@ -255,7 +255,7 @@ export default {
</gl-form-group>
</div>
<gl-form-group class="gl-mb-0">
<div class="gl-display-flex gl-flex-wrap-wrap gl-gap-3">
<div class="gl-display-flex gl-flex-wrap gl-gap-3">
<gl-button
variant="confirm"
category="primary"

View File

@ -59,7 +59,7 @@ export default {
<gl-icon :name="getEventIcon(action)" class="note-icon" />
</div>
<div class="timeline-event-note timeline-event-border" data-testid="event-text-container">
<div class="gl-display-flex gl-flex-wrap-wrap gl-align-items-center gl-gap-3 gl-mb-2">
<div class="gl-display-flex gl-flex-wrap gl-align-items-center gl-gap-3 gl-mb-2">
<h3
class="timeline-event-note-date gl-font-weight-bold gl-font-sm gl-my-0"
data-testid="event-time"

View File

@ -180,7 +180,7 @@ export default {
<div class="top-bar gl-display-flex gl-justify-content-space-between">
<!-- truncate information -->
<div
class="truncated-info gl-display-none gl-sm-display-flex gl-flex-wrap-wrap gl-align-items-center"
class="truncated-info gl-display-none gl-sm-display-flex gl-flex-wrap gl-align-items-center"
data-testid="log-truncated-info"
>
<template v-if="isJobLogSizeVisible">

View File

@ -130,7 +130,7 @@ export default {
</div>
<div class="gl-w-full gl-display-flex">
<ul
class="merge-request-tabs nav-tabs nav nav-links gl-display-flex gl-flex-wrap-nowrap gl-m-0 gl-p-0 gl-border-b-0"
class="merge-request-tabs nav-tabs nav nav-links gl-display-flex gl-flex-nowrap gl-m-0 gl-p-0 gl-border-b-0"
>
<li
v-for="(tab, index) in tabs"

View File

@ -35,7 +35,7 @@ export default {
</script>
<template>
<div class="detail-page-header gl-flex-wrap-wrap">
<div class="detail-page-header gl-flex-wrap">
<div class="detail-page-header-body">
<h1 class="page-title gl-font-size-h-display flex-fill">{{ title }}</h1>

View File

@ -15,7 +15,7 @@ export default {
<template>
<div
ref="linksSection"
class="gl-sm-display-flex gl-sm-flex-wrap-wrap gl-mt-5 gl-p-3 gl-bg-gray-10 border gl-rounded-base links-section"
class="gl-sm-display-flex gl-sm-flex-wrap gl-mt-5 gl-p-3 gl-bg-gray-10 border gl-rounded-base links-section"
>
<div
v-for="(link, key) in links"

View File

@ -384,7 +384,7 @@ export default {
</template>
</label>
</p>
<div class="gl-display-flex gl-flex-wrap-wrap gl-mb-n3">
<div class="gl-display-flex gl-flex-wrap gl-mb-n3">
<gl-button
:disabled="isDisabled"
category="primary"
@ -418,7 +418,7 @@ export default {
</div>
</template>
<template v-else>
<div class="gl-display-sm-flex gl-flex-wrap-wrap">
<div class="gl-display-sm-flex gl-flex-wrap">
<gl-button
:disabled="isDisabled"
category="primary"

View File

@ -61,7 +61,7 @@ export default {
<template>
<li
:class="liClasses"
class="toggle-replies-widget gl-display-flex! gl-align-items-center gl-flex-wrap-wrap gl-bg-gray-10 gl-py-3 gl-px-5 gl-border"
class="toggle-replies-widget gl-display-flex! gl-align-items-center gl-flex-wrap gl-bg-gray-10 gl-py-3 gl-px-5 gl-border"
>
<gl-button
ref="toggle"

View File

@ -76,7 +76,7 @@ export default {
</script>
<template>
<div class="gl-display-flex gl-flex-wrap-wrap gl-gap-5">
<div class="gl-display-flex gl-flex-wrap gl-gap-5">
<input-copy-toggle-visibility
v-if="secret"
:copy-button-title="$options.COPY_SECRET"

View File

@ -8,7 +8,6 @@ import {
GlFormInputGroup,
GlModal,
GlModalDirective,
GlSkeletonLoader,
GlSprintf,
} from '@gitlab/ui';
import { __, s__, n__, sprintf } from '~/locale';
@ -30,7 +29,6 @@ export default {
GlFormGroup,
GlFormInputGroup,
GlModal,
GlSkeletonLoader,
GlSprintf,
ClipboardButton,
TitleArea,
@ -208,11 +206,9 @@ export default {
</template>
</gl-form-group>
<gl-skeleton-loader v-if="$apollo.queries.group.loading" />
<div v-else data-testid="main-area">
<manifests-list
v-if="manifests && manifests.length"
:loading="$apollo.queries.group.loading"
:manifests="manifests"
:pagination="pageInfo"
@prev-page="fetchPreviousPage"
@ -224,7 +220,6 @@ export default {
:svg-path="noManifestsIllustration"
:title="$options.i18n.noManifestTitle"
/>
</div>
<gl-modal
:modal-id="$options.confirmClearCacheModal"

View File

@ -1,5 +1,5 @@
<script>
import { GlKeysetPagination } from '@gitlab/ui';
import { GlKeysetPagination, GlSkeletonLoader } from '@gitlab/ui';
import { s__ } from '~/locale';
import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
@ -8,6 +8,7 @@ export default {
components: {
ManifestRow,
GlKeysetPagination,
GlSkeletonLoader,
},
props: {
manifests: {
@ -19,6 +20,11 @@ export default {
type: Object,
required: true,
},
loading: {
type: Boolean,
required: false,
default: () => false,
},
},
i18n: {
listTitle: s__('DependencyProxy|Image list'),
@ -34,6 +40,8 @@ export default {
<template>
<div class="gl-mt-6">
<h3 class="gl-font-base">{{ $options.i18n.listTitle }}</h3>
<gl-skeleton-loader v-if="loading" />
<div v-else data-testid="main-area">
<div
class="gl-border-t-1 gl-border-gray-100 gl-border-t-solid gl-display-flex gl-flex-direction-column"
>
@ -49,4 +57,5 @@ export default {
/>
</div>
</div>
</div>
</template>

View File

@ -139,7 +139,7 @@ export default {
:disabled="!action.canPlayJob"
@click="onClickAction(action)"
>
<div class="gl-display-flex gl-justify-content-space-between gl-flex-wrap-wrap">
<div class="gl-display-flex gl-justify-content-space-between gl-flex-wrap">
{{ action.name }}
<span v-if="action.scheduledAt">
<gl-icon name="clock" />

View File

@ -78,14 +78,14 @@ export default {
:title="testCase.classname"
:action-primary="$options.modalCloseButton"
>
<div class="gl-display-flex gl-flex-wrap-wrap gl-mx-n4 gl-my-3">
<div class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3">
<strong class="gl-text-right col-sm-3">{{ $options.text.name }}</strong>
<div class="col-sm-9" data-testid="test-case-name">
{{ testCase.name }}
</div>
</div>
<div v-if="testCase.file" class="gl-display-flex gl-flex-wrap-wrap gl-mx-n4 gl-my-3">
<div v-if="testCase.file" class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3">
<strong class="gl-text-right col-sm-3">{{ $options.text.file }}</strong>
<div class="col-sm-9" data-testid="test-case-file">
<gl-link v-if="testCase.filePath" :href="testCase.filePath">
@ -102,7 +102,7 @@ export default {
</div>
</div>
<div class="gl-display-flex gl-flex-wrap-wrap gl-mx-n4 gl-my-3">
<div class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3">
<strong class="gl-text-right col-sm-3">{{ $options.text.duration }}</strong>
<div v-if="testCase.formattedTime" class="col-sm-9" data-testid="test-case-duration">
{{ testCase.formattedTime }}
@ -112,14 +112,14 @@ export default {
</div>
</div>
<div v-if="testCase.recent_failures" class="gl-display-flex gl-flex-wrap-wrap gl-mx-n4 gl-my-3">
<div v-if="testCase.recent_failures" class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3">
<strong class="gl-text-right col-sm-3">{{ $options.text.history }}</strong>
<div class="col-sm-9" data-testid="test-case-recent-failures">
<gl-badge variant="warning">{{ failureHistoryMessage }}</gl-badge>
</div>
</div>
<div v-if="testCase.attachment_url" class="gl-display-flex gl-flex-wrap-wrap gl-mx-n4 gl-my-3">
<div v-if="testCase.attachment_url" class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3">
<strong class="gl-text-right col-sm-3">{{ $options.text.attachment }}</strong>
<gl-link
class="col-sm-9"
@ -133,7 +133,7 @@ export default {
<div
v-if="testCase.system_output"
class="gl-display-flex gl-flex-wrap-wrap gl-mx-n4 gl-my-3"
class="gl-display-flex gl-flex-wrap gl-mx-n4 gl-my-3"
data-testid="test-case-trace"
>
<strong class="gl-text-right col-sm-3">{{ $options.text.trace }}</strong>

View File

@ -187,7 +187,7 @@ export default {
@click="onIssuableFormWrapperClick"
>
<ul
class="gl-display-flex gl-flex-wrap-wrap gl-align-items-baseline gl-list-style-none gl-m-0 gl-p-0"
class="gl-display-flex gl-flex-wrap gl-align-items-baseline gl-list-style-none gl-m-0 gl-p-0"
>
<li
v-for="(reference, index) in references"

View File

@ -132,7 +132,7 @@ export default {
};
</script>
<template>
<div class="release-block-milestone-info gl-display-flex gl-flex-wrap-wrap">
<div class="release-block-milestone-info gl-display-flex gl-flex-wrap">
<div
v-gl-tooltip
class="milestone-progress-bar-container js-milestone-progress-bar-container gl-display-flex gl-flex-direction-column gl-mr-6 gl-mb-5"
@ -147,7 +147,7 @@ export default {
class="gl-display-flex gl-flex-direction-column gl-mr-6 gl-mb-5 js-milestone-list-container"
>
<span class="gl-mb-2">{{ milestoneLabelText }}</span>
<div class="gl-display-flex gl-flex-wrap-wrap gl-align-items-flex-end">
<div class="gl-display-flex gl-flex-wrap gl-align-items-flex-end">
<template v-for="(milestone, index) in milestonesToDisplay">
<gl-link
:key="milestone.id"

View File

@ -4,7 +4,7 @@ export const SCOPE_BLOB = 'blobs';
export const LABEL_DEFAULT_CLASSES = [
'gl-display-flex',
'gl-flex-direction-row',
'gl-flex-wrap-nowrap',
'gl-flex-nowrap',
'gl-text-gray-900',
];
export const NAV_LINK_DEFAULT_CLASSES = [

View File

@ -72,7 +72,7 @@ export default {
<template>
<div>
<div class="gl-display-flex gl-flex-wrap-wrap">
<div class="gl-display-flex gl-flex-wrap">
<div
v-for="(user, index) in uncollapsedUsers"
:key="user.id"

View File

@ -111,7 +111,7 @@ export default {
<div class="title hide-collapsed gl-mb-2 gl-line-height-20 gl-font-weight-bold">
{{ contactsLabel }}
</div>
<div class="hide-collapsed gl-display-flex gl-flex-wrap-wrap">
<div class="hide-collapsed gl-display-flex gl-flex-wrap">
<div
v-for="(contact, index) in contacts"
:id="`contact_container_${index}`"

View File

@ -110,7 +110,7 @@ export default {
<gl-loading-icon v-if="loading" size="sm" :inline="true" />
{{ participantLabel }}
</div>
<div class="hide-collapsed gl-display-flex gl-flex-wrap-wrap">
<div class="hide-collapsed gl-display-flex gl-flex-wrap">
<div
v-for="participant in visibleParticipants"
:key="participant.id"

View File

@ -172,9 +172,7 @@ export default {
</form>
<template #modal-footer>
<div
class="gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap-wrap gl-m-0"
>
<div class="gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-m-0">
<gl-button data-testid="delete-tag-cancel-button" @click="closeModal">
{{ $options.i18n.cancelButtonText }}
</gl-button>

View File

@ -323,7 +323,7 @@ export default {
data-testid="widget-extension-top-level"
>
<div
class="gl-flex-grow-1 gl-display-flex gl-align-items-center gl-flex-wrap-wrap"
class="gl-flex-grow-1 gl-display-flex gl-align-items-center gl-flex-wrap"
data-testid="widget-extension-top-level-summary"
>
<div v-if="isLoadingSummary" class="gl-w-full gl-line-height-normal">

View File

@ -82,8 +82,8 @@ export default {
<status-icon :icon-name="data.icon.name" :size="12" class="gl-m-auto" />
</div>
<div class="gl-w-full">
<div class="gl-display-flex gl-flex-wrap-nowrap">
<div class="gl-flex-wrap-wrap gl-display-flex gl-w-full">
<div class="gl-display-flex gl-flex-nowrap">
<div class="gl-flex-wrap gl-display-flex gl-w-full">
<div class="gl-display-flex gl-align-items-center">
<p v-safe-html="generateText(data.text)" class="gl-m-0"></p>
</div>

View File

@ -95,14 +95,14 @@ export default {
<div
:class="{
'gl-display-flex gl-align-items-center': actions.length,
'gl-md-display-flex gl-align-items-center gl-flex-wrap-wrap gl-gap-3': !actions.length,
'gl-md-display-flex gl-align-items-center gl-flex-wrap gl-gap-3': !actions.length,
}"
class="media-body gl-line-height-normal"
>
<slot></slot>
<div
:class="{
'state-container-action-buttons gl-flex-wrap-wrap gl-lg-justify-content-end': !actions.length,
'state-container-action-buttons gl-flex-wrap gl-lg-justify-content-end': !actions.length,
'gl-md-pt-0 gl-pt-3': hasActionsSlot,
}"
class="gl-display-flex gl-font-size-0 gl-gap-3"

View File

@ -224,7 +224,7 @@ export default {
</span>
<div
v-if="!rebaseInProgress && canPushToSourceBranch && !isMakingRequest"
class="accept-merge-holder clearfix js-toggle-container media gl-md-display-flex gl-flex-wrap-wrap gl-flex-grow-1"
class="accept-merge-holder clearfix js-toggle-container media gl-md-display-flex gl-flex-wrap gl-flex-grow-1"
>
<span
v-if="!rebasingError"

View File

@ -523,12 +523,10 @@ export default {
class="mr-widget-body mr-widget-body-ready-merge media gl-display-flex gl-align-items-center"
>
<div class="media-body">
<div
class="mr-widget-body-controls gl-display-flex gl-align-items-center gl-flex-wrap-wrap"
>
<div class="mr-widget-body-controls gl-display-flex gl-align-items-center gl-flex-wrap">
<template v-if="shouldShowMergeControls">
<div
class="gl-display-flex gl-sm-flex-direction-column gl-md-align-items-center gl-flex-wrap-wrap gl-w-full gl-md-pb-2"
class="gl-display-flex gl-sm-flex-direction-column gl-md-align-items-center gl-flex-wrap gl-w-full gl-md-pb-2"
>
<gl-form-checkbox
v-if="canRemoveSourceBranch"

View File

@ -39,7 +39,7 @@ export default {
</script>
<template>
<div>
<div class="gl-display-flex gl-flex-wrap-wrap gl-gap-5">
<div class="gl-display-flex gl-flex-wrap gl-gap-5">
<segmented-control-button-group v-model="selectedChart" :options="chartRanges" />
<slot name="extend-button-group"></slot>
</div>

View File

@ -134,8 +134,7 @@ export default {
},
parentClass() {
return {
'gl-relative gl-display-flex gl-align-items-flex-start gl-flex-wrap-nowrap': !this
.isEditing,
'gl-relative gl-display-flex gl-align-items-flex-start gl-flex-nowrap': !this.isEditing,
};
},
isProjectArchived() {

View File

@ -295,7 +295,7 @@ export default {
</script>
<template>
<div class="form-row gl-mb-5 work-item-assignees gl-relative gl-flex-wrap-nowrap">
<div class="form-row gl-mb-5 work-item-assignees gl-relative gl-flex-nowrap">
<span
:id="assigneesTitleId"
class="gl-font-weight-bold gl-mt-2 col-lg-2 col-3 gl-pt-2 min-w-fit-content gl-overflow-wrap-break"

View File

@ -272,7 +272,7 @@ export default {
</script>
<template>
<div class="form-row gl-mb-5 work-item-labels gl-relative gl-flex-wrap-nowrap">
<div class="form-row gl-mb-5 work-item-labels gl-relative gl-flex-nowrap">
<span
:id="labelsTitleId"
class="gl-font-weight-bold gl-mt-2 col-lg-2 col-3 gl-pt-2 min-w-fit-content gl-overflow-wrap-break"

View File

@ -217,7 +217,7 @@ export default {
<template>
<gl-form-group
class="work-item-dropdown gl-flex-wrap-nowrap"
class="work-item-dropdown gl-flex-nowrap"
:label="$options.i18n.MILESTONE"
label-for="milestone-value"
label-class="gl-pb-0! gl-mt-3 gl-overflow-wrap-break"

View File

@ -76,8 +76,8 @@
}
}
.gl-md-flex-wrap-nowrap.gl-md-flex-wrap-nowrap {
.gl-md-flex-nowrap.gl-md-flex-nowrap {
@include gl-media-breakpoint-up(md) {
@include gl-flex-wrap-nowrap;
@include gl-flex-nowrap;
}
}

View File

@ -44,6 +44,10 @@ module Mutations
::Types::WorkItems::Widgets::CurrentUserTodosInputType,
required: false,
description: 'Input for to-dos widget.'
argument :award_emoji_widget,
::Types::WorkItems::Widgets::AwardEmojiUpdateInputType,
required: false,
description: 'Input for award emoji widget.'
end
end
end

View File

@ -12,6 +12,18 @@ module Subscriptions
current_user.reset if current_user
end
# We override graphql-ruby's default `subscribe` since it returns
# :no_response instead, which leads to empty hashes rendered out
# to the caller which has caused problems in the client.
#
# Eventually, we should move to an approach where the caller receives
# a response here upon subscribing, but we don't need this currently
# because Vue components also perform an initial fetch query.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/402614
def subscribe(*)
nil
end
def authorized?(*)
raise NotImplementedError
end

View File

@ -10,10 +10,6 @@ module Subscriptions
required: true,
description: 'ID of the issuable.'
def subscribe(issuable_id:)
nil
end
def authorized?(issuable_id:)
issuable = force(GitlabSchema.find_by_gid(issuable_id))

View File

@ -9,10 +9,6 @@ module Subscriptions
required: false,
description: 'ID of the noteable.'
def subscribe(*args)
nil
end
def authorized?(noteable_id:)
noteable = force(GitlabSchema.find_by_gid(noteable_id))

View File

@ -0,0 +1,13 @@
# frozen_string_literal: true
module Types
module WorkItems
class AwardEmojiUpdateActionEnum < BaseEnum
graphql_name 'WorkItemAwardEmojiUpdateAction'
description 'Values for work item award emoji update enum'
value 'ADD', 'Adds the emoji.', value: :add
value 'REMOVE', 'Removes the emoji.', value: :remove
end
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
module Types
module WorkItems
module Widgets
class AwardEmojiUpdateInputType < BaseInputObject
graphql_name 'WorkItemWidgetAwardEmojiUpdateInput'
argument :action, ::Types::WorkItems::AwardEmojiUpdateActionEnum,
required: true,
description: 'Action for the update.'
argument :name,
GraphQL::Types::String,
required: true,
description: copy_field_description(Types::AwardEmojis::AwardEmojiType, :name)
end
end
end
end

View File

@ -0,0 +1,33 @@
# frozen_string_literal: true
module WorkItems
module Widgets
module AwardEmojiService
class UpdateService < WorkItems::Widgets::BaseService
def before_update_in_transaction(params:)
return unless params.present? && params.key?(:name) && params.key?(:action)
return unless has_permission?(:award_emoji)
service_response!(service_result(params[:action], params[:name]))
end
private
def service_result(action, name)
class_name = {
add: ::AwardEmojis::AddService,
remove: ::AwardEmojis::DestroyService
}
return invalid_action_error(action) unless class_name.key?(action)
class_name[action].new(work_item, name, current_user).execute
end
def invalid_action_error(key)
error(format(_("%{key} is not a valid action."), key: key))
end
end
end
end
end

View File

@ -25,6 +25,12 @@ module WorkItems
def has_permission?(permission)
can?(current_user, permission, widget.work_item)
end
def service_response!(result)
return result unless result[:status] == :error
raise WidgetError, result[:message]
end
end
end
end

View File

@ -63,9 +63,7 @@ module WorkItems
work_item.reload_work_item_parent
work_item.work_item_children.reset
return result unless result[:status] == :error
raise WidgetError, result[:message]
super
end
end
end

View File

@ -4,7 +4,7 @@
= text_field_tag :name, @name, placeholder: "My awesome project", class: "js-project-name form-control gl-form-input input-lg", autofocus: true, required: true, aria: { required: true }, data: { qa_selector: 'project_name_field' }
.form-group.col-12.col-sm-6.gl-pr-0
= label_tag :namespace_id, _('Project URL'), class: 'label-bold'
.input-group.gl-flex-wrap-nowrap
.input-group.gl-flex-nowrap
- if current_user.can_select_namespace?
- namespace_id = namespace_id_from(params)
.js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path || current_user.namespace.full_path,

View File

@ -16,7 +16,7 @@
.form-group.project-path.col-sm-6.gl-pr-0
= f.label :namespace_id, class: 'label-bold' do
%span= _('Project URL')
.input-group.gl-flex-wrap-nowrap
.input-group.gl-flex-nowrap
- if current_user.can_select_namespace?
- namespace_id = namespace_id_from(params)
.js-vue-new-project-url-select{ data: { namespace_full_path: GroupFinder.new(current_user).execute(id: namespace_id)&.full_path || @current_user_group&.full_path,

View File

@ -1,7 +1,7 @@
- f ||= local_assigns[:f]
.project-templates-buttons
= gl_tabs_nav({ class: 'nav-links scrolling-tabs gl-display-flex gl-flex-grow-1 gl-flex-wrap-nowrap gl-border-0' }) do
= gl_tabs_nav({ class: 'nav-links scrolling-tabs gl-display-flex gl-flex-grow-1 gl-flex-nowrap gl-border-0' }) do
= gl_tab_link_to '#built-in', tab_class: 'built-in-tab', class: 'active', data: { toggle: 'tab' } do
= _('Built-in')
= gl_tab_counter_badge Gitlab::ProjectTemplate.all.count + Gitlab::SampleDataTemplate.all.count

View File

@ -29,7 +29,7 @@
= render "projects/merge_requests/mr_box"
.merge-request-tabs-holder{ class: "#{'js-tabs-affix' unless ENV['RAILS_ENV'] == 'test'} #{'gl-static' if moved_mr_sidebar_enabled?}" }
.merge-request-tabs-container.gl-display-flex.gl-justify-content-space-between{ class: "#{'is-merge-request' if Feature.enabled?(:moved_mr_sidebar, @project) && !fluid_layout}" }
%ul.merge-request-tabs.nav-tabs.nav.nav-links.gl-display-flex.gl-flex-wrap-nowrap.gl-m-0.gl-p-0{ class: "#{'gl-w-full gl-lg-w-auto!' if Feature.enabled?(:moved_mr_sidebar, @project)}" }
%ul.merge-request-tabs.nav-tabs.nav.nav-links.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0{ class: "#{'gl-w-full gl-lg-w-auto!' if Feature.enabled?(:moved_mr_sidebar, @project)}" }
= render "projects/merge_requests/tabs/tab", class: "notes-tab", qa_selector: "notes_tab" do
= tab_link_for @merge_request, :show, force_link: @commit.present? do
= _("Overview")

View File

@ -17,7 +17,7 @@
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-wrap-nowrap.gl-m-0.gl-p-0.js-tabs-affix
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0.js-tabs-affix
%li.commits-tab.new-tab
= link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do
= _("Commits")
@ -34,7 +34,7 @@
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= sprite_icon('chevron-lg-left', size: 12)
.fade-right= sprite_icon('chevron-lg-right', size: 12)
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-wrap-nowrap.gl-m-0.gl-p-0.js-tabs-affix
%ul.merge-request-tabs.nav.nav-tabs.nav-links.no-top.no-bottom.gl-display-flex.gl-flex-nowrap.gl-m-0.gl-p-0.js-tabs-affix
%li.commits-tab.new-tab
= link_to url_for(safe_params), data: {target: 'div#commits', action: 'new', toggle: 'tabvue'} do
= _("Commits")

View File

@ -2,7 +2,7 @@
- badge_classes = 'issuable-status-badge gl-mr-3'
.detail-page-header
.detail-page-header-body.gl-flex-wrap-wrap
.detail-page-header-body.gl-flex-wrap
= gl_badge_tag({ variant: :info, icon: 'issue-closed', icon_classes: 'gl-mr-0!' }, { class: "#{issue_status_visibility(issuable, status_box: :closed)} #{badge_classes} issuable-status-badge-closed" }) do
.gl-display-none.gl-sm-display-block.gl-ml-2
= issue_closed_text(issuable, current_user)

View File

@ -1,5 +1,5 @@
.detail-page-header
.detail-page-header-body.gl-flex-wrap-wrap
.detail-page-header-body.gl-flex-wrap
= gl_badge_tag milestone_status_string(milestone), { variant: milestone_badge_variant(milestone) }, { class: 'gl-mr-3' }
.header-text-content

View File

@ -27,7 +27,7 @@
= render Pajamas::AvatarComponent.new(project, size: 48, alt: '', class: 'gl-mr-5')
.project-cell{ class: css_class }
.project-details.gl-pr-9.gl-sm-pr-0.gl-w-full.gl-display-flex.gl-flex-direction-column{ data: { qa_selector: 'project_content', qa_project_name: project.name } }
.gl-display-flex.gl-align-items-center.gl-flex-wrap-wrap
.gl-display-flex.gl-align-items-center.gl-flex-wrap
%h2.gl-font-base.gl-line-height-20.gl-my-0
= link_to project_path(project), class: 'text-plain gl-mr-3 js-prefetch-document' do
%span.namespace-name.gl-font-weight-normal

View File

@ -2,7 +2,7 @@
- admin_view ||= false
- top_padding = admin_view ? 'gl-lg-pt-3' : ''
= form_tag filter_projects_path, method: :get, class: "project-filter-form gl-display-flex! gl-flex-wrap-wrap gl-w-full gl-gap-3 #{top_padding}", data: { qa_selector: 'project_filter_form_container' }, id: 'project-filter-form' do |f|
= form_tag filter_projects_path, method: :get, class: "project-filter-form gl-display-flex! gl-flex-wrap gl-w-full gl-gap-3 #{top_padding}", data: { qa_selector: 'project_filter_form_container' }, id: 'project-filter-form' do |f|
= search_field_tag :name, params[:name],
placeholder: placeholder,
class: "project-filter-form-field form-control input-short js-projects-list-filter gl-m-0!",

View File

@ -2,7 +2,7 @@
- include_private = local_assigns.fetch(:include_private, false)
- params[:scope] ||= []
= gl_tabs_nav({ class: 'js-snippets-nav-tabs gl-border-b-0 gl-overflow-x-auto gl-flex-grow-1 gl-flex-wrap-nowrap' }) do
= gl_tabs_nav({ class: 'js-snippets-nav-tabs gl-border-b-0 gl-overflow-x-auto gl-flex-grow-1 gl-flex-nowrap' }) do
= gl_tab_link_to subject_snippets_path(subject), { item_active: params[:scope].empty? } do
= _('All')
= gl_tab_counter_badge(include_private ? counts[:total] : counts[:are_public_or_internal])

View File

@ -0,0 +1,14 @@
- title: "openSUSE Leap 15.3 packages" # (required) Clearly explain the change, or planned change. For example, "The `confidential` field for a `Note` is deprecated" or "CI/CD job names will be limited to 250 characters."
announcement_milestone: "15.8" # (required) The milestone when this feature was first announced as deprecated.
removal_milestone: "15.11" # (required) The milestone when this feature is planned to be removed
breaking_change: false # (required) Change to false if this is not a breaking change.
reporter: twk3 # (required) GitLab username of the person reporting the change
stage: distribution # (required) String value of the stage that the feature was created in. e.g., Growth
issue_url: https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/7371 # (required) Link to the deprecation issue in GitLab
body: | # (required) Do not modify this line, instead modify the lines below.
Distribution support and security updates for openSUSE Leap 15.3 [ended December 2022](https://en.opensuse.org/Lifetime#Discontinued_distributions).
Starting in GitLab 15.7 we started providing packages for openSUSE Leap 15.4, and in GitLab 15.11 we stopped providing packages for openSUSE Leap 15.3.
To continue using GitLab, [upgrade](https://en.opensuse.org/SDB:System_upgrade) to openSUSE Leap 15.4.
documentation_url: https://docs.gitlab.com/ee/administration/package_information/supported_os.html

View File

@ -6746,6 +6746,7 @@ Input type: `WorkItemUpdateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationworkitemupdateassigneeswidget"></a>`assigneesWidget` | [`WorkItemWidgetAssigneesInput`](#workitemwidgetassigneesinput) | Input for assignees widget. |
| <a id="mutationworkitemupdateawardemojiwidget"></a>`awardEmojiWidget` | [`WorkItemWidgetAwardEmojiUpdateInput`](#workitemwidgetawardemojiupdateinput) | Input for award emoji widget. |
| <a id="mutationworkitemupdateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationworkitemupdateconfidential"></a>`confidential` | [`Boolean`](#boolean) | Sets the work item confidentiality. |
| <a id="mutationworkitemupdatecurrentusertodoswidget"></a>`currentUserTodosWidget` | [`WorkItemWidgetCurrentUserTodosInput`](#workitemwidgetcurrentusertodosinput) | Input for to-dos widget. |
@ -25020,6 +25021,15 @@ Weight ID wildcard values.
| <a id="weightwildcardidany"></a>`ANY` | Weight is assigned. |
| <a id="weightwildcardidnone"></a>`NONE` | No weight is assigned. |
### `WorkItemAwardEmojiUpdateAction`
Values for work item award emoji update enum.
| Value | Description |
| ----- | ----------- |
| <a id="workitemawardemojiupdateactionadd"></a>`ADD` | Adds the emoji. |
| <a id="workitemawardemojiupdateactionremove"></a>`REMOVE` | Removes the emoji. |
### `WorkItemSort`
Values for sorting work items.
@ -27081,6 +27091,7 @@ A time-frame defined as a closed inclusive range of two dates.
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemupdatedtaskinputassigneeswidget"></a>`assigneesWidget` | [`WorkItemWidgetAssigneesInput`](#workitemwidgetassigneesinput) | Input for assignees widget. |
| <a id="workitemupdatedtaskinputawardemojiwidget"></a>`awardEmojiWidget` | [`WorkItemWidgetAwardEmojiUpdateInput`](#workitemwidgetawardemojiupdateinput) | Input for award emoji widget. |
| <a id="workitemupdatedtaskinputconfidential"></a>`confidential` | [`Boolean`](#boolean) | Sets the work item confidentiality. |
| <a id="workitemupdatedtaskinputcurrentusertodoswidget"></a>`currentUserTodosWidget` | [`WorkItemWidgetCurrentUserTodosInput`](#workitemwidgetcurrentusertodosinput) | Input for to-dos widget. |
| <a id="workitemupdatedtaskinputdescriptionwidget"></a>`descriptionWidget` | [`WorkItemWidgetDescriptionInput`](#workitemwidgetdescriptioninput) | Input for description widget. |
@ -27101,6 +27112,15 @@ A time-frame defined as a closed inclusive range of two dates.
| ---- | ---- | ----------- |
| <a id="workitemwidgetassigneesinputassigneeids"></a>`assigneeIds` | [`[UserID!]!`](#userid) | Global IDs of assignees. |
### `WorkItemWidgetAwardEmojiUpdateInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="workitemwidgetawardemojiupdateinputaction"></a>`action` | [`WorkItemAwardEmojiUpdateAction!`](#workitemawardemojiupdateaction) | Action for the update. |
| <a id="workitemwidgetawardemojiupdateinputname"></a>`name` | [`String!`](#string) | Emoji name. |
### `WorkItemWidgetCurrentUserTodosInput`
#### Arguments

View File

@ -45,6 +45,14 @@ relevant feature flags.
From GitLab 15.11, importing a JSON-formatted project file exports is not supported.
### openSUSE Leap 15.3 packages
Distribution support and security updates for openSUSE Leap 15.3 [ended December 2022](https://en.opensuse.org/Lifetime#Discontinued_distributions).
Starting in GitLab 15.7 we started providing packages for openSUSE Leap 15.4, and in GitLab 15.11 we stopped providing packages for openSUSE Leap 15.3.
To continue using GitLab, [upgrade](https://en.opensuse.org/SDB:System_upgrade) to openSUSE Leap 15.4.
## Removed in 15.9
### Live Preview no longer available in the Web IDE

View File

@ -1,193 +1,13 @@
---
stage: Plan
group: Optimize
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
remove_date: '2023-07-22'
redirect_to: '../group/value_stream_analytics/index.md'
---
# Value stream analytics for projects **(FREE)**
> - Introduced as cycle analytics prior to GitLab 12.3 at the project level.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12077) in GitLab Premium 12.3 at the group level.
> - [Renamed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23427) from cycle analytics to value stream analytics in GitLab 12.8.
This document was moved to [another location](../group/value_stream_analytics/index.md).
Value stream analytics provides metrics about each stage of your software development process.
For more information, see [How value stream analytics measures stages](../group/value_stream_analytics/index.md#how-value-stream-analytics-measures-stages).
A **value stream** is the entire work process that delivers value to customers. For example,
the [DevOps lifecycle](https://about.gitlab.com/stages-devops-lifecycle/) is a value stream that starts
with the Plan stage and ends with the Govern stage.
Use value stream analytics to identify:
- The amount of time it takes to go from an idea to production.
- The velocity of a given project.
- Bottlenecks in the development process.
- Factors that cause your software development lifecycle to slow down.
Value stream analytics is also available for [groups](../group/value_stream_analytics).
## View value stream analytics
> - Filtering [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326701) in GitLab 14.3
> - Sorting [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335974) in GitLab 14.4.
To view value stream analytics for your project:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Analytics > Value stream**.
1. To view metrics for a particular stage, select a stage below the **Filter results** text box.
1. Optional. Filter the results:
1. Select the **Filter results** text box.
1. Select a parameter.
1. Select a value or enter text to refine the results.
1. To adjust the date range:
- In the **From** field, select a start date.
- In the **To** field, select an end date.
1. Optional. Sort results by ascending or descending:
- To sort by most recent or oldest workflow item, select the **Last event** header.
- To sort by most or least amount of time spent in each stage, select the **Duration** header.
The table shows a list of related workflow items for the selected stage. Based on the stage you choose, this can be:
- CI/CD jobs
- Issues
- Merge requests
- Pipelines
A badge next to the workflow items table header shows the number of workflow items that completed the selected stage.
## View time spent in each development stage
Value stream analytics shows the median time spent by issues or merge requests in each development stage.
To view the median time spent in each stage:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Analytics > Value stream**.
1. Optional. Filter the results:
1. Select the **Filter results** text box.
1. Select a parameter.
1. Select a value or enter text to refine the results.
1. To adjust the date range:
- In the **From** field, select a start date.
- In the **To** field, select an end date.
1. To view the median time for each stage, above the **Filter results** text box, point to a stage.
## View the lead time and cycle time for issues
Value stream analytics shows the lead time and cycle time for issues in your project:
- Lead time: Median time from when the issue was created to when it was closed.
- Cycle time: Median time from first commit to issue closed. GitLab measures cycle time from the earliest commit of a [linked issue's merge request](../project/issues/crosslinking_issues.md) to when that issue is closed. The cycle time approach underestimates the lead time because merge request creation is always later than commit time.
To view the lead time and cycle time for issues:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Analytics > Value stream**.
1. Optional. Filter the results:
1. Select the **Filter results** text box.
1. Select a parameter.
1. Select a value or enter text to refine the results.
1. To adjust the date range:
- In the **From** field, select a start date.
- In the **To** field, select an end date.
The **Lead Time** and **Cycle Time** metrics display below the **Filter results** text box.
## View lead time for changes for merge requests **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/340150) in GitLab 14.5.
Lead time for changes is the median duration between when a merge request is merged and when it's deployed to production.
To view the lead time for changes for merge requests in your project:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Analytics > Value stream**.
1. Optional. Filter the results:
1. Select the **Filter results** text box.
1. Select a parameter.
1. Select a value or enter text to refine the results.
1. To adjust the date range:
- In the **From** field, select a start date.
- In the **To** field, select an end date.
The **Lead Time for Changes** metrics display below the **Filter results** text box.
## View number of successful deployments
Prerequisites:
- To view deployment metrics, you must have a
[production environment configured](../../ci/environments/index.md#deployment-tier-of-environments).
Value stream analytics shows the following deployment metrics for your project within the specified date range:
- Deploys: The number of successful deployments in the date range.
- Deployment Frequency: The average number of successful deployments per day in the date range.
If you have a GitLab Premium or Ultimate subscription:
- The number of successful deployments is calculated with DORA data.
- The data is filtered based on environment and environment tier.
To view deployment metrics for your project:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Analytics > Value stream**.
1. Optional. Filter the results:
1. Select the **Filter results** text box.
1. Select a parameter.
1. Select a value or enter text to refine the results.
1. To adjust the date range:
- In the **From** field, select a start date.
- In the **To** field, select an end date.
The **Deploys** and **Deployment Frequency** metrics display below the **Filter results** text box.
Deployment metrics are calculated based on data from the
[DORA API](../../api/dora/metrics.md#devops-research-and-assessment-dora-key-metrics-api).
NOTE:
In GitLab 13.9 and later, metrics are calculated based on when the deployment was finished.
In GitLab 13.8 and earlier, metrics are calculated based on when the deployment was created.
## Access permissions for value stream analytics
Access permissions for value stream analytics depend on the project type.
| Project type | Permissions |
|--------------|----------------------------------------|
| Public | Anyone can access. |
| Internal | Any authenticated user can access. |
| Private | Any member Guest and above can access. |
## Troubleshooting
### 100% CPU utilization by Sidekiq `cronjob:analytics_cycle_analytics`
It is possible that Value stream analytics background jobs
strongly impact performance by monopolizing CPU resources.
To recover from this situation:
1. Disable the feature for all projects in [the Rails console](../../administration/operations/rails_console.md),
and remove existing jobs:
```ruby
Project.find_each do |p|
p.analytics_access_level='disabled';
p.save!
end
Analytics::CycleAnalytics::GroupStage.delete_all
Analytics::CycleAnalytics::Aggregation.delete_all
```
1. Configure a [Sidekiq routing](../../administration/sidekiq/processing_specific_job_classes.md)
with for example a single `feature_category=value_stream_management`
and multiple `feature_category!=value_stream_management` entries.
Find other relevant queue metadata in the
[Enterprise Edition list](../../administration/sidekiq/processing_specific_job_classes.md#list-of-available-job-classes).
1. Enable value stream analytics for one project after another.
You might need to tweak the Sidekiq routing further according to your performance requirements.
<!-- This redirect file can be deleted after 2023-07-22. -->
<!-- Redirects that point to other docs in the same project expire in three months. -->
<!-- Redirects that point to docs in a different project or site (for example, link is not relative and starts with `https:`) expire in one year. -->
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->

View File

@ -19,7 +19,20 @@ The Value Streams Dashboard is a customizable dashboard that enables decision-ma
This page is a work in progress, and we're updating the information as we add more features.
For more information, see the [Value Stream Management category direction page](https://about.gitlab.com/direction/plan/value_stream_management/).
After the feature flag is enabled, to open the new page, append this path `/analytics/dashboards/value_streams_dashboard` to the group URL
## View the value streams dashboard
Prerequisite:
- To view the value streams dashboard for a group, you must have at least the Reporter role for the group.
To view the value streams dashboard:
1. On the top bar, select **Main menu**, and:
- For a project, select **Projects** and find your project.
- For a group, select **Groups** and find your group.
1. On the left sidebar, select **Analytics > Value stream**.
1. Below the **Filter results** text box, in the **Key metrics** row, select **Value Streams Dashboard | DORA**.
1. Optional. To open the new page, append this path `/analytics/dashboards/value_streams_dashboard` to the group URL
(for example, `https://gitlab.com/groups/gitlab-org/-/analytics/dashboards/value_streams_dashboard`).
## Initial use case
@ -74,7 +87,7 @@ For example, the parameter `query=gitlab-org/gitlab-foss,gitlab-org/gitlab,gitla
| Lead time for changes | The time to successfully deliver a commit into production. This metric reflects the efficiency of CI/CD pipelines. | [Lead time tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=lead-time) | [Lead time for changes](dora_metrics.md#lead-time-for-changes) |
| Time to restore service | The time it takes an organization to recover from a failure in production. | [Time to restore service tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=time-to-restore-service) | [Time to restore service](dora_metrics.md#time-to-restore-service) |
| Change failure rate | Percentage of deployments that cause an incident in production. | [Change failure rate tab](https://gitlab.com/groups/gitlab-org/-/analytics/ci_cd?tab=change-failure-rate) | [Change failure rate](dora_metrics.md#change-failure-rate) |
| VSA Lead time | Median time from issue created to issue closed. | [Value Stream Analytics](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](value_stream_analytics.md#view-the-lead-time-and-cycle-time-for-issues) |
| VSA Cycle time | Median time from the earliest commit of a linked issue's merge request to when that issue is closed. | [VSA overview](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](value_stream_analytics.md#view-the-lead-time-and-cycle-time-for-issues) |
| Lead time | Median time from issue created to issue closed. | [Value Stream Analytics](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](../group/value_stream_analytics/index.md#key-metrics) |
| Cycle time | Median time from the earliest commit of a linked issue's merge request to when that issue is closed. | [VSA overview](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](../group/value_stream_analytics/index.md#key-metrics) |
| New issues | Number of new issues created. | [Issue Analytics](https://gitlab.com/groups/gitlab-org/-/issues_analytics) | Issue analytics [for projects](issue_analytics.md) and [for groups](../../user/group/issues_analytics/index.md) |
| Number of deploys | Total number of deploys to production. | [Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Merge request analytics](merge_request_analytics.md) |

View File

@ -28,7 +28,7 @@ A top-level group can also have one complete
[Security Dashboard and Center](../application_security/security_dashboard/index.md),
[Vulnerability](../application_security/vulnerability_report/index.md#vulnerability-report) and
[Compliance Report](../compliance/compliance_report/index.md), and
[Value stream analytics](../group/value_stream_analytics/index.md#value-stream-analytics-for-groups).
[Value stream analytics](../group/value_stream_analytics/index.md).
## Group visibility

View File

@ -16,7 +16,7 @@ A top-level group can also have one complete
[Security Dashboard and Center](../application_security/security_dashboard/index.md),
[Vulnerability](../application_security/vulnerability_report/index.md#vulnerability-report) and
[Compliance Report](../compliance/compliance_report/index.md), and
[Value stream analytics](../group/value_stream_analytics/index.md#value-stream-analytics-for-groups).
[Value stream analytics](../group/value_stream_analytics/index.md).
## View groups

View File

@ -5,7 +5,7 @@ group: Optimize
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Value stream analytics for groups **(PREMIUM)**
# Value stream analytics **(FREE)**
Value stream analytics measures the time it takes to go from an idea to production.
@ -27,7 +27,29 @@ Value stream analytics helps businesses:
- Identify and solve inefficiencies.
- Optimize their workstreams to deliver more value, faster.
Value stream analytics is also available for [projects](../../analytics/value_stream_analytics.md).
Value stream analytics is available for projects and groups.
## Feature availability
Value stream analytics offers different features at the project and group level for FOSS and licensed versions.
- On GitLab Free, value stream analytics does not aggregate data. It queries the database directly where the date range filter is applied to the creation date of issues and merge request. You can view value stream analytics with pre-defined default stages.
- On GitLab Premium, value stream analytics aggregates data and applies the date range filter on the end event. You can also create, edit, and delete value streams.
|Feature|Group level (licensed)|Project level (licensed)|Project level (FOSS)|
|-|-|-|-|
|Create custom value streams|Yes|Yes|no, only one value stream (default) is present with the default stages|
|Create custom stages|Yes|Yes|No|
|Filtering (for example, by author, label, milestone)|Yes|Yes|Yes|
|Stage time chart|Yes|Yes|No|
|Total time chart|Yes|Yes|No|
|Task by type chart|Yes|No|No|
|DORA Metrics|Yes|Yes|No|
|Cycle time and lead time summary (Key metrics)|Yes|Yes|No|
|New issues, commits, and deploys (Key metrics)|Yes, excluding commits|Yes|Yes|
|Uses aggregated backend|Yes|Yes|No|
|Date filter behavior|Filters items [finished within the date range](https://gitlab.com/groups/gitlab-org/-/epics/6046)|Filters items by creation date.|Filters items by creation date.|
|Authorization|At least reporter|At least reporter|Can be public|
## How value stream analytics works
@ -73,7 +95,7 @@ These events play a key role in the duration calculation, which is calculated by
To learn what start and end events can be paired, see [Validating start and end events](../../../development/value_stream_analytics.md#validating-start-and-end-events).
### How value stream analytics aggregates data
### How value stream analytics aggregates data **(PREMIUM)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/335391) in GitLab 14.5.
> - Filter by stop date toggle [added](https://gitlab.com/gitlab-org/gitlab/-/issues/352428) in GitLab 14.9
@ -177,12 +199,14 @@ You can change the name of a project environment in your GitLab CI/CD configurat
Prerequisites:
- You must have at least the Reporter role to view value stream analytics for groups.
- You must create a [custom value stream](#create-a-value-stream-with-gitlab-default-stages). Value stream analytics only shows custom value streams created for your group.
- You must have at least the Reporter role.
- You must create a [custom value stream](#create-a-value-stream-with-gitlab-default-stages). Value stream analytics only shows custom value streams created for your group or project.
To view value stream analytics for your group:
To view value stream analytics for your group or project:
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the top bar, select **Main menu**, and:
- For a project, select **Projects** and find your project.
- For a group, select **Groups** and find your group.
1. On the left sidebar, select **Analytics > Value stream**.
1. To view metrics for a particular stage, select a stage below the **Filter results** text box.
1. Optional. Filter the results:
@ -214,7 +238,7 @@ The **Overview** page in value stream analytics displays key metrics and DORA me
### Key metrics
Value stream analytics includes the following key metrics that measure team performance:
Value stream analytics includes the following key metrics:
- **Lead time**: Median time from when the issue was created to when it was closed.
- **Cycle time**: Median time from first commit to issue closed. GitLab measures cycle time from the earliest commit of a
@ -240,6 +264,11 @@ Value stream analytics includes the following [DORA](../../../user/analytics/dor
DORA metrics are calculated based on data from the
[DORA API](../../../api/dora/metrics.md#devops-research-and-assessment-dora-key-metrics-api).
If you have a GitLab Premium or Ultimate subscription:
- The number of successful deployments is calculated with DORA data.
- The data is filtered based on environment and environment tier.
NOTE:
In GitLab 13.9 and later, deployment frequency metrics are calculated based on when the deployment was finished.
In GitLab 13.8 and earlier, deployment frequency metrics are calculated based on when the deployment was created.
@ -253,7 +282,9 @@ Prerequisite:
To view the DORA metrics and key metrics:
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the top bar, select **Main menu**, and:
- For a project, select **Projects** and find your project.
- For a group, select **Groups** and find your group.
1. On the left sidebar, select **Analytics > Value stream**.
Key metrics and DORA metrics display below the **Filter results** text box.
1. Optional. Filter the results:
@ -275,7 +306,9 @@ Value stream analytics shows the median time spent by issues or merge requests i
To view the median time spent in each stage by a group:
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the top bar, select **Main menu**, and:
- For a project, select **Projects** and find your project.
- For a group, select **Groups** and find your group.
1. On the left sidebar, select **Analytics > Value stream**.
1. Optional. Filter the results:
1. Select the **Filter results** text box.
@ -290,12 +323,12 @@ NOTE:
The date range selector filters items by the event time. The event time is when the
selected stage finished for the given item.
## View tasks by type
## View tasks by type **(PREMIUM)**
The **Tasks by type** chart displays the cumulative number of issues and merge requests per day for your group.
The chart uses the global page filters to display data based on the selected
group, projects, and time frame.
group and time frame.
To view tasks by type:
@ -307,7 +340,7 @@ To view tasks by type:
1. To add or remove labels, select the **Settings** (**{settings}**) dropdown list
and select or search for a label. By default the top group-level labels (maximum 10) are selected. You can select a maximum of 15 labels.
## Create a value stream
## Create a value stream **(PREMIUM)**
### Create a value stream with GitLab default stages
@ -316,7 +349,9 @@ To view tasks by type:
When you create a value stream, you can use GitLab default stages and hide or re-order them. You can also
create custom stages in addition to those provided in the default template.
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the top bar, select **Main menu**, and:
- For a project, select **Projects** and find your project.
- For a group, select **Groups** and find your group.
1. On the left sidebar, select **Analytics > Value Stream**.
1. Select **Create new Value Stream**.
1. Enter a name for the value stream.
@ -340,7 +375,9 @@ If you have recently upgraded to GitLab Premium, it can take up to 30 minutes fo
When you create a value stream, you can create and add custom stages that align with your own development workflows.
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the top bar, select **Main menu**, and:
- For a project, select **Projects** and find your project.
- For a group, select **Groups** and find your group.
1. On the left sidebar, select **Analytics > Value Stream**.
1. Select **Create value stream**.
1. For each stage:
@ -368,13 +405,15 @@ In the example above, two independent value streams are set up for two teams tha
The first value stream uses standard timestamp-based events for defining the stages. The second value stream uses label events.
## Edit a value stream
## Edit a value stream **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/267537) in GitLab 13.10.
After you create a value stream, you can customize it to suit your purposes. To edit a value stream:
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the top bar, select **Main menu**, and:
- For a project, select **Projects** and find your project.
- For a group, select **Groups** and find your group.
1. On the left sidebar, select **Analytics > Value Stream**.
1. In the upper-right corner, select the dropdown list, then select a value stream.
1. Next to the value stream dropdown list, select **Edit**.
@ -387,21 +426,22 @@ After you create a value stream, you can customize it to suit your purposes. To
1. Optional. To undo any modifications, select **Restore value stream defaults**.
1. Select **Save Value Stream**.
## Delete a value stream
## Delete a value stream **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221205) in GitLab 13.4.
To delete a custom value stream:
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the left sidebar, select **Analytics > Value Stream**.
1. On the top bar, select **Main menu**, and:
- For a project, select **Projects** and find your project.
- For a group, select **Groups** and find your group.
1. In the upper-right corner, select the dropdown list, then select the value stream you would like to delete.
1. Select **Delete (name of value stream)**.
1. To confirm, select **Delete**.
![Delete value stream](img/delete_value_stream_v13_12.png "Deleting a custom value stream")
## View number of days for a cycle to complete
## View number of days for a cycle to complete **(PREMIUM)**
> - Chart median line [removed](https://gitlab.com/gitlab-org/gitlab/-/issues/235455) in GitLab 13.4.
> - Totals [replaced](https://gitlab.com/gitlab-org/gitlab/-/issues/262070) with averages in GitLab 13.12.
@ -409,7 +449,9 @@ To delete a custom value stream:
The **Total time chart** shows the average number of days it takes for development cycles to complete.
The chart shows data for the last 500 workflow items.
1. On the top bar, select **Main menu > Groups** and find your group.
1. On the top bar, select **Main menu**, and:
- For a project, select **Projects** and find your project.
- For a group, select **Groups** and find your group.
1. On the left sidebar, select **Analytics > Value stream**.
1. Above the **Filter results** box, select a stage:
- To view a summary of the cycle time for all stages, select **Overview**.
@ -422,6 +464,42 @@ The chart shows data for the last 500 workflow items.
- In the **From** field, select a start date.
- In the **To** field, select an end date.
## Access permissions for value stream analytics
Access permissions for value stream analytics depend on the project type.
| Project type | Permissions |
|--------------|----------------------------------------|
| Public | Anyone can access. |
| Internal | Any authenticated user can access. |
| Private | Any member Guest and above can access. |
## Troubleshooting
See [Value stream analytics for projects](../../analytics/value_stream_analytics.md#troubleshooting).
### 100% CPU utilization by Sidekiq `cronjob:analytics_cycle_analytics`
It is possible that value stream analytics background jobs
strongly impact performance by monopolizing CPU resources.
To recover from this situation:
1. Disable the feature for all projects in [the Rails console](../../../administration/operations/rails_console.md),
and remove existing jobs:
```ruby
Project.find_each do |p|
p.analytics_access_level='disabled';
p.save!
end
Analytics::CycleAnalytics::GroupStage.delete_all
Analytics::CycleAnalytics::Aggregation.delete_all
```
1. Configure a [Sidekiq routing](../../../administration/sidekiq/processing_specific_job_classes.md)
with for example a single `feature_category=value_stream_management`
and multiple `feature_category!=value_stream_management` entries.
Find other relevant queue metadata in the
[Enterprise Edition list](../../../administration/sidekiq/processing_specific_job_classes.md#list-of-available-job-classes).
1. Enable value stream analytics for one project after another.
You might need to tweak the Sidekiq routing further according to your performance requirements.

View File

@ -59,7 +59,7 @@ The following table lists project permissions available for each role:
|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------|-----------|------------|----------|
| [Analytics](analytics/index.md):<br>View [issue analytics](analytics/issue_analytics.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Analytics](analytics/index.md):<br>View [merge request analytics](analytics/merge_request_analytics.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Analytics](analytics/index.md):<br>View [value stream analytics](analytics/value_stream_analytics.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Analytics](analytics/index.md):<br>View [value stream analytics](group/value_stream_analytics/index.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
| [Analytics](analytics/index.md):<br>View [DORA metrics](analytics/ci_cd_analytics.md) | | ✓ | ✓ | ✓ | ✓ |
| [Analytics](analytics/index.md):<br>View [CI/CD analytics](analytics/ci_cd_analytics.md) | | ✓ | ✓ | ✓ | ✓ |
| [Analytics](analytics/index.md):<br>View [code review analytics](analytics/code_review_analytics.md) | | ✓ | ✓ | ✓ | ✓ |
@ -429,7 +429,7 @@ For more information, see
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40942) in GitLab 13.4.
> - Support for inviting users with Minimal Access role [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106438) in GitLab 15.9.
Users with the Minimal Access role:
Users with the Minimal Access role do not:
- Count as licensed seats on self-managed Ultimate subscriptions or any GitLab.com subscriptions.
- Automatically have access to projects and subgroups in that root group.
@ -455,7 +455,7 @@ To work around the issue, give these users the Guest role or higher to any proje
- [Members](project/members/index.md)
- Customize permissions on [protected branches](project/protected_branches.md)
- [LDAP user permissions](group/access_and_permissions.md#manage-group-memberships-via-ldap)
- [Value stream analytics permissions](analytics/value_stream_analytics.md#access-permissions-for-value-stream-analytics)
- [Value stream analytics permissions](group/value_stream_analytics/index.md#access-permissions-for-value-stream-analytics)
- [Project aliases](../user/project/import/index.md#project-aliases)
- [Auditor users](../administration/auditor_users.md)
- [Confidential issues](project/issues/confidential_issues.md)

View File

@ -861,6 +861,7 @@ Payload example:
"visibility_level":20,
"path_with_namespace":"gitlabhq/gitlab-test",
"default_branch":"master",
"ci_config_path":"",
"homepage":"http://example.com/gitlabhq/gitlab-test",
"url":"http://example.com/gitlabhq/gitlab-test.git",
"ssh_url":"git@example.com:gitlabhq/gitlab-test.git",
@ -885,7 +886,10 @@ Payload example:
"title": "MS-Viewport",
"created_at": "2013-12-03T17:23:34Z",
"updated_at": "2013-12-03T17:23:34Z",
"last_edited_at": "2013-12-03T17:23:34Z",
"last_edited_by_id": 1,
"milestone_id": null,
"state_id": 1,
"state": "opened",
"blocking_discussions_resolved": true,
"work_in_progress": false,
@ -893,6 +897,11 @@ Payload example:
"merge_status": "unchecked",
"target_project_id": 14,
"description": "",
"total_time_spent": 1800,
"time_change": 30,
"human_total_time_spent": "30m",
"human_time_change": "30s",
"human_time_estimate": "30m",
"url": "http://example.com/diaspora/merge_requests/1",
"source": {
"name":"Awesome Project",
@ -929,6 +938,7 @@ Payload example:
"last_commit": {
"id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"message": "fixed readme",
"title": "Update file README.md",
"timestamp": "2012-01-03T23:36:29+02:00",
"url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7",
"author": {
@ -997,7 +1007,15 @@ Payload example:
"type": "ProjectLabel",
"group_id": 41
}]
}
},
"last_edited_at": {
"previous": null,
"current": "2023-03-15 00:00:10 UTC"
},
"last_edited_by_id": {
"previous": null,
"current": 3278533
},
},
"assignees": [
{
@ -1074,7 +1092,8 @@ Payload example:
"message": "adding an awesome page to the wiki",
"slug": "awesome",
"url": "http://example.com/root/awesome-project/-/wikis/awesome",
"action": "create"
"action": "create",
"diff_url": "http://example.com/root/awesome-project/-/wikis/home/diff?version_id=78ee4a6705abfbff4f4132c6646dbaae9c8fb6ec"
}
}
```

View File

@ -6,6 +6,10 @@ module Gitlab
class ActionCable < ActiveSupport::Subscriber
include Gitlab::Utils::StrongMemoize
BROADCASTING_GRAPHQL_EVENT = 'graphql-event'
BROADCASTING_GRAPHQL_SUBSCRIPTION = 'graphql-subscription'
BROADCASTING_OTHER = 'other'
attach_to :action_cable
SINGLE_CLIENT_TRANSMISSION = :action_cable_single_client_transmissions_total
@ -35,11 +39,25 @@ module Gitlab
end
def broadcast(event)
broadcast_counter.increment
broadcast_counter.increment({ broadcasting: broadcasting_from(event.payload) })
end
private
# Since broadcastings can have high dimensionality when they carry IDs, we need to
# collapse them. If it's not a well-know broadcast, we report it as "other".
def broadcasting_from(payload)
broadcasting = payload[:broadcasting]
if broadcasting.start_with?(BROADCASTING_GRAPHQL_EVENT)
# Take at most two levels of topic namespacing.
broadcasting.split(':').reject(&:empty?).take(2).join(':') # rubocop: disable CodeReuse/ActiveRecord
elsif broadcasting.start_with?(BROADCASTING_GRAPHQL_SUBSCRIPTION)
BROADCASTING_GRAPHQL_SUBSCRIPTION
else
BROADCASTING_OTHER
end
end
# When possible tries to query operation name
def operation_name_from(payload)
data = payload.dig(:data, 'result', 'data') || {}

View File

@ -778,6 +778,9 @@ msgstr ""
msgid "%{key} is not a valid URL."
msgstr ""
msgid "%{key} is not a valid action."
msgstr ""
msgid "%{labelStart}Actual response:%{labelEnd} %{headers}"
msgstr ""

View File

@ -5,7 +5,6 @@ import {
GlFormInputGroup,
GlFormGroup,
GlModal,
GlSkeletonLoader,
GlSprintf,
GlEmptyState,
} from '@gitlab/ui';
@ -72,8 +71,6 @@ describe('DependencyProxyApp', () => {
const findClipBoardButton = () => wrapper.findComponent(ClipboardButton);
const findFormGroup = () => wrapper.findComponent(GlFormGroup);
const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup);
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findMainArea = () => wrapper.findByTestId('main-area');
const findProxyCountText = () => wrapper.findByTestId('proxy-count');
const findManifestList = () => wrapper.findComponent(ManifestsList);
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
@ -99,23 +96,11 @@ describe('DependencyProxyApp', () => {
describe('when the dependency proxy is available', () => {
describe('when is loading', () => {
it('renders the skeleton loader', () => {
createComponent();
expect(findSkeletonLoader().exists()).toBe(true);
});
it('does not render a form group with label', () => {
createComponent();
expect(findFormGroup().exists()).toBe(false);
});
it('does not show the main section', () => {
createComponent();
expect(findMainArea().exists()).toBe(false);
});
});
describe('when the app is loaded', () => {
@ -125,10 +110,6 @@ describe('DependencyProxyApp', () => {
return waitForPromises();
});
it('renders the main area', () => {
expect(findMainArea().exists()).toBe(true);
});
it('renders a form group with a label', () => {
expect(findFormGroup().attributes('label')).toBe(
DependencyProxyApp.i18n.proxyImagePrefix,
@ -213,13 +194,6 @@ describe('DependencyProxyApp', () => {
});
describe('triggering page event on list', () => {
it('re-renders the skeleton loader', async () => {
findManifestList().vm.$emit('next-page');
await nextTick();
expect(findSkeletonLoader().exists()).toBe(true);
});
it('renders form group with label', async () => {
findManifestList().vm.$emit('next-page');
await nextTick();
@ -228,13 +202,6 @@ describe('DependencyProxyApp', () => {
expect.stringMatching(DependencyProxyApp.i18n.proxyImagePrefix),
);
});
it('does not show the main section', async () => {
findManifestList().vm.$emit('next-page');
await nextTick();
expect(findMainArea().exists()).toBe(false);
});
});
it('shows the clear cache dropdown list', () => {

View File

@ -1,7 +1,6 @@
import { GlKeysetPagination } from '@gitlab/ui';
import { GlKeysetPagination, GlSkeletonLoader } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ManifestRow from '~/packages_and_registries/dependency_proxy/components/manifest_row.vue';
import Component from '~/packages_and_registries/dependency_proxy/components/manifests_list.vue';
import {
proxyManifests,
@ -14,6 +13,7 @@ describe('Manifests List', () => {
const defaultProps = {
manifests: proxyManifests(),
pagination: pagination(),
loading: false,
};
const createComponent = (propsData = defaultProps) => {
@ -24,6 +24,8 @@ describe('Manifests List', () => {
const findRows = () => wrapper.findAllComponents(ManifestRow);
const findPagination = () => wrapper.findComponent(GlKeysetPagination);
const findMainArea = () => wrapper.findByTestId('main-area');
const findSkeletonLoader = () => wrapper.findComponent(GlSkeletonLoader);
it('has the correct title', () => {
createComponent();
@ -45,6 +47,19 @@ describe('Manifests List', () => {
});
});
describe('loading', () => {
it.each`
loading | expectLoader | expectContent
${false} | ${false} | ${true}
${true} | ${true} | ${false}
`('when loading is $loading', ({ loading, expectLoader, expectContent }) => {
createComponent({ ...defaultProps, loading });
expect(findSkeletonLoader().exists()).toBe(expectLoader);
expect(findMainArea().exists()).toBe(expectContent);
});
});
describe('pagination', () => {
it('is hidden when there is no next or prev pages', () => {
createComponent({ ...defaultProps, pagination: {} });

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Metrics::Subscribers::ActionCable, :request_store do
RSpec.describe Gitlab::Metrics::Subscribers::ActionCable, :request_store, feature_category: :application_performance do
let(:subscriber) { described_class.new }
let(:counter) { double(:counter) }
let(:data) { { 'result' => { 'data' => { 'event' => 'updated' } } } }
@ -55,7 +55,6 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActionCable, :request_store do
{ event: :updated }
end
let(:broadcasting) { 'issues:Z2lkOi8vZ2l0bGFiL0lzc3VlLzQ0Ng' }
let(:payload) do
{
broadcasting: broadcasting,
@ -64,17 +63,43 @@ RSpec.describe Gitlab::Metrics::Subscribers::ActionCable, :request_store do
}
end
it 'tracks the broadcast event' do
before do
allow(::Gitlab::Metrics).to receive(:counter).with(
:action_cable_broadcasts_total, /broadcast/
).and_return(counter)
end
expect(counter).to receive(:increment)
context 'when broadcast is for a GraphQL event' do
let(:broadcasting) { 'graphql-event::issuableEpicUpdated:issuableId:Z2lkOi8vZ2l0bGFiL0lzc3VlLzM' }
it 'tracks the event with broadcasting set to event topic' do
expect(counter).to receive(:increment).with({ broadcasting: 'graphql-event:issuableEpicUpdated' })
subscriber.broadcast(event)
end
end
context 'when broadcast is for a GraphQL channel subscription' do
let(:broadcasting) { 'graphql-subscription:09ae595a-45c4-4ae0-b765-4e503203211d' }
it 'strips out subscription ID from broadcasting' do
expect(counter).to receive(:increment).with({ broadcasting: 'graphql-subscription' })
subscriber.broadcast(event)
end
end
context 'when broadcast is something else' do
let(:broadcasting) { 'unknown-topic' }
it 'tracks the event as "other"' do
expect(counter).to receive(:increment).with({ broadcasting: 'other' })
subscriber.broadcast(event)
end
end
end
describe '#transmit_subscription_confirmation' do
let(:name) { 'transmit_subscription_confirmation.action_cable' }
let(:channel_class) { 'IssuesChannel' }

View File

@ -1222,6 +1222,139 @@ RSpec.describe 'Update a work item', feature_category: :team_planning do
end
end
context 'when updating awardEmoji' do
let_it_be(:current_user) { work_item.author }
let_it_be(:upvote) { create(:award_emoji, :upvote, awardable: work_item, user: current_user) }
let(:award_action) { 'ADD' }
let(:award_name) { 'star' }
let(:input) { { 'awardEmojiWidget' => { 'action' => award_action, 'name' => award_name } } }
let(:fields) do
<<~FIELDS
workItem {
widgets {
type
... on WorkItemWidgetAwardEmoji {
upvotes
downvotes
awardEmoji {
nodes {
name
user { id }
}
}
}
}
}
errors
FIELDS
end
subject(:update_work_item) { post_graphql_mutation(mutation, current_user: current_user) }
context 'when user cannot award work item' do
before do
allow(Ability).to receive(:allowed?).and_call_original
allow(Ability).to receive(:allowed?)
.with(current_user, :award_emoji, work_item).and_return(false)
end
it 'ignores the update request' do
expect do
update_work_item
end.to not_change(AwardEmoji, :count)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['errors']).to be_empty
expect(graphql_errors).to be_blank
end
end
context 'when user can award work item' do
shared_examples 'request with error' do |message|
it 'ignores update and returns an error' do
expect do
update_work_item
end.not_to change(AwardEmoji, :count)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['workItem']).to be_nil
expect(mutation_response['errors'].first).to include(message)
end
end
shared_examples 'request that removes emoji' do
it "updates work item's award emoji" do
expect do
update_work_item
end.to change(AwardEmoji, :count).by(-1)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['workItem']['widgets']).to include(
{
'upvotes' => 0,
'downvotes' => 0,
'awardEmoji' => { 'nodes' => [] },
'type' => 'AWARD_EMOJI'
}
)
end
end
shared_examples 'request that adds emoji' do
it "updates work item's award emoji" do
expect do
update_work_item
end.to change(AwardEmoji, :count).by(1)
expect(response).to have_gitlab_http_status(:success)
expect(mutation_response['workItem']['widgets']).to include(
{
'upvotes' => 1,
'downvotes' => 0,
'awardEmoji' => { 'nodes' => [
{ 'name' => 'thumbsup', 'user' => { 'id' => current_user.to_gid.to_s } },
{ 'name' => award_name, 'user' => { 'id' => current_user.to_gid.to_s } }
] },
'type' => 'AWARD_EMOJI'
}
)
end
end
context 'when adding award emoji' do
it_behaves_like 'request that adds emoji'
context 'when the emoji name is not valid' do
let(:award_name) { 'xxqq' }
it_behaves_like 'request with error', 'Name is not a valid emoji name'
end
end
context 'when removing award emoji' do
let(:award_action) { 'REMOVE' }
context 'when emoji was awarded by current user' do
let(:award_name) { 'thumbsup' }
it_behaves_like 'request that removes emoji'
end
context 'when emoji was awarded by a different user' do
let(:award_name) { 'thumbsdown' }
before do
create(:award_emoji, :downvote, awardable: work_item)
end
it_behaves_like 'request with error',
'User has not awarded emoji of type thumbsdown on the awardable'
end
end
end
end
context 'when unsupported widget input is sent' do
let_it_be(:work_item) { create(:work_item, :incident, project: project) }

View File

@ -412,7 +412,10 @@ RSpec.describe API::ProjectSnippets, :aggregate_failures, feature_category: :sou
let(:path) { "/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw" }
it_behaves_like 'GET request permissions for admin mode' do
let(:failed_status_code) { :ok }
let_it_be(:snippet_with_empty_repo) { create(:project_snippet, :empty_repo, author: admin, project: project) }
let(:snippet) { snippet_with_empty_repo }
let(:failed_status_code) { :not_found }
end
it 'returns raw text' do

View File

@ -1215,11 +1215,6 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :projects d
describe 'POST /projects' do
let(:path) { '/projects' }
it_behaves_like 'POST request permissions for admin mode' do
let(:params) { { name: 'Foo Project' } }
let(:failed_status_code) { :created }
end
context 'maximum number of projects reached' do
it 'does not create new project and respond with 403' do
allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0)
@ -1667,11 +1662,6 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :projects d
describe 'GET /users/:user_id/projects/' do
let!(:public_project) { create(:project, :public, name: 'public_project', creator_id: user4.id, namespace: user4.namespace) }
it_behaves_like 'GET request permissions for admin mode' do
let(:path) { "/users/#{user.id}/projects/" }
let(:failed_status_code) { :ok }
end
it 'returns error when user not found' do
get api("/users/#{non_existing_record_id}/projects/")
@ -1822,15 +1812,6 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :projects d
let(:path) { "/users/#{user3.id}/starred_projects/" }
it_behaves_like 'GET request permissions for admin mode' do
before do
user3.update!(private_profile: true)
user3.reload
end
let(:failed_status_code) { :ok }
end
it 'returns error when user not found' do
get api("/users/#{non_existing_record_id}/starred_projects/")

View File

@ -0,0 +1,96 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe WorkItems::Widgets::AwardEmojiService::UpdateService, feature_category: :team_planning do
let_it_be(:reporter) { create(:user) }
let_it_be(:unauthorized_user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let_it_be(:work_item) { create(:work_item, project: project) }
let(:current_user) { reporter }
let(:widget) { work_item.widgets.find { |widget| widget.is_a?(WorkItems::Widgets::AwardEmoji) } }
before_all do
project.add_reporter(reporter)
end
describe '#before_update_in_transaction' do
subject do
described_class.new(widget: widget, current_user: current_user)
.before_update_in_transaction(params: params)
end
shared_examples 'raises a WidgetError' do
it { expect { subject }.to raise_error(described_class::WidgetError, message) }
end
context 'when awarding an emoji' do
let(:params) { { action: :add, name: 'star' } }
context 'when user has no access' do
let(:current_user) { unauthorized_user }
it 'does not award the emoji' do
expect { subject }.not_to change { AwardEmoji.count }
end
end
context 'when user has access' do
it 'awards the emoji to the work item' do
expect { subject }.to change { AwardEmoji.count }.by(1)
emoji = AwardEmoji.last
expect(emoji.name).to eq('star')
expect(emoji.awardable_id).to eq(work_item.id)
expect(emoji.user).to eq(current_user)
end
context 'when the name is incorrect' do
let(:params) { { action: :add, name: 'foo' } }
it_behaves_like 'raises a WidgetError' do
let(:message) { 'Name is not a valid emoji name' }
end
end
context 'when the action is incorrect' do
let(:params) { { action: :foo, name: 'star' } }
it_behaves_like 'raises a WidgetError' do
let(:message) { 'foo is not a valid action.' }
end
end
end
end
context 'when removing emoji' do
let(:params) { { action: :remove, name: 'thumbsup' } }
context 'when user has no access' do
let(:current_user) { unauthorized_user }
it 'does not remove the emoji' do
expect { subject }.not_to change { AwardEmoji.count }
end
end
context 'when user has access' do
it 'removes existing emoji' do
create(:award_emoji, :upvote, awardable: work_item, user: current_user)
expect { subject }.to change { AwardEmoji.count }.by(-1)
end
context 'when work item does not have the emoji' do
let(:params) { { action: :remove, name: 'star' } }
it_behaves_like 'raises a WidgetError' do
let(:message) { 'User has not awarded emoji of type star on the awardable' }
end
end
end
end
end
end