diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-30 18:12:04 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-30 18:12:04 +0300 |
commit | 93501e7509fb61b25f091d81381d3e86842a9cdd (patch) | |
tree | e11e5528450c3e3815f59c13d8799722d8d754c1 | |
parent | bfa1adf9773ba7ea7cde546ea545b72721d36faa (diff) |
Add latest changes from gitlab-org/gitlab@master
95 files changed, 1725 insertions, 564 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b9442c00e59..fbc0aa31341 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -152,7 +152,7 @@ variables: DOCKER_VERSION: "23.0.1" RUBY_VERSION: "2.7" RUBYGEMS_VERSION: "3.4" - GO_VERSION: "1.18" + GO_VERSION: "1.19" RUST_VERSION: "1.65" FLAKY_RSPEC_SUITE_REPORT_PATH: rspec/flaky/report-suite.json diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index cdb455c8c31..d8162d108ba 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -283,6 +283,7 @@ # This list should match the list in Tasks::Gitlab::Assets.assets_impacting_compilation .assets-compilation-patterns: &assets-compilation-patterns - "{package.json,yarn.lock}" + - "{Gemfile,Gemfile.lock}" - ".browserslistrc" - "babel.config.js" - "config/webpack.config.js" diff --git a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md index c33e5f8eb68..60b091b04a2 100644 --- a/.gitlab/issue_templates/Geo Replicate a new Git repository type.md +++ b/.gitlab/issue_templates/Geo Replicate a new Git repository type.md @@ -465,6 +465,7 @@ That's all of the required database changes. state { Geo::CoolWidgetRegistry.state_value(:failed) } last_synced_at { 1.day.ago } retry_count { 2 } + retry_at { 2.hours.from_now } last_sync_failure { 'Random error' } end diff --git a/.gitlab/issue_templates/Geo Replicate a new blob type.md b/.gitlab/issue_templates/Geo Replicate a new blob type.md index 0c5dbaebacf..df2d1871a85 100644 --- a/.gitlab/issue_templates/Geo Replicate a new blob type.md +++ b/.gitlab/issue_templates/Geo Replicate a new blob type.md @@ -425,6 +425,7 @@ That's all of the required database changes. state { Geo::CoolWidgetRegistry.state_value(:failed) } last_synced_at { 1.day.ago } retry_count { 2 } + retry_at { 2.hours.from_now } last_sync_failure { 'Random error' } end diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 3019d72ae6f..e09b2dc4deb 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -140ee5c9aa5f4fb7dd4a0017677788ee88ce7149 +64d8d3472d0e490a914018438d4f531cc1b659f5 diff --git a/app/assets/javascripts/design_management/pages/index.vue b/app/assets/javascripts/design_management/pages/index.vue index 2b98c7b708c..beefdba6d1f 100644 --- a/app/assets/javascripts/design_management/pages/index.vue +++ b/app/assets/javascripts/design_management/pages/index.vue @@ -104,7 +104,7 @@ export default { return this.permissions.createDesign; }, showToolbar() { - return this.canCreateDesign && this.allVersions.length > 0; + return this.allVersions.length > 0; }, hasDesigns() { return this.designs.length > 0; @@ -375,6 +375,7 @@ export default { <design-version-dropdown /> </div> <div + v-if="canCreateDesign" v-show="hasDesigns" class="gl-display-flex gl-align-items-center" data-testid="design-selector-toolbar" @@ -489,7 +490,11 @@ export default { /> </li> <template #header> - <li :class="designDropzoneWrapperClass" data-testid="design-dropzone-wrapper"> + <li + v-if="canCreateDesign" + :class="designDropzoneWrapperClass" + data-testid="design-dropzone-wrapper" + > <design-dropzone :enable-drag-behavior="isDraggingDesign" :class="{ 'design-list-item design-list-item-new': !isDesignListEmpty }" diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 5951912af55..99bc3780b55 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -47,6 +47,7 @@ import { reviewStatuses } from '../utils/file_reviews'; import { diffsApp } from '../utils/performance'; import { updateChangesTabCount } from '../utils/merge_request'; import { queueRedisHllEvents } from '../utils/queue_events'; +import FindingsDrawer from './shared/findings_drawer.vue'; import CollapsedFilesWarning from './collapsed_files_warning.vue'; import CommitWidget from './commit_widget.vue'; import CompareVersions from './compare_versions.vue'; @@ -60,6 +61,7 @@ import PreRenderer from './pre_renderer.vue'; export default { name: 'DiffsApp', components: { + FindingsDrawer, DynamicScroller, DynamicScrollerItem, PreRenderer, @@ -200,6 +202,7 @@ export default { numTotalFiles: 'realSize', numVisibleFiles: 'size', }), + ...mapState('findingsDrawer', ['activeDrawer']), ...mapState('diffs', [ 'showTreeList', 'isLoading', @@ -234,6 +237,7 @@ export default { 'flatBlobsList', ]), ...mapGetters(['isNotesFetched', 'getNoteableData']), + ...mapGetters('findingsDrawer', ['activeDrawer']), diffs() { if (!this.viewDiffsFileByFile) { return this.diffFiles; @@ -437,6 +441,10 @@ export default { 'setFileByFile', 'disableVirtualScroller', ]), + ...mapActions('findingsDrawer', ['setDrawer']), + closeDrawer() { + this.setDrawer({}); + }, subscribeToEvents() { notesEventHub.$once('fetchDiffData', this.fetchData); notesEventHub.$on('refetchDiffData', this.refetchDiffData); @@ -631,6 +639,11 @@ export default { <template> <div v-show="shouldShow"> + <findings-drawer + v-if="glFeatures.codeQualityInlineDrawer" + :drawer="activeDrawer" + @close="closeDrawer" + /> <div v-if="isLoading || !isTreeLoaded" class="loading"><gl-loading-icon size="lg" /></div> <div v-else id="diffs" :class="{ active: shouldShow }" class="diffs tab-pane"> <compare-versions :diff-files-count-text="numTotalFiles" /> diff --git a/app/assets/javascripts/diffs/components/diff_code_quality.vue b/app/assets/javascripts/diffs/components/diff_code_quality.vue index 5392c631c14..f3f05e3d9d9 100644 --- a/app/assets/javascripts/diffs/components/diff_code_quality.vue +++ b/app/assets/javascripts/diffs/components/diff_code_quality.vue @@ -1,27 +1,19 @@ <script> -import { GlButton, GlIcon } from '@gitlab/ui'; -import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants'; +import { GlButton } from '@gitlab/ui'; import { NEW_CODE_QUALITY_FINDINGS } from '../i18n'; +import DiffCodeQualityItem from './diff_code_quality_item.vue'; export default { i18n: { newFindings: NEW_CODE_QUALITY_FINDINGS, }, - components: { GlButton, GlIcon }, + components: { GlButton, DiffCodeQualityItem }, props: { codeQuality: { type: Array, required: true, }, }, - methods: { - severityClass(severity) { - return SEVERITY_CLASSES[severity] || SEVERITY_CLASSES.unknown; - }, - severityIcon(severity) { - return SEVERITY_ICONS[severity] || SEVERITY_ICONS.unknown; - }, - }, }; </script> @@ -37,23 +29,11 @@ export default { {{ $options.i18n.newFindings }} </h4> <ul class="gl-list-style-none gl-mb-0 gl-p-0"> - <li + <diff-code-quality-item v-for="finding in codeQuality" :key="finding.description" - class="gl-pt-1 gl-pb-1 gl-font-regular gl-display-flex" - > - <span class="gl-mr-3"> - <gl-icon - :size="12" - :name="severityIcon(finding.severity)" - :class="severityClass(finding.severity)" - class="codequality-severity-icon" - /> - </span> - <span> - <span class="severity-copy">{{ finding.severity }}</span> - {{ finding.description }} - </span> - </li> + :finding="finding" + /> </ul> <gl-button data-testid="diff-codequality-close" diff --git a/app/assets/javascripts/diffs/components/diff_code_quality_item.vue b/app/assets/javascripts/diffs/components/diff_code_quality_item.vue new file mode 100644 index 00000000000..eede110f46c --- /dev/null +++ b/app/assets/javascripts/diffs/components/diff_code_quality_item.vue @@ -0,0 +1,54 @@ +<script> +import { GlLink, GlIcon } from '@gitlab/ui'; +import { mapActions } from 'vuex'; +import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants'; +import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; + +export default { + components: { GlLink, GlIcon }, + mixins: [glFeatureFlagsMixin()], + props: { + finding: { + type: Object, + required: true, + }, + }, + methods: { + severityClass(severity) { + return SEVERITY_CLASSES[severity] || SEVERITY_CLASSES.unknown; + }, + severityIcon(severity) { + return SEVERITY_ICONS[severity] || SEVERITY_ICONS.unknown; + }, + toggleDrawer() { + this.setDrawer(this.finding); + }, + ...mapActions('findingsDrawer', ['setDrawer']), + }, +}; +</script> + +<template> + <li class="gl-py-1 gl-font-regular gl-display-flex"> + <span class="gl-mr-3"> + <gl-icon + :size="12" + :name="severityIcon(finding.severity)" + :class="severityClass(finding.severity)" + class="codequality-severity-icon" + /> + </span> + <span + v-if="glFeatures.codeQualityInlineDrawer" + data-testid="description-button-section" + class="gl-display-flex" + > + <gl-link category="primary" variant="link" @click="toggleDrawer"> + {{ finding.severity }} - {{ finding.description }}</gl-link + > + </span> + <span v-else data-testid="description-plain-text" class="gl-display-flex"> + {{ finding.severity }} - {{ finding.description }} + </span> + </li> +</template> diff --git a/app/assets/javascripts/diffs/components/shared/findings_drawer.vue b/app/assets/javascripts/diffs/components/shared/findings_drawer.vue new file mode 100644 index 00000000000..da880c6f3ca --- /dev/null +++ b/app/assets/javascripts/diffs/components/shared/findings_drawer.vue @@ -0,0 +1,110 @@ +<script> +import { GlDrawer, GlIcon, GlLink } from '@gitlab/ui'; +import SafeHtml from '~/vue_shared/directives/safe_html'; +import { s__ } from '~/locale'; +import { DRAWER_Z_INDEX } from '~/lib/utils/constants'; +import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants'; +import { getContentWrapperHeight } from '~/lib/utils/dom_utils'; + +export const i18n = { + severity: s__('FindingsDrawer|Severity:'), + engine: s__('FindingsDrawer|Engine:'), + category: s__('FindingsDrawer|Category:'), + otherLocations: s__('FindingsDrawer|Other locations:'), +}; + +export default { + i18n, + components: { GlDrawer, GlIcon, GlLink }, + directives: { + SafeHtml, + }, + props: { + drawer: { + type: Object, + required: true, + }, + }, + safeHtmlConfig: { + ALLOWED_TAGS: ['a', 'h1', 'h2', 'p'], + ALLOWED_ATTR: ['href', 'rel'], + }, + computed: { + drawerOffsetTop() { + return getContentWrapperHeight('.content-wrapper'); + }, + }, + DRAWER_Z_INDEX, + methods: { + severityClass(severity) { + return SEVERITY_CLASSES[severity] || SEVERITY_CLASSES.unknown; + }, + severityIcon(severity) { + return SEVERITY_ICONS[severity] || SEVERITY_ICONS.unknown; + }, + }, +}; +</script> +<template> + <gl-drawer + :header-height="drawerOffsetTop" + :z-index="$options.DRAWER_Z_INDEX" + class="findings-drawer" + :open="Object.keys(drawer).length !== 0" + @close="$emit('close')" + > + <template #title> + <h2 data-testid="findings-drawer-heading" class="gl-font-size-h2 gl-mt-0 gl-mb-0"> + {{ drawer.description }} + </h2> + </template> + + <template #default> + <ul class="gl-list-style-none gl-border-b-initial gl-mb-0 gl-pb-0!"> + <li data-testid="findings-drawer-severity" class="gl-mb-4"> + <span class="gl-font-weight-bold">{{ $options.i18n.severity }}</span> + <gl-icon + data-testid="findings-drawer-severity-icon" + :size="12" + :name="severityIcon(drawer.severity)" + :class="severityClass(drawer.severity)" + class="codequality-severity-icon" + /> + + {{ drawer.severity }} + </li> + <li data-testid="findings-drawer-engine" class="gl-mb-4"> + <span class="gl-font-weight-bold">{{ $options.i18n.engine }}</span> + {{ drawer.engineName }} + </li> + <li data-testid="findings-drawer-category" class="gl-mb-4"> + <span class="gl-font-weight-bold">{{ $options.i18n.category }}</span> + {{ drawer.categories ? drawer.categories[0] : '' }} + </li> + <li data-testid="findings-drawer-other-locations" class="gl-mb-4"> + <span class="gl-font-weight-bold gl-mb-3 gl-display-block">{{ + $options.i18n.otherLocations + }}</span> + <ul class="gl-pl-6"> + <li + v-for="otherLocation in drawer.otherLocations" + :key="otherLocation.path" + class="gl-mb-1" + > + <gl-link + data-testid="findings-drawer-other-locations-link" + :href="otherLocation.href" + >{{ otherLocation.path }}</gl-link + > + </li> + </ul> + </li> + </ul> + <span + v-safe-html:[$options.safeHtmlConfig]="drawer.content ? drawer.content.body : ''" + data-testid="findings-drawer-body" + class="drawer-body gl-display-block gl-px-3 gl-py-0!" + ></span> + </template> + </gl-drawer> +</template> diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue index 74eef50ebaf..d49598d2f21 100644 --- a/app/assets/javascripts/environments/components/environment_actions.vue +++ b/app/assets/javascripts/environments/components/environment_actions.vue @@ -1,5 +1,5 @@ <script> -import { GlDropdown, GlDropdownItem, GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { GlIcon, GlDisclosureDropdown, GlDisclosureDropdownItem } from '@gitlab/ui'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import { formatTime } from '~/lib/utils/datetime_utility'; import { __, s__, sprintf } from '~/locale'; @@ -7,12 +7,9 @@ import eventHub from '../event_hub'; import actionMutation from '../graphql/mutations/action.mutation.graphql'; export default { - directives: { - GlTooltip: GlTooltipDirective, - }, components: { - GlDropdown, - GlDropdownItem, + GlDisclosureDropdownItem, + GlDisclosureDropdown, GlIcon, }, props: { @@ -36,6 +33,16 @@ export default { title() { return __('Deploy to...'); }, + actionItems() { + return this.actions.map((actionItem) => ({ + text: actionItem.name, + action: () => this.onClickAction(actionItem), + extraAttrs: { + disabled: this.isActionDisabled(actionItem), + }, + ...actionItem, + })); + }, }, methods: { async onClickAction(action) { @@ -48,7 +55,6 @@ export default { ); const confirmed = await confirmAction(confirmationMessage); - if (!confirmed) { return; } @@ -80,30 +86,31 @@ export default { }; </script> <template> - <gl-dropdown - v-gl-tooltip + <gl-disclosure-dropdown :text="title" :title="title" :loading="isLoading" :aria-label="title" + :items="actionItems" icon="play" text-sr-only right data-container="body" data-testid="environment-actions-button" > - <gl-dropdown-item - v-for="(action, i) in actions" - :key="i" - :disabled="isActionDisabled(action)" + <gl-disclosure-dropdown-item + v-for="item in actionItems" + :key="item.name" + :item="item" data-testid="manual-action-link" - @click="onClickAction(action)" > - <span class="gl-flex-grow-1">{{ action.name }}</span> - <span v-if="action.scheduledAt" class="gl-text-gray-500 float-right"> - <gl-icon name="clock" /> - {{ remainingTime(action) }} - </span> - </gl-dropdown-item> - </gl-dropdown> + <template #list-item> + <span class="gl-flex-grow-1">{{ item.text }}</span> + <span v-if="item.scheduledAt" class="gl-text-gray-500 float-right"> + <gl-icon name="clock" /> + {{ remainingTime(item) }} + </span> + </template> + </gl-disclosure-dropdown-item> + </gl-disclosure-dropdown> </template> diff --git a/app/assets/javascripts/environments/components/new_environment_item.vue b/app/assets/javascripts/environments/components/new_environment_item.vue index a945e89393b..f46bd0e3780 100644 --- a/app/assets/javascripts/environments/components/new_environment_item.vue +++ b/app/assets/javascripts/environments/components/new_environment_item.vue @@ -270,7 +270,6 @@ export default { <stop-component v-if="canStop" :environment="environment" - class="gl-z-index-2" data-track-action="click_button" data-track-label="environment_stop" graphql diff --git a/app/assets/javascripts/graphql_shared/possible_types.json b/app/assets/javascripts/graphql_shared/possible_types.json index 22629dfb7d8..105a02a021c 100644 --- a/app/assets/javascripts/graphql_shared/possible_types.json +++ b/app/assets/javascripts/graphql_shared/possible_types.json @@ -21,7 +21,8 @@ "Epic", "EpicIssue", "Issue", - "MergeRequest" + "MergeRequest", + "WorkItemWidgetCurrentUserTodos" ], "DependencyLinkMetadata": [ "NugetDependencyLinkMetadata" @@ -145,6 +146,7 @@ ], "WorkItemWidget": [ "WorkItemWidgetAssignees", + "WorkItemWidgetCurrentUserTodos", "WorkItemWidgetDescription", "WorkItemWidgetHealthStatus", "WorkItemWidgetHierarchy", diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js index 967c83b320f..29c44d2f596 100644 --- a/app/assets/javascripts/ide/index.js +++ b/app/assets/javascripts/ide/index.js @@ -72,7 +72,6 @@ export const initLegacyWebIDE = (el, options = {}) => { environmentsGuidanceAlertDismissed: !parseBoolean(el.dataset.enableEnvironmentsGuidance), previewMarkdownPath: el.dataset.previewMarkdownPath, userPreferencesPath: el.dataset.userPreferencesPath, - learnGitlabSource: parseBoolean(el.dataset.learnGitlabSource), }); }, beforeDestroy() { diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js index 572465f7587..79a8ccf2285 100644 --- a/app/assets/javascripts/ide/stores/modules/commit/actions.js +++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js @@ -1,7 +1,6 @@ import { createAlert } from '~/alert'; import { addNumericSuffix } from '~/ide/utils'; import { sprintf, __ } from '~/locale'; -import Tracking from '~/tracking'; import { leftSidebarViews } from '../../../constants'; import eventHub from '../../../eventhub'; import { parseCommitError } from '../../../lib/errors'; @@ -163,10 +162,6 @@ export const commitChanges = ({ commit, state, getters, dispatch, rootState, roo ); } - if (rootState.learnGitlabSource) { - Tracking.event(undefined, 'commit', { label: 'web_ide_learn_gitlab_source' }); - } - dispatch('setLastCommitMessage', data); dispatch('updateCommitMessage', ''); return dispatch('updateFilesAfterCommit', { diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index 013a0c3ce8f..356bbf28a48 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -32,5 +32,4 @@ export default () => ({ environmentsGuidanceAlertDetected: false, previewMarkdownPath: '', userPreferencesPath: '', - learnGitlabSource: false, }); diff --git a/app/assets/javascripts/mr_notes/stores/drawer/actions.js b/app/assets/javascripts/mr_notes/stores/drawer/actions.js new file mode 100644 index 00000000000..4f8c3bb7f20 --- /dev/null +++ b/app/assets/javascripts/mr_notes/stores/drawer/actions.js @@ -0,0 +1,5 @@ +import * as types from './mutation_types'; + +export const setDrawer = ({ commit }, data) => { + return commit(types.default.SET_DRAWER, data); +}; diff --git a/app/assets/javascripts/mr_notes/stores/drawer/getters.js b/app/assets/javascripts/mr_notes/stores/drawer/getters.js new file mode 100644 index 00000000000..dd61bc900fa --- /dev/null +++ b/app/assets/javascripts/mr_notes/stores/drawer/getters.js @@ -0,0 +1 @@ +export const activeDrawer = (state) => state.activeDrawer; diff --git a/app/assets/javascripts/mr_notes/stores/drawer/index.js b/app/assets/javascripts/mr_notes/stores/drawer/index.js new file mode 100644 index 00000000000..c4a7eacb78a --- /dev/null +++ b/app/assets/javascripts/mr_notes/stores/drawer/index.js @@ -0,0 +1,13 @@ +import * as actions from './actions'; +import * as getters from './getters'; +import mutations from './mutations'; + +export default () => ({ + namespaced: true, + state: { + activeDrawer: {}, + }, + mutations, + actions, + getters, +}); diff --git a/app/assets/javascripts/mr_notes/stores/drawer/mutation_types.js b/app/assets/javascripts/mr_notes/stores/drawer/mutation_types.js new file mode 100644 index 00000000000..5fe8a9ba20d --- /dev/null +++ b/app/assets/javascripts/mr_notes/stores/drawer/mutation_types.js @@ -0,0 +1,3 @@ +export default { + SET_DRAWER: 'SET_DRAWER', +}; diff --git a/app/assets/javascripts/mr_notes/stores/drawer/mutations.js b/app/assets/javascripts/mr_notes/stores/drawer/mutations.js new file mode 100644 index 00000000000..eee43f2b316 --- /dev/null +++ b/app/assets/javascripts/mr_notes/stores/drawer/mutations.js @@ -0,0 +1,7 @@ +import types from './mutation_types'; + +export default { + [types.SET_DRAWER](state, drawer) { + Object.assign(state, { activeDrawer: drawer }); + }, +}; diff --git a/app/assets/javascripts/mr_notes/stores/index.js b/app/assets/javascripts/mr_notes/stores/index.js index 7527c685c71..1f8e61beff0 100644 --- a/app/assets/javascripts/mr_notes/stores/index.js +++ b/app/assets/javascripts/mr_notes/stores/index.js @@ -4,6 +4,7 @@ import batchCommentsModule from '~/batch_comments/stores/modules/batch_comments' import diffsModule from '~/diffs/store/modules'; import notesModule from '~/notes/stores/modules'; import mrPageModule from './modules'; +import findingsDrawer from './drawer'; Vue.use(Vuex); @@ -12,6 +13,7 @@ export const createModules = () => ({ notes: notesModule(), diffs: diffsModule(), batchComments: batchCommentsModule(), + findingsDrawer: findingsDrawer(), }); export const createStore = () => diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 35cf60a2c24..adadf422284 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -43,6 +43,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:mr_experience_survey, project) push_frontend_feature_flag(:realtime_mr_status_change, project) push_frontend_feature_flag(:saved_replies, current_user) + push_frontend_feature_flag(:code_quality_inline_drawer, project) end around_action :allow_gitaly_ref_name_caching, only: [:index, :show, :diffs, :discussions] diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb index c3c90f00ade..84eabd3a142 100644 --- a/app/controllers/projects/pipelines_controller.rb +++ b/app/controllers/projects/pipelines_controller.rb @@ -9,16 +9,16 @@ class Projects::PipelinesController < Projects::ApplicationController urgency :low, [ :index, :new, :builds, :show, :failures, :create, :stage, :retry, :dag, :cancel, :test_report, - :charts, :config_variables, :destroy, :status + :charts, :destroy, :status ] before_action :disable_query_limiting, only: [:create, :retry] - before_action :pipeline, except: [:index, :new, :create, :charts, :config_variables] + before_action :pipeline, except: [:index, :new, :create, :charts] before_action :set_pipeline_path, only: [:show] before_action :authorize_read_pipeline! before_action :authorize_read_build!, only: [:index, :show] before_action :authorize_read_ci_cd_analytics!, only: [:charts] - before_action :authorize_create_pipeline!, only: [:new, :create, :config_variables] + before_action :authorize_create_pipeline!, only: [:new, :create] before_action :authorize_update_pipeline!, only: [:retry, :cancel] before_action :ensure_pipeline, only: [:show, :downloadable_artifacts] before_action :reject_if_build_artifacts_size_refreshing!, only: [:destroy] @@ -45,7 +45,7 @@ class Projects::PipelinesController < Projects::ApplicationController POLLING_INTERVAL = 10_000 feature_category :continuous_integration, [ - :charts, :show, :config_variables, :stage, :cancel, :retry, + :charts, :show, :stage, :cancel, :retry, :builds, :dag, :failures, :status, :index, :create, :new, :destroy ] @@ -216,18 +216,6 @@ class Projects::PipelinesController < Projects::ApplicationController end end - def config_variables - respond_to do |format| - format.json do - # Even if the parameter name is `sha`, it is actually a ref name. We always send `ref` to the endpoint. - # See: https://gitlab.com/gitlab-org/gitlab/-/issues/389065 - result = Ci::ListConfigVariablesService.new(@project, current_user).execute(params[:sha]) - - result.nil? ? head(:no_content) : render(json: result) - end - end - end - def downloadable_artifacts render json: Ci::DownloadableArtifactSerializer.new( project: project, diff --git a/app/graphql/types/work_items/widget_interface.rb b/app/graphql/types/work_items/widget_interface.rb index 50f8e4f7d8a..e562312d06e 100644 --- a/app/graphql/types/work_items/widget_interface.rb +++ b/app/graphql/types/work_items/widget_interface.rb @@ -19,7 +19,8 @@ module Types ::Types::WorkItems::Widgets::StartAndDueDateType, ::Types::WorkItems::Widgets::MilestoneType, ::Types::WorkItems::Widgets::NotesType, - ::Types::WorkItems::Widgets::NotificationsType + ::Types::WorkItems::Widgets::NotificationsType, + ::Types::WorkItems::Widgets::CurrentUserTodosType ].freeze def self.ce_orphan_types @@ -47,6 +48,8 @@ module Types ::Types::WorkItems::Widgets::NotesType when ::WorkItems::Widgets::Notifications ::Types::WorkItems::Widgets::NotificationsType + when ::WorkItems::Widgets::CurrentUserTodos + ::Types::WorkItems::Widgets::CurrentUserTodosType else raise "Unknown GraphQL type for widget #{object}" end diff --git a/app/graphql/types/work_items/widgets/current_user_todos_type.rb b/app/graphql/types/work_items/widgets/current_user_todos_type.rb new file mode 100644 index 00000000000..1c7cdd631e2 --- /dev/null +++ b/app/graphql/types/work_items/widgets/current_user_todos_type.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Types + module WorkItems + module Widgets + # Disabling widget level authorization as it might be too granular + # and we already authorize the parent work item + # rubocop:disable Graphql/AuthorizeTypes + class CurrentUserTodosType < BaseObject + graphql_name 'WorkItemWidgetCurrentUserTodos' + description 'Represents a todos widget' + + implements Types::WorkItems::WidgetInterface + implements Types::CurrentUserTodos + + private + + # Overriden as `Types::CurrentUserTodos` relies on `unpresented` being the Issuable record. + def unpresented + object.work_item + end + end + # rubocop:enable Graphql/AuthorizeTypes + end + end +end diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb index 2157b3044eb..a8dbaa4325f 100644 --- a/app/helpers/ide_helper.rb +++ b/app/helpers/ide_helper.rb @@ -83,5 +83,3 @@ module IdeHelper current_user.dismissed_callout?(feature_name: 'web_ide_ci_environments_guidance') end end - -IdeHelper.prepend_mod_with('IdeHelper') diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb index 56f4187ae42..a37c4e057b3 100644 --- a/app/helpers/sidebars_helper.rb +++ b/app/helpers/sidebars_helper.rb @@ -4,6 +4,8 @@ module SidebarsHelper include MergeRequestsHelper include Nav::NewDropdownHelper + USER_BAR_COUNT_LIMIT = 99 + def sidebar_tracking_attributes_by_object(object) sidebar_attributes_for_object(object).fetch(:tracking_attrs, {}) end @@ -62,10 +64,10 @@ module SidebarsHelper }, can_sign_out: current_user_menu?(:sign_out), sign_out_link: destroy_user_session_path, - assigned_open_issues_count: user.assigned_open_issues_count, - todos_pending_count: user.todos_pending_count, + assigned_open_issues_count: format_user_bar_count(user.assigned_open_issues_count), + todos_pending_count: format_user_bar_count(user.todos_pending_count), issues_dashboard_path: issues_dashboard_path(assignee_username: user.username), - total_merge_requests_count: user_merge_requests_counts[:total], + total_merge_requests_count: format_user_bar_count(user_merge_requests_counts[:total]), create_new_menu_groups: create_new_menu_groups(group: group, project: project), merge_request_menu: create_merge_request_menu(user), projects_path: projects_path, @@ -292,6 +294,17 @@ module SidebarsHelper links end + + # Formats the counts to be shown in the super sidebar's top section (issues, MRs and todos). + # We want to avoid printing huge numbers there, so when the count exceeds USER_BAR_COUNT_LIMIT, + # we cap it to USER_BAR_COUNT_LIMIT and append a "+" to it. + def format_user_bar_count(count) + if count > USER_BAR_COUNT_LIMIT + "#{USER_BAR_COUNT_LIMIT}+" + else + count.to_s + end + end end SidebarsHelper.prepend_mod_with('SidebarsHelper') diff --git a/app/models/group.rb b/app/models/group.rb index 70199424b8c..8e0c267185d 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -201,13 +201,22 @@ class Group < Namespace end scope :project_creation_allowed, -> do - permitted_levels = [ + project_creation_allowed_on_levels = [ ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS, ::Gitlab::Access::MAINTAINER_PROJECT_ACCESS, nil ] - where(project_creation_level: permitted_levels) + # When the value of application_settings.default_project_creation is set to `NO_ONE_PROJECT_ACCESS`, + # it means that a `nil` value for `groups.project_creation_level` is telling us: + # do not allow project creation in such groups. + # ie, `nil` is a placeholder value for inheriting the value from the ApplicationSetting. + # So we remove `nil` from the list when the application_setting's value is `NO_ONE_PROJECT_ACCESS` + if ::Gitlab::CurrentSettings.default_project_creation == ::Gitlab::Access::NO_ONE_PROJECT_ACCESS + project_creation_allowed_on_levels.delete(nil) + end + + where(project_creation_level: project_creation_allowed_on_levels) end scope :shared_into_ancestors, -> (group) do diff --git a/app/models/work_items/widget_definition.rb b/app/models/work_items/widget_definition.rb index 9e8c421d740..9bfdb25ce47 100644 --- a/app/models/work_items/widget_definition.rb +++ b/app/models/work_items/widget_definition.rb @@ -29,7 +29,8 @@ module WorkItems status: 11, # EE-only requirement_legacy: 12, # EE-only test_reports: 13, # EE-only - notifications: 14 + notifications: 14, + current_user_todos: 15 } def self.available_widgets diff --git a/app/models/work_items/widgets/current_user_todos.rb b/app/models/work_items/widgets/current_user_todos.rb new file mode 100644 index 00000000000..61c4fcb453b --- /dev/null +++ b/app/models/work_items/widgets/current_user_todos.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module WorkItems + module Widgets + class CurrentUserTodos < Base + end + end +end diff --git a/app/services/bulk_imports/create_service.rb b/app/services/bulk_imports/create_service.rb index 80293771b5f..348280030f4 100644 --- a/app/services/bulk_imports/create_service.rb +++ b/app/services/bulk_imports/create_service.rb @@ -45,7 +45,12 @@ module BulkImports bulk_import = create_bulk_import - Gitlab::Tracking.event(self.class.name, 'create', label: 'bulk_import_group') + Gitlab::Tracking.event( + self.class.name, + 'create', + label: 'bulk_import_group', + extra: { source_equals_destination: source_equals_destination? } + ) BulkImportWorker.perform_async(bulk_import.id) @@ -126,6 +131,10 @@ module BulkImports ) end + def source_equals_destination? + credentials[:url].starts_with?(Settings.gitlab.base_url) + end + def validate_destination_full_path(entity_params) source_type = entity_params[:source_type] diff --git a/app/services/ci/pipeline_processing/atomic_processing_service.rb b/app/services/ci/pipeline_processing/atomic_processing_service.rb index a863e63c0e1..4c087d23a53 100644 --- a/app/services/ci/pipeline_processing/atomic_processing_service.rb +++ b/app/services/ci/pipeline_processing/atomic_processing_service.rb @@ -97,7 +97,7 @@ module Ci def status_of_previous_jobs(job) if job.scheduling_type_dag? # job uses DAG, get status of all dependent needs - @collection.status_of_jobs(job.aggregated_needs_names.to_a, dag: true) + @collection.status_of_jobs(job.aggregated_needs_names.to_a) else # job uses Stages, get status of prior stage @collection.status_of_jobs_prior_to_stage(job.stage_idx.to_i) diff --git a/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb index ca3e198cfc7..85646b79254 100644 --- a/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb +++ b/app/services/ci/pipeline_processing/atomic_processing_service/status_collection.rb @@ -25,7 +25,7 @@ module Ci # This methods gets composite status of all jobs def status_of_all - status_for_array(all_jobs, dag: false) + status_for_array(all_jobs) end # This methods gets composite status for jobs at a given stage @@ -33,15 +33,15 @@ module Ci strong_memoize("status_of_stage_#{stage_position}") do stage_jobs = all_jobs_grouped_by_stage_position[stage_position].to_a - status_for_array(stage_jobs.flatten, dag: false) + status_for_array(stage_jobs.flatten) end end # This methods gets composite status for jobs with given names - def status_of_jobs(names, dag:) + def status_of_jobs(names) jobs = all_jobs_by_name.slice(*names) - status_for_array(jobs.values, dag: dag) + status_for_array(jobs.values, dag: true) end # This methods gets composite status for jobs before given stage @@ -50,7 +50,7 @@ module Ci stage_jobs = all_jobs_grouped_by_stage_position .select { |position, _| position < stage_position } - status_for_array(stage_jobs.values.flatten, dag: false) + status_for_array(stage_jobs.values.flatten) end end @@ -75,9 +75,9 @@ module Ci :stage_idx, :processed, :lock_version ].freeze - def status_for_array(jobs, dag:) + def status_for_array(jobs, dag: false) result = Gitlab::Ci::Status::Composite - .new(jobs, dag: dag) + .new(jobs, dag: dag, project: pipeline.project) .status result || 'success' end diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml index ee51ee9b0e2..63b44de0d74 100644 --- a/app/views/projects/pipelines/new.html.haml +++ b/app/views/projects/pipelines/new.html.haml @@ -7,7 +7,6 @@ #js-new-pipeline{ data: { project_id: @project.id, pipelines_path: project_pipelines_path(@project), - config_variables_path: config_variables_namespace_project_pipelines_path(@project.namespace, @project), default_branch: @project.default_branch, pipelines_editor_path: project_ci_pipeline_editor_path(@project), can_view_pipeline_editor: can_view_pipeline_editor?(@project), diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index fa718a9c907..48633ce0ce7 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -13,8 +13,8 @@ - @options && @options.each do |key, value| = hidden_field_tag key, value, id: nil .dropdown - = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: ref, ref_type: @ref_type, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: field_name, submit_form_on_click: true, visit: true, qa_selector: "branches_dropdown", testid: "branches-select" }, { toggle_class: "js-project-refs-dropdown" } - .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging{ class: ("dropdown-menu-right" if local_assigns[:align_right]), data: { qa_selector: "branches_dropdown_content" } } + = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: ref, ref_type: @ref_type, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: field_name, submit_form_on_click: true, visit: true, testid: "branches-select" }, { toggle_class: "js-project-refs-dropdown" } + .dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging{ class: ("dropdown-menu-right" if local_assigns[:align_right]) } .dropdown-page-one = dropdown_title _("Switch branch/tag") = dropdown_filter _("Search branches and tags") diff --git a/config/feature_flags/development/ci_simplify_dag_status_calculation_for_processing.yml b/config/feature_flags/development/ci_simplify_dag_status_calculation_for_processing.yml new file mode 100644 index 00000000000..bde5bce548f --- /dev/null +++ b/config/feature_flags/development/ci_simplify_dag_status_calculation_for_processing.yml @@ -0,0 +1,8 @@ +--- +name: ci_simplify_dag_status_calculation_for_processing +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/115684 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/403005 +milestone: '15.11' +type: development +group: group::pipeline authoring +default_enabled: false diff --git a/config/feature_flags/development/code_quality_inline_drawer.yml b/config/feature_flags/development/code_quality_inline_drawer.yml new file mode 100644 index 00000000000..0af4c98ada8 --- /dev/null +++ b/config/feature_flags/development/code_quality_inline_drawer.yml @@ -0,0 +1,8 @@ +--- +name: code_quality_inline_drawer +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/114649 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/397037 +milestone: '15.11' +type: development +group: group::static analysis +default_enabled: false diff --git a/config/routes/pipelines.rb b/config/routes/pipelines.rb index ef390d7988b..10e0e948c62 100644 --- a/config/routes/pipelines.rb +++ b/config/routes/pipelines.rb @@ -7,7 +7,6 @@ resources :pipelines, only: [:index, :new, :create, :show, :destroy] do scope '(*ref)', constraints: { ref: Gitlab::PathRegex.git_reference_regex } do get :latest, action: :show, defaults: { latest: true } end - get :config_variables end member do diff --git a/db/migrate/20230317162059_add_current_user_todos_work_item_widget.rb b/db/migrate/20230317162059_add_current_user_todos_work_item_widget.rb new file mode 100644 index 00000000000..4ed1fd6869c --- /dev/null +++ b/db/migrate/20230317162059_add_current_user_todos_work_item_widget.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +class AddCurrentUserTodosWorkItemWidget < Gitlab::Database::Migration[2.1] + class WorkItemType < MigrationRecord + self.table_name = 'work_item_types' + end + + class WidgetDefinition < MigrationRecord + self.table_name = 'work_item_widget_definitions' + end + + restrict_gitlab_migration gitlab_schema: :gitlab_main + disable_ddl_transaction! + + WIDGET_NAME = 'Current user todos' + WIDGET_ENUM_VALUE = 15 + WORK_ITEM_TYPES = [ + 'Issue', + 'Incident', + 'Test Case', + 'Requirement', + 'Task', + 'Objective', + 'Key Result' + ].freeze + + def up + widgets = [] + + WORK_ITEM_TYPES.each do |type_name| + type = WorkItemType.find_by_name_and_namespace_id(type_name, nil) + + unless type + Gitlab::AppLogger.warn("type #{type_name} is missing, not adding widget") + + next + end + + widgets << { + work_item_type_id: type.id, + name: WIDGET_NAME, + widget_type: WIDGET_ENUM_VALUE + } + end + + return if widgets.empty? + + WidgetDefinition.upsert_all( + widgets, + unique_by: :index_work_item_widget_definitions_on_default_witype_and_name + ) + end + + def down + WidgetDefinition.where(name: WIDGET_NAME).delete_all + end +end diff --git a/db/post_migrate/20230328111013_re_migrate_redis_slot_keys.rb b/db/post_migrate/20230328111013_re_migrate_redis_slot_keys.rb new file mode 100644 index 00000000000..0d4b31ecc99 --- /dev/null +++ b/db/post_migrate/20230328111013_re_migrate_redis_slot_keys.rb @@ -0,0 +1,73 @@ +# frozen_string_literal: true + +class ReMigrateRedisSlotKeys < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + Gitlab::UsageDataCounters::HLLRedisCounter.known_events.each do |event| + if event[:aggregation].to_sym == :daily + migrate_daily_aggregated(event) + else + migrate_weekly_aggregated(event) + end + end + end + + def down + # no-op + end + + private + + def migrate_daily_aggregated(event) + days_back = Gitlab::UsageDataCounters::HLLRedisCounter::DEFAULT_DAILY_KEY_EXPIRY_LENGTH + start_date = Date.today - days_back - 1.day + end_date = Date.today + 1.day + + (start_date..end_date).each do |date| + rename_key(event, date) + end + end + + def migrate_weekly_aggregated(event) + weeks_back = Gitlab::UsageDataCounters::HLLRedisCounter::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH + start_date = (Date.today - weeks_back).beginning_of_week - 1.day + end_date = Date.today.end_of_week + 1.day + + (start_date..end_date).step(7).each { |date| rename_key(event, date) } + end + + def rename_key(event, date) + old_key = old_redis_key(event, date) + new_key = Gitlab::UsageDataCounters::HLLRedisCounter.send(:redis_key, event, date) + + # cannot simply rename due to different slots + Gitlab::Redis::SharedState.with do |redis| + hll_blob = redis.get(old_key) + + break unless hll_blob + + temp_key = new_key + "_#{Time.current.to_i}" + ttl = redis.ttl(old_key) + ttl = ttl > 0 ? ttl : Gitlab::UsageDataCounters::HLLRedisCounter.send(:expiry, event) + + redis.multi do |multi| + multi.set(temp_key, hll_blob, ex: 1.day.to_i) + multi.pfmerge(new_key, new_key, temp_key) + multi.expire(new_key, ttl) + end + + redis.del(temp_key) + end + end + + def old_redis_key(event, time) + name_with_slot = if event[:redis_slot].present? + event[:name].to_s.gsub(event[:redis_slot], "{#{event[:redis_slot]}}") + else + "{#{event[:name]}}" + end + + Gitlab::UsageDataCounters::HLLRedisCounter.send(:apply_time_aggregation, name_with_slot, time, event) + end +end diff --git a/db/schema_migrations/20230317162059 b/db/schema_migrations/20230317162059 new file mode 100644 index 00000000000..9adcabb9aeb --- /dev/null +++ b/db/schema_migrations/20230317162059 @@ -0,0 +1 @@ +86f8982c21b25cfc1914304b0083075226e0f8182b66a766a4f354b1b5fc8f7d
\ No newline at end of file diff --git a/db/schema_migrations/20230328111013 b/db/schema_migrations/20230328111013 new file mode 100644 index 00000000000..18b085e1df1 --- /dev/null +++ b/db/schema_migrations/20230328111013 @@ -0,0 +1 @@ +6acd119e30d05fc794998cd4a80416cb91e11021659eb62ac93590175039a6cf
\ No newline at end of file diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 98c9c1755f1..3fd73c8c0d6 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -22318,6 +22318,34 @@ Represents an assignees widget. | <a id="workitemwidgetassigneescaninvitemembers"></a>`canInviteMembers` | [`Boolean!`](#boolean) | Indicates whether the current user can invite members to the work item's project. | | <a id="workitemwidgetassigneestype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | +### `WorkItemWidgetCurrentUserTodos` + +Represents a todos widget. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="workitemwidgetcurrentusertodostype"></a>`type` | [`WorkItemWidgetType`](#workitemwidgettype) | Widget type. | + +#### Fields with arguments + +##### `WorkItemWidgetCurrentUserTodos.currentUserTodos` + +To-do items for the current user. + +Returns [`TodoConnection!`](#todoconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="workitemwidgetcurrentusertodoscurrentusertodosstate"></a>`state` | [`TodoStateEnum`](#todostateenum) | State of the to-do items. | + ### `WorkItemWidgetDescription` Represents a description widget. @@ -24778,6 +24806,7 @@ Type of a work item widget. | Value | Description | | ----- | ----------- | | <a id="workitemwidgettypeassignees"></a>`ASSIGNEES` | Assignees widget. | +| <a id="workitemwidgettypecurrent_user_todos"></a>`CURRENT_USER_TODOS` | Current User Todos widget. | | <a id="workitemwidgettypedescription"></a>`DESCRIPTION` | Description widget. | | <a id="workitemwidgettypehealth_status"></a>`HEALTH_STATUS` | Health Status widget. | | <a id="workitemwidgettypehierarchy"></a>`HIERARCHY` | Hierarchy widget. | @@ -25654,6 +25683,7 @@ Implementations: - [`EpicIssue`](#epicissue) - [`Issue`](#issue) - [`MergeRequest`](#mergerequest) +- [`WorkItemWidgetCurrentUserTodos`](#workitemwidgetcurrentusertodos) ##### Fields with arguments @@ -26149,6 +26179,7 @@ four standard [pagination arguments](#connection-pagination-arguments): Implementations: - [`WorkItemWidgetAssignees`](#workitemwidgetassignees) +- [`WorkItemWidgetCurrentUserTodos`](#workitemwidgetcurrentusertodos) - [`WorkItemWidgetDescription`](#workitemwidgetdescription) - [`WorkItemWidgetHealthStatus`](#workitemwidgethealthstatus) - [`WorkItemWidgetHierarchy`](#workitemwidgethierarchy) diff --git a/doc/architecture/blueprints/database_testing/index.md b/doc/architecture/blueprints/database_testing/index.md index 21d22574302..4e142a9345c 100644 --- a/doc/architecture/blueprints/database_testing/index.md +++ b/doc/architecture/blueprints/database_testing/index.md @@ -10,7 +10,7 @@ participating-stages: [] # Database Testing -**Notice:** This blueprints has been partially implemented. We still plan to +**Notice:** This blueprint has been partially implemented. We still plan to iterate on the tooling. The content below is a historical version of the blueprint, written prior to incorporating database testing into our development workflow. diff --git a/lib/gitlab/ci/status/composite.rb b/lib/gitlab/ci/status/composite.rb index ed2e29e257f..8e55b414364 100644 --- a/lib/gitlab/ci/status/composite.rb +++ b/lib/gitlab/ci/status/composite.rb @@ -8,7 +8,7 @@ module Gitlab # This class accepts an array of arrays/hashes/or objects # `with_allow_failure` will be removed when deleting ci_remove_ensure_stage_service - def initialize(all_jobs, with_allow_failure: true, dag: false) + def initialize(all_jobs, with_allow_failure: true, dag: false, project: nil) unless all_jobs.respond_to?(:pluck) raise ArgumentError, "all_jobs needs to respond to `.pluck`" end @@ -17,6 +17,7 @@ module Gitlab @status_key = 0 @allow_failure_key = 1 if with_allow_failure @dag = dag + @project = project consume_all_jobs(all_jobs) end @@ -28,11 +29,13 @@ module Gitlab # based on what statuses are no longer valid based on the # data set that we have # - # This method is used for two cases: + # This method is used for three cases: # 1. When it is called for a stage or a pipeline (with `all_jobs` from all jobs in a stage or a pipeline), # then, the returned status is assigned to the stage or pipeline. # 2. When it is called for a job (with `all_jobs` from all previous jobs or all needed jobs), # then, the returned status is used to determine if the job is processed or not. + # 3. When it is called for a group (of jobs that are related), + # then, the returned status is used to show the overall status of the group. # rubocop: disable Metrics/CyclomaticComplexity # rubocop: disable Metrics/PerceivedComplexity def status @@ -42,7 +45,9 @@ module Gitlab if @dag && any_skipped_or_ignored? # The DAG job is skipped if one of the needs does not run at all. 'skipped' - elsif @dag && !only_of?(:success, :failed, :canceled, :skipped, :success_with_warnings) + elsif ::Feature.disabled?(:ci_simplify_dag_status_calculation_for_processing, @project) && + @dag && + !only_of?(:success, :failed, :canceled, :skipped, :success_with_warnings) # DAG is blocked from executing if a dependent is not "complete" 'pending' elsif only_of?(:skipped, :ignored) diff --git a/lib/gitlab/database_importers/work_items/base_type_importer.rb b/lib/gitlab/database_importers/work_items/base_type_importer.rb index 85ac816f712..c6c45ca4d05 100644 --- a/lib/gitlab/database_importers/work_items/base_type_importer.rb +++ b/lib/gitlab/database_importers/work_items/base_type_importer.rb @@ -19,7 +19,8 @@ module Gitlab status: 'Status', requirement_legacy: 'Requirement legacy', test_reports: 'Test reports', - notifications: 'Notifications' + notifications: 'Notifications', + current_user_todos: "Current user todos" }.freeze WIDGETS_FOR_TYPE = { @@ -34,18 +35,21 @@ module Gitlab :iteration, :weight, :health_status, - :notifications + :notifications, + :current_user_todos ], incident: [ :description, :hierarchy, :notes, - :notifications + :notifications, + :current_user_todos ], test_case: [ :description, :notes, - :notifications + :notifications, + :current_user_todos ], requirement: [ :description, @@ -53,7 +57,8 @@ module Gitlab :status, :requirement_legacy, :test_reports, - :notifications + :notifications, + :current_user_todos ], task: [ :assignees, @@ -65,7 +70,8 @@ module Gitlab :notes, :iteration, :weight, - :notifications + :notifications, + :current_user_todos ], objective: [ :assignees, @@ -76,7 +82,8 @@ module Gitlab :notes, :health_status, :progress, - :notifications + :notifications, + :current_user_todos ], key_result: [ :assignees, @@ -87,7 +94,8 @@ module Gitlab :notes, :health_status, :progress, - :notifications + :notifications, + :current_user_todos ] }.freeze diff --git a/lib/gitlab/usage_data_counters/known_events/analytics.yml b/lib/gitlab/usage_data_counters/known_events/analytics.yml index 0b30308b552..1c390f2d7fd 100644 --- a/lib/gitlab/usage_data_counters/known_events/analytics.yml +++ b/lib/gitlab/usage_data_counters/known_events/analytics.yml @@ -1,26 +1,39 @@ - name: users_viewing_analytics_group_devops_adoption + redis_slot: analytics aggregation: weekly - name: i_analytics_dev_ops_adoption + redis_slot: analytics aggregation: weekly - name: i_analytics_dev_ops_score + redis_slot: analytics aggregation: weekly - name: i_analytics_instance_statistics + redis_slot: analytics aggregation: weekly - name: p_analytics_pipelines + redis_slot: analytics aggregation: weekly - name: p_analytics_valuestream + redis_slot: analytics aggregation: weekly - name: p_analytics_repo + redis_slot: analytics aggregation: weekly - name: i_analytics_cohorts + redis_slot: analytics aggregation: weekly - name: p_analytics_ci_cd_pipelines + redis_slot: analytics aggregation: weekly - name: p_analytics_ci_cd_deployment_frequency + redis_slot: analytics aggregation: weekly - name: p_analytics_ci_cd_lead_time + redis_slot: analytics aggregation: weekly - name: p_analytics_ci_cd_time_to_restore_service + redis_slot: analytics aggregation: weekly - name: p_analytics_ci_cd_change_failure_rate + redis_slot: analytics aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml b/lib/gitlab/usage_data_counters/known_events/ci_templates.yml index 82c023e6e38..e717679e3dc 100644 --- a/lib/gitlab/usage_data_counters/known_events/ci_templates.yml +++ b/lib/gitlab/usage_data_counters/known_events/ci_templates.yml @@ -4,304 +4,455 @@ # Do not edit it manually! --- - name: p_ci_templates_terraform_base_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_terraform_base + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_dotnet + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_nodejs + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_openshift + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_auto_devops + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_bash + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_rust + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_elixir + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_clojure + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_crystal + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_getting_started + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_code_quality + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_verify_load_performance_testing + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_verify_accessibility + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_verify_failfast + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_verify_browser_performance + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_verify_browser_performance_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_grails + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_sast + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_dast_runner_validation + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_dast_on_demand_scan + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_secret_detection + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_license_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_coverage_fuzzing_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_dast_on_demand_api_scan + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_coverage_fuzzing + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_api_fuzzing_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_secure_binaries + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_dast_api + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_container_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_dast_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_sast_iac + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_dependency_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_dast_api_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_container_scanning_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_api_fuzzing + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_dast + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_api_discovery + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_fortify_fod_sast + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_security_sast_iac_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_qualys_iac_security + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_ios_fastlane + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_composer + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_c + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_python + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_android_fastlane + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_android_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_django + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_maven + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_liquibase + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_flutter + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_workflows_branch_pipelines + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_workflows_mergerequest_pipelines + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_laravel + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_kaniko + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_php + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_packer + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_themekit + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_terraform + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_katalon + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_mono + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_go + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_scala + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_latex + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_android + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_indeni_cloudrail + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_matlab + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_deploy_ecs + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_aws_cf_provision_and_deploy_ec2 + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_aws_deploy_ecs + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_gradle + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_chef + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_dast_default_branch_deploy + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_load_performance_testing + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_helm_2to3 + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_sast + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_secret_detection + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_license_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_code_intelligence + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_code_quality + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_deploy_ecs + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_deploy_ec2 + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_license_scanning_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_deploy + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_build + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_browser_performance_testing + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_container_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_container_scanning_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_dependency_scanning_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_test + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_sast_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_sast_iac + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_secret_detection_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_dependency_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_deploy_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_browser_performance_testing_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_cf_provision + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_build_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_jobs_sast_iac_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_terraform_latest + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_swift + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_jekyll + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_harp + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_octopress + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_brunch + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_doxygen + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_hyde + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_lektor + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_jbake + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_hexo + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_middleman + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_hugo + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_pelican + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_nanoc + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_swaggerui + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_jigsaw + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_metalsmith + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_gatsby + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_pages_html + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_dart + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_docker + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_julia + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_npm + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_dotnet_core + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_5_minute_production_app + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_ruby + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_auto_devops + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_browser_performance_testing + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_build + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_code_intelligence + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_code_quality + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_container_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_dast_default_branch_deploy + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_dependency_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_deploy + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_deploy_ec2 + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_deploy_ecs + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_helm_2to3 + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_license_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_sast + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_secret_detection + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_jobs_test + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_security_container_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_security_dast + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_security_dependency_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_security_license_scanning + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_security_sast + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_implicit_security_secret_detection + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_terraform_module_base + redis_slot: ci_templates aggregation: weekly - name: p_ci_templates_terraform_module + redis_slot: ci_templates aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/known_events/ci_users.yml b/lib/gitlab/usage_data_counters/known_events/ci_users.yml index 49757c6e672..6db10366b83 100644 --- a/lib/gitlab/usage_data_counters/known_events/ci_users.yml +++ b/lib/gitlab/usage_data_counters/known_events/ci_users.yml @@ -1,4 +1,6 @@ - name: ci_users_executing_deployment_job + redis_slot: ci_users aggregation: weekly - name: ci_users_executing_verify_environment_job + redis_slot: ci_users aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml index db0c0653f63..f64da801c39 100644 --- a/lib/gitlab/usage_data_counters/known_events/code_review_events.yml +++ b/lib/gitlab/usage_data_counters/known_events/code_review_events.yml @@ -1,233 +1,345 @@ --- - name: i_code_review_create_note_in_ipynb_diff + redis_slot: code_review aggregation: weekly - name: i_code_review_create_note_in_ipynb_diff_mr + redis_slot: code_review aggregation: weekly - name: i_code_review_create_note_in_ipynb_diff_commit + redis_slot: code_review aggregation: weekly - name: i_code_review_user_create_note_in_ipynb_diff + redis_slot: code_review aggregation: weekly - name: i_code_review_user_create_note_in_ipynb_diff_mr + redis_slot: code_review aggregation: weekly - name: i_code_review_user_create_note_in_ipynb_diff_commit + redis_slot: code_review aggregation: weekly - name: i_code_review_mr_diffs + redis_slot: code_review aggregation: weekly - name: i_code_review_user_single_file_diffs + redis_slot: code_review aggregation: weekly - name: i_code_review_mr_single_file_diffs + redis_slot: code_review aggregation: weekly - name: i_code_review_user_toggled_task_item_status + redis_slot: code_review aggregation: weekly - name: i_code_review_create_mr + redis_slot: code_review aggregation: weekly - name: i_code_review_user_create_mr + redis_slot: code_review aggregation: weekly - name: i_code_review_user_close_mr + redis_slot: code_review aggregation: weekly - name: i_code_review_user_reopen_mr + redis_slot: code_review aggregation: weekly - name: i_code_review_user_approve_mr + redis_slot: code_review aggregation: weekly - name: i_code_review_user_unapprove_mr + redis_slot: code_review aggregation: weekly - name: i_code_review_user_resolve_thread + redis_slot: code_review aggregation: weekly - name: i_code_review_user_unresolve_thread + redis_slot: code_review aggregation: weekly - name: i_code_review_edit_mr_title + redis_slot: code_review aggregation: weekly - name: i_code_review_edit_mr_desc + redis_slot: code_review aggregation: weekly - name: i_code_review_user_merge_mr + redis_slot: code_review aggregation: weekly - name: i_code_review_user_create_mr_comment + redis_slot: code_review aggregation: weekly - name: i_code_review_user_edit_mr_comment + redis_slot: code_review aggregation: weekly - name: i_code_review_user_remove_mr_comment + redis_slot: code_review aggregation: weekly - name: i_code_review_user_create_review_note + redis_slot: code_review aggregation: weekly - name: i_code_review_user_publish_review + redis_slot: code_review aggregation: weekly - name: i_code_review_user_create_multiline_mr_comment + redis_slot: code_review aggregation: weekly - name: i_code_review_user_edit_multiline_mr_comment + redis_slot: code_review aggregation: weekly - name: i_code_review_user_remove_multiline_mr_comment + redis_slot: code_review aggregation: weekly - name: i_code_review_user_add_suggestion + redis_slot: code_review aggregation: weekly - name: i_code_review_user_apply_suggestion + redis_slot: code_review aggregation: weekly - name: i_code_review_user_assigned + redis_slot: code_review aggregation: weekly - name: i_code_review_user_marked_as_draft + redis_slot: code_review aggregation: weekly - name: i_code_review_user_unmarked_as_draft + redis_slot: code_review aggregation: weekly - name: i_code_review_user_review_requested + redis_slot: code_review aggregation: weekly - name: i_code_review_user_approval_rule_added + redis_slot: code_review aggregation: weekly - name: i_code_review_user_approval_rule_deleted + redis_slot: code_review aggregation: weekly - name: i_code_review_user_approval_rule_edited + redis_slot: code_review aggregation: weekly - name: i_code_review_user_vs_code_api_request + redis_slot: code_review aggregation: weekly - name: i_code_review_user_jetbrains_api_request + redis_slot: code_review aggregation: weekly - name: i_code_review_user_gitlab_cli_api_request + redis_slot: code_review aggregation: weekly - name: i_code_review_user_create_mr_from_issue + redis_slot: code_review aggregation: weekly - name: i_code_review_user_mr_discussion_locked + redis_slot: code_review aggregation: weekly - name: i_code_review_user_mr_discussion_unlocked + redis_slot: code_review aggregation: weekly - name: i_code_review_user_time_estimate_changed + redis_slot: code_review aggregation: weekly - name: i_code_review_user_time_spent_changed + redis_slot: code_review aggregation: weekly - name: i_code_review_user_assignees_changed + redis_slot: code_review aggregation: weekly - name: i_code_review_user_reviewers_changed + redis_slot: code_review aggregation: weekly - name: i_code_review_user_milestone_changed + redis_slot: code_review aggregation: weekly - name: i_code_review_user_labels_changed + redis_slot: code_review aggregation: weekly # Diff settings events - name: i_code_review_click_diff_view_setting + redis_slot: code_review aggregation: weekly - name: i_code_review_click_single_file_mode_setting + redis_slot: code_review aggregation: weekly - name: i_code_review_click_file_browser_setting + redis_slot: code_review aggregation: weekly - name: i_code_review_click_whitespace_setting + redis_slot: code_review aggregation: weekly - name: i_code_review_diff_view_inline + redis_slot: code_review aggregation: weekly - name: i_code_review_diff_view_parallel + redis_slot: code_review aggregation: weekly - name: i_code_review_file_browser_tree_view + redis_slot: code_review aggregation: weekly - name: i_code_review_file_browser_list_view + redis_slot: code_review aggregation: weekly - name: i_code_review_diff_show_whitespace + redis_slot: code_review aggregation: weekly - name: i_code_review_diff_hide_whitespace + redis_slot: code_review aggregation: weekly - name: i_code_review_diff_single_file + redis_slot: code_review aggregation: weekly - name: i_code_review_diff_multiple_files + redis_slot: code_review aggregation: weekly - name: i_code_review_user_load_conflict_ui + redis_slot: code_review aggregation: weekly - name: i_code_review_user_resolve_conflict + redis_slot: code_review aggregation: weekly - name: i_code_review_user_searches_diff + redis_slot: code_review aggregation: weekly - name: i_code_review_total_suggestions_applied + redis_slot: code_review aggregation: weekly - name: i_code_review_total_suggestions_added + redis_slot: code_review aggregation: weekly - name: i_code_review_user_resolve_thread_in_issue + redis_slot: code_review aggregation: weekly - name: i_code_review_widget_nothing_merge_click_new_file + redis_slot: code_review aggregation: weekly - name: i_code_review_post_merge_delete_branch + redis_slot: code_review aggregation: weekly - name: i_code_review_post_merge_click_revert + redis_slot: code_review aggregation: weekly - name: i_code_review_post_merge_click_cherry_pick + redis_slot: code_review aggregation: weekly - name: i_code_review_post_merge_submit_revert_modal + redis_slot: code_review aggregation: weekly - name: i_code_review_post_merge_submit_cherry_pick_modal + redis_slot: code_review aggregation: weekly # MR Widget Extensions ## Test Summary - name: i_code_review_merge_request_widget_test_summary_view + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_test_summary_full_report_clicked + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_test_summary_expand + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_test_summary_expand_success + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_test_summary_expand_warning + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_test_summary_expand_failed + redis_slot: code_review aggregation: weekly ## Accessibility - name: i_code_review_merge_request_widget_accessibility_view + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_accessibility_full_report_clicked + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_accessibility_expand + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_accessibility_expand_success + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_accessibility_expand_warning + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_accessibility_expand_failed + redis_slot: code_review aggregation: weekly ## Code Quality - name: i_code_review_merge_request_widget_code_quality_view + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_code_quality_full_report_clicked + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_code_quality_expand + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_code_quality_expand_success + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_code_quality_expand_warning + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_code_quality_expand_failed + redis_slot: code_review aggregation: weekly ## Terraform - name: i_code_review_merge_request_widget_terraform_view + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_terraform_full_report_clicked + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_terraform_expand + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_terraform_expand_success + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_terraform_expand_warning + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_terraform_expand_failed + redis_slot: code_review aggregation: weekly - name: i_code_review_submit_review_approve + redis_slot: code_review aggregation: weekly - name: i_code_review_submit_review_comment + redis_slot: code_review aggregation: weekly ## License Compliance - name: i_code_review_merge_request_widget_license_compliance_view + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_license_compliance_full_report_clicked + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_license_compliance_expand + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_license_compliance_expand_success + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_license_compliance_expand_warning + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_license_compliance_expand_failed + redis_slot: code_review aggregation: weekly ## Security Reports - name: i_code_review_merge_request_widget_security_reports_view + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_security_reports_full_report_clicked + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_security_reports_expand + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_security_reports_expand_success + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_security_reports_expand_warning + redis_slot: code_review aggregation: weekly - name: i_code_review_merge_request_widget_security_reports_expand_failed + redis_slot: code_review aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/known_events/common.yml b/lib/gitlab/usage_data_counters/known_events/common.yml index d3520961665..3314997f873 100644 --- a/lib/gitlab/usage_data_counters/known_events/common.yml +++ b/lib/gitlab/usage_data_counters/known_events/common.yml @@ -1,14 +1,19 @@ --- # Compliance category - name: g_edit_by_web_ide + redis_slot: edit aggregation: daily - name: g_edit_by_sfe + redis_slot: edit aggregation: daily - name: g_edit_by_snippet_ide + redis_slot: edit aggregation: daily - name: g_edit_by_live_preview + redis_slot: edit aggregation: daily - name: i_search_total + redis_slot: search aggregation: weekly - name: wiki_action aggregation: daily @@ -21,145 +26,209 @@ - name: merge_request_action aggregation: daily - name: i_source_code_code_intelligence + redis_slot: source_code aggregation: daily # Incident management - name: incident_management_alert_status_changed + redis_slot: incident_management aggregation: weekly - name: incident_management_alert_assigned + redis_slot: incident_management aggregation: weekly - name: incident_management_alert_todo + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_created + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_reopened + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_closed + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_assigned + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_todo + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_comment + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_zoom_meeting + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_relate + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_unrelate + redis_slot: incident_management aggregation: weekly - name: incident_management_incident_change_confidential + redis_slot: incident_management aggregation: weekly # Incident management timeline events - name: incident_management_timeline_event_created + redis_slot: incident_management aggregation: weekly - name: incident_management_timeline_event_edited + redis_slot: incident_management aggregation: weekly - name: incident_management_timeline_event_deleted + redis_slot: incident_management aggregation: weekly # Incident management alerts - name: incident_management_alert_create_incident + redis_slot: incident_management aggregation: weekly # Testing category - name: i_testing_test_case_parsed + redis_slot: testing aggregation: weekly - name: i_testing_test_report_uploaded + redis_slot: testing aggregation: weekly - name: i_testing_coverage_report_uploaded + redis_slot: testing aggregation: weekly # Project Management group - name: g_project_management_issue_title_changed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_description_changed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_assignee_changed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_made_confidential + redis_slot: project_management aggregation: daily - name: g_project_management_issue_made_visible + redis_slot: project_management aggregation: daily - name: g_project_management_issue_created + redis_slot: project_management aggregation: daily - name: g_project_management_issue_closed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_reopened + redis_slot: project_management aggregation: daily - name: g_project_management_issue_label_changed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_milestone_changed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_cross_referenced + redis_slot: project_management aggregation: daily - name: g_project_management_issue_moved + redis_slot: project_management aggregation: daily - name: g_project_management_issue_related + redis_slot: project_management aggregation: daily - name: g_project_management_issue_unrelated + redis_slot: project_management aggregation: daily - name: g_project_management_issue_marked_as_duplicate + redis_slot: project_management aggregation: daily - name: g_project_management_issue_locked + redis_slot: project_management aggregation: daily - name: g_project_management_issue_unlocked + redis_slot: project_management aggregation: daily - name: g_project_management_issue_designs_added + redis_slot: project_management aggregation: daily - name: g_project_management_issue_designs_modified + redis_slot: project_management aggregation: daily - name: g_project_management_issue_designs_removed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_due_date_changed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_design_comments_removed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_time_estimate_changed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_time_spent_changed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_comment_added + redis_slot: project_management aggregation: daily - name: g_project_management_issue_comment_edited + redis_slot: project_management aggregation: daily - name: g_project_management_issue_comment_removed + redis_slot: project_management aggregation: daily - name: g_project_management_issue_cloned + redis_slot: project_management aggregation: daily # Runner group - name: g_runner_fleet_read_jobs_statistics + redis_slot: runner aggregation: weekly # Secrets Management - name: i_snippets_show + redis_slot: snippets aggregation: weekly # Terraform - name: p_terraform_state_api_unique_users + redis_slot: terraform aggregation: weekly # Pipeline Authoring group - name: o_pipeline_authoring_unique_users_committing_ciconfigfile + redis_slot: pipeline_authoring aggregation: weekly - name: o_pipeline_authoring_unique_users_pushing_mr_ciconfigfile + redis_slot: pipeline_authoring aggregation: weekly - name: i_ci_secrets_management_id_tokens_build_created + redis_slot: ci_secrets_management aggregation: weekly # Merge request widgets - name: users_expanding_secure_security_report + redis_slot: secure aggregation: weekly - name: users_expanding_testing_code_quality_report + redis_slot: testing aggregation: weekly - name: users_expanding_testing_accessibility_report + redis_slot: testing aggregation: weekly - name: users_expanding_testing_license_compliance_report + redis_slot: testing aggregation: weekly - name: users_visiting_testing_license_compliance_full_report + redis_slot: testing aggregation: weekly - name: users_visiting_testing_manage_license_compliance + redis_slot: testing aggregation: weekly - name: users_clicking_license_testing_visiting_external_website + redis_slot: testing aggregation: weekly # Geo group - name: g_geo_proxied_requests + redis_slot: geo aggregation: daily # Manage - name: unique_active_user aggregation: weekly # Environments page - name: users_visiting_environments_pages + redis_slot: users aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/known_events/container_registry_events.yml b/lib/gitlab/usage_data_counters/known_events/container_registry_events.yml index aa0f9965fa7..ac40079a6dc 100644 --- a/lib/gitlab/usage_data_counters/known_events/container_registry_events.yml +++ b/lib/gitlab/usage_data_counters/known_events/container_registry_events.yml @@ -1,11 +1,16 @@ --- - name: i_container_registry_push_tag_user aggregation: weekly + redis_slot: container_registry - name: i_container_registry_delete_tag_user aggregation: weekly + redis_slot: container_registry - name: i_container_registry_push_repository_user aggregation: weekly + redis_slot: container_registry - name: i_container_registry_delete_repository_user aggregation: weekly + redis_slot: container_registry - name: i_container_registry_create_repository_user aggregation: weekly + redis_slot: container_registry diff --git a/lib/gitlab/usage_data_counters/known_events/ecosystem.yml b/lib/gitlab/usage_data_counters/known_events/ecosystem.yml index 6e4a893d19a..03bbba663c5 100644 --- a/lib/gitlab/usage_data_counters/known_events/ecosystem.yml +++ b/lib/gitlab/usage_data_counters/known_events/ecosystem.yml @@ -1,24 +1,35 @@ --- # Ecosystem category - name: i_ecosystem_jira_service_close_issue + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_jira_service_cross_reference + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_slack_service_issue_notification + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_slack_service_push_notification + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_slack_service_deployment_notification + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_slack_service_wiki_page_notification + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_slack_service_merge_request_notification + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_slack_service_note_notification + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_slack_service_tag_push_notification + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_slack_service_confidential_note_notification + redis_slot: ecosystem aggregation: weekly - name: i_ecosystem_slack_service_confidential_issue_notification + redis_slot: ecosystem aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/known_events/error_tracking.yml b/lib/gitlab/usage_data_counters/known_events/error_tracking.yml index ebfd1b274f9..efed16c11f8 100644 --- a/lib/gitlab/usage_data_counters/known_events/error_tracking.yml +++ b/lib/gitlab/usage_data_counters/known_events/error_tracking.yml @@ -1,5 +1,7 @@ --- - name: error_tracking_view_details + redis_slot: error_tracking aggregation: weekly - name: error_tracking_view_list + redis_slot: error_tracking aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/known_events/importer_events.yml b/lib/gitlab/usage_data_counters/known_events/importer_events.yml index 3346c0556d6..a6c90a6c762 100644 --- a/lib/gitlab/usage_data_counters/known_events/importer_events.yml +++ b/lib/gitlab/usage_data_counters/known_events/importer_events.yml @@ -1,10 +1,13 @@ --- # Importer events - name: github_import_project_start + redis_slot: import aggregation: weekly - name: github_import_project_success + redis_slot: import aggregation: weekly - name: github_import_project_failure + redis_slot: import aggregation: weekly - name: github_import_project_cancelled redis_slot: import @@ -12,3 +15,4 @@ - name: github_import_project_partially_completed redis_slot: import aggregation: weekly + diff --git a/lib/gitlab/usage_data_counters/known_events/kubernetes_agent.yml b/lib/gitlab/usage_data_counters/known_events/kubernetes_agent.yml index b3d1c51c0e7..9703c022ef5 100644 --- a/lib/gitlab/usage_data_counters/known_events/kubernetes_agent.yml +++ b/lib/gitlab/usage_data_counters/known_events/kubernetes_agent.yml @@ -1,2 +1,3 @@ - name: agent_users_using_ci_tunnel + redis_slot: agent aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/known_events/package_events.yml b/lib/gitlab/usage_data_counters/known_events/package_events.yml index 47cc7f98838..d9797635240 100644 --- a/lib/gitlab/usage_data_counters/known_events/package_events.yml +++ b/lib/gitlab/usage_data_counters/known_events/package_events.yml @@ -1,45 +1,67 @@ --- - name: i_package_composer_deploy_token aggregation: weekly + redis_slot: package - name: i_package_composer_user aggregation: weekly + redis_slot: package - name: i_package_conan_deploy_token aggregation: weekly + redis_slot: package - name: i_package_conan_user aggregation: weekly + redis_slot: package - name: i_package_generic_deploy_token aggregation: weekly + redis_slot: package - name: i_package_generic_user aggregation: weekly + redis_slot: package - name: i_package_helm_deploy_token aggregation: weekly + redis_slot: package - name: i_package_helm_user aggregation: weekly + redis_slot: package - name: i_package_maven_deploy_token aggregation: weekly + redis_slot: package - name: i_package_maven_user aggregation: weekly + redis_slot: package - name: i_package_npm_deploy_token aggregation: weekly + redis_slot: package - name: i_package_npm_user aggregation: weekly + redis_slot: package - name: i_package_nuget_deploy_token aggregation: weekly + redis_slot: package - name: i_package_nuget_user aggregation: weekly + redis_slot: package - name: i_package_pypi_deploy_token aggregation: weekly + redis_slot: package - name: i_package_pypi_user aggregation: weekly + redis_slot: package - name: i_package_rubygems_deploy_token aggregation: weekly + redis_slot: package - name: i_package_rubygems_user aggregation: weekly + redis_slot: package - name: i_package_terraform_module_deploy_token aggregation: weekly + redis_slot: package - name: i_package_terraform_module_user aggregation: weekly + redis_slot: package - name: i_package_rpm_user aggregation: weekly + redis_slot: package - name: i_package_rpm_deploy_token aggregation: weekly + redis_slot: package diff --git a/lib/gitlab/usage_data_counters/known_events/quickactions.yml b/lib/gitlab/usage_data_counters/known_events/quickactions.yml index 7006173cc59..306ed79ea23 100644 --- a/lib/gitlab/usage_data_counters/known_events/quickactions.yml +++ b/lib/gitlab/usage_data_counters/known_events/quickactions.yml @@ -1,127 +1,190 @@ --- - name: i_quickactions_assign_multiple + redis_slot: quickactions aggregation: weekly - name: i_quickactions_approve + redis_slot: quickactions aggregation: weekly - name: i_quickactions_unapprove + redis_slot: quickactions aggregation: weekly - name: i_quickactions_assign_single + redis_slot: quickactions aggregation: weekly - name: i_quickactions_assign_self + redis_slot: quickactions aggregation: weekly - name: i_quickactions_assign_reviewer + redis_slot: quickactions aggregation: weekly - name: i_quickactions_award + redis_slot: quickactions aggregation: weekly - name: i_quickactions_board_move + redis_slot: quickactions aggregation: weekly - name: i_quickactions_clone + redis_slot: quickactions aggregation: weekly - name: i_quickactions_close + redis_slot: quickactions aggregation: weekly - name: i_quickactions_confidential + redis_slot: quickactions aggregation: weekly - name: i_quickactions_copy_metadata_merge_request + redis_slot: quickactions aggregation: weekly - name: i_quickactions_copy_metadata_issue + redis_slot: quickactions aggregation: weekly - name: i_quickactions_create_merge_request + redis_slot: quickactions aggregation: weekly - name: i_quickactions_done + redis_slot: quickactions aggregation: weekly - name: i_quickactions_draft + redis_slot: quickactions aggregation: weekly - name: i_quickactions_due + redis_slot: quickactions aggregation: weekly - name: i_quickactions_duplicate + redis_slot: quickactions aggregation: weekly - name: i_quickactions_estimate + redis_slot: quickactions aggregation: weekly - name: i_quickactions_label + redis_slot: quickactions aggregation: weekly - name: i_quickactions_lock + redis_slot: quickactions aggregation: weekly - name: i_quickactions_merge + redis_slot: quickactions aggregation: weekly - name: i_quickactions_milestone + redis_slot: quickactions aggregation: weekly - name: i_quickactions_move + redis_slot: quickactions aggregation: weekly - name: i_quickactions_promote_to_incident + redis_slot: quickactions aggregation: weekly - name: i_quickactions_timeline + redis_slot: quickactions aggregation: weekly - name: i_quickactions_ready + redis_slot: quickactions aggregation: weekly - name: i_quickactions_reassign + redis_slot: quickactions aggregation: weekly - name: i_quickactions_reassign_reviewer + redis_slot: quickactions aggregation: weekly - name: i_quickactions_rebase + redis_slot: quickactions aggregation: weekly - name: i_quickactions_relabel + redis_slot: quickactions aggregation: weekly - name: i_quickactions_relate + redis_slot: quickactions aggregation: weekly - name: i_quickactions_remove_due_date + redis_slot: quickactions aggregation: weekly - name: i_quickactions_remove_estimate + redis_slot: quickactions aggregation: weekly - name: i_quickactions_remove_milestone + redis_slot: quickactions aggregation: weekly - name: i_quickactions_remove_time_spent + redis_slot: quickactions aggregation: weekly - name: i_quickactions_remove_zoom + redis_slot: quickactions aggregation: weekly - name: i_quickactions_reopen + redis_slot: quickactions aggregation: weekly - name: i_quickactions_severity + redis_slot: quickactions aggregation: weekly - name: i_quickactions_shrug + redis_slot: quickactions aggregation: weekly - name: i_quickactions_spend_subtract + redis_slot: quickactions aggregation: weekly - name: i_quickactions_spend_add + redis_slot: quickactions aggregation: weekly - name: i_quickactions_submit_review + redis_slot: quickactions aggregation: weekly - name: i_quickactions_subscribe + redis_slot: quickactions aggregation: weekly - name: i_quickactions_tableflip + redis_slot: quickactions aggregation: weekly - name: i_quickactions_tag + redis_slot: quickactions aggregation: weekly - name: i_quickactions_target_branch + redis_slot: quickactions aggregation: weekly - name: i_quickactions_title + redis_slot: quickactions aggregation: weekly - name: i_quickactions_todo + redis_slot: quickactions aggregation: weekly - name: i_quickactions_unassign_specific + redis_slot: quickactions aggregation: weekly - name: i_quickactions_unassign_all + redis_slot: quickactions aggregation: weekly - name: i_quickactions_unassign_reviewer + redis_slot: quickactions aggregation: weekly - name: i_quickactions_unlabel_specific + redis_slot: quickactions aggregation: weekly - name: i_quickactions_unlabel_all + redis_slot: quickactions aggregation: weekly - name: i_quickactions_unlock + redis_slot: quickactions aggregation: weekly - name: i_quickactions_unsubscribe + redis_slot: quickactions aggregation: weekly - name: i_quickactions_wip + redis_slot: quickactions aggregation: weekly - name: i_quickactions_zoom + redis_slot: quickactions aggregation: weekly - name: i_quickactions_link + redis_slot: quickactions aggregation: weekly - name: i_quickactions_invite_email_single + redis_slot: quickactions aggregation: weekly - name: i_quickactions_invite_email_multiple + redis_slot: quickactions aggregation: weekly - name: i_quickactions_add_contacts + redis_slot: quickactions aggregation: weekly - name: i_quickactions_remove_contacts + redis_slot: quickactions aggregation: weekly diff --git a/lib/gitlab/usage_data_counters/known_events/work_items.yml b/lib/gitlab/usage_data_counters/known_events/work_items.yml index a6e5b9e1af5..1f0cc0c8a2e 100644 --- a/lib/gitlab/usage_data_counters/known_events/work_items.yml +++ b/lib/gitlab/usage_data_counters/known_events/work_items.yml @@ -1,21 +1,28 @@ --- - name: users_updating_work_item_title + redis_slot: users aggregation: weekly - name: users_creating_work_items + redis_slot: users aggregation: weekly - name: users_updating_work_item_dates + redis_slot: users aggregation: weekly - name: users_updating_work_item_labels + redis_slot: users aggregation: weekly - name: users_updating_work_item_milestone + redis_slot: users aggregation: weekly - name: users_updating_work_item_iteration # The event tracks an EE feature. # It's added here so it can be aggregated into the CE/EE 'OR' aggregate metrics. # It will report 0 for CE instances and should not be used with 'AND' aggregators. + redis_slot: users aggregation: weekly - name: users_updating_weight_estimate # The event tracks an EE feature. # It's added here so it can be aggregated into the CE/EE 'OR' aggregate metrics. # It will report 0 for CE instances and should not be used with 'AND' aggregators. + redis_slot: users aggregation: weekly diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake index 0936430a0f2..5d6d395037c 100644 --- a/lib/tasks/gitlab/assets.rake +++ b/lib/tasks/gitlab/assets.rake @@ -55,6 +55,9 @@ module Tasks Digest::SHA256.hexdigest(assets_sha256).tap { |sha256| puts "=> SHA256 generated in #{Time.now - start_time}: #{sha256}" if verbose } end + # Files listed here should match the list in: + # .assets-compilation-patterns in .gitlab/ci/rules.gitlab-ci.yml + # So we make sure that any impacting changes we do rebuild cache def self.assets_impacting_compilation assets_folders = FOSS_ASSET_FOLDERS assets_folders += EE_ASSET_FOLDERS if ::Gitlab.ee? diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 4360f3a5644..c81ae43e09f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4834,6 +4834,9 @@ msgstr "" msgid "Analytics|No dashboard matches the specified URL path." msgstr "" +msgid "Analytics|No results match your query or filter" +msgstr "" + msgid "Analytics|OS" msgstr "" @@ -18162,6 +18165,18 @@ msgstr "" msgid "FindFile|Switch branch/tag" msgstr "" +msgid "FindingsDrawer|Category:" +msgstr "" + +msgid "FindingsDrawer|Engine:" +msgstr "" + +msgid "FindingsDrawer|Other locations:" +msgstr "" + +msgid "FindingsDrawer|Severity:" +msgstr "" + msgid "Fingerprint (MD5)" msgstr "" diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb index b89e17910c9..b5fce559496 100644 --- a/qa/qa/page/project/show.rb +++ b/qa/qa/page/project/show.rb @@ -9,6 +9,7 @@ module QA include Page::Component::Breadcrumbs include Page::Project::SubMenus::Settings include Page::File::Shared::CommitMessage + include ::QA::Page::Component::Dropdown prepend Mobile::Page::Project::Show if Runtime::Env.mobile_layout? view 'app/assets/javascripts/repository/components/preview/index.vue' do @@ -68,11 +69,6 @@ module QA element :web_ide_button end - view 'app/views/shared/_ref_switcher.html.haml' do - element :branches_dropdown - element :branches_dropdown_content - end - view 'app/views/projects/blob/viewers/_loading.html.haml' do element :spinner_placeholder end @@ -184,11 +180,8 @@ module QA end def switch_to_branch(branch_name) - find_element(:branches_dropdown).click - - within_element(:branches_dropdown_content) do - click_on branch_name - end + expand_select_list + select_item(branch_name) end def wait_for_import diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb index b98bb8592d3..679f273d0f4 100644 --- a/qa/qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/repository/branch_with_unusual_name_spec.rb @@ -2,10 +2,7 @@ module QA RSpec.describe 'Create' do - describe 'Branch with unusual name', product_group: :source_code, quarantine: { - issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/364565', - type: :bug - } do + describe 'Branch with unusual name', product_group: :source_code do let(:branch_name) { 'unUsually/named#br--anch' } let(:project) do Resource::Project.fabricate_via_api! do |resource| diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb index 09b703a48d6..fe528719f26 100644 --- a/spec/controllers/projects/pipelines_controller_spec.rb +++ b/spec/controllers/projects/pipelines_controller_spec.rb @@ -1311,148 +1311,6 @@ RSpec.describe Projects::PipelinesController, feature_category: :continuous_inte end end - describe 'GET config_variables.json', :use_clean_rails_memory_store_caching do - include ReactiveCachingHelpers - - let(:ci_config) { '' } - let(:files) { { '.gitlab-ci.yml' => YAML.dump(ci_config) } } - let(:project) { create(:project, :auto_devops_disabled, :custom_repo, files: files) } - let(:service) { Ci::ListConfigVariablesService.new(project, user) } - - before do - allow(Ci::ListConfigVariablesService) - .to receive(:new) - .and_return(service) - end - - context 'when sending a valid ref' do - let(:ref) { 'master' } - let(:ci_config) do - { - variables: { - KEY1: { value: 'val 1', description: 'description 1' } - }, - test: { - stage: 'test', - script: 'echo' - } - } - end - - before do - synchronous_reactive_cache(service) - end - - it 'returns variable list' do - get_config_variables - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['KEY1']).to eq({ 'value' => 'val 1', 'description' => 'description 1' }) - end - end - - context 'when sending an invalid ref' do - let(:ref) { 'invalid-ref' } - - before do - synchronous_reactive_cache(service) - end - - it 'returns empty json' do - get_config_variables - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to eq({}) - end - end - - context 'when sending an invalid config' do - let(:ref) { 'master' } - let(:ci_config) do - { - variables: { - KEY1: { value: 'val 1', description: 'description 1' } - }, - test: { - stage: 'invalid', - script: 'echo' - } - } - end - - before do - synchronous_reactive_cache(service) - end - - it 'returns empty result' do - get_config_variables - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response).to eq({}) - end - end - - context 'when the cache is empty' do - let(:ref) { 'master' } - let(:ci_config) do - { - variables: { - KEY1: { value: 'val 1', description: 'description 1' } - }, - test: { - stage: 'test', - script: 'echo' - } - } - end - - it 'returns no content' do - get_config_variables - - expect(response).to have_gitlab_http_status(:no_content) - end - end - - context 'when project uses external project ci config' do - let(:other_project) { create(:project, :custom_repo, files: other_project_files) } - let(:other_project_files) { { '.gitlab-ci.yml' => YAML.dump(other_project_ci_config) } } - let(:ref) { 'master' } - - let(:other_project_ci_config) do - { - variables: { - KEY1: { value: 'val 1', description: 'description 1' } - }, - test: { - stage: 'test', - script: 'echo' - } - } - end - - before do - other_project.add_developer(user) - project.update!(ci_config_path: ".gitlab-ci.yml@#{other_project.full_path}:master") - synchronous_reactive_cache(service) - end - - it 'returns other project config variables' do - get_config_variables - - expect(response).to have_gitlab_http_status(:ok) - expect(json_response['KEY1']).to eq({ 'value' => 'val 1', 'description' => 'description 1' }) - end - end - - private - - def get_config_variables - get :config_variables, params: { - namespace_id: project.namespace, project_id: project, sha: ref - }, format: :json - end - end - describe 'GET downloadable_artifacts.json' do context 'when pipeline is empty' do let(:pipeline) { create(:ci_empty_pipeline) } diff --git a/spec/frontend/design_management/pages/index_spec.js b/spec/frontend/design_management/pages/index_spec.js index 1ddf757eb19..9b6ed37f92d 100644 --- a/spec/frontend/design_management/pages/index_spec.js +++ b/spec/frontend/design_management/pages/index_spec.js @@ -100,6 +100,7 @@ describe('Design management index page', () => { let wrapper; let fakeApollo; let moveDesignHandler; + let permissionsQueryHandler; const findDesignCheckboxes = () => wrapper.findAll('.design-checkbox'); const findSelectAllButton = () => wrapper.findByTestId('select-all-designs-button'); @@ -174,14 +175,16 @@ describe('Design management index page', () => { } function createComponentWithApollo({ + permissionsHandler = jest.fn().mockResolvedValue(getPermissionsQueryResponse()), moveHandler = jest.fn().mockResolvedValue(moveDesignMutationResponse), }) { Vue.use(VueApollo); + permissionsQueryHandler = permissionsHandler; moveDesignHandler = moveHandler; const requestHandlers = [ [getDesignListQuery, jest.fn().mockResolvedValue(designListQueryResponse)], - [permissionsQuery, jest.fn().mockResolvedValue(getPermissionsQueryResponse())], + [permissionsQuery, permissionsQueryHandler], [moveDesignMutation, moveDesignHandler], ]; @@ -230,13 +233,6 @@ describe('Design management index page', () => { expect(findDesignUploadButton().exists()).toBe(true); }); - it('does not render toolbar when there is no permission', () => { - createComponent({ designs: mockDesigns, allVersions: [mockVersion], createDesign: false }); - - expect(findDesignToolbarWrapper().exists()).toBe(false); - expect(findDesignUploadButton().exists()).toBe(false); - }); - it('has correct classes applied to design dropzone', () => { createComponent({ designs: mockDesigns, allVersions: [mockVersion] }); expect(dropzoneClasses()).toContain('design-list-item'); @@ -744,6 +740,17 @@ describe('Design management index page', () => { }); }); + describe('when there is no permission to create a design', () => { + beforeEach(() => { + createComponent({ designs: mockDesigns, allVersions: [mockVersion], createDesign: false }); + }); + + it("doesn't render the design toolbar and dropzone", () => { + expect(findToolbar().exists()).toBe(false); + expect(findDropzoneWrapper().exists()).toBe(false); + }); + }); + describe('with mocked Apollo client', () => { it('has a design with id 1 as a first one', async () => { createComponentWithApollo({}); @@ -819,5 +826,17 @@ describe('Design management index page', () => { 'Something went wrong when reordering designs. Please try again', ); }); + + it("doesn't render the design toolbar and dropzone if the user can't edit", async () => { + createComponentWithApollo({ + permissionsHandler: jest.fn().mockResolvedValue(getPermissionsQueryResponse(false)), + }); + + await waitForPromises(); + + expect(permissionsQueryHandler).toHaveBeenCalled(); + expect(findToolbar().exists()).toBe(false); + expect(findDropzoneWrapper().exists()).toBe(false); + }); }); }); diff --git a/spec/frontend/diffs/components/app_spec.js b/spec/frontend/diffs/components/app_spec.js index 06995706a2b..545ba4a0e04 100644 --- a/spec/frontend/diffs/components/app_spec.js +++ b/spec/frontend/diffs/components/app_spec.js @@ -11,6 +11,7 @@ import CommitWidget from '~/diffs/components/commit_widget.vue'; import CompareVersions from '~/diffs/components/compare_versions.vue'; import DiffFile from '~/diffs/components/diff_file.vue'; import NoChanges from '~/diffs/components/no_changes.vue'; +import findingsDrawer from '~/diffs/components/shared/findings_drawer.vue'; import TreeList from '~/diffs/components/tree_list.vue'; import CollapsedFilesWarning from '~/diffs/components/collapsed_files_warning.vue'; @@ -741,4 +742,20 @@ describe('diffs/components/app', () => { ); }); }); + + describe('findings-drawer', () => { + it('does not render findings-drawer when codeQualityInlineDrawer flag is off', () => { + createComponent(); + expect(wrapper.findComponent(findingsDrawer).exists()).toBe(false); + }); + + it('does render findings-drawer when codeQualityInlineDrawer flag is on', () => { + createComponent({}, () => {}, { + glFeatures: { + codeQualityInlineDrawer: true, + }, + }); + expect(wrapper.findComponent(findingsDrawer).exists()).toBe(true); + }); + }); }); diff --git a/spec/frontend/diffs/components/diff_code_quality_item_spec.js b/spec/frontend/diffs/components/diff_code_quality_item_spec.js new file mode 100644 index 00000000000..be9fb61a77d --- /dev/null +++ b/spec/frontend/diffs/components/diff_code_quality_item_spec.js @@ -0,0 +1,66 @@ +import { GlIcon, GlLink } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import DiffCodeQualityItem from '~/diffs/components/diff_code_quality_item.vue'; +import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants'; +import { multipleFindingsArr } from '../mock_data/diff_code_quality'; + +let wrapper; + +const findIcon = () => wrapper.findComponent(GlIcon); +const findButton = () => wrapper.findComponent(GlLink); +const findDescriptionPlainText = () => wrapper.findByTestId('description-plain-text'); +const findDescriptionLinkSection = () => wrapper.findByTestId('description-button-section'); + +describe('DiffCodeQuality', () => { + const createWrapper = ({ glFeatures = {} } = {}) => { + return shallowMountExtended(DiffCodeQualityItem, { + propsData: { + finding: multipleFindingsArr[0], + }, + provide: { + glFeatures, + }, + }); + }; + + it('shows icon for given degradation', () => { + wrapper = createWrapper(); + expect(findIcon().exists()).toBe(true); + + expect(findIcon().attributes()).toMatchObject({ + class: `codequality-severity-icon ${SEVERITY_CLASSES[multipleFindingsArr[0].severity]}`, + name: SEVERITY_ICONS[multipleFindingsArr[0].severity], + size: '12', + }); + }); + + describe('with codeQualityInlineDrawer flag false', () => { + it('should render severity + description in plain text', () => { + wrapper = createWrapper({ + glFeatures: { + codeQualityInlineDrawer: false, + }, + }); + expect(findDescriptionPlainText().text()).toContain(multipleFindingsArr[0].severity); + expect(findDescriptionPlainText().text()).toContain(multipleFindingsArr[0].description); + }); + }); + + describe('with codeQualityInlineDrawer flag true', () => { + beforeEach(() => { + wrapper = createWrapper({ + glFeatures: { + codeQualityInlineDrawer: true, + }, + }); + }); + + it('should render severity as plain text', () => { + expect(findDescriptionLinkSection().text()).toContain(multipleFindingsArr[0].severity); + }); + + it('should render button with description text', () => { + expect(findButton().text()).toContain(multipleFindingsArr[0].description); + }); + }); +}); diff --git a/spec/frontend/diffs/components/diff_code_quality_spec.js b/spec/frontend/diffs/components/diff_code_quality_spec.js index e5ca90eb7c8..9ecfb62e1c5 100644 --- a/spec/frontend/diffs/components/diff_code_quality_spec.js +++ b/spec/frontend/diffs/components/diff_code_quality_spec.js @@ -1,13 +1,12 @@ -import { GlIcon } from '@gitlab/ui'; import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; import DiffCodeQuality from '~/diffs/components/diff_code_quality.vue'; -import { SEVERITY_CLASSES, SEVERITY_ICONS } from '~/ci/reports/codequality_report/constants'; +import DiffCodeQualityItem from '~/diffs/components/diff_code_quality_item.vue'; import { NEW_CODE_QUALITY_FINDINGS } from '~/diffs/i18n'; import { multipleFindingsArr } from '../mock_data/diff_code_quality'; let wrapper; -const findIcon = () => wrapper.findComponent(GlIcon); +const diffItems = () => wrapper.findAllComponents(DiffCodeQualityItem); const findHeading = () => wrapper.findByTestId(`diff-codequality-findings-heading`); describe('DiffCodeQuality', () => { @@ -28,37 +27,12 @@ describe('DiffCodeQuality', () => { expect(wrapper.emitted('hideCodeQualityFindings').length).toBe(1); }); - it('renders heading and correct amount of list items for codequality array and their description', async () => { - wrapper = createWrapper(multipleFindingsArr); - expect(findHeading().text()).toEqual(NEW_CODE_QUALITY_FINDINGS); - - const listItems = wrapper.findAll('li'); - expect(wrapper.findAll('li').length).toBe(5); + it('renders heading and correct amount of list items for codequality array and their description', () => { + wrapper = createWrapper(multipleFindingsArr, shallowMountExtended); - listItems.wrappers.map((e, i) => { - return expect(e.text()).toContain( - `${multipleFindingsArr[i].severity} - ${multipleFindingsArr[i].description}`, - ); - }); - }); - - it.each` - severity - ${'info'} - ${'minor'} - ${'major'} - ${'critical'} - ${'blocker'} - ${'unknown'} - `('shows icon for $severity degradation', ({ severity }) => { - wrapper = createWrapper([{ severity }], shallowMountExtended); - - expect(findIcon().exists()).toBe(true); + expect(findHeading().text()).toEqual(NEW_CODE_QUALITY_FINDINGS); - expect(findIcon().attributes()).toMatchObject({ - class: `codequality-severity-icon ${SEVERITY_CLASSES[severity]}`, - name: SEVERITY_ICONS[severity], - size: '12', - }); + expect(diffItems()).toHaveLength(multipleFindingsArr.length); + expect(diffItems().at(0).props().finding).toEqual(multipleFindingsArr[0]); }); }); diff --git a/spec/frontend/diffs/components/shared/__snapshots__/findings_drawer_spec.js.snap b/spec/frontend/diffs/components/shared/__snapshots__/findings_drawer_spec.js.snap new file mode 100644 index 00000000000..ab330ffbb38 --- /dev/null +++ b/spec/frontend/diffs/components/shared/__snapshots__/findings_drawer_spec.js.snap @@ -0,0 +1,126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`FindingsDrawer matches the snapshot 1`] = ` +<gl-drawer-stub + class="findings-drawer" + headerheight="" + open="true" + variant="default" + zindex="252" +> + <h2 + class="gl-font-size-h2 gl-mt-0 gl-mb-0" + data-testid="findings-drawer-heading" + > + + Unused method argument - \`c\`. If it's necessary, use \`_\` or \`_c\` as an argument name to indicate that it won't be used. + + </h2> + <ul + class="gl-list-style-none gl-border-b-initial gl-mb-0 gl-pb-0!" + > + <li + class="gl-mb-4" + data-testid="findings-drawer-severity" + > + <span + class="gl-font-weight-bold" + > + Severity: + </span> + + <gl-icon-stub + class="codequality-severity-icon gl-text-orange-200" + data-testid="findings-drawer-severity-icon" + name="severity-low" + size="12" + /> + + + minor + + </li> + + <li + class="gl-mb-4" + data-testid="findings-drawer-engine" + > + <span + class="gl-font-weight-bold" + > + Engine: + </span> + + testengine name + + </li> + + <li + class="gl-mb-4" + data-testid="findings-drawer-category" + > + <span + class="gl-font-weight-bold" + > + Category: + </span> + + testcategory 1 + + </li> + + <li + class="gl-mb-4" + data-testid="findings-drawer-other-locations" + > + <span + class="gl-font-weight-bold gl-mb-3 gl-display-block" + > + Other locations: + </span> + + <ul + class="gl-pl-6" + > + <li + class="gl-mb-1" + > + <gl-link-stub + data-testid="findings-drawer-other-locations-link" + href="http://testlink.com" + > + testpath + </gl-link-stub> + </li> + <li + class="gl-mb-1" + > + <gl-link-stub + data-testid="findings-drawer-other-locations-link" + href="http://testlink.com" + > + testpath 1 + </gl-link-stub> + </li> + <li + class="gl-mb-1" + > + <gl-link-stub + data-testid="findings-drawer-other-locations-link" + href="http://testlink.com" + > + testpath2 + </gl-link-stub> + </li> + </ul> + </li> + </ul> + + <span + class="drawer-body gl-display-block gl-px-3 gl-py-0!" + data-testid="findings-drawer-body" + > + Duplicated Code Duplicated code + </span> +</gl-drawer-stub> +`; diff --git a/spec/frontend/diffs/components/shared/findings_drawer_spec.js b/spec/frontend/diffs/components/shared/findings_drawer_spec.js new file mode 100644 index 00000000000..0af6e0f0e96 --- /dev/null +++ b/spec/frontend/diffs/components/shared/findings_drawer_spec.js @@ -0,0 +1,19 @@ +import FindingsDrawer from '~/diffs/components/shared/findings_drawer.vue'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import mockFinding from '../../mock_data/findings_drawer'; + +let wrapper; +describe('FindingsDrawer', () => { + const createWrapper = () => { + return shallowMountExtended(FindingsDrawer, { + propsData: { + drawer: mockFinding, + }, + }); + }; + + it('matches the snapshot', () => { + wrapper = createWrapper(); + expect(wrapper.element).toMatchSnapshot(); + }); +}); diff --git a/spec/frontend/diffs/create_diffs_store.js b/spec/frontend/diffs/create_diffs_store.js index 307ebdaa4ac..92f38858ca5 100644 --- a/spec/frontend/diffs/create_diffs_store.js +++ b/spec/frontend/diffs/create_diffs_store.js @@ -3,6 +3,7 @@ import Vuex from 'vuex'; import batchCommentsModule from '~/batch_comments/stores/modules/batch_comments'; import diffsModule from '~/diffs/store/modules'; import notesModule from '~/notes/stores/modules'; +import findingsDrawer from '~/mr_notes/stores/drawer'; Vue.use(Vuex); @@ -18,6 +19,7 @@ export default function createDiffsStore() { diffs: diffsModule(), notes: notesModule(), batchComments: batchCommentsModule(), + findingsDrawer: findingsDrawer(), }, }); } diff --git a/spec/frontend/diffs/mock_data/diff_code_quality.js b/spec/frontend/diffs/mock_data/diff_code_quality.js index 7558592f6a4..29f16da8d89 100644 --- a/spec/frontend/diffs/mock_data/diff_code_quality.js +++ b/spec/frontend/diffs/mock_data/diff_code_quality.js @@ -24,6 +24,11 @@ export const multipleFindingsArr = [ description: 'mocked blocker Issue', line: 3, }, + { + severity: 'unknown', + description: 'mocked unknown Issue', + line: 3, + }, ]; export const fiveFindings = { diff --git a/spec/frontend/diffs/mock_data/findings_drawer.js b/spec/frontend/diffs/mock_data/findings_drawer.js new file mode 100644 index 00000000000..d7e7e957c83 --- /dev/null +++ b/spec/frontend/diffs/mock_data/findings_drawer.js @@ -0,0 +1,21 @@ +export default { + line: 7, + description: + "Unused method argument - `c`. If it's necessary, use `_` or `_c` as an argument name to indicate that it won't be used.", + severity: 'minor', + engineName: 'testengine name', + categories: ['testcategory 1', 'testcategory 2'], + content: { + body: 'Duplicated Code Duplicated code', + }, + location: { + path: 'workhorse/config_test.go', + lines: { begin: 221, end: 284 }, + }, + otherLocations: [ + { path: 'testpath', href: 'http://testlink.com' }, + { path: 'testpath 1', href: 'http://testlink.com' }, + { path: 'testpath2', href: 'http://testlink.com' }, + ], + type: 'issue', +}; diff --git a/spec/frontend/environments/environment_actions_spec.js b/spec/frontend/environments/environment_actions_spec.js index 3c9b4144e45..dcfefbb2072 100644 --- a/spec/frontend/environments/environment_actions_spec.js +++ b/spec/frontend/environments/environment_actions_spec.js @@ -1,14 +1,8 @@ -import { GlDropdown, GlDropdownItem, GlLoadingIcon, GlIcon } from '@gitlab/ui'; -import { shallowMount, mount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; -import VueApollo from 'vue-apollo'; +import { GlDisclosureDropdown, GlDisclosureDropdownItem, GlIcon } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import { TEST_HOST } from 'helpers/test_constants'; -import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import EnvironmentActions from '~/environments/components/environment_actions.vue'; -import eventHub from '~/environments/event_hub'; -import actionMutation from '~/environments/graphql/mutations/action.mutation.graphql'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; -import createMockApollo from 'helpers/mock_apollo_helper'; jest.mock('~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'); @@ -29,15 +23,9 @@ const expiredJobAction = { describe('EnvironmentActions Component', () => { let wrapper; - const findEnvironmentActionsButton = () => - wrapper.find('[data-testid="environment-actions-button"]'); - - function createComponent(props, { mountFn = shallowMount, options = {} } = {}) { - wrapper = mountFn(EnvironmentActions, { + function createComponent(props, { options = {} } = {}) { + wrapper = mount(EnvironmentActions, { propsData: { actions: [], ...props }, - directives: { - GlTooltip: createMockDirective('gl-tooltip'), - }, ...options, }); } @@ -46,9 +34,10 @@ describe('EnvironmentActions Component', () => { return createComponent({ actions: [scheduledJobAction, expiredJobAction] }, opts); } + const findDropdownItems = () => wrapper.findAllComponents(GlDisclosureDropdownItem); const findDropdownItem = (action) => { - const buttons = wrapper.findAllComponents(GlDropdownItem); - return buttons.filter((button) => button.text().startsWith(action.name)).at(0); + const items = findDropdownItems(); + return items.filter((item) => item.text().startsWith(action.name)).at(0); }; afterEach(() => { @@ -56,19 +45,15 @@ describe('EnvironmentActions Component', () => { }); it('should render a dropdown button with 2 icons', () => { - createComponent({}, { mountFn: mount }); - expect(wrapper.findComponent(GlDropdown).findAllComponents(GlIcon).length).toBe(2); - }); - - it('should render a dropdown button with aria-label description', () => { createComponent(); - expect(wrapper.findComponent(GlDropdown).attributes('aria-label')).toBe('Deploy to...'); + expect(wrapper.findComponent(GlDisclosureDropdown).findAllComponents(GlIcon).length).toBe(2); }); - it('should render a tooltip', () => { + it('should render a dropdown button with aria-label description', () => { createComponent(); - const tooltip = getBinding(findEnvironmentActionsButton().element, 'gl-tooltip'); - expect(tooltip).toBeDefined(); + expect(wrapper.findComponent(GlDisclosureDropdown).attributes('aria-label')).toBe( + 'Deploy to...', + ); }); describe('manual actions', () => { @@ -93,96 +78,31 @@ describe('EnvironmentActions Component', () => { }); it('should render a dropdown with the provided list of actions', () => { - expect(wrapper.findAllComponents(GlDropdownItem)).toHaveLength(actions.length); + expect(findDropdownItems()).toHaveLength(actions.length); }); it("should render a disabled action when it's not playable", () => { - const dropdownItems = wrapper.findAllComponents(GlDropdownItem); + const dropdownItems = findDropdownItems(); const lastDropdownItem = dropdownItems.at(dropdownItems.length - 1); - expect(lastDropdownItem.attributes('disabled')).toBe('true'); + expect(lastDropdownItem.find('button').attributes('disabled')).toBe('disabled'); }); }); describe('scheduled jobs', () => { - let emitSpy; - - const clickAndConfirm = async ({ confirm = true } = {}) => { - confirmAction.mockResolvedValueOnce(confirm); - - findDropdownItem(scheduledJobAction).vm.$emit('click'); - await nextTick(); - }; - beforeEach(() => { - emitSpy = jest.fn(); - eventHub.$on('postAction', emitSpy); jest.spyOn(Date, 'now').mockImplementation(() => new Date('2063-04-04T00:42:00Z').getTime()); }); - describe('when postAction event is confirmed', () => { - beforeEach(async () => { - createComponentWithScheduledJobs({ mountFn: mount }); - clickAndConfirm(); - }); - - it('emits postAction event', () => { - expect(confirmAction).toHaveBeenCalled(); - expect(emitSpy).toHaveBeenCalledWith({ endpoint: scheduledJobAction.playPath }); - }); - - it('should render a dropdown button with a loading icon', () => { - expect(wrapper.findComponent(GlLoadingIcon).isVisible()).toBe(true); - }); - }); - - describe('when postAction event is denied', () => { - beforeEach(async () => { - createComponentWithScheduledJobs({ mountFn: mount }); - clickAndConfirm({ confirm: false }); - }); - - it('does not emit postAction event if confirmation is cancelled', () => { - expect(confirmAction).toHaveBeenCalled(); - expect(emitSpy).not.toHaveBeenCalled(); - }); - }); - it('displays the remaining time in the dropdown', () => { + confirmAction.mockResolvedValueOnce(true); createComponentWithScheduledJobs(); expect(findDropdownItem(scheduledJobAction).text()).toContain('24:00:00'); }); it('displays 00:00:00 for expired jobs in the dropdown', () => { + confirmAction.mockResolvedValueOnce(true); createComponentWithScheduledJobs(); expect(findDropdownItem(expiredJobAction).text()).toContain('00:00:00'); }); }); - - describe('graphql', () => { - Vue.use(VueApollo); - - const action = { - name: 'bar', - play_path: 'https://gitlab.com/play', - }; - - let mockApollo; - - beforeEach(() => { - mockApollo = createMockApollo(); - createComponent( - { actions: [action], graphql: true }, - { options: { apolloProvider: mockApollo } }, - ); - }); - - it('should trigger a graphql mutation on click', () => { - jest.spyOn(mockApollo.defaultClient, 'mutate'); - findDropdownItem(action).vm.$emit('click'); - expect(mockApollo.defaultClient.mutate).toHaveBeenCalledWith({ - mutation: actionMutation, - variables: { action }, - }); - }); - }); }); diff --git a/spec/frontend/environments/new_environment_item_spec.js b/spec/frontend/environments/new_environment_item_spec.js index b2088ad410b..89a9ca725ba 100644 --- a/spec/frontend/environments/new_environment_item_spec.js +++ b/spec/frontend/environments/new_environment_item_spec.js @@ -7,6 +7,7 @@ import { stubTransition } from 'helpers/stub_transition'; import { formatDate, getTimeago } from '~/lib/utils/datetime_utility'; import { __, s__, sprintf } from '~/locale'; import EnvironmentItem from '~/environments/components/new_environment_item.vue'; +import EnvironmentActions from '~/environments/components/environment_actions.vue'; import Deployment from '~/environments/components/deployment.vue'; import DeployBoardWrapper from '~/environments/components/deploy_board_wrapper.vue'; import KubernetesOverview from '~/environments/components/kubernetes_overview.vue'; @@ -30,6 +31,7 @@ describe('~/environments/components/new_environment_item.vue', () => { }); const findDeployment = () => wrapper.findComponent(Deployment); + const findActions = () => wrapper.findComponent(EnvironmentActions); const findKubernetesOverview = () => wrapper.findComponent(KubernetesOverview); const expandCollapsedSection = async () => { @@ -124,9 +126,7 @@ describe('~/environments/components/new_environment_item.vue', () => { it('shows a dropdown if there are actions to perform', () => { wrapper = createWrapper({ apolloProvider: createApolloProvider() }); - const actions = wrapper.findByRole('button', { name: __('Deploy to...') }); - - expect(actions.exists()).toBe(true); + expect(findActions().exists()).toBe(true); }); it('does not show a dropdown if there are no actions to perform', () => { @@ -140,17 +140,15 @@ describe('~/environments/components/new_environment_item.vue', () => { }, }); - const actions = wrapper.findByRole('button', { name: __('Deploy to...') }); - - expect(actions.exists()).toBe(false); + expect(findActions().exists()).toBe(false); }); it('passes all the actions down to the action component', () => { wrapper = createWrapper({ apolloProvider: createApolloProvider() }); - const action = wrapper.findByRole('menuitem', { name: 'deploy-staging' }); - - expect(action.exists()).toBe(true); + expect(findActions().props('actions')).toMatchObject( + resolvedEnvironment.lastDeployment.manualActions, + ); }); }); diff --git a/spec/frontend/ide/stores/modules/commit/actions_spec.js b/spec/frontend/ide/stores/modules/commit/actions_spec.js index 872aa9b6e6b..3eaff92d321 100644 --- a/spec/frontend/ide/stores/modules/commit/actions_spec.js +++ b/spec/frontend/ide/stores/modules/commit/actions_spec.js @@ -1,7 +1,6 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; -import { mockTracking, unmockTracking } from 'helpers/tracking_helper'; import { file } from 'jest/ide/helpers'; import { commitActionTypes, PERMISSION_CREATE_MR } from '~/ide/constants'; import eventHub from '~/ide/eventhub'; @@ -40,14 +39,12 @@ describe('IDE commit module actions', () => { let mock; let store; let router; - let trackingSpy; beforeEach(() => { store = createStore(); router = createRouter(store); gon.api_version = 'v1'; mock = new MockAdapter(axios); - trackingSpy = mockTracking(undefined, undefined, jest.spyOn); jest.spyOn(router, 'push').mockImplementation(); mock @@ -56,7 +53,6 @@ describe('IDE commit module actions', () => { }); afterEach(() => { - unmockTracking(); mock.restore(); }); @@ -426,28 +422,6 @@ describe('IDE commit module actions', () => { }); }); }); - - describe('learnGitlabSource', () => { - describe('learnGitlabSource is true', () => { - it('tracks commit', async () => { - store.state.learnGitlabSource = true; - - await store.dispatch('commit/commitChanges'); - - expect(trackingSpy).toHaveBeenCalledWith(undefined, 'commit', { - label: 'web_ide_learn_gitlab_source', - }); - }); - }); - - describe('learnGitlabSource is false', () => { - it('does not track commit', async () => { - await store.dispatch('commit/commitChanges'); - - expect(trackingSpy).not.toHaveBeenCalled(); - }); - }); - }); }); describe('success response with failed message', () => { @@ -465,26 +439,6 @@ describe('IDE commit module actions', () => { expect(alert.textContent.trim()).toBe('failed message'); }); - - describe('learnGitlabSource', () => { - describe('learnGitlabSource is true', () => { - it('does not track commit', async () => { - store.state.learnGitlabSource = true; - - await store.dispatch('commit/commitChanges'); - - expect(trackingSpy).not.toHaveBeenCalled(); - }); - }); - - describe('learnGitlabSource is false', () => { - it('does not track commit', async () => { - await store.dispatch('commit/commitChanges'); - - expect(trackingSpy).not.toHaveBeenCalled(); - }); - }); - }); }); describe('failed response', () => { @@ -504,26 +458,6 @@ describe('IDE commit module actions', () => { ['commit/SET_ERROR', createUnexpectedCommitError(), undefined], ]); }); - - describe('learnGitlabSource', () => { - describe('learnGitlabSource is true', () => { - it('does not track commit', async () => { - store.state.learnGitlabSource = true; - - await store.dispatch('commit/commitChanges').catch(() => {}); - - expect(trackingSpy).not.toHaveBeenCalled(); - }); - }); - - describe('learnGitlabSource is false', () => { - it('does not track commit', async () => { - await store.dispatch('commit/commitChanges').catch(() => {}); - - expect(trackingSpy).not.toHaveBeenCalled(); - }); - }); - }); }); describe('first commit of a branch', () => { diff --git a/spec/frontend/notes/components/discussion_filter_spec.js b/spec/frontend/notes/components/discussion_filter_spec.js index ed1ced1b3d1..28e5e65c177 100644 --- a/spec/frontend/notes/components/discussion_filter_spec.js +++ b/spec/frontend/notes/components/discussion_filter_spec.js @@ -1,4 +1,4 @@ -import { GlDropdown } from '@gitlab/ui'; +import { GlDropdown, GlDropdownItem } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import Vue, { nextTick } from 'vue'; import AxiosMockAdapter from 'axios-mock-adapter'; @@ -77,17 +77,16 @@ describe('DiscussionFilter component', () => { // as it doesn't matter for our tests here mock.onGet(DISCUSSION_PATH).reply(HTTP_STATUS_OK, ''); window.mrTabs = undefined; - wrapper = mountComponent(); jest.spyOn(Tracking, 'event'); }); afterEach(() => { - wrapper.vm.$destroy(); mock.restore(); }); describe('default', () => { beforeEach(() => { + wrapper = mountComponent(); jest.spyOn(store, 'dispatch').mockImplementation(); }); @@ -104,6 +103,7 @@ describe('DiscussionFilter component', () => { describe('when asc', () => { beforeEach(() => { + wrapper = mountComponent(); jest.spyOn(store, 'dispatch').mockImplementation(); }); @@ -123,6 +123,7 @@ describe('DiscussionFilter component', () => { describe('when desc', () => { beforeEach(() => { + wrapper = mountComponent(); store.state.discussionSortOrder = DESC; jest.spyOn(store, 'dispatch').mockImplementation(); }); @@ -145,56 +146,62 @@ describe('DiscussionFilter component', () => { }); }); - it('renders the all filters', () => { - expect(wrapper.findAll('.discussion-filter-container .dropdown-item').length).toBe( - discussionFiltersMock.length, - ); - }); + describe('discussion filter functionality', () => { + beforeEach(() => { + wrapper = mountComponent(); + }); - it('renders the default selected item', () => { - expect(wrapper.find('.discussion-filter-container .dropdown-item').text().trim()).toBe( - discussionFiltersMock[0].title, - ); - }); + it('renders the all filters', () => { + expect(wrapper.findAll('.discussion-filter-container .dropdown-item').length).toBe( + discussionFiltersMock.length, + ); + }); - it('disables the dropdown when discussions are loading', () => { - store.state.isLoading = true; + it('renders the default selected item', () => { + expect(wrapper.find('.discussion-filter-container .dropdown-item').text().trim()).toBe( + discussionFiltersMock[0].title, + ); + }); - expect(wrapper.findComponent(GlDropdown).props('disabled')).toBe(true); - }); + it('disables the dropdown when discussions are loading', () => { + store.state.isLoading = true; - it('updates to the selected item', () => { - const filterItem = findFilter(DISCUSSION_FILTER_TYPES.ALL); + expect(wrapper.findComponent(GlDropdown).props('disabled')).toBe(true); + }); - filterItem.trigger('click'); + it('updates to the selected item', () => { + const filterItem = findFilter(DISCUSSION_FILTER_TYPES.ALL); - expect(wrapper.vm.currentFilter.title).toBe(filterItem.text().trim()); - }); + filterItem.trigger('click'); - it('only updates when selected filter changes', () => { - findFilter(DISCUSSION_FILTER_TYPES.ALL).trigger('click'); + expect(filterItem.text().trim()).toBe('Show all activity'); + }); - expect(filterDiscussion).not.toHaveBeenCalled(); - }); + it('only updates when selected filter changes', () => { + findFilter(DISCUSSION_FILTER_TYPES.ALL).trigger('click'); + + expect(filterDiscussion).not.toHaveBeenCalled(); + }); - it('disables timeline view if it was enabled', () => { - store.state.isTimelineEnabled = true; + it('disables timeline view if it was enabled', () => { + store.state.isTimelineEnabled = true; - findFilter(DISCUSSION_FILTER_TYPES.HISTORY).trigger('click'); + findFilter(DISCUSSION_FILTER_TYPES.HISTORY).trigger('click'); - expect(wrapper.vm.$store.state.isTimelineEnabled).toBe(false); - }); + expect(store.state.isTimelineEnabled).toBe(false); + }); - it('disables commenting when "Show history only" filter is applied', () => { - findFilter(DISCUSSION_FILTER_TYPES.HISTORY).trigger('click'); + it('disables commenting when "Show history only" filter is applied', () => { + findFilter(DISCUSSION_FILTER_TYPES.HISTORY).trigger('click'); - expect(wrapper.vm.$store.state.commentsDisabled).toBe(true); - }); + expect(store.state.commentsDisabled).toBe(true); + }); - it('enables commenting when "Show history only" filter is not applied', () => { - findFilter(DISCUSSION_FILTER_TYPES.ALL).trigger('click'); + it('enables commenting when "Show history only" filter is not applied', () => { + findFilter(DISCUSSION_FILTER_TYPES.ALL).trigger('click'); - expect(wrapper.vm.$store.state.commentsDisabled).toBe(false); + expect(store.state.commentsDisabled).toBe(false); + }); }); describe('Merge request tabs', () => { @@ -222,52 +229,41 @@ describe('DiscussionFilter component', () => { }); describe('URL with Links to notes', () => { + const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); + afterEach(() => { window.location.hash = ''; }); - it('updates the filter when the URL links to a note', async () => { - window.location.hash = `note_${discussionMock.notes[0].id}`; - wrapper.vm.currentValue = discussionFiltersMock[2].value; - wrapper.vm.handleLocationHash(); - - await nextTick(); - expect(wrapper.vm.currentValue).toBe(DISCUSSION_FILTERS_DEFAULT_VALUE); - }); - it('does not update the filter when the current filter is "Show all activity"', async () => { window.location.hash = `note_${discussionMock.notes[0].id}`; - wrapper.vm.handleLocationHash(); + wrapper = mountComponent(); await nextTick(); - expect(wrapper.vm.currentValue).toBe(DISCUSSION_FILTERS_DEFAULT_VALUE); + const filtered = findDropdownItems().filter((el) => el.classes('is-active')); + + expect(filtered).toHaveLength(1); + expect(filtered.at(0).text()).toBe(discussionFiltersMock[0].title); }); it('only updates filter when the URL links to a note', async () => { window.location.hash = `testing123`; - wrapper.vm.handleLocationHash(); + wrapper = mountComponent(); await nextTick(); - expect(wrapper.vm.currentValue).toBe(DISCUSSION_FILTERS_DEFAULT_VALUE); - }); + const filtered = findDropdownItems().filter((el) => el.classes('is-active')); - it('fetches discussions when there is a hash', async () => { - window.location.hash = `note_${discussionMock.notes[0].id}`; - wrapper.vm.currentValue = discussionFiltersMock[2].value; - jest.spyOn(wrapper.vm, 'selectFilter').mockImplementation(() => {}); - wrapper.vm.handleLocationHash(); - - await nextTick(); - expect(wrapper.vm.selectFilter).toHaveBeenCalled(); + expect(filtered).toHaveLength(1); + expect(filtered.at(0).text()).toBe(discussionFiltersMock[0].title); }); it('does not fetch discussions when there is no hash', async () => { window.location.hash = ''; - jest.spyOn(wrapper.vm, 'selectFilter').mockImplementation(() => {}); - wrapper.vm.handleLocationHash(); + const selectFilterSpy = jest.spyOn(wrapper.vm, 'selectFilter').mockImplementation(() => {}); + wrapper = mountComponent(); await nextTick(); - expect(wrapper.vm.selectFilter).not.toHaveBeenCalled(); + expect(selectFilterSpy).not.toHaveBeenCalled(); }); }); }); diff --git a/spec/graphql/types/work_items/widget_interface_spec.rb b/spec/graphql/types/work_items/widget_interface_spec.rb index d1dcfb961cb..045c1620815 100644 --- a/spec/graphql/types/work_items/widget_interface_spec.rb +++ b/spec/graphql/types/work_items/widget_interface_spec.rb @@ -21,6 +21,7 @@ RSpec.describe Types::WorkItems::WidgetInterface do WorkItems::Widgets::Labels | Types::WorkItems::Widgets::LabelsType WorkItems::Widgets::Notes | Types::WorkItems::Widgets::NotesType WorkItems::Widgets::Notifications | Types::WorkItems::Widgets::NotificationsType + WorkItems::Widgets::CurrentUserTodos | Types::WorkItems::Widgets::CurrentUserTodosType end with_them do diff --git a/spec/graphql/types/work_items/widgets/current_user_todos_type_spec.rb b/spec/graphql/types/work_items/widgets/current_user_todos_type_spec.rb new file mode 100644 index 00000000000..b39adefbd87 --- /dev/null +++ b/spec/graphql/types/work_items/widgets/current_user_todos_type_spec.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Types::WorkItems::Widgets::CurrentUserTodosType, feature_category: :team_planning do + it 'exposes the expected fields' do + expected_fields = %i[current_user_todos type] + + expect(described_class).to have_graphql_fields(*expected_fields) + end +end diff --git a/spec/helpers/sidebars_helper_spec.rb b/spec/helpers/sidebars_helper_spec.rb index bdfc45811df..c7d9c36f1a4 100644 --- a/spec/helpers/sidebars_helper_spec.rb +++ b/spec/helpers/sidebars_helper_spec.rb @@ -112,10 +112,10 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do }, can_sign_out: helper.current_user_menu?(:sign_out), sign_out_link: destroy_user_session_path, - assigned_open_issues_count: 1, - todos_pending_count: 3, + assigned_open_issues_count: "1", + todos_pending_count: "3", issues_dashboard_path: issues_dashboard_path(assignee_username: user.username), - total_merge_requests_count: 4, + total_merge_requests_count: "4", projects_path: projects_path, groups_path: groups_path, support_path: helper.support_url, @@ -195,6 +195,23 @@ RSpec.describe SidebarsHelper, feature_category: :navigation do ) end + context 'when counts are high' do + before do + allow(user).to receive(:assigned_open_issues_count).and_return(1000) + allow(user).to receive(:todos_pending_count).and_return(3000) + allow(user).to receive(:assigned_open_merge_requests_count).and_return(50) + allow(user).to receive(:review_requested_open_merge_requests_count).and_return(50) + end + + it 'caps counts to USER_BAR_COUNT_LIMIT and appends a "+" to them' do + expect(subject).to include( + assigned_open_issues_count: "99+", + todos_pending_count: "99+", + total_merge_requests_count: "99+" + ) + end + end + describe 'current context' do context 'when current context is a project' do let_it_be(:project) { build(:project) } diff --git a/spec/lib/gitlab/ci/status/composite_spec.rb b/spec/lib/gitlab/ci/status/composite_spec.rb index 9f133d2c16f..cbf0976c976 100644 --- a/spec/lib/gitlab/ci/status/composite_spec.rb +++ b/spec/lib/gitlab/ci/status/composite_spec.rb @@ -63,8 +63,8 @@ RSpec.describe Gitlab::Ci::Status::Composite, feature_category: :continuous_inte %i(created success pending) | false | 'running' | false %i(skipped success failed) | false | 'failed' | false %i(skipped success failed) | true | 'skipped' | false - %i(success manual) | true | 'pending' | false - %i(success failed created) | true | 'pending' | false + %i(success manual) | true | 'manual' | false + %i(success failed created) | true | 'running' | false end with_them do diff --git a/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb b/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb new file mode 100644 index 00000000000..4c6d4907c29 --- /dev/null +++ b/spec/migrations/20230302811133_re_migrate_redis_slot_keys_spec.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe ReMigrateRedisSlotKeys, :migration, feature_category: :service_ping do + let(:date) { Date.yesterday.strftime('%G-%j') } + let(:week) { Date.yesterday.strftime('%G-%V') } + let(:known_events) do + [ + { + redis_slot: 'analytics', + aggregation: 'daily', + name: 'users_viewing_analytics_group_devops_adoption' + }, { + aggregation: 'weekly', + name: 'wiki_action' + }, { + aggregation: 'weekly', + name: 'non_existing_event' + }, { + aggregation: 'weekly', + name: 'event_without_expiry' + } + ] + end + + describe "#up" do + it 'rename keys', :aggregate_failures do + allow(Gitlab::UsageDataCounters::HLLRedisCounter).to receive(:known_events) + .and_return(known_events) + + expiry_daily = Gitlab::UsageDataCounters::HLLRedisCounter::DEFAULT_DAILY_KEY_EXPIRY_LENGTH + expiry_weekly = Gitlab::UsageDataCounters::HLLRedisCounter::DEFAULT_WEEKLY_KEY_EXPIRY_LENGTH + + default_slot = Gitlab::UsageDataCounters::HLLRedisCounter::REDIS_SLOT + + old_slot_a = "#{date}-users_viewing_{analytics}_group_devops_adoption" + old_slot_b = "{wiki_action}-#{week}" + old_slot_without_expiry = "{event_without_expiry}-#{week}" + + new_slot_a = "#{date}-{#{default_slot}}_users_viewing_analytics_group_devops_adoption" + new_slot_b = "{#{default_slot}}_wiki_action-#{week}" + new_slot_without_expiry = "{#{default_slot}}_event_without_expiry-#{week}" + + Gitlab::Redis::HLL.add(key: old_slot_a, value: 1, expiry: expiry_daily) + Gitlab::Redis::HLL.add(key: old_slot_b, value: 1, expiry: expiry_weekly) + Gitlab::Redis::HLL.add(key: old_slot_a, value: 2, expiry: expiry_daily) + Gitlab::Redis::HLL.add(key: old_slot_b, value: 2, expiry: expiry_weekly) + Gitlab::Redis::HLL.add(key: old_slot_b, value: 2, expiry: expiry_weekly) + Gitlab::Redis::SharedState.with { |redis| redis.pfadd(old_slot_without_expiry, 1) } + + # check that we merge values during migration + # i.e. we dont drop keys created after code deploy but before the migration + Gitlab::Redis::HLL.add(key: new_slot_a, value: 3, expiry: expiry_daily) + Gitlab::Redis::HLL.add(key: new_slot_b, value: 3, expiry: expiry_weekly) + Gitlab::Redis::HLL.add(key: new_slot_without_expiry, value: 2, expiry: expiry_weekly) + + migrate! + + expect(Gitlab::Redis::HLL.count(keys: new_slot_a)).to eq(3) + expect(Gitlab::Redis::HLL.count(keys: new_slot_b)).to eq(3) + expect(Gitlab::Redis::HLL.count(keys: new_slot_without_expiry)).to eq(2) + expect(with_redis { |r| r.ttl(new_slot_a) }).to be_within(600).of(expiry_daily) + expect(with_redis { |r| r.ttl(new_slot_b) }).to be_within(600).of(expiry_weekly) + expect(with_redis { |r| r.ttl(new_slot_without_expiry) }).to be_within(600).of(expiry_weekly) + end + + it 'runs without errors' do + expect { migrate! }.not_to raise_error + end + end + + def with_redis(&block) + Gitlab::Redis::SharedState.with(&block) + end +end diff --git a/spec/migrations/20230317162059_add_current_user_todos_work_item_widget_spec.rb b/spec/migrations/20230317162059_add_current_user_todos_work_item_widget_spec.rb new file mode 100644 index 00000000000..f044b15fa6b --- /dev/null +++ b/spec/migrations/20230317162059_add_current_user_todos_work_item_widget_spec.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe AddCurrentUserTodosWorkItemWidget, :migration, feature_category: :team_planning do + let(:migration) { described_class.new } + let(:work_item_definitions) { table(:work_item_widget_definitions) } + + describe '#up' do + it 'creates notifications widget definition in all types' do + work_item_definitions.where(name: 'Current user todos').delete_all + + expect { migrate! }.to change { work_item_definitions.count }.by(7) + expect(work_item_definitions.all.pluck(:name)).to include('Current user todos') + end + end + + describe '#down' do + it 'removes definitions for notifications widget' do + migrate! + + expect { migration.down }.to change { work_item_definitions.count }.by(-7) + expect(work_item_definitions.all.pluck(:name)).not_to include('Current user todos') + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 3134f2ba248..ac774c84335 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -981,6 +981,22 @@ RSpec.describe Group, feature_category: :subgroups do expect(result).to include(group_2, group_3, group_4) expect(result).not_to include(group_1) end + + context 'when the application_setting is set to `NO_ONE_PROJECT_ACCESS`' do + before do + stub_application_setting(default_project_creation: Gitlab::Access::NO_ONE_PROJECT_ACCESS) + end + + it 'only includes groups where project creation is allowed' do + result = described_class.project_creation_allowed + + expect(result).to include(group_2, group_3) + + # group_4 won't be included because it has `project_creation_level: nil`, + # and that means it behaves like the value of the application_setting will inherited. + expect(result).not_to include(group_1, group_4) + end + end end describe 'by_ids_or_paths' do diff --git a/spec/models/work_items/widget_definition_spec.rb b/spec/models/work_items/widget_definition_spec.rb index 3a4670c996f..f4d132bec52 100644 --- a/spec/models/work_items/widget_definition_spec.rb +++ b/spec/models/work_items/widget_definition_spec.rb @@ -12,7 +12,8 @@ RSpec.describe WorkItems::WidgetDefinition, feature_category: :team_planning do ::WorkItems::Widgets::StartAndDueDate, ::WorkItems::Widgets::Milestone, ::WorkItems::Widgets::Notes, - ::WorkItems::Widgets::Notifications + ::WorkItems::Widgets::Notifications, + ::WorkItems::Widgets::CurrentUserTodos ] if Gitlab.ee? diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb index 24c72a8bb00..fe6f75548a5 100644 --- a/spec/requests/api/graphql/work_item_spec.rb +++ b/spec/requests/api/graphql/work_item_spec.rb @@ -399,6 +399,93 @@ RSpec.describe 'Query.work_item(id)', feature_category: :team_planning do ) end end + + describe 'currentUserTodos widget' do + let_it_be(:current_user) { developer } + let_it_be(:other_todo) { create(:todo, state: :pending, user: current_user) } + + let_it_be(:done_todo) do + create(:todo, state: :done, target: work_item, target_type: work_item.class.name, user: current_user) + end + + let_it_be(:pending_todo) do + create(:todo, state: :pending, target: work_item, target_type: work_item.class.name, user: current_user) + end + + let_it_be(:other_user_todo) do + create(:todo, state: :pending, target: work_item, target_type: work_item.class.name, user: create(:user)) + end + + let(:work_item_fields) do + <<~GRAPHQL + id + widgets { + type + ... on WorkItemWidgetCurrentUserTodos { + currentUserTodos { + nodes { + id + state + } + } + } + } + GRAPHQL + end + + context 'with access' do + it 'returns widget information' do + expect(work_item_data).to include( + 'id' => work_item.to_gid.to_s, + 'widgets' => include( + hash_including( + 'type' => 'CURRENT_USER_TODOS', + 'currentUserTodos' => { + 'nodes' => match_array( + [done_todo, pending_todo].map { |t| { 'id' => t.to_gid.to_s, 'state' => t.state } } + ) + } + ) + ) + ) + end + end + + context 'with filter' do + let(:work_item_fields) do + <<~GRAPHQL + id + widgets { + type + ... on WorkItemWidgetCurrentUserTodos { + currentUserTodos(state: done) { + nodes { + id + state + } + } + } + } + GRAPHQL + end + + it 'returns widget information' do + expect(work_item_data).to include( + 'id' => work_item.to_gid.to_s, + 'widgets' => include( + hash_including( + 'type' => 'CURRENT_USER_TODOS', + 'currentUserTodos' => { + 'nodes' => match_array( + [done_todo].map { |t| { 'id' => t.to_gid.to_s, 'state' => t.state } } + ) + } + ) + ) + ) + end + end + end end context 'when an Issue Global ID is provided' do diff --git a/spec/services/bulk_imports/create_service_spec.rb b/spec/services/bulk_imports/create_service_spec.rb index 35398e4cae0..6fe93437694 100644 --- a/spec/services/bulk_imports/create_service_spec.rb +++ b/spec/services/bulk_imports/create_service_spec.rb @@ -150,7 +150,8 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do expect_snowplow_event( category: 'BulkImports::CreateService', action: 'create', - label: 'bulk_import_group' + label: 'bulk_import_group', + extra: { source_equals_destination: false } ) expect_snowplow_event( @@ -162,6 +163,23 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do ) end + context 'on the same instance' do + before do + allow(Settings.gitlab).to receive(:base_url).and_return('http://gitlab.example') + end + + it 'tracks the same instance migration' do + expect { subject.execute }.to change { BulkImport.count }.by(1) + + expect_snowplow_event( + category: 'BulkImports::CreateService', + action: 'create', + label: 'bulk_import_group', + extra: { source_equals_destination: true } + ) + end + end + describe 'projects migration flag' do let(:import) { BulkImport.last } @@ -229,7 +247,8 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do expect_snowplow_event( category: 'BulkImports::CreateService', action: 'create', - label: 'bulk_import_group' + label: 'bulk_import_group', + extra: { source_equals_destination: false } ) expect_snowplow_event( @@ -241,6 +260,23 @@ RSpec.describe BulkImports::CreateService, feature_category: :importers do ) end + context 'on the same instance' do + before do + allow(Settings.gitlab).to receive(:base_url).and_return('http://gitlab.example') + end + + it 'tracks the same instance migration' do + expect { subject.execute }.to change { BulkImport.count }.by(1) + + expect_snowplow_event( + category: 'BulkImports::CreateService', + action: 'create', + label: 'bulk_import_group', + extra: { source_equals_destination: true } + ) + end + end + it 'creates bulk import entities' do expect { subject.execute }.to change { BulkImports::Entity.count }.by(3) end diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb index ceef369f868..89b3c45485b 100644 --- a/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb +++ b/spec/services/ci/pipeline_processing/atomic_processing_service/status_collection_spec.rb @@ -36,7 +36,7 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection it 'does update existing status of job' do collection.set_job_status(test_a.id, 'success', 100) - expect(collection.status_of_jobs(['test-a'], dag: false)).to eq('success') + expect(collection.status_of_jobs(['test-a'])).to eq('success') end it 'ignores a missing job' do @@ -51,18 +51,15 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService::StatusCollection end describe '#status_of_jobs' do - where(:names, :status, :dag) do - %w[build-a] | 'success' | false - %w[build-a build-b] | 'failed' | false - %w[build-a test-a] | 'running' | false - %w[build-a] | 'success' | true - %w[build-a build-b] | 'failed' | true - %w[build-a test-a] | 'pending' | true + where(:names, :status) do + %w[build-a] | 'success' + %w[build-a build-b] | 'failed' + %w[build-a test-a] | 'running' end with_them do it 'returns composite status of given names' do - expect(collection.status_of_jobs(names, dag: dag)).to eq(status) + expect(collection.status_of_jobs(names)).to eq(status) end end end diff --git a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb index d0496acc6fe..d59d8e24af6 100644 --- a/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb +++ b/spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb @@ -33,6 +33,25 @@ RSpec.describe Ci::PipelineProcessing::AtomicProcessingService, feature_category end end + context 'when the FF ci_simplify_dag_status_calculation_for_processing is disabled' do + before do + stub_feature_flags(ci_simplify_dag_status_calculation_for_processing: false) + end + + # This is duplicate of the one above but it is temporary + it 'follows transitions' do + expect(pipeline).to be_persisted + Sidekiq::Worker.drain_all # ensure that all async jobs are executed + check_expectation(test_file.dig('init', 'expect'), "init") + + test_file['transitions'].each_with_index do |transition, idx| + process_events(transition) + Sidekiq::Worker.drain_all # ensure that all async jobs are executed + check_expectation(transition['expect'], "transition:#{idx}") + end + end + end + private def check_expectation(expectation, message) diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 727b8a6b880..a53e1e1002c 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -234,7 +234,7 @@ module TestEnv end def workhorse_dir - @workhorse_path ||= File.join('tmp', 'tests', 'gitlab-workhorse') + @workhorse_path ||= Rails.root.join('tmp', 'tests', 'gitlab-workhorse') end def with_workhorse(host, port, upstream, &blk) diff --git a/workhorse/.tool-versions b/workhorse/.tool-versions index 18a7cdea814..0c4422a9db4 100644 --- a/workhorse/.tool-versions +++ b/workhorse/.tool-versions @@ -1 +1 @@ -golang 1.18.9 +golang 1.19.7 diff --git a/workhorse/internal/httprs/httprs.go b/workhorse/internal/httprs/httprs.go index f7767d2ee28..811a50035f3 100644 --- a/workhorse/internal/httprs/httprs.go +++ b/workhorse/internal/httprs/httprs.go @@ -11,6 +11,7 @@ Usage : io.ReadFull(rs, buf) // does a range request and reads from the response body If you want use a specific http.Client for additional range requests : + rs := httprs.NewHttpReadSeeker(resp, client) */ package httprs diff --git a/workhorse/internal/queueing/queue.go b/workhorse/internal/queueing/queue.go index c8d32280355..266f8897450 100644 --- a/workhorse/internal/queueing/queue.go +++ b/workhorse/internal/queueing/queue.go @@ -27,8 +27,9 @@ type queueMetrics struct { // newQueueMetrics prepares Prometheus metrics for queueing mechanism // name specifies name of the queue, used to label metrics with ConstLabel `queue_name` // timeout specifies the timeout of storing a request in queue - queueMetrics -// uses it to calculate histogram buckets for gitlab_workhorse_queueing_waiting_time -// metric +// +// uses it to calculate histogram buckets for gitlab_workhorse_queueing_waiting_time +// metric func newQueueMetrics(name string, timeout time.Duration, reg prometheus.Registerer) *queueMetrics { waitingTimeBuckets := []float64{ timeout.Seconds() * 0.01, diff --git a/workhorse/internal/queueing/requests.go b/workhorse/internal/queueing/requests.go index c3df614de41..a6ca98a674a 100644 --- a/workhorse/internal/queueing/requests.go +++ b/workhorse/internal/queueing/requests.go @@ -16,7 +16,9 @@ const ( // QueueRequests creates a new request queue // name specifies the name of queue, used to label Prometheus metrics -// Don't call QueueRequests twice with the same name argument! +// +// Don't call QueueRequests twice with the same name argument! +// // h specifies a http.Handler which will handle the queue requests // limit specifies number of requests run concurrently // queueLimit specifies maximum number of requests that can be queued |