diff options
173 files changed, 1876 insertions, 2486 deletions
diff --git a/app/assets/javascripts/issues/show/components/fields/description.vue b/app/assets/javascripts/issues/show/components/fields/description.vue index 5476a1ef897..d5ac7b28afc 100644 --- a/app/assets/javascripts/issues/show/components/fields/description.vue +++ b/app/assets/javascripts/issues/show/components/fields/description.vue @@ -1,13 +1,12 @@ <script> import markdownField from '~/vue_shared/components/markdown/field.vue'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import updateMixin from '../../mixins/update'; export default { components: { markdownField, }, - mixins: [glFeatureFlagsMixin(), updateMixin], + mixins: [updateMixin], props: { formState: { type: Object, @@ -56,7 +55,7 @@ export default { v-model="formState.description" class="note-textarea js-gfm-input js-autosize markdown-area qa-description-textarea" dir="auto" - :data-supports-quick-actions="!glFeatures.tributeAutocomplete" + data-supports-quick-actions="true" :aria-label="__('Description')" :placeholder="__('Write a comment or drag your files here…')" @keydown.meta.enter="updateIssuable" diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 996c008b881..a9948fed3b6 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -369,7 +369,7 @@ export default { class="note-textarea js-vue-comment-form js-note-text js-gfm-input js-autosize markdown-area" data-qa-selector="comment_field" data-testid="comment-field" - :data-supports-quick-actions="!glFeatures.tributeAutocomplete" + data-supports-quick-actions="true" :aria-label="$options.i18n.comment" :placeholder="$options.i18n.bodyPlaceholder" @keydown.up="editCurrentUserLastNote()" diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue index d6b65ed0e8b..ee22c118e11 100644 --- a/app/assets/javascripts/notes/components/note_form.vue +++ b/app/assets/javascripts/notes/components/note_form.vue @@ -5,7 +5,6 @@ import { getDraft, updateDraft } from '~/lib/utils/autosave'; import { mergeUrlParams } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; import markdownField from '~/vue_shared/components/markdown/field.vue'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import eventHub from '../event_hub'; import issuableStateMixin from '../mixins/issuable_state'; import resolvable from '../mixins/resolvable'; @@ -20,7 +19,7 @@ export default { GlSprintf, GlLink, }, - mixins: [glFeatureFlagsMixin(), issuableStateMixin, resolvable], + mixins: [issuableStateMixin, resolvable], props: { noteBody: { type: String, @@ -349,7 +348,7 @@ export default { ref="textarea" v-model="updatedNoteBody" :disabled="isSubmitting" - :data-supports-quick-actions="!isEditing && !glFeatures.tributeAutocomplete" + :data-supports-quick-actions="!isEditing" name="note[note]" class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form" data-qa-selector="reply_field" diff --git a/app/assets/javascripts/pages/admin/index.js b/app/assets/javascripts/pages/admin/index.js index 8d5dfd689e8..f0f85b82e2b 100644 --- a/app/assets/javascripts/pages/admin/index.js +++ b/app/assets/javascripts/pages/admin/index.js @@ -1,8 +1,10 @@ +import initGitlabVersionCheck from '~/gitlab_version_check'; import initAdminStatisticsPanel from '../../admin/statistics_panel/index'; import initVueAlerts from '../../vue_alerts'; import initAdmin from './admin'; initVueAlerts(); +initGitlabVersionCheck(); const statisticsPanelContainer = document.getElementById('js-admin-statistics-container'); initAdmin(); diff --git a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue deleted file mode 100644 index 9ab91e567e6..00000000000 --- a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue +++ /dev/null @@ -1,106 +0,0 @@ -<script> -import Tribute from '@gitlab/tributejs'; -import { - GfmAutocompleteType, - tributeConfig, -} from 'ee_else_ce/vue_shared/components/gfm_autocomplete/utils'; -import * as Emoji from '~/emoji'; -import createFlash from '~/flash'; -import axios from '~/lib/utils/axios_utils'; -import { __ } from '~/locale'; -import SidebarMediator from '~/sidebar/sidebar_mediator'; - -export default { - errorMessage: __( - 'An error occurred while getting autocomplete data. Please refresh the page and try again.', - ), - props: { - autocompleteTypes: { - type: Array, - required: false, - default: () => Object.values(GfmAutocompleteType), - }, - dataSources: { - type: Object, - required: false, - default: () => gl.GfmAutoComplete?.dataSources || {}, - }, - }, - computed: { - config() { - return this.autocompleteTypes.map((type) => ({ - ...tributeConfig[type].config, - loadingItemTemplate: `<span class="gl-spinner gl-vertical-align-text-bottom gl-ml-3 gl-mr-2"></span>${__( - 'Loading', - )}`, - requireLeadingSpace: true, - values: this.getValues(type), - })); - }, - }, - mounted() { - this.cache = {}; - this.tribute = new Tribute({ collection: this.config }); - - const input = this.$slots.default?.[0]?.elm; - this.tribute.attach(input); - }, - beforeDestroy() { - const input = this.$slots.default?.[0]?.elm; - this.tribute.detach(input); - }, - methods: { - cacheAssignees() { - const isAssigneesLengthSame = - this.assignees?.length === SidebarMediator.singleton?.store?.assignees?.length; - - if (!this.assignees || !isAssigneesLengthSame) { - this.assignees = - SidebarMediator.singleton?.store?.assignees?.map((assignee) => assignee.username) || []; - } - }, - filterValues(type) { - // The assignees AJAX response can come after the user first invokes autocomplete - // so we need to check more than once if we need to update the assignee cache - this.cacheAssignees(); - - return tributeConfig[type].filterValues - ? tributeConfig[type].filterValues({ - assignees: this.assignees, - collection: this.cache[type], - fullText: this.$slots.default?.[0]?.elm?.value, - selectionStart: this.$slots.default?.[0]?.elm?.selectionStart, - }) - : this.cache[type]; - }, - getValues(type) { - return (inputText, processValues) => { - if (this.cache[type]) { - processValues(this.filterValues(type)); - } else if (type === GfmAutocompleteType.Emojis) { - Emoji.initEmojiMap() - .then(() => { - const emojis = Emoji.getValidEmojiNames(); - this.cache[type] = emojis; - processValues(emojis); - }) - .catch(() => createFlash({ message: this.$options.errorMessage })); - } else if (this.dataSources[type]) { - axios - .get(this.dataSources[type]) - .then((response) => { - this.cache[type] = response.data; - processValues(this.filterValues(type)); - }) - .catch(() => createFlash({ message: this.$options.errorMessage })); - } else { - processValues([]); - } - }; - }, - }, - render(createElement) { - return createElement('div', this.$slots.default); - }, -}; -</script> diff --git a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js b/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js deleted file mode 100644 index 44c3fc34ba6..00000000000 --- a/app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js +++ /dev/null @@ -1,195 +0,0 @@ -import { escape, last } from 'lodash'; -import * as Emoji from '~/emoji'; -import { spriteIcon } from '~/lib/utils/common_utils'; - -const groupType = 'Group'; // eslint-disable-line @gitlab/require-i18n-strings - -// Number of users to show in the autocomplete menu to avoid doing a mass fetch of 100+ avatars -const memberLimit = 10; - -const nonWordOrInteger = /\W|^\d+$/; - -export const menuItemLimit = 100; - -export const GfmAutocompleteType = { - Emojis: 'emojis', - Issues: 'issues', - Labels: 'labels', - Members: 'members', - MergeRequests: 'mergeRequests', - Milestones: 'milestones', - QuickActions: 'commands', - Snippets: 'snippets', -}; - -function doesCurrentLineStartWith(searchString, fullText, selectionStart) { - const currentLineNumber = fullText.slice(0, selectionStart).split('\n').length; - const currentLine = fullText.split('\n')[currentLineNumber - 1]; - return currentLine.startsWith(searchString); -} - -export const tributeConfig = { - [GfmAutocompleteType.Emojis]: { - config: { - trigger: ':', - lookup: (value) => value, - menuItemLimit, - menuItemTemplate: ({ original }) => `${original} ${Emoji.glEmojiTag(original)}`, - selectTemplate: ({ original }) => `:${original}:`, - }, - }, - - [GfmAutocompleteType.Issues]: { - config: { - trigger: '#', - lookup: (value) => `${value.iid}${value.title}`, - menuItemLimit, - menuItemTemplate: ({ original }) => - `<small>${original.reference || original.iid}</small> ${escape(original.title)}`, - selectTemplate: ({ original }) => original.reference || `#${original.iid}`, - }, - }, - - [GfmAutocompleteType.Labels]: { - config: { - trigger: '~', - lookup: 'title', - menuItemLimit, - menuItemTemplate: ({ original }) => ` - <span class="dropdown-label-box" style="background: ${escape(original.color)};"></span> - ${escape(original.title)}`, - selectTemplate: ({ original }) => - nonWordOrInteger.test(original.title) - ? `~"${escape(original.title)}"` - : `~${escape(original.title)}`, - }, - filterValues({ collection, fullText, selectionStart }) { - if (doesCurrentLineStartWith('/label', fullText, selectionStart)) { - return collection.filter((label) => !label.set); - } - - if (doesCurrentLineStartWith('/unlabel', fullText, selectionStart)) { - return collection.filter((label) => label.set); - } - - return collection; - }, - }, - - [GfmAutocompleteType.Members]: { - config: { - trigger: '@', - fillAttr: 'username', - lookup: (value) => - value.type === groupType ? last(value.name.split(' / ')) : `${value.name}${value.username}`, - menuItemLimit: memberLimit, - menuItemTemplate: ({ original }) => { - const commonClasses = 'gl-avatar gl-avatar-s32 gl-flex-shrink-0'; - const noAvatarClasses = `${commonClasses} gl-rounded-small - gl-display-flex gl-align-items-center gl-justify-content-center`; - - const avatar = original.avatar_url - ? `<img class="${commonClasses} gl-avatar-circle" src="${original.avatar_url}" alt="" />` - : `<div class="${noAvatarClasses}" aria-hidden="true"> - ${original.username.charAt(0).toUpperCase()}</div>`; - - let displayName = original.name; - let parentGroupOrUsername = `@${original.username}`; - - if (original.type === groupType) { - const splitName = original.name.split(' / '); - displayName = splitName.pop(); - parentGroupOrUsername = splitName.pop(); - } - - const count = original.count && !original.mentionsDisabled ? ` (${original.count})` : ''; - - const disabledMentionsIcon = original.mentionsDisabled - ? spriteIcon('notifications-off', 's16 gl-ml-3') - : ''; - - return ` - <div class="gl-display-flex gl-align-items-center"> - ${avatar} - <div class="gl-line-height-normal gl-ml-4"> - <div>${escape(displayName)}${count}</div> - <div class="gl-text-gray-700">${escape(parentGroupOrUsername)}</div> - </div> - ${disabledMentionsIcon} - </div> - `; - }, - }, - filterValues({ assignees, collection, fullText, selectionStart }) { - if (doesCurrentLineStartWith('/assign', fullText, selectionStart)) { - return collection.filter((member) => !assignees.includes(member.username)); - } - - if (doesCurrentLineStartWith('/unassign', fullText, selectionStart)) { - return collection.filter((member) => assignees.includes(member.username)); - } - - return collection; - }, - }, - - [GfmAutocompleteType.MergeRequests]: { - config: { - trigger: '!', - lookup: (value) => `${value.iid}${value.title}`, - menuItemLimit, - menuItemTemplate: ({ original }) => - `<small>${original.reference || original.iid}</small> ${escape(original.title)}`, - selectTemplate: ({ original }) => original.reference || `!${original.iid}`, - }, - }, - - [GfmAutocompleteType.Milestones]: { - config: { - trigger: '%', - lookup: 'title', - menuItemLimit, - menuItemTemplate: ({ original }) => escape(original.title), - selectTemplate: ({ original }) => `%"${escape(original.title)}"`, - }, - }, - - [GfmAutocompleteType.QuickActions]: { - config: { - trigger: '/', - fillAttr: 'name', - lookup: (value) => `${value.name}${value.aliases.join()}`, - menuItemLimit, - menuItemTemplate: ({ original }) => { - const aliases = original.aliases.length - ? `<small>(or /${original.aliases.join(', /')})</small>` - : ''; - - const params = original.params.length ? `<small>${original.params.join(' ')}</small>` : ''; - - let description = ''; - - if (original.warning) { - const confidentialIcon = - original.icon === 'confidential' ? spriteIcon('eye-slash', 's16 gl-mr-2') : ''; - description = `<small>${confidentialIcon}<em>${original.warning}</em></small>`; - } else if (original.description) { - description = `<small><em>${original.description}</em></small>`; - } - - return `<div>/${original.name} ${aliases} ${params}</div> - <div>${description}</div>`; - }, - }, - }, - - [GfmAutocompleteType.Snippets]: { - config: { - trigger: '$', - fillAttr: 'id', - lookup: (value) => `${value.id}${value.title}`, - menuItemLimit, - menuItemTemplate: ({ original }) => `<small>${original.id}</small> ${escape(original.title)}`, - }, - }, -}; diff --git a/app/assets/javascripts/vue_shared/components/help_popover.vue b/app/assets/javascripts/vue_shared/components/help_popover.vue index f36b9107a6e..f3b871c91b6 100644 --- a/app/assets/javascripts/vue_shared/components/help_popover.vue +++ b/app/assets/javascripts/vue_shared/components/help_popover.vue @@ -33,6 +33,9 @@ export default { <template #default> <div v-safe-html="options.content"></div> </template> + <template v-for="slot in Object.keys($slots)" #[slot]> + <slot :name="slot"></slot> + </template> </gl-popover> </span> </template> diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index 5c86c928ce3..603ad71adb9 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -8,9 +8,7 @@ import GLForm from '~/gl_form'; import axios from '~/lib/utils/axios_utils'; import { stripHtml } from '~/lib/utils/text_utility'; import { __, sprintf } from '~/locale'; -import GfmAutocomplete from '~/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue'; import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; -import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import MarkdownHeader from './header.vue'; import MarkdownToolbar from './toolbar.vue'; @@ -20,13 +18,11 @@ function cleanUpLine(content) { export default { components: { - GfmAutocomplete, MarkdownHeader, MarkdownToolbar, GlIcon, Suggestions, }, - mixins: [glFeatureFlagsMixin()], props: { /** * This prop should be bound to the value of the `<textarea>` element @@ -212,14 +208,14 @@ export default { return new GLForm( $(this.$refs['gl-form']), { - emojis: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, - members: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, - issues: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, - mergeRequests: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, - epics: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, - milestones: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, - labels: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, - snippets: this.enableAutocomplete && !this.glFeatures.tributeAutocomplete, + emojis: this.enableAutocomplete, + members: this.enableAutocomplete, + issues: this.enableAutocomplete, + mergeRequests: this.enableAutocomplete, + epics: this.enableAutocomplete, + milestones: this.enableAutocomplete, + labels: this.enableAutocomplete, + snippets: this.enableAutocomplete, vulnerabilities: this.enableAutocomplete, }, true, @@ -311,10 +307,7 @@ export default { /> <div v-show="!previewMarkdown" class="md-write-holder"> <div class="zen-backdrop"> - <gfm-autocomplete v-if="glFeatures.tributeAutocomplete"> - <slot name="textarea"></slot> - </gfm-autocomplete> - <slot v-else name="textarea"></slot> + <slot name="textarea"></slot> <a class="zen-control zen-control-leave js-zen-leave gl-text-gray-500" href="#" diff --git a/app/assets/stylesheets/vendors/tribute.scss b/app/assets/stylesheets/vendors/tribute.scss deleted file mode 100644 index 65f3d1b6199..00000000000 --- a/app/assets/stylesheets/vendors/tribute.scss +++ /dev/null @@ -1,41 +0,0 @@ -.tribute-container { - background: $white; - border: 1px solid $gray-100; - border-radius: $border-radius-base; - box-shadow: 0 0 5px $issue-boards-card-shadow; - color: $black; - margin-top: $gl-padding-12; - max-height: 200px; - min-width: 120px; - overflow-y: auto; - z-index: 11110 !important; - - ul { - list-style: none; - margin-bottom: 0; - padding: $gl-padding-8 1px; - } - - li { - cursor: pointer; - padding: $gl-padding-8 $gl-padding; - white-space: nowrap; - - small { - color: $gray-500; - } - - &.highlight { - background-color: $gray-darker; - - .avatar { - @include disable-all-animation; - border: 1px solid $white; - } - - small { - color: inherit; - } - } - } -} diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 8b5e9fa8bb9..89e87c4345e 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -42,7 +42,6 @@ class Projects::IssuesController < Projects::ApplicationController if: -> { Feature.disabled?('rate_limited_service_issues_create', project, default_enabled: :yaml) } before_action do - push_frontend_feature_flag(:tribute_autocomplete, @project) push_frontend_feature_flag(:improved_emoji_picker, project, default_enabled: :yaml) push_frontend_feature_flag(:vue_issues_list, project&.group, default_enabled: :yaml) push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml) diff --git a/app/graphql/resolvers/ci/project_pipeline_counts_resolver.rb b/app/graphql/resolvers/ci/project_pipeline_counts_resolver.rb new file mode 100644 index 00000000000..728bc9627c5 --- /dev/null +++ b/app/graphql/resolvers/ci/project_pipeline_counts_resolver.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Resolvers + module Ci + class ProjectPipelineCountsResolver < BaseResolver + type Types::Ci::PipelineCountsType, null: true + + argument :ref, + GraphQL::Types::String, + required: false, + description: "Filter pipelines by the ref they are run for." + + argument :sha, + GraphQL::Types::String, + required: false, + description: "Filter pipelines by the SHA of the commit they are run for." + + argument :source, + GraphQL::Types::String, + required: false, + description: "Filter pipelines by their source." + + def resolve(**args) + ::Gitlab::PipelineScopeCounts.new(context[:current_user], object, args) + end + end + end +end diff --git a/app/graphql/types/ci/pipeline_counts_type.rb b/app/graphql/types/ci/pipeline_counts_type.rb new file mode 100644 index 00000000000..9c2b822091e --- /dev/null +++ b/app/graphql/types/ci/pipeline_counts_type.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +module Types + module Ci + class PipelineCountsType < BaseObject + graphql_name 'PipelineCounts' + description "Represents pipeline counts for the project" + + authorize :read_pipeline + + (::Types::Ci::PipelineScopeEnum.values.keys - %w[BRANCHES TAGS]).each do |scope| + field scope.downcase, + GraphQL::Types::Int, + null: true, + description: "Number of pipelines with scope #{scope} for the project" + end + + field :all, + GraphQL::Types::Int, + null: true, + description: 'Total number of pipelines for the project.' + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index 8acc9b375be..e193d52ec35 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -195,6 +195,12 @@ module Types extras: [:lookahead], resolver: Resolvers::ProjectPipelineResolver + field :pipeline_counts, + Types::Ci::PipelineCountsType, + null: true, + description: 'Build pipeline counts of the project.', + resolver: Resolvers::Ci::ProjectPipelineCountsResolver + field :ci_cd_settings, Types::Ci::CiCdSettingType, null: true, diff --git a/app/models/ci/instance_variable.rb b/app/models/ci/instance_variable.rb index da077d31519..da9d4dea537 100644 --- a/app/models/ci/instance_variable.rb +++ b/app/models/ci/instance_variable.rb @@ -2,6 +2,7 @@ module Ci class InstanceVariable < Ci::ApplicationRecord + extend Gitlab::ProcessMemoryCache::Helper include Ci::NewHasVariable include Ci::Maskable include Limitable @@ -35,23 +36,15 @@ module Ci cached_data[:unprotected] end - def invalidate_memory_cache(key) - cache_backend.delete(key) - end - private def cached_data - cache_backend.fetch(:ci_instance_variable_data, expires_in: 30.seconds) do + fetch_memory_cache(:ci_instance_variable_data) do all_records = unscoped.all.to_a { all: all_records, unprotected: all_records.reject(&:protected?) } end end - - def cache_backend - Gitlab::ProcessMemoryCache.cache_backend - end end end end diff --git a/app/policies/ci/project_pipelines_policy.rb b/app/policies/ci/project_pipelines_policy.rb new file mode 100644 index 00000000000..aab1208a8fe --- /dev/null +++ b/app/policies/ci/project_pipelines_policy.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Ci + class ProjectPipelinesPolicy < BasePolicy + delegate { @subject.project } + end +end diff --git a/app/services/ci/process_sync_events_service.rb b/app/services/ci/process_sync_events_service.rb index 11ce6e8eeaf..184dd9c2c8a 100644 --- a/app/services/ci/process_sync_events_service.rb +++ b/app/services/ci/process_sync_events_service.rb @@ -13,8 +13,6 @@ module Ci end def execute - return unless ::Feature.enabled?(:ci_namespace_project_mirrors, default_enabled: :yaml) - # preventing parallel processing over the same event table try_obtain_lease { process_events } diff --git a/app/views/projects/_remove.html.haml b/app/views/projects/_remove.html.haml index 815e76ebcb9..d0dfbb89ca7 100644 --- a/app/views/projects/_remove.html.haml +++ b/app/views/projects/_remove.html.haml @@ -1,6 +1,7 @@ - return unless can?(current_user, :remove_project, project) - merge_requests_count = Projects::AllMergeRequestsCountService.new(project).count - issues_count = Projects::AllIssuesCountService.new(project).count +- forks_count = Projects::ForksCountService.new(project).count .sub-section %h4.danger-title= _('Delete project') @@ -9,4 +10,4 @@ = link_to _('Learn more.'), help_page_path('user/project/settings/index', anchor: 'removing-a-fork-relationship'), target: '_blank', rel: 'noopener noreferrer' %p %strong= _('Deleted projects cannot be restored!') - #js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: delete_confirm_phrase(project), is_fork: project.forked?.to_s, issues_count: number_with_delimiter(issues_count), merge_requests_count: number_with_delimiter(merge_requests_count), forks_count: number_with_delimiter(project.forks_count), stars_count: number_with_delimiter(project.star_count) } } + #js-project-delete-button{ data: { form_path: project_path(project), confirm_phrase: delete_confirm_phrase(project), is_fork: project.forked?.to_s, issues_count: number_with_delimiter(issues_count), merge_requests_count: number_with_delimiter(merge_requests_count), forks_count: number_with_delimiter(forks_count), stars_count: number_with_delimiter(project.star_count) } } diff --git a/config/feature_flags/development/ci_decompose_for_namespace_monthly_usage_query.yml b/config/feature_flags/development/ci_decompose_for_namespace_monthly_usage_query.yml deleted file mode 100644 index 7b90c3e19b2..00000000000 --- a/config/feature_flags/development/ci_decompose_for_namespace_monthly_usage_query.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_decompose_for_namespace_monthly_usage_query -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77952 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/350146 -milestone: '14.7' -type: development -group: group::pipeline execution -default_enabled: false diff --git a/config/feature_flags/development/ci_namespace_project_mirrors.yml b/config/feature_flags/development/ci_namespace_project_mirrors.yml deleted file mode 100644 index a2d674c3770..00000000000 --- a/config/feature_flags/development/ci_namespace_project_mirrors.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_namespace_project_mirrors -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75517 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/346786 -milestone: '14.6' -type: development -group: group::sharding -default_enabled: false diff --git a/config/feature_flags/development/tribute_autocomplete.yml b/config/feature_flags/development/tribute_autocomplete.yml deleted file mode 100644 index 02094350b32..00000000000 --- a/config/feature_flags/development/tribute_autocomplete.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: tribute_autocomplete -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/32671 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/292804 -milestone: '13.2' -type: development -group: group::project management -default_enabled: false diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb index 15757c05bd0..17ce2a30d66 100644 --- a/config/initializers/7_prometheus_metrics.rb +++ b/config/initializers/7_prometheus_metrics.rb @@ -95,13 +95,6 @@ if Gitlab::Runtime.web_server? Gitlab::Metrics::Exporter::WebExporter.instance.start end - # DEPRECATED: TO BE REMOVED - # This is needed to implement blackout period of `web_exporter` - # https://gitlab.com/gitlab-org/gitlab/issues/35343#note_238479057 - Gitlab::Cluster::LifecycleEvents.on_before_blackout_period do - Gitlab::Metrics::Exporter::WebExporter.instance.mark_as_not_running! - end - Gitlab::Cluster::LifecycleEvents.on_before_graceful_shutdown do # We need to ensure that before we re-exec or shutdown server # we do stop the exporter diff --git a/config/webpack.config.js b/config/webpack.config.js index 912c2fe5c45..152a5a69842 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -260,8 +260,7 @@ module.exports = { { test: /\.js$/, exclude: (modulePath) => - /node_modules\/(?!tributejs)|node_modules|vendor[\\/]assets/.test(modulePath) && - !/\.vue\.js/.test(modulePath), + /node_modules|vendor[\\/]assets/.test(modulePath) && !/\.vue\.js/.test(modulePath), loader: 'babel-loader', options: { cacheDirectory: path.join(CACHE_PATH, 'babel-loader'), diff --git a/db/post_migrate/20220124153233_remove_projects_ci_job_artifacts_project_id_fk.rb b/db/post_migrate/20220124153233_remove_projects_ci_job_artifacts_project_id_fk.rb new file mode 100644 index 00000000000..1948a78916d --- /dev/null +++ b/db/post_migrate/20220124153233_remove_projects_ci_job_artifacts_project_id_fk.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class RemoveProjectsCiJobArtifactsProjectIdFk < Gitlab::Database::Migration[1.0] + disable_ddl_transaction! + + def up + return if Gitlab.com? # unsafe migration, skip on GitLab.com due to https://gitlab.com/groups/gitlab-org/-/epics/7249#note_819625526 + return unless foreign_key_exists?(:ci_job_artifacts, :projects, name: "fk_rails_9862d392f9") + + with_lock_retries do + execute('LOCK projects, ci_job_artifacts IN ACCESS EXCLUSIVE MODE') if transaction_open? + + remove_foreign_key_if_exists(:ci_job_artifacts, :projects, name: "fk_rails_9862d392f9") + end + end + + def down + add_concurrent_foreign_key(:ci_job_artifacts, :projects, name: "fk_rails_9862d392f9", column: :project_id, target_column: :id, on_delete: :cascade) + end +end diff --git a/db/schema_migrations/20220124153233 b/db/schema_migrations/20220124153233 new file mode 100644 index 00000000000..bfb0d6f3c38 --- /dev/null +++ b/db/schema_migrations/20220124153233 @@ -0,0 +1 @@ +f62f3d4cc6f4704e7b4e7d0b6b8e46ed3de4407f0db4282e2ce845aa6c0b3f3f
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 9e40580d3f5..99c2bec4dcc 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -30908,9 +30908,6 @@ ALTER TABLE ONLY group_repository_storage_moves ALTER TABLE ONLY resource_label_events ADD CONSTRAINT fk_rails_9851a00031 FOREIGN KEY (merge_request_id) REFERENCES merge_requests(id) ON DELETE CASCADE; -ALTER TABLE ONLY ci_job_artifacts - ADD CONSTRAINT fk_rails_9862d392f9 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; - ALTER TABLE ONLY board_project_recent_visits ADD CONSTRAINT fk_rails_98f8843922 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; diff --git a/doc/administration/logs.md b/doc/administration/logs.md index 0d7635405d6..c4e9642d048 100644 --- a/doc/administration/logs.md +++ b/doc/administration/logs.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/github_imports.md b/doc/administration/monitoring/github_imports.md index e91483eb79d..e16e9bb0336 100644 --- a/doc/administration/monitoring/github_imports.md +++ b/doc/administration/monitoring/github_imports.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md index 1cf4e5a25ba..43c366e9754 100644 --- a/doc/administration/monitoring/gitlab_self_monitoring_project/index.md +++ b/doc/administration/monitoring/gitlab_self_monitoring_project/index.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/index.md b/doc/administration/monitoring/index.md index 4c49efd6bd5..df655053723 100644 --- a/doc/administration/monitoring/index.md +++ b/doc/administration/monitoring/index.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/ip_whitelist.md b/doc/administration/monitoring/ip_whitelist.md index 75b09f8a366..b8347ba3f0d 100644 --- a/doc/administration/monitoring/ip_whitelist.md +++ b/doc/administration/monitoring/ip_whitelist.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/performance/gitlab_configuration.md b/doc/administration/monitoring/performance/gitlab_configuration.md index f316a75a868..128ddad6555 100644 --- a/doc/administration/monitoring/performance/gitlab_configuration.md +++ b/doc/administration/monitoring/performance/gitlab_configuration.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md index c37a264938e..79612145327 100644 --- a/doc/administration/monitoring/performance/grafana_configuration.md +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/performance/index.md b/doc/administration/monitoring/performance/index.md index f3db6ac9f03..20fad8baf91 100644 --- a/doc/administration/monitoring/performance/index.md +++ b/doc/administration/monitoring/performance/index.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md index 14a560223f9..59237a83e4d 100644 --- a/doc/administration/monitoring/performance/performance_bar.md +++ b/doc/administration/monitoring/performance/performance_bar.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md index ebdca8d3960..9f3b629fbae 100644 --- a/doc/administration/monitoring/performance/request_profiling.md +++ b/doc/administration/monitoring/performance/request_profiling.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/prometheus/gitlab_exporter.md b/doc/administration/monitoring/prometheus/gitlab_exporter.md index d9852524aec..15ec880533e 100644 --- a/doc/administration/monitoring/prometheus/gitlab_exporter.md +++ b/doc/administration/monitoring/prometheus/gitlab_exporter.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md index c5b87afd94b..5f18c981c73 100644 --- a/doc/administration/monitoring/prometheus/gitlab_metrics.md +++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/prometheus/node_exporter.md b/doc/administration/monitoring/prometheus/node_exporter.md index 68d997d7596..d7a4a96cd9a 100644 --- a/doc/administration/monitoring/prometheus/node_exporter.md +++ b/doc/administration/monitoring/prometheus/node_exporter.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/prometheus/pgbouncer_exporter.md b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md index aba1561500a..979a6bcd232 100644 --- a/doc/administration/monitoring/prometheus/pgbouncer_exporter.md +++ b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/prometheus/postgres_exporter.md b/doc/administration/monitoring/prometheus/postgres_exporter.md index 8a851afe35b..95a6540bd19 100644 --- a/doc/administration/monitoring/prometheus/postgres_exporter.md +++ b/doc/administration/monitoring/prometheus/postgres_exporter.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/prometheus/puma_exporter.md b/doc/administration/monitoring/prometheus/puma_exporter.md index 804c4243cfa..794e2c10b25 100644 --- a/doc/administration/monitoring/prometheus/puma_exporter.md +++ b/doc/administration/monitoring/prometheus/puma_exporter.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/prometheus/redis_exporter.md b/doc/administration/monitoring/prometheus/redis_exporter.md index 6cc262842a1..a5f12bbc52f 100644 --- a/doc/administration/monitoring/prometheus/redis_exporter.md +++ b/doc/administration/monitoring/prometheus/redis_exporter.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/administration/monitoring/prometheus/registry_exporter.md b/doc/administration/monitoring/prometheus/registry_exporter.md index 3a2acd47338..f4fa35c206e 100644 --- a/doc/administration/monitoring/prometheus/registry_exporter.md +++ b/doc/administration/monitoring/prometheus/registry_exporter.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md index 68d339837b2..4d4aaf5610b 100644 --- a/doc/api/container_registry.md +++ b/doc/api/container_registry.md @@ -396,10 +396,6 @@ If your Container Registry has a large number of tags to delete, only some of them will be deleted, and you might need to call this API multiple times. To schedule tags for automatic deletion, use a [cleanup policy](../user/packages/container_registry/reduce_container_registry_storage.md#cleanup-policy) instead. -NOTE: -In GitLab 12.4 and later, individual tags are deleted. -For more details, see the [discussion](https://gitlab.com/gitlab-org/gitlab/-/issues/15737). - Examples: 1. Remove tag names that are matching the regex (Git SHA), keep always at least 5, diff --git a/doc/api/epics.md b/doc/api/epics.md index f3137559220..deb74cf21e9 100644 --- a/doc/api/epics.md +++ b/doc/api/epics.md @@ -40,12 +40,13 @@ are paginated. Read more on [pagination](index.md#pagination). WARNING: -> `reference` attribute in response is deprecated in favour of `references`. -> Introduced in [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20354) +In [GitLab 12.6](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20354) and later, +the `reference` attribute in responses is deprecated in favor of `references`. NOTE: -> `references.relative` is relative to the group that the epic is being requested. When epic is fetched from its origin group -> `relative` format would be the same as `short` format and when requested cross groups it is expected to be the same as `full` format. +`references.relative` is relative to the group that the epic is being requested from. When an epic +is fetched from its origin group, the `relative` format is the same as the `short` format. +When an epic is requested across groups, the `relative` format is expected to be the same as the `full` format. ## List epics for a group diff --git a/doc/api/error_tracking.md b/doc/api/error_tracking.md index c62d33f82f4..0bb63e06540 100644 --- a/doc/api/error_tracking.md +++ b/doc/api/error_tracking.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 5a4ec8cedac..de2a7f6ccd8 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -13207,6 +13207,19 @@ Represents the Geo sync and verification state of a pipeline artifact. | <a id="pipelineartifactregistryretrycount"></a>`retryCount` | [`Int`](#int) | Number of consecutive failed sync attempts of the PipelineArtifactRegistry. | | <a id="pipelineartifactregistrystate"></a>`state` | [`RegistryState`](#registrystate) | Sync state of the PipelineArtifactRegistry. | +### `PipelineCounts` + +Represents pipeline counts for the project. + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="pipelinecountsall"></a>`all` | [`Int`](#int) | Total number of pipelines for the project. | +| <a id="pipelinecountsfinished"></a>`finished` | [`Int`](#int) | Number of pipelines with scope FINISHED for the project. | +| <a id="pipelinecountspending"></a>`pending` | [`Int`](#int) | Number of pipelines with scope PENDING for the project. | +| <a id="pipelinecountsrunning"></a>`running` | [`Int`](#int) | Number of pipelines with scope RUNNING for the project. | + ### `PipelineMessage` #### Fields @@ -13977,6 +13990,20 @@ Returns [`Pipeline`](#pipeline). | <a id="projectpipelineiid"></a>`iid` | [`ID`](#id) | IID of the Pipeline. For example, "1". | | <a id="projectpipelinesha"></a>`sha` | [`String`](#string) | SHA of the Pipeline. For example, "dyd0f15ay83993f5ab66k927w28673882x99100b". | +##### `Project.pipelineCounts` + +Build pipeline counts of the project. + +Returns [`PipelineCounts`](#pipelinecounts). + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="projectpipelinecountsref"></a>`ref` | [`String`](#string) | Filter pipelines by the ref they are run for. | +| <a id="projectpipelinecountssha"></a>`sha` | [`String`](#string) | Filter pipelines by the SHA of the commit they are run for. | +| <a id="projectpipelinecountssource"></a>`source` | [`String`](#string) | Filter pipelines by their source. | + ##### `Project.pipelines` Build pipelines of the project. diff --git a/doc/api/metrics_dashboard_annotations.md b/doc/api/metrics_dashboard_annotations.md index feba57a7ced..7732bf61d77 100644 --- a/doc/api/metrics_dashboard_annotations.md +++ b/doc/api/metrics_dashboard_annotations.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments type: concepts, howto --- diff --git a/doc/api/metrics_user_starred_dashboards.md b/doc/api/metrics_user_starred_dashboards.md index f615ddaaa71..3e54ec74b24 100644 --- a/doc/api/metrics_user_starred_dashboards.md +++ b/doc/api/metrics_user_starred_dashboards.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments type: concepts, howto --- diff --git a/doc/architecture/blueprints/runner_scaling/index.md b/doc/architecture/blueprints/runner_scaling/index.md index 8e47b5fda8c..7fa39a3cfdb 100644 --- a/doc/architecture/blueprints/runner_scaling/index.md +++ b/doc/architecture/blueprints/runner_scaling/index.md @@ -204,6 +204,35 @@ document, define requirements and score the solution accordingly. This will allow us to choose a solution that will work best for us and the wider community. +### Plugin system design principles + +Our goal is to design a GitLab Runner plugin system interface that is flexible +and simple for the wider community to consume. As we cannot build plugins for +all cloud platforms, we want to ensure a low entry barrier for anyone who needs +to develop a plugin. We want to allow everyone to contribute. + +To achieve this goal, we will follow a few critical design principles. These +principles will guide our development process for the new plugin system +abstraction. + +General high-level principles: + +1. Make the entry barrier for writing a new plugin low. +1. Developing a new plugin should be simple and require only basic knowledge of + a programming language and a cloud provider's API. +1. Strive for a balance between the plugin system's simplicity and flexibility. + These are not mutually exclusive. +1. Abstract away as many technical details as possible but do not hide them completely. +1. Build an abstraction that serves our community well but allows us to ship it quickly. +1. Invest in a flexible solution, avoid one-way-door decisions, foster iteration. +1. When in doubts err on the side of making things more simple for the wider community. + +A few most important technical details: + +1. Favor gRPC communication between a plugin and GitLab Runner. +1. Make it possible to version communication interface and support many versions. +1. Make Go a primary language for writing plugins but accept other languages too. + ## Status Status: RFC. diff --git a/doc/development/distributed_tracing.md b/doc/development/distributed_tracing.md index 1e85abf585c..680ac71f857 100644 --- a/doc/development/distributed_tracing.md +++ b/doc/development/distributed_tracing.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/development/documentation/styleguide/index.md b/doc/development/documentation/styleguide/index.md index 419f7c7fa98..3e9c0177d48 100644 --- a/doc/development/documentation/styleguide/index.md +++ b/doc/development/documentation/styleguide/index.md @@ -1638,11 +1638,12 @@ If the content in a topic is not ready, use the disclaimer in the topic. ### Removing versions after each major release -Whenever a major GitLab release occurs, we remove all version references -to now-unsupported versions of GitLab. Note that this includes the removal of -specific instructions for users of non-supported GitLab versions. For example, -if GitLab versions 11.x and later are supported, special -instructions for users of GitLab 10 should be removed. +When a major GitLab release occurs, we remove all references +to now-unsupported versions. This removal includes version-specific instructions. For example, +if GitLab version 12.1 and later are supported, +instructions for users of GitLab 11 should be removed. + +[View the list of supported versions](https://about.gitlab.com/support/statement-of-support.html#version-support). To view historical information about a feature, review GitLab [release posts](https://about.gitlab.com/releases/), or search for the issue or diff --git a/doc/development/logging.md b/doc/development/logging.md index a4eda6ad02e..d90f0913e39 100644 --- a/doc/development/logging.md +++ b/doc/development/logging.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md index da6ba14cdd8..b3f259efc3d 100644 --- a/doc/development/prometheus_metrics.md +++ b/doc/development/prometheus_metrics.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/error_tracking.md b/doc/operations/error_tracking.md index 7533646aa34..6f97f002a32 100644 --- a/doc/operations/error_tracking.md +++ b/doc/operations/error_tracking.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/alerts.md b/doc/operations/incident_management/alerts.md index cdf7ca5c8bc..02eea62d46d 100644 --- a/doc/operations/incident_management/alerts.md +++ b/doc/operations/incident_management/alerts.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/escalation_policies.md b/doc/operations/incident_management/escalation_policies.md index 5f132720000..ed5a5a8ee52 100644 --- a/doc/operations/incident_management/escalation_policies.md +++ b/doc/operations/incident_management/escalation_policies.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/incidents.md b/doc/operations/incident_management/incidents.md index ada1f426dd8..b7cf181c070 100644 --- a/doc/operations/incident_management/incidents.md +++ b/doc/operations/incident_management/incidents.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/index.md b/doc/operations/incident_management/index.md index ff5f41e59e9..3b38d4ab427 100644 --- a/doc/operations/incident_management/index.md +++ b/doc/operations/incident_management/index.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/integrations.md b/doc/operations/incident_management/integrations.md index a8b455e05a0..a24a755c049 100644 --- a/doc/operations/incident_management/integrations.md +++ b/doc/operations/incident_management/integrations.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/oncall_schedules.md b/doc/operations/incident_management/oncall_schedules.md index 2a8f0eac59c..458e144b744 100644 --- a/doc/operations/incident_management/oncall_schedules.md +++ b/doc/operations/incident_management/oncall_schedules.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/paging.md b/doc/operations/incident_management/paging.md index 6fdf880783a..b6f77de3b4f 100644 --- a/doc/operations/incident_management/paging.md +++ b/doc/operations/incident_management/paging.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/incident_management/status_page.md b/doc/operations/incident_management/status_page.md index 241112df521..da96b31c2b4 100644 --- a/doc/operations/incident_management/status_page.md +++ b/doc/operations/incident_management/status_page.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/index.md b/doc/operations/index.md index 5a83e47b556..9b988ff561d 100644 --- a/doc/operations/index.md +++ b/doc/operations/index.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/alerts.md b/doc/operations/metrics/alerts.md index 712ee04e916..1cf2c4ee2c7 100644 --- a/doc/operations/metrics/alerts.md +++ b/doc/operations/metrics/alerts.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/dashboards/default.md b/doc/operations/metrics/dashboards/default.md index 295c146f0d5..3e14917209a 100644 --- a/doc/operations/metrics/dashboards/default.md +++ b/doc/operations/metrics/dashboards/default.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/dashboards/develop.md b/doc/operations/metrics/dashboards/develop.md index 38f375c40a6..fc7686c8f86 100644 --- a/doc/operations/metrics/dashboards/develop.md +++ b/doc/operations/metrics/dashboards/develop.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/dashboards/index.md b/doc/operations/metrics/dashboards/index.md index 9a75703a2f1..a8ca23b7002 100644 --- a/doc/operations/metrics/dashboards/index.md +++ b/doc/operations/metrics/dashboards/index.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/dashboards/panel_types.md b/doc/operations/metrics/dashboards/panel_types.md index 9b015760fe9..09e969e8af6 100644 --- a/doc/operations/metrics/dashboards/panel_types.md +++ b/doc/operations/metrics/dashboards/panel_types.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/dashboards/settings.md b/doc/operations/metrics/dashboards/settings.md index f4c37718c52..14da5cf4a04 100644 --- a/doc/operations/metrics/dashboards/settings.md +++ b/doc/operations/metrics/dashboards/settings.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/dashboards/templating_variables.md b/doc/operations/metrics/dashboards/templating_variables.md index 8ccd334dac3..531693d032f 100644 --- a/doc/operations/metrics/dashboards/templating_variables.md +++ b/doc/operations/metrics/dashboards/templating_variables.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/dashboards/variables.md b/doc/operations/metrics/dashboards/variables.md index 0008706df40..369bcd1ddeb 100644 --- a/doc/operations/metrics/dashboards/variables.md +++ b/doc/operations/metrics/dashboards/variables.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/dashboards/yaml.md b/doc/operations/metrics/dashboards/yaml.md index 9d1c270388e..81f1354d3c0 100644 --- a/doc/operations/metrics/dashboards/yaml.md +++ b/doc/operations/metrics/dashboards/yaml.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/dashboards/yaml_number_format.md b/doc/operations/metrics/dashboards/yaml_number_format.md index ce9e359a587..fd83bff3c08 100644 --- a/doc/operations/metrics/dashboards/yaml_number_format.md +++ b/doc/operations/metrics/dashboards/yaml_number_format.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/embed.md b/doc/operations/metrics/embed.md index e84c190e08d..17ba8cafa10 100644 --- a/doc/operations/metrics/embed.md +++ b/doc/operations/metrics/embed.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/embed_grafana.md b/doc/operations/metrics/embed_grafana.md index 81b1f8a3bc6..5bfb097619d 100644 --- a/doc/operations/metrics/embed_grafana.md +++ b/doc/operations/metrics/embed_grafana.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/metrics/index.md b/doc/operations/metrics/index.md index f09b9f35d88..b04e19807f8 100644 --- a/doc/operations/metrics/index.md +++ b/doc/operations/metrics/index.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/operations/tracing.md b/doc/operations/tracing.md index 09a31c12bf4..044f6800e73 100644 --- a/doc/operations/tracing.md +++ b/doc/operations/tracing.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/raketasks/generate_sample_prometheus_data.md b/doc/raketasks/generate_sample_prometheus_data.md index f014b82cca1..cdc95c1f3cc 100644 --- a/doc/raketasks/generate_sample_prometheus_data.md +++ b/doc/raketasks/generate_sample_prometheus_data.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/subscriptions/gitlab_com/index.md b/doc/subscriptions/gitlab_com/index.md index b278e1c5e71..8441a8f8480 100644 --- a/doc/subscriptions/gitlab_com/index.md +++ b/doc/subscriptions/gitlab_com/index.md @@ -14,7 +14,7 @@ You don't need to install anything to use GitLab SaaS, you only need to - [A subscription](https://about.gitlab.com/pricing/). - [The number of seats you want](#how-seat-usage-is-determined). -The subscription determines which features are available for your private projects. Public projects automatically get **Ultimate** tier features. +The subscription determines which features are available for your private projects. Organizations with public open source projects can actively apply to our [GitLab for Open Source Program](https://about.gitlab.com/solutions/open-source/join/). Qualifying open source projects also get 50,000 CI/CD minutes and free access to the **Ultimate** tier through the [GitLab for Open Source program](https://about.gitlab.com/solutions/open-source/). diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md index 75905d60c4e..213bddec325 100644 --- a/doc/user/admin_area/monitoring/health_check.md +++ b/doc/user/admin_area/monitoring/health_check.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/clusters/agent/index.md b/doc/user/clusters/agent/index.md index b8466ddf40f..06084186590 100644 --- a/doc/user/clusters/agent/index.md +++ b/doc/user/clusters/agent/index.md @@ -391,4 +391,5 @@ Alternatively, you can mount the certificate file at a different location and in ``` This error is shown if the manifest project is not public. To fix it, -[make sure your manifest project is public](repository.md#synchronize-manifest-projects). +[make sure your manifest project is public](repository.md#synchronize-manifest-projects) or your manifest files +are stored in the Agent's configuration repository. diff --git a/doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md b/doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md index 3bd675b7439..f9d0948a2bb 100644 --- a/doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md +++ b/doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/infrastructure/clusters/manage/management_project_applications/prometheus.md b/doc/user/infrastructure/clusters/manage/management_project_applications/prometheus.md index fd2eed25997..f76c7363a83 100644 --- a/doc/user/infrastructure/clusters/manage/management_project_applications/prometheus.md +++ b/doc/user/infrastructure/clusters/manage/management_project_applications/prometheus.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/infrastructure/clusters/manage/management_project_applications/sentry.md b/doc/user/infrastructure/clusters/manage/management_project_applications/sentry.md index 9e5d7860a67..b968e63d632 100644 --- a/doc/user/infrastructure/clusters/manage/management_project_applications/sentry.md +++ b/doc/user/infrastructure/clusters/manage/management_project_applications/sentry.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md index b3baac02d74..b5e2a1bad51 100644 --- a/doc/user/project/clusters/kubernetes_pod_logs.md +++ b/doc/user/project/clusters/kubernetes_pod_logs.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md index de7ac6782d6..760b5030416 100644 --- a/doc/user/project/integrations/prometheus.md +++ b/doc/user/project/integrations/prometheus.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/integrations/prometheus_library/cloudwatch.md b/doc/user/project/integrations/prometheus_library/cloudwatch.md index a07abf26fba..e8d611af30d 100644 --- a/doc/user/project/integrations/prometheus_library/cloudwatch.md +++ b/doc/user/project/integrations/prometheus_library/cloudwatch.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/integrations/prometheus_library/haproxy.md b/doc/user/project/integrations/prometheus_library/haproxy.md index 97f69d65412..76d13d5487c 100644 --- a/doc/user/project/integrations/prometheus_library/haproxy.md +++ b/doc/user/project/integrations/prometheus_library/haproxy.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/integrations/prometheus_library/index.md b/doc/user/project/integrations/prometheus_library/index.md index a5fc398e558..9bdd4945f5d 100644 --- a/doc/user/project/integrations/prometheus_library/index.md +++ b/doc/user/project/integrations/prometheus_library/index.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/integrations/prometheus_library/kubernetes.md b/doc/user/project/integrations/prometheus_library/kubernetes.md index 26d006adeb9..33a06958e0c 100644 --- a/doc/user/project/integrations/prometheus_library/kubernetes.md +++ b/doc/user/project/integrations/prometheus_library/kubernetes.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/integrations/prometheus_library/nginx.md b/doc/user/project/integrations/prometheus_library/nginx.md index ad89543e9a6..ecf75d7b17a 100644 --- a/doc/user/project/integrations/prometheus_library/nginx.md +++ b/doc/user/project/integrations/prometheus_library/nginx.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress.md b/doc/user/project/integrations/prometheus_library/nginx_ingress.md index 03bf9258659..e123000e0c5 100644 --- a/doc/user/project/integrations/prometheus_library/nginx_ingress.md +++ b/doc/user/project/integrations/prometheus_library/nginx_ingress.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md index 89c174f8fb9..fda7744e847 100644 --- a/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md +++ b/doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md @@ -1,6 +1,6 @@ --- stage: Monitor -group: Monitor +group: Respond info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- diff --git a/lib/generators/gitlab/snowplow_event_definition_generator.rb b/lib/generators/gitlab/snowplow_event_definition_generator.rb index 497d0cd512a..827e87dc313 100644 --- a/lib/generators/gitlab/snowplow_event_definition_generator.rb +++ b/lib/generators/gitlab/snowplow_event_definition_generator.rb @@ -65,7 +65,12 @@ module Gitlab end def file_name - "#{event_category}_#{event_action}.yml".underscore.gsub("/", "__") + name = remove_special_chars("#{Time.current.to_i}_#{event_category}_#{event_action}") + "#{name[0..95]}.yml" # max 100 chars, see https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/2030#note_679501200 + end + + def remove_special_chars(input) + input.gsub("::", "__").gsub(/[^A-Za-z0-9_]/, '') end end end diff --git a/lib/gitlab/database/gitlab_loose_foreign_keys.yml b/lib/gitlab/database/gitlab_loose_foreign_keys.yml index 531b9f58a48..4a796b6c265 100644 --- a/lib/gitlab/database/gitlab_loose_foreign_keys.yml +++ b/lib/gitlab/database/gitlab_loose_foreign_keys.yml @@ -87,6 +87,10 @@ ci_build_report_results: - table: projects column: project_id on_delete: async_delete +ci_job_artifacts: + - table: projects + column: project_id + on_delete: async_delete ci_builds: - table: users column: user_id diff --git a/lib/gitlab/metrics/exporter/base_exporter.rb b/lib/gitlab/metrics/exporter/base_exporter.rb index 190d3d3fd2f..4874ef62e41 100644 --- a/lib/gitlab/metrics/exporter/base_exporter.rb +++ b/lib/gitlab/metrics/exporter/base_exporter.rb @@ -9,8 +9,6 @@ module Gitlab class BaseExporter < Daemon attr_reader :server - attr_accessor :readiness_checks - def initialize(settings, log_enabled:, log_file:, gc_requests: false, **options) super(**options) @@ -85,7 +83,7 @@ module Gitlab end def readiness_probe - ::Gitlab::HealthChecks::Probes::Collection.new(*readiness_checks) + ::Gitlab::HealthChecks::Probes::Collection.new end def liveness_probe diff --git a/lib/gitlab/metrics/exporter/web_exporter.rb b/lib/gitlab/metrics/exporter/web_exporter.rb index c05ad8ccf42..761fcdceb5b 100644 --- a/lib/gitlab/metrics/exporter/web_exporter.rb +++ b/lib/gitlab/metrics/exporter/web_exporter.rb @@ -4,17 +4,6 @@ module Gitlab module Metrics module Exporter class WebExporter < BaseExporter - ExporterCheck = Struct.new(:exporter) do - def readiness - Gitlab::HealthChecks::Result.new( - 'web_exporter', exporter.running) - end - - def available? - true - end - end - RailsMetricsInitializer = Struct.new(:app) do def call(env) Gitlab::Metrics::RailsSlis.initialize_request_slis_if_needed! @@ -23,24 +12,9 @@ module Gitlab end end - attr_reader :running - # This exporter is always run on master process def initialize(**options) super(Settings.monitoring.web_exporter, log_enabled: true, log_file: 'web_exporter.log', **options) - - # DEPRECATED: - # these `readiness_checks` are deprecated - # as presenting no value in a way how we run - # application: https://gitlab.com/gitlab-org/gitlab/issues/35343 - self.readiness_checks = [ - WebExporter::ExporterCheck.new(self), - Gitlab::HealthChecks::PumaCheck - ] - end - - def mark_as_not_running! - @running = false end private @@ -53,16 +27,6 @@ module Gitlab run app end end - - def start_working - @running = true - super - end - - def stop_working - mark_as_not_running! - super - end end end end diff --git a/lib/gitlab/pipeline_scope_counts.rb b/lib/gitlab/pipeline_scope_counts.rb new file mode 100644 index 00000000000..02f4ea33ddf --- /dev/null +++ b/lib/gitlab/pipeline_scope_counts.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +module Gitlab + class PipelineScopeCounts + attr_reader :project + + PIPELINES_COUNT_LIMIT = 1000 + + def self.declarative_policy_class + 'Ci::ProjectPipelinesPolicy' + end + + def initialize(current_user, project, params) + @current_user = current_user + @project = project + @params = params + end + + def all + finder.execute.limit(PIPELINES_COUNT_LIMIT).count + end + + def running + finder({ scope: "running" }).execute.limit(PIPELINES_COUNT_LIMIT).count + end + + def finished + finder({ scope: "finished" }).execute.limit(PIPELINES_COUNT_LIMIT).count + end + + def pending + finder({ scope: "pending" }).execute.limit(PIPELINES_COUNT_LIMIT).count + end + + private + + def finder(params = {}) + ::Ci::PipelinesFinder.new(@project, @current_user, @params.merge(params)) + end + end +end diff --git a/lib/gitlab/process_memory_cache/helper.rb b/lib/gitlab/process_memory_cache/helper.rb new file mode 100644 index 00000000000..8d436c14b48 --- /dev/null +++ b/lib/gitlab/process_memory_cache/helper.rb @@ -0,0 +1,51 @@ +# frozen_string_literal: true + +module Gitlab + class ProcessMemoryCache + module Helper + def fetch_memory_cache(key, &payload) + cache = cache_backend.read(key) + + if cache && !stale_cache?(key, cache) + cache[:data] + else + store_cache(key, &payload) + end + end + + def invalidate_memory_cache(key) + touch_cache_timestamp(key) + end + + private + + def touch_cache_timestamp(key, time = Time.current.to_f) + shared_backend.write(key, time) + end + + def stale_cache?(key, cache_info) + shared_timestamp = shared_backend.read(key) + return true unless shared_timestamp + + shared_timestamp.to_f > cache_info[:cached_at].to_f + end + + def store_cache(key) + data = yield + time = Time.current.to_f + + cache_backend.write(key, data: data, cached_at: time) + touch_cache_timestamp(key, time) unless shared_backend.read(key) + data + end + + def shared_backend + Rails.cache + end + + def cache_backend + ::Gitlab::ProcessMemoryCache.cache_backend + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 73eb07d34fc..3b4c43d1cc6 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3818,9 +3818,6 @@ msgstr "" msgid "An error occurred while fetching this tab." msgstr "" -msgid "An error occurred while getting autocomplete data. Please refresh the page and try again." -msgstr "" - msgid "An error occurred while getting files for - %{branchId}" msgstr "" diff --git a/package.json b/package.json index f8943cf35f6..a298c3507a9 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "2.2.0", - "@gitlab/tributejs": "1.0.0", "@gitlab/ui": "33.1.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "6.1.4-1", diff --git a/qa/qa/fixtures/package_managers/composer/composer.json.erb b/qa/qa/fixtures/package_managers/composer/composer.json.erb new file mode 100644 index 00000000000..a1e31e2599f --- /dev/null +++ b/qa/qa/fixtures/package_managers/composer/composer.json.erb @@ -0,0 +1,13 @@ +{ + "name": "<%= project.path_with_namespace %>/<%= package.name %>", + "description": "Library XY", + "type": "library", + "license": "GPL-3.0-only", + "authors": [ + { + "name": "John Doe", + "email": "john@example.com" + } + ], + "require": {} +}
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/composer/composer_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/composer/composer_upload_package.yaml.erb new file mode 100644 index 00000000000..b6bcfafffee --- /dev/null +++ b/qa/qa/fixtures/package_managers/composer/composer_upload_package.yaml.erb @@ -0,0 +1,13 @@ +publish: + image: curlimages/curl:latest + stage: build + variables: + URL: "$CI_SERVER_PROTOCOL://$CI_SERVER_HOST:$CI_SERVER_PORT/api/v4/projects/$CI_PROJECT_ID/packages/composer?job_token=$CI_JOB_TOKEN" + script: + - version=$([[ -z "$CI_COMMIT_TAG" ]] && echo "branch=$CI_COMMIT_REF_NAME" || echo "tag=$CI_COMMIT_TAG") + - insecure=$([ "$CI_SERVER_PROTOCOL" = "http" ] && echo "--insecure" || echo "") + - response=$(curl -s -w "%{http_code}" $insecure --data $version $URL) + - code=$(echo "$response" | tail -n 1) + - body=$(echo "$response" | head -n 1) + tags: + - "runner-for-<%= project.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb b/qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb new file mode 100644 index 00000000000..39c04f6511b --- /dev/null +++ b/qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb @@ -0,0 +1,12 @@ +image: conanio/gcc7 + +test_package: + stage: deploy + script: + - conan remote add gitlab <%= gitlab_address_with_port %>/api/v4/projects/<%= project.id %>/packages/conan + - conan new <%= package.name %>/0.1 -t + - conan create . mycompany/stable + - "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload <%= package.name %>/0.1@mycompany/stable --all --remote=gitlab" + - conan install <%= package.name %>/0.1@mycompany/stable --remote=gitlab + tags: + - runner-for-<%= project.name %>
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/generic/generic_upload_install_package.yaml.erb b/qa/qa/fixtures/package_managers/generic/generic_upload_install_package.yaml.erb new file mode 100644 index 00000000000..13fe3e2c62e --- /dev/null +++ b/qa/qa/fixtures/package_managers/generic/generic_upload_install_package.yaml.erb @@ -0,0 +1,18 @@ +image: curlimages/curl:latest + +stages: + - upload + - download + +upload: + stage: upload + script: + - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file file.txt ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/<%= package.name %>/0.0.1/file.txt' + tags: + - runner-for-<%= project.name %> +download: + stage: download + script: + - 'wget --header="JOB-TOKEN: $CI_JOB_TOKEN" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/<%= package.name %>/0.0.1/file.txt -O file_downloaded.txt' + tags: + - runner-for-<%= project.name %>
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/helm/Chart.yaml.erb b/qa/qa/fixtures/package_managers/helm/Chart.yaml.erb new file mode 100644 index 00000000000..5a56533c65d --- /dev/null +++ b/qa/qa/fixtures/package_managers/helm/Chart.yaml.erb @@ -0,0 +1,6 @@ +apiVersion: v2 +name: <%= package_name %> +description: GitLab QA helm package +type: application +version: <%= package_version %> +appVersion: "1.16.0"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb b/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb new file mode 100644 index 00000000000..786b0592153 --- /dev/null +++ b/qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb @@ -0,0 +1,11 @@ +pull: + image: alpine:3 + script: + - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing + - helm repo add --username <%= username %> --password <%= access_token %> gitlab_qa ${CI_API_V4_URL}/projects/<%= package_project.id %>/packages/helm/stable + - helm repo update + - helm pull gitlab_qa/<%= package_name %> + only: + - <%= client_project.default_branch %> + tags: + - runner-for-<%=client_project.group.name %>
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb new file mode 100644 index 00000000000..b3e907b50f4 --- /dev/null +++ b/qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb @@ -0,0 +1,14 @@ +deploy: + image: alpine:3 + script: + - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing + - apk add curl + - helm create <%= package_name %> + - cp ./Chart.yaml <%= package_name %> + - helm package <%= package_name %> + - http_code=$(curl --write-out "%{http_code}" --request POST --form 'chart=@<%= package_name %>-<%= package_version %>.tgz' --user <%= username %>:<%= access_token %> ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts --output /dev/null --silent) + - '[ $http_code = "201" ]' + only: + - <%= package_project.default_branch %> + tags: + - runner-for-<%= package_project.group.name %>
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb b/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb new file mode 100644 index 00000000000..303a64ad233 --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/build_install.gradle.erb @@ -0,0 +1,28 @@ +plugins { + id 'java' + id 'application' +} + +repositories { + jcenter() + maven { + url "<%= gitlab_address_with_port %>/api/v4/projects/<%= package_project.id %>/packages/maven" + name "GitLab" + credentials(HttpHeaderCredentials) { + name = '<%= maven_header_name %>' + value = <%= token %> + } + authentication { + header(HttpHeaderAuthentication) + } + } +} + +dependencies { + implementation group: '<%= group_id %>', name: '<%= artifact_id %>', version: '<%= package_version %>' + testImplementation 'junit:junit:4.12' +} + +application { + mainClassName = 'gradle_maven_app.App' +}
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/build_upload.gradle.erb b/qa/qa/fixtures/package_managers/maven/build_upload.gradle.erb new file mode 100644 index 00000000000..c14e63e11df --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/build_upload.gradle.erb @@ -0,0 +1,27 @@ +plugins { + id 'java' + id 'maven-publish' +} + +publishing { + publications { + library(MavenPublication) { + groupId '<%= group_id %>' + artifactId '<%= artifact_id %>' + version '<%= package_version %>' + from components.java + } + } + repositories { + maven { + url "<%= gitlab_address_with_port %>/api/v4/projects/<%= package_project.id %>/packages/maven" + credentials(HttpHeaderCredentials) { + name = "Private-Token" + value = "<%= personal_access_token %>" + } + authentication { + header(HttpHeaderAuthentication) + } + } + } +}
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/client_pom.xml.erb b/qa/qa/fixtures/package_managers/maven/client_pom.xml.erb new file mode 100644 index 00000000000..20bb5f3964e --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/client_pom.xml.erb @@ -0,0 +1,19 @@ +<project> + <groupId><%= group_id %></groupId> + <artifactId>maven_client</artifactId> + <version>1.0</version> + <modelVersion>4.0.0</modelVersion> + <repositories> + <repository> + <id><%= package_project.name %></id> + <url><%= gitlab_address_with_port %>/api/v4/groups/<%= package_project.group.id %>/-/packages/maven</url> + </repository> + </repositories> + <dependencies> + <dependency> + <groupId><%= group_id %></groupId> + <artifactId><%= artifact_id %></artifactId> + <version><%= package_version %></version> + </dependency> + </dependencies> +</project>
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/gradle_install_package.yaml.erb b/qa/qa/fixtures/package_managers/maven/gradle_install_package.yaml.erb new file mode 100644 index 00000000000..49873f124cc --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/gradle_install_package.yaml.erb @@ -0,0 +1,8 @@ + build: + image: gradle:6.5-jdk11 + script: + - 'gradle build' + only: + - "<%= client_project.default_branch %>" + tags: + - "runner-for-<%= client_project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/gradle_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/maven/gradle_upload_package.yaml.erb new file mode 100644 index 00000000000..3f3c7dce03c --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/gradle_upload_package.yaml.erb @@ -0,0 +1,8 @@ +deploy: + image: gradle:6.5-jdk11 + script: + - 'gradle publish' + only: + - "<%= package_project.default_branch %>" + tags: + - "runner-for-<%= package_project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/maven_install_package.yaml.erb b/qa/qa/fixtures/package_managers/maven/maven_install_package.yaml.erb new file mode 100644 index 00000000000..78d6255e9a9 --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/maven_install_package.yaml.erb @@ -0,0 +1,8 @@ +install: + image: maven:3.6-jdk-11 + script: + - "mvn install -s settings.xml" + only: + - "<%= client_project.default_branch %>" + tags: + - "runner-for-<%= client_project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/maven_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/maven/maven_upload_package.yaml.erb new file mode 100644 index 00000000000..64a63bf0bd8 --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/maven_upload_package.yaml.erb @@ -0,0 +1,8 @@ + deploy: + image: maven:3.6-jdk-11 + script: + - 'mvn deploy -s settings.xml' + only: + - "<%= package_project.default_branch %>" + tags: + - "runner-for-<%= package_project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/package_pom.xml.erb b/qa/qa/fixtures/package_managers/maven/package_pom.xml.erb new file mode 100644 index 00000000000..5159172a170 --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/package_pom.xml.erb @@ -0,0 +1,22 @@ + <project> + <groupId><%= group_id %></groupId> + <artifactId><%= artifact_id %></artifactId> + <version><%= package_version %></version> + <modelVersion>4.0.0</modelVersion> + <repositories> + <repository> + <id><%= package_project.name %></id> + <url><%= gitlab_address_with_port %>/api/v4/groups/<%= package_project.group.id %>/-/packages/maven</url> + </repository> + </repositories> + <distributionManagement> + <repository> + <id><%= package_project.name %></id> + <url><%= gitlab_address_with_port %>/api/v4/projects/<%= package_project.id %>/packages/maven</url> + </repository> + <snapshotRepository> + <id><%= package_project.name %></id> + <url><%= gitlab_address_with_port %>/api/v4/projects/<%= package_project.id %>/packages/maven</url> + </snapshotRepository> + </distributionManagement> +</project>
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/settings.xml.erb b/qa/qa/fixtures/package_managers/maven/settings.xml.erb new file mode 100644 index 00000000000..b670b83cf85 --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/settings.xml.erb @@ -0,0 +1,16 @@ +<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" +xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"> +<servers> + <server> + <id><%= package_project.name %></id> + <configuration> + <httpHeaders> + <property> + <name><%= maven_header_name %></name> + <value><%= token %></value> + </property> + </httpHeaders> + </configuration> + </server> +</servers> +</settings>
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/maven/settings_with_pat.xml.erb b/qa/qa/fixtures/package_managers/maven/settings_with_pat.xml.erb new file mode 100644 index 00000000000..611c232819f --- /dev/null +++ b/qa/qa/fixtures/package_managers/maven/settings_with_pat.xml.erb @@ -0,0 +1,16 @@ +<settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"> + <servers> + <server> + <id><%= package_project.name %></id> + <configuration> + <httpHeaders> + <property> + <name>Private-Token</name> + <value><%= personal_access_token %></value> + </property> + </httpHeaders> + </configuration> + </server> + </servers> +</settings>
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/npm/npm_install_package_instance.yaml.erb b/qa/qa/fixtures/package_managers/npm/npm_install_package_instance.yaml.erb new file mode 100644 index 00000000000..a396fc98e95 --- /dev/null +++ b/qa/qa/fixtures/package_managers/npm/npm_install_package_instance.yaml.erb @@ -0,0 +1,21 @@ +image: node:latest + +stages: + - install + +install: + stage: install + script: + - "npm config set @<%= registry_scope %>:registry <%= gitlab_address_with_port %>/api/v4/packages/npm/" + - "npm install <%= package.name %>" + cache: + key: ${CI_BUILD_REF_NAME} + paths: + - node_modules/ + artifacts: + paths: + - node_modules/ + only: + - "<%= another_project.default_branch %>" + tags: + - "runner-for-<%= another_project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb b/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb new file mode 100644 index 00000000000..8d94d03ef9b --- /dev/null +++ b/qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb @@ -0,0 +1,31 @@ +image: node:latest + +stages: + - deploy + - install + +deploy: + stage: deploy + script: + - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=<%= auth_token %>">.npmrc + - npm publish + only: + - "<%= project.default_branch %>" + tags: + - "runner-for-<%= project.name %>" +install: + stage: install + script: + - "npm config set @<%= registry_scope %>:registry <%= gitlab_address_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" + - "npm install <%= package.name %>" + cache: + key: ${CI_BUILD_REF_NAME} + paths: + - node_modules/ + artifacts: + paths: + - node_modules/ + only: + - "<%= project.default_branch %>" + tags: + - "runner-for-<%= project.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/npm/npm_upload_package_instance.yaml.erb b/qa/qa/fixtures/package_managers/npm/npm_upload_package_instance.yaml.erb new file mode 100644 index 00000000000..13c00cd17c4 --- /dev/null +++ b/qa/qa/fixtures/package_managers/npm/npm_upload_package_instance.yaml.erb @@ -0,0 +1,14 @@ +image: node:latest + +stages: + - deploy + +deploy: + stage: deploy + script: + - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=<%= auth_token %>">.npmrc + - npm publish + only: + - "<%= project.default_branch %>" + tags: + - "runner-for-<%= project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/npm/package_instance.json.erb b/qa/qa/fixtures/package_managers/npm/package_instance.json.erb new file mode 100644 index 00000000000..46fecf97e2c --- /dev/null +++ b/qa/qa/fixtures/package_managers/npm/package_instance.json.erb @@ -0,0 +1,8 @@ +{ + "name": "<%= package.name %>", + "version": "1.0.0", + "description": "Example package for GitLab npm registry", + "publishConfig": { + "@<%= registry_scope %>:registry": "<%= gitlab_address_with_port %>/api/v4/projects/<%= project.id %>/packages/npm/" + } +}
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/npm/package_project.json.erb b/qa/qa/fixtures/package_managers/npm/package_project.json.erb new file mode 100644 index 00000000000..46fecf97e2c --- /dev/null +++ b/qa/qa/fixtures/package_managers/npm/package_project.json.erb @@ -0,0 +1,8 @@ +{ + "name": "<%= package.name %>", + "version": "1.0.0", + "description": "Example package for GitLab npm registry", + "publishConfig": { + "@<%= registry_scope %>:registry": "<%= gitlab_address_with_port %>/api/v4/projects/<%= project.id %>/packages/npm/" + } +}
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/nuget/nuget_install_package.yaml.erb b/qa/qa/fixtures/package_managers/nuget/nuget_install_package.yaml.erb new file mode 100644 index 00000000000..39b65a55884 --- /dev/null +++ b/qa/qa/fixtures/package_managers/nuget/nuget_install_package.yaml.erb @@ -0,0 +1,15 @@ +image: mcr.microsoft.com/dotnet/sdk:5.0 + +stages: + - install + +install: + stage: install + script: + - dotnet nuget locals all --clear + - dotnet nuget add source "$CI_SERVER_URL/api/v4/groups/<%= another_project.group.id %>/-/packages/nuget/index.json" --name gitlab --username <%= auth_token_username %> --password <%= auth_token_password %> --store-password-in-clear-text + - "dotnet add otherdotnet.csproj package <%= package.name %> --version 1.0.0" + only: + - "<%= another_project.default_branch %>" + tags: + - "runner-for-<%= project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/nuget/nuget_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/nuget/nuget_upload_package.yaml.erb new file mode 100644 index 00000000000..7c88eb49be0 --- /dev/null +++ b/qa/qa/fixtures/package_managers/nuget/nuget_upload_package.yaml.erb @@ -0,0 +1,17 @@ +image: mcr.microsoft.com/dotnet/sdk:5.0 + +stages: + - deploy + +deploy: + stage: deploy + script: + - dotnet restore -p:Configuration=Release + - dotnet build -c Release + - dotnet pack -c Release -p:PackageID=<%= package.name %> + - dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username <%= auth_token_username %> --password <%= auth_token_password %> --store-password-in-clear-text + - dotnet nuget push "bin/Release/*.nupkg" --source gitlab + rules: + - if: '$CI_COMMIT_BRANCH == "<%= project.default_branch %>"' + tags: + - "runner-for-<%= project.group.name %>"
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/pypi/pypi_upload_install_package.yaml.erb b/qa/qa/fixtures/package_managers/pypi/pypi_upload_install_package.yaml.erb new file mode 100644 index 00000000000..3ea71152801 --- /dev/null +++ b/qa/qa/fixtures/package_managers/pypi/pypi_upload_install_package.yaml.erb @@ -0,0 +1,19 @@ +image: python:latest +stages: + - run + - install + +run: + stage: run + script: + - pip install twine + - python setup.py sdist bdist_wheel + - "TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine upload --repository-url <%= gitlab_address_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/*" + tags: + - runner-for-<%= project.name %> +install: + stage: install + script: + - "pip install <%= package.name %> --no-deps --index-url <%= uri.scheme %>://<%= personal_access_token %>:<%= personal_access_token %>@<%= gitlab_host_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple --trusted-host <%= gitlab_host_with_port %>" + tags: + - runner-for-<%= project.name %>
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/pypi/setup.py.erb b/qa/qa/fixtures/package_managers/pypi/setup.py.erb new file mode 100644 index 00000000000..d365f93cb5e --- /dev/null +++ b/qa/qa/fixtures/package_managers/pypi/setup.py.erb @@ -0,0 +1,16 @@ +import setuptools + +setuptools.setup( + name="<%= package.name %>", + version="0.0.1", + author="Example Author", + author_email="author@example.com", + description="A small example package", + packages=setuptools.find_packages(), + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + ], + python_requires='>=3.6', +)
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/rubygems/package.gemspec.erb b/qa/qa/fixtures/package_managers/rubygems/package.gemspec.erb new file mode 100644 index 00000000000..915deb0335d --- /dev/null +++ b/qa/qa/fixtures/package_managers/rubygems/package.gemspec.erb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +Gem::Specification.new do |s| + s.name = '<%= package.name %>' + s.authors = ['Tanuki Steve', 'Hal 9000'] + s.author = 'Tanuki Steve' + s.version = '0.0.1' + s.date = '2011-09-29' + s.summary = 'this is a test package' + s.files = ['lib/hello_gem.rb'] + s.require_paths = ['lib'] + + s.description = 'A test package for GitLab.' + s.email = 'tanuki@not_real.com' + s.homepage = 'https://gitlab.com/ruby-co/my-package' + s.license = 'MIT' + + s.metadata = { + 'bug_tracker_uri' => 'https://gitlab.com/ruby-co/my-package/issues', + 'changelog_uri' => 'https://gitlab.com/ruby-co/my-package/CHANGELOG.md', + 'documentation_uri' => 'https://gitlab.com/ruby-co/my-package/docs', + 'mailing_list_uri' => 'https://gitlab.com/ruby-co/my-package/mailme', + 'source_code_uri' => 'https://gitlab.com/ruby-co/my-package' + } + + s.bindir = 'bin' + s.platform = Gem::Platform::RUBY + s.post_install_message = 'Installed, thank you!' + s.rdoc_options = ['--main'] + s.required_ruby_version = '>= 2.7.0' + s.required_rubygems_version = '>= 1.8.11' + s.requirements = 'A high powered server or calculator' + s.rubygems_version = '1.8.09' + + s.add_dependency 'dependency_1', '~> 1.2.3' + s.add_dependency 'dependency_2', '3.0.0' + s.add_dependency 'dependency_3', '>= 1.0.0' + s.add_dependency 'dependency_4' +end
\ No newline at end of file diff --git a/qa/qa/fixtures/package_managers/rubygems/rubygems_upload_package.yaml.erb b/qa/qa/fixtures/package_managers/rubygems/rubygems_upload_package.yaml.erb new file mode 100644 index 00000000000..29038130f1b --- /dev/null +++ b/qa/qa/fixtures/package_managers/rubygems/rubygems_upload_package.yaml.erb @@ -0,0 +1,15 @@ +image: ruby + +test_package: + stage: deploy + before_script: + - mkdir ~/.gem + - echo "---" > ~/.gem/credentials + - | + echo "<%= gitlab_address_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/rubygems: '${CI_JOB_TOKEN}'" >> ~/.gem/credentials + - chmod 0600 ~/.gem/credentials + script: + - gem build <%= package.name %> + - gem push <%= package.name %>-0.0.1.gem --host <%= gitlab_address_with_port %>/api/v4/projects/${CI_PROJECT_ID}/packages/rubygems + tags: + - runner-for-<%= project.name %>
\ No newline at end of file diff --git a/qa/qa/resource/base.rb b/qa/qa/resource/base.rb index 0112e766cf0..f8522872ddf 100644 --- a/qa/qa/resource/base.rb +++ b/qa/qa/resource/base.rb @@ -31,7 +31,7 @@ module QA parents = options.fetch(:parents) { [] } do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do - log_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) } + log_and_record_fabrication(:browser_ui, resource, parents, args) { resource.fabricate!(*args) } current_url end @@ -47,7 +47,7 @@ module QA resource.eager_load_api_client! do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do - log_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! } + log_and_record_fabrication(:api, resource, parents, args) { resource.fabricate_via_api! } end end @@ -59,7 +59,7 @@ module QA resource.eager_load_api_client! do_fabricate!(resource: resource, prepare_block: prepare_block, parents: parents) do - log_fabrication(:api, resource, parents, args) { resource.remove_via_api! } + log_and_record_fabrication(:api, resource, parents, args) { resource.remove_via_api! } end end @@ -71,36 +71,19 @@ module QA resource_web_url = yield resource.web_url = resource_web_url - QA::Tools::TestResourceDataProcessor.collect(resource, resource_identifier(resource)) - resource end - def resource_identifier(resource) - if resource.respond_to?(:username) && resource.username - "with username '#{resource.username}'" - elsif resource.respond_to?(:full_path) && resource.full_path - "with full_path '#{resource.full_path}'" - elsif resource.respond_to?(:name) && resource.name - "with name '#{resource.name}'" - elsif resource.respond_to?(:id) && resource.id - "with id '#{resource.id}'" - elsif resource.respond_to?(:iid) && resource.iid - "with iid '#{resource.iid}'" - end - rescue QA::Resource::Base::NoValueError - nil - end - - def log_fabrication(method, resource, parents, args) + def log_and_record_fabrication(fabrication_method, resource, parents, args) start = Time.now Support::FabricationTracker.start_fabrication result = yield.tap do fabrication_time = Time.now - start + identifier = resource_identifier(resource) fabrication_http_method = if resource.api_fabrication_http_method == :get - if self.include?(Reusable) + if include?(Reusable) "Retrieved for reuse" else "Retrieved" @@ -109,16 +92,23 @@ module QA "Built" end - Support::FabricationTracker.save_fabrication(:"#{method}_fabrication", fabrication_time) + Support::FabricationTracker.save_fabrication(:"#{fabrication_method}_fabrication", fabrication_time) + Tools::TestResourceDataProcessor.collect( + resource: resource, + info: identifier, + fabrication_method: fabrication_method, + fabrication_time: fabrication_time + ) + Runtime::Logger.debug do msg = ["==#{'=' * parents.size}>"] msg << "#{fabrication_http_method} a #{name}" - msg << resource_identifier(resource) if resource_identifier(resource) + msg << identifier msg << "as a dependency of #{parents.last}" if parents.any? - msg << "via #{method}" + msg << "via #{fabrication_method}" msg << "in #{fabrication_time} seconds" - msg.join(' ') + msg.compact.join(' ') end end Support::FabricationTracker.finish_fabrication @@ -126,6 +116,26 @@ module QA result end + # Unique resource identifier + # + # @param [QA::Resource::Base] resource + # @return [String] + def resource_identifier(resource) + if resource.respond_to?(:username) && resource.username + "with username '#{resource.username}'" + elsif resource.respond_to?(:full_path) && resource.full_path + "with full_path '#{resource.full_path}'" + elsif resource.respond_to?(:name) && resource.name + "with name '#{resource.name}'" + elsif resource.respond_to?(:id) && resource.id + "with id '#{resource.id}'" + elsif resource.respond_to?(:iid) && resource.iid + "with iid '#{resource.iid}'" + end + rescue QA::Resource::Base::NoValueError + nil + end + # Define custom attribute # # @param [Symbol] name diff --git a/qa/qa/resource/fork.rb b/qa/qa/resource/fork.rb index d0313670e8b..d60b90b534f 100644 --- a/qa/qa/resource/fork.rb +++ b/qa/qa/resource/fork.rb @@ -31,6 +31,8 @@ module QA end end + delegate :path_with_namespace, to: :project + def fabricate! populate(:upstream, :user) diff --git a/qa/qa/resource/group.rb b/qa/qa/resource/group.rb index a325d96ccc2..dcb1e580804 100644 --- a/qa/qa/resource/group.rb +++ b/qa/qa/resource/group.rb @@ -50,12 +50,6 @@ module QA resource_web_url(api_get) rescue ResourceNotFoundError super - - Support::Retrier.retry_on_exception(sleep_interval: 5) do - resource = resource_web_url(api_get) - populate(:runners_token) - resource - end end def api_get_path diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 555bfb1abc9..8e7527bccd4 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -51,14 +51,6 @@ module QA resource_web_url(api_get) rescue ResourceNotFoundError super - - # If the group was just created the runners token might not be - # available via the API immediately. - Support::Retrier.retry_on_exception(sleep_interval: 5) do - resource = resource_web_url(api_get) - populate(:runners_token) - resource - end end def api_get_path diff --git a/qa/qa/runtime/fixtures.rb b/qa/qa/runtime/fixtures.rb index 05dee4bfce5..41d7ce5d178 100644 --- a/qa/qa/runtime/fixtures.rb +++ b/qa/qa/runtime/fixtures.rb @@ -33,6 +33,14 @@ module QA FileUtils.remove_entry(dir, true) end + def read_fixture(fixture_path, file_name) + file_path = Pathname + .new(__dir__) + .join("../fixtures/#{fixture_path}/#{file_name}") + + File.read(file_path) + end + private def api_client diff --git a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb index 22bb5fed84c..0bc3fb7b829 100644 --- a/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb +++ b/qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb @@ -25,7 +25,7 @@ module QA Resource::Runner.fabricate! do |runner| runner.name = executor runner.tags = [executor] - runner.token = group.sandbox.runners_token + runner.token = group.reload!.runners_token end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb index 92e4d64fee4..2da0f6a0cf8 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb @@ -32,55 +32,22 @@ module QA "#{uri.scheme}://#{uri.host}:#{uri.port}" end - let(:composer_json_file) do - <<~EOF - { - "name": "#{project.path_with_namespace}/#{package.name}", - "description": "Library XY", - "type": "library", - "license": "GPL-3.0-only", - "authors": [ - { - "name": "John Doe", - "email": "john@example.com" - } - ], - "require": {} - } - EOF - end - - let(:gitlab_ci_yaml) do - <<~YAML - publish: - image: curlimages/curl:latest - stage: build - variables: - URL: "$CI_SERVER_PROTOCOL://$CI_SERVER_HOST:$CI_SERVER_PORT/api/v4/projects/$CI_PROJECT_ID/packages/composer?job_token=$CI_JOB_TOKEN" - script: - - version=$([[ -z "$CI_COMMIT_TAG" ]] && echo "branch=$CI_COMMIT_REF_NAME" || echo "tag=$CI_COMMIT_TAG") - - insecure=$([ "$CI_SERVER_PROTOCOL" = "http" ] && echo "--insecure" || echo "") - - response=$(curl -s -w "%{http_code}" $insecure --data $version $URL) - - code=$(echo "$response" | tail -n 1) - - body=$(echo "$response" | head -n 1) - tags: - - "runner-for-#{project.name}" - YAML - end - before do Flow::Login.sign_in Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + composer_yaml = ERB.new(read_fixture('package_managers/composer', 'composer_upload_package.yaml.erb')).result(binding) + composer_json = ERB.new(read_fixture('package_managers/composer', 'composer.json.erb')).result(binding) + commit.project = project - commit.commit_message = 'Add .gitlab-ci.yml' + commit.commit_message = 'Add files' commit.add_files([{ file_path: '.gitlab-ci.yml', - content: gitlab_ci_yaml + content: composer_yaml }, { file_path: 'composer.json', - content: composer_json_file + content: composer_json }] ) end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb index 15578cd5e6b..6f6b9f7caf7 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb @@ -46,25 +46,13 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + conan_yaml = ERB.new(read_fixture('package_managers/conan', 'conan_upload_install_package.yaml.erb')).result(binding) + commit.project = project commit.commit_message = 'Add .gitlab-ci.yml' commit.add_files([{ file_path: '.gitlab-ci.yml', - content: - <<~YAML - image: conanio/gcc7 - - test_package: - stage: deploy - script: - - "conan remote add gitlab #{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/conan" - - "conan new #{package.name}/0.1 -t" - - "conan create . mycompany/stable" - - "CONAN_LOGIN_USERNAME=ci_user CONAN_PASSWORD=${CI_JOB_TOKEN} conan upload #{package.name}/0.1@mycompany/stable --all --remote=gitlab" - - "conan install #{package.name}/0.1@mycompany/stable --remote=gitlab" - tags: - - "runner-for-#{project.name}" - YAML + content: conan_yaml }]) end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb index ded90607d67..080a7779cea 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb @@ -3,6 +3,8 @@ module QA RSpec.describe 'Package', :orchestrated, :packages, :object_storage do describe 'Generic Repository' do + include Runtime::Fixtures + let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'generic-package-project' @@ -25,29 +27,6 @@ module QA end end - let(:gitlab_ci_yaml) do - <<~YAML - image: curlimages/curl:latest - - stages: - - upload - - download - - upload: - stage: upload - script: - - 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file file.txt ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/#{package.name}/0.0.1/file.txt' - tags: - - "runner-for-#{project.name}" - download: - stage: download - script: - - 'wget --header="JOB-TOKEN: $CI_JOB_TOKEN" ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/#{package.name}/0.0.1/file.txt -O file_downloaded.txt' - tags: - - "runner-for-#{project.name}" - YAML - end - let(:file_txt) do <<~EOF Hello, world! @@ -59,11 +38,13 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + generic_packages_yaml = ERB.new(read_fixture('package_managers/generic', 'generic_upload_install_package.yaml.erb')).result(binding) + commit.project = project - commit.commit_message = 'Add .gitlab-ci.yml' + commit.commit_message = 'Add files' commit.add_files([{ file_path: '.gitlab-ci.yml', - content: gitlab_ci_yaml + content: generic_packages_yaml }, { file_path: 'file.txt', diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb index 92d0f547764..a74c8a81358 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb @@ -10,64 +10,6 @@ module QA let(:package_version) { '1.3.7' } let(:package_type) { 'helm' } - let(:package_gitlab_ci_file) do - { - file_path: '.gitlab-ci.yml', - content: - <<~YAML - deploy: - image: alpine:3 - script: - - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing - - apk add curl - - helm create #{package_name} - - cp ./Chart.yaml #{package_name} - - helm package #{package_name} - - http_code=$(curl --write-out "%{http_code}" --request POST --form 'chart=@#{package_name}-#{package_version}.tgz' --user #{username}:#{access_token} ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/helm/api/stable/charts --output /dev/null --silent) - - '[ $http_code = "201" ]' - only: - - "#{package_project.default_branch}" - tags: - - "runner-for-#{package_project.group.name}" - YAML - } - end - - let(:package_chart_yaml_file) do - { - file_path: "Chart.yaml", - content: - <<~EOF - apiVersion: v2 - name: #{package_name} - description: GitLab QA helm package - type: application - version: #{package_version} - appVersion: "1.16.0" - EOF - } - end - - let(:client_gitlab_ci_file) do - { - file_path: '.gitlab-ci.yml', - content: - <<~YAML - pull: - image: alpine:3 - script: - - apk add helm --repository=http://dl-cdn.alpinelinux.org/alpine/edge/testing - - helm repo add --username #{username} --password #{access_token} gitlab_qa ${CI_API_V4_URL}/projects/#{package_project.id}/packages/helm/stable - - helm repo update - - helm pull gitlab_qa/#{package_name} - only: - - "#{client_project.default_branch}" - tags: - - "runner-for-#{client_project.group.name}" - YAML - } - end - %i[personal_access_token ci_job_token project_deploy_token].each do |authentication_token_type| context "using a #{authentication_token_type}" do let(:username) do @@ -95,9 +37,21 @@ module QA it "pushes and pulls a helm chart" do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + helm_upload_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_upload_package.yaml.erb')).result(binding) + helm_chart_yaml = ERB.new(read_fixture('package_managers/helm', 'Chart.yaml.erb')).result(binding) + commit.project = package_project commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files([package_gitlab_ci_file, package_chart_yaml_file]) + commit.add_files([ + { + file_path: '.gitlab-ci.yml', + content: helm_upload_yaml + }, + { + file_path: 'Chart.yaml', + content: helm_chart_yaml + } + ]) end end @@ -127,9 +81,16 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + helm_install_yaml = ERB.new(read_fixture('package_managers/helm', 'helm_install_package.yaml.erb')).result(binding) + commit.project = client_project commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files([client_gitlab_ci_file]) + commit.add_files([ + { + file_path: '.gitlab-ci.yml', + content: helm_install_yaml + } + ]) end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb index 57e1aa6a087..e9520b3f834 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb @@ -13,76 +13,6 @@ module QA let(:package_version) { '1.3.7' } let(:package_type) { 'maven_gradle' } - let(:package_gitlab_ci_file) do - { - file_path: '.gitlab-ci.yml', - content: - <<~YAML - deploy: - image: gradle:6.5-jdk11 - script: - - 'gradle publish' - only: - - "#{package_project.default_branch}" - tags: - - "runner-for-#{package_project.group.name}" - YAML - } - end - - let(:package_build_gradle_file) do - { - file_path: 'build.gradle', - content: - <<~EOF - plugins { - id 'java' - id 'maven-publish' - } - - publishing { - publications { - library(MavenPublication) { - groupId '#{group_id}' - artifactId '#{artifact_id}' - version '#{package_version}' - from components.java - } - } - repositories { - maven { - url "#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven" - credentials(HttpHeaderCredentials) { - name = "Private-Token" - value = "#{personal_access_token}" - } - authentication { - header(HttpHeaderAuthentication) - } - } - } - } - EOF - } - end - - let(:client_gitlab_ci_file) do - { - file_path: '.gitlab-ci.yml', - content: - <<~YAML - build: - image: gradle:6.5-jdk11 - script: - - 'gradle build' - only: - - "#{client_project.default_branch}" - tags: - - "runner-for-#{client_project.group.name}" - YAML - } - end - where(:authentication_token_type, :maven_header_name) do :personal_access_token | 'Private-Token' :ci_job_token | 'Job-Token' @@ -101,49 +31,24 @@ module QA end end - let(:client_build_gradle_file) do - { - file_path: 'build.gradle', - content: - <<~EOF - plugins { - id 'java' - id 'application' - } - - repositories { - jcenter() - maven { - url "#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven" - name "GitLab" - credentials(HttpHeaderCredentials) { - name = '#{maven_header_name}' - value = #{token} - } - authentication { - header(HttpHeaderAuthentication) - } - } - } - - dependencies { - implementation group: '#{group_id}', name: '#{artifact_id}', version: '#{package_version}' - testImplementation 'junit:junit:4.12' - } - - application { - mainClassName = 'gradle_maven_app.App' - } - EOF - } - end - it "pushes and pulls a maven package via gradle using #{params[:authentication_token_type]}" do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + gradle_upload_yaml = ERB.new(read_fixture('package_managers/maven', 'gradle_upload_package.yaml.erb')).result(binding) + build_upload_gradle = ERB.new(read_fixture('package_managers/maven', 'build_upload.gradle.erb')).result(binding) + commit.project = package_project commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files([package_gitlab_ci_file, package_build_gradle_file]) + commit.add_files([ + { + file_path: '.gitlab-ci.yml', + content: gradle_upload_yaml + }, + { + file_path: 'build.gradle', + content: build_upload_gradle + } + ]) end end @@ -173,9 +78,21 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + gradle_install_yaml = ERB.new(read_fixture('package_managers/maven', 'gradle_install_package.yaml.erb')).result(binding) + build_install_gradle = ERB.new(read_fixture('package_managers/maven', 'build_install.gradle.erb')).result(binding) + commit.project = client_project - commit.commit_message = 'Add .gitlab-ci.yml' - commit.add_files([client_gitlab_ci_file, client_build_gradle_file]) + commit.commit_message = 'Add files' + commit.add_files([ + { + file_path: '.gitlab-ci.yml', + content: gradle_install_yaml + }, + { + file_path: 'build.gradle', + content: build_install_gradle + } + ]) end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb index e6591b6adb9..b4ebb9dd475 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb @@ -13,121 +13,6 @@ module QA let(:package_version) { '1.3.7' } let(:package_type) { 'maven' } - let(:package_gitlab_ci_file) do - { - file_path: '.gitlab-ci.yml', - content: - <<~YAML - deploy: - image: maven:3.6-jdk-11 - script: - - 'mvn deploy -s settings.xml' - only: - - "#{package_project.default_branch}" - tags: - - "runner-for-#{package_project.group.name}" - YAML - } - end - - let(:package_pom_file) do - { - file_path: 'pom.xml', - content: <<~XML - <project> - <groupId>#{group_id}</groupId> - <artifactId>#{artifact_id}</artifactId> - <version>#{package_version}</version> - <modelVersion>4.0.0</modelVersion> - <repositories> - <repository> - <id>#{package_project.name}</id> - <url>#{gitlab_address_with_port}/api/v4/groups/#{package_project.group.id}/-/packages/maven</url> - </repository> - </repositories> - <distributionManagement> - <repository> - <id>#{package_project.name}</id> - <url>#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven</url> - </repository> - <snapshotRepository> - <id>#{package_project.name}</id> - <url>#{gitlab_address_with_port}/api/v4/projects/#{package_project.id}/packages/maven</url> - </snapshotRepository> - </distributionManagement> - </project> - XML - } - end - - let(:client_gitlab_ci_file) do - { - file_path: '.gitlab-ci.yml', - content: - <<~YAML - install: - image: maven:3.6-jdk-11 - script: - - "mvn install -s settings.xml" - only: - - "#{client_project.default_branch}" - tags: - - "runner-for-#{client_project.group.name}" - YAML - } - end - - let(:client_pom_file) do - { - file_path: 'pom.xml', - content: <<~XML - <project> - <groupId>#{group_id}</groupId> - <artifactId>maven_client</artifactId> - <version>1.0</version> - <modelVersion>4.0.0</modelVersion> - <repositories> - <repository> - <id>#{package_project.name}</id> - <url>#{gitlab_address_with_port}/api/v4/groups/#{package_project.group.id}/-/packages/maven</url> - </repository> - </repositories> - <dependencies> - <dependency> - <groupId>#{group_id}</groupId> - <artifactId>#{artifact_id}</artifactId> - <version>#{package_version}</version> - </dependency> - </dependencies> - </project> - XML - } - end - - let(:settings_xml_with_pat) do - { - file_path: 'settings.xml', - content: <<~XML - <settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"> - <servers> - <server> - <id>#{package_project.name}</id> - <configuration> - <httpHeaders> - <property> - <name>Private-Token</name> - <value>#{personal_access_token}</value> - </property> - </httpHeaders> - </configuration> - </server> - </servers> - </settings> - XML - } - end - where(:authentication_token_type, :maven_header_name) do :personal_access_token | 'Private-Token' :ci_job_token | 'Job-Token' @@ -146,39 +31,28 @@ module QA end end - let(:settings_xml) do - { - file_path: 'settings.xml', - content: <<~XML - <settings xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" - xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"> - <servers> - <server> - <id>#{package_project.name}</id> - <configuration> - <httpHeaders> - <property> - <name>#{maven_header_name}</name> - <value>#{token}</value> - </property> - </httpHeaders> - </configuration> - </server> - </servers> - </settings> - XML - } - end - it "pushes and pulls a maven package via maven using #{params[:authentication_token_type]}" do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + maven_upload_package_yaml = ERB.new(read_fixture('package_managers/maven', 'maven_upload_package.yaml.erb')).result(binding) + package_pom_xml = ERB.new(read_fixture('package_managers/maven', 'package_pom.xml.erb')).result(binding) + settings_xml = ERB.new(read_fixture('package_managers/maven', 'settings.xml.erb')).result(binding) + commit.project = package_project - commit.commit_message = 'Add .gitlab-ci.yml' + commit.commit_message = 'Add files' commit.add_files([ - package_gitlab_ci_file, - package_pom_file, - settings_xml + { + file_path: '.gitlab-ci.yml', + content: maven_upload_package_yaml + }, + { + file_path: 'pom.xml', + content: package_pom_xml + }, + { + file_path: 'settings.xml', + content: settings_xml + } ]) end end @@ -209,12 +83,25 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + maven_install_package_yaml = ERB.new(read_fixture('package_managers/maven', 'maven_install_package.yaml.erb')).result(binding) + client_pom_xml = ERB.new(read_fixture('package_managers/maven', 'client_pom.xml.erb')).result(binding) + settings_xml = ERB.new(read_fixture('package_managers/maven', 'settings.xml.erb')).result(binding) + commit.project = client_project - commit.commit_message = 'Add .gitlab-ci.yml' + commit.commit_message = 'Add files' commit.add_files([ - client_gitlab_ci_file, - client_pom_file, - settings_xml + { + file_path: '.gitlab-ci.yml', + content: maven_install_package_yaml + }, + { + file_path: 'pom.xml', + content: client_pom_xml + }, + { + file_path: 'settings.xml', + content: settings_xml + } ]) end end @@ -278,7 +165,19 @@ module QA end def create_duplicated_package - with_fixtures([package_pom_file, settings_xml_with_pat]) do |dir| + settings_xml_with_pat = ERB.new(read_fixture('package_managers/maven', 'settings_with_pat.xml.erb')).result(binding) + package_pom_xml = ERB.new(read_fixture('package_managers/maven', 'package_pom.xml.erb')).result(binding) + + with_fixtures([ + { + file_path: 'pom.xml', + content: package_pom_xml + }, + { + file_path: 'settings.xml', + content: settings_xml_with_pat + } + ]) do |dir| Service::DockerRun::Maven.new(dir).publish! end @@ -294,12 +193,25 @@ module QA def push_duplicated_package Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + maven_upload_package_yaml = ERB.new(read_fixture('package_managers/maven', 'maven_upload_package.yaml.erb')).result(binding) + package_pom_xml = ERB.new(read_fixture('package_managers/maven', 'package_pom.xml.erb')).result(binding) + settings_xml = ERB.new(read_fixture('package_managers/maven', 'settings.xml.erb')).result(binding) + commit.project = client_project commit.commit_message = 'Add .gitlab-ci.yml' commit.add_files([ - package_gitlab_ci_file, - package_pom_file, - settings_xml + { + file_path: '.gitlab-ci.yml', + content: maven_upload_package_yaml + }, + { + file_path: 'pom.xml', + content: package_pom_xml + }, + { + file_path: 'settings.xml', + content: settings_xml + } ]) end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb index 177b9b18e50..0535aad8daf 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb @@ -50,79 +50,10 @@ module QA runner.name = "qa-runner-#{Time.now.to_i}" runner.tags = ["runner-for-#{project.group.name}"] runner.executor = :docker - runner.token = project.group.runners_token + runner.token = project.group.reload!.runners_token end end - let(:gitlab_ci_deploy_yaml) do - { - file_path: '.gitlab-ci.yml', - content: - <<~YAML - image: node:latest - - stages: - - deploy - - deploy: - stage: deploy - script: - - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=#{auth_token}">.npmrc - - npm publish - only: - - "#{project.default_branch}" - tags: - - "runner-for-#{project.group.name}" - YAML - } - end - - let(:gitlab_ci_install_yaml) do - { - file_path: '.gitlab-ci.yml', - content: - <<~YAML - image: node:latest - - stages: - - install - - install: - stage: install - script: - - "npm config set @#{registry_scope}:registry #{gitlab_address_with_port}/api/v4/packages/npm/" - - "npm install #{package.name}" - cache: - key: ${CI_BUILD_REF_NAME} - paths: - - node_modules/ - artifacts: - paths: - - node_modules/ - only: - - "#{another_project.default_branch}" - tags: - - "runner-for-#{another_project.group.name}" - YAML - } - end - - let(:package_json) do - { - file_path: 'package.json', - content: <<~JSON - { - "name": "#{package.name}", - "version": "1.0.0", - "description": "Example package for GitLab npm registry", - "publishConfig": { - "@#{registry_scope}:registry": "#{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/npm/" - } - } - JSON - } - end - let(:package) do Resource::Package.init do |package| package.name = "@#{registry_scope}/#{project.name}-#{SecureRandom.hex(8)}" @@ -157,12 +88,21 @@ module QA it "push and pull a npm package via CI using a #{params[:token_name]}" do Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do + npm_upload_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_package_instance.yaml.erb')).result(binding) + package_json = ERB.new(read_fixture('package_managers/npm', 'package_instance.json.erb')).result(binding) + Resource::Repository::Commit.fabricate_via_api! do |commit| commit.project = project - commit.commit_message = 'Add .gitlab-ci.yml' + commit.commit_message = 'Add files' commit.add_files([ - gitlab_ci_deploy_yaml, - package_json + { + file_path: '.gitlab-ci.yml', + content: npm_upload_yaml + }, + { + file_path: 'package.json', + content: package_json + } ]) end end @@ -180,10 +120,15 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + npm_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_install_package_instance.yaml.erb')).result(binding) + commit.project = another_project commit.commit_message = 'Add .gitlab-ci.yml' commit.add_files([ - gitlab_ci_install_yaml + { + file_path: '.gitlab-ci.yml', + content: npm_install_yaml + } ]) end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb index 457d21bc3ef..2c8edfe272b 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb @@ -46,62 +46,6 @@ module QA end end - let(:gitlab_ci_yaml) do - { - file_path: '.gitlab-ci.yml', - content: - <<~YAML - image: node:latest - - stages: - - deploy - - install - - deploy: - stage: deploy - script: - - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=#{auth_token}">.npmrc - - npm publish - only: - - "#{project.default_branch}" - tags: - - "runner-for-#{project.name}" - install: - stage: install - script: - - "npm config set @#{registry_scope}:registry #{gitlab_address_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/" - - "npm install #{package.name}" - cache: - key: ${CI_BUILD_REF_NAME} - paths: - - node_modules/ - artifacts: - paths: - - node_modules/ - only: - - "#{project.default_branch}" - tags: - - "runner-for-#{project.name}" - YAML - } - end - - let(:package_json) do - { - file_path: 'package.json', - content: <<~JSON - { - "name": "#{package.name}", - "version": "1.0.0", - "description": "Example package for GitLab npm registry", - "publishConfig": { - "@#{registry_scope}:registry": "#{gitlab_address_with_port}/api/v4/projects/#{project.id}/packages/npm/" - } - } - JSON - } - end - let(:package) do Resource::Package.init do |package| package.name = "@#{registry_scope}/mypackage-#{SecureRandom.hex(8)}" @@ -135,11 +79,20 @@ module QA it "push and pull a npm package via CI using a #{params[:token_name]}" do Resource::Repository::Commit.fabricate_via_api! do |commit| + npm_upload_install_yaml = ERB.new(read_fixture('package_managers/npm', 'npm_upload_install_package_project.yaml.erb')).result(binding) + package_json = ERB.new(read_fixture('package_managers/npm', 'package_project.json.erb')).result(binding) + commit.project = project commit.commit_message = 'Add .gitlab-ci.yml' commit.add_files([ - gitlab_ci_yaml, - package_json + { + file_path: '.gitlab-ci.yml', + content: npm_upload_install_yaml + }, + { + file_path: 'package.json', + content: package_json + } ]) end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb index d63bf486f11..0a9901ab5d5 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb @@ -5,6 +5,7 @@ module QA describe 'NuGet Repository' do using RSpec::Parameterized::TableSyntax include Runtime::Fixtures + let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'nuget-package-project' @@ -53,7 +54,7 @@ module QA runner.name = "qa-runner-#{Time.now.to_i}" runner.tags = ["runner-for-#{project.group.name}"] runner.executor = :docker - runner.token = project.group.runners_token + runner.token = project.group.reload!.runners_token end end @@ -96,31 +97,14 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + nuget_upload_yaml = ERB.new(read_fixture('package_managers/nuget', 'nuget_upload_package.yaml.erb')).result(binding) commit.project = project commit.commit_message = 'Add .gitlab-ci.yml' commit.update_files( [ { file_path: '.gitlab-ci.yml', - content: <<~YAML - image: mcr.microsoft.com/dotnet/sdk:5.0 - - stages: - - deploy - - deploy: - stage: deploy - script: - - dotnet restore -p:Configuration=Release - - dotnet build -c Release - - dotnet pack -c Release -p:PackageID=#{package.name} - - dotnet nuget add source "$CI_SERVER_URL/api/v4/projects/$CI_PROJECT_ID/packages/nuget/index.json" --name gitlab --username #{auth_token_username} --password #{auth_token_password} --store-password-in-clear-text - - dotnet nuget push "bin/Release/*.nupkg" --source gitlab - rules: - - if: '$CI_COMMIT_BRANCH == "#{project.default_branch}"' - tags: - - "runner-for-#{project.group.name}" - YAML + content: nuget_upload_yaml } ] ) @@ -142,6 +126,8 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + nuget_install_yaml = ERB.new(read_fixture('package_managers/nuget', 'nuget_install_package.yaml.erb')).result(binding) + commit.project = another_project commit.commit_message = 'Add new csproj file' commit.add_files( @@ -165,23 +151,7 @@ module QA [ { file_path: '.gitlab-ci.yml', - content: <<~YAML - image: mcr.microsoft.com/dotnet/sdk:5.0 - - stages: - - install - - install: - stage: install - script: - - dotnet nuget locals all --clear - - dotnet nuget add source "$CI_SERVER_URL/api/v4/groups/#{another_project.group.id}/-/packages/nuget/index.json" --name gitlab --username #{auth_token_username} --password #{auth_token_password} --store-password-in-clear-text - - "dotnet add otherdotnet.csproj package #{package.name} --version 1.0.0" - only: - - "#{another_project.default_branch}" - tags: - - "runner-for-#{project.group.name}" - YAML + content: nuget_install_yaml } ] ) diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb index 2e7bd8fc5d7..db9b15b6ece 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb @@ -4,6 +4,7 @@ module QA RSpec.describe 'Package', :orchestrated, :packages, :object_storage do describe 'PyPI Repository' do include Runtime::Fixtures + let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'pypi-package-project' @@ -36,56 +37,18 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + pypi_yaml = ERB.new(read_fixture('package_managers/pypi', 'pypi_upload_install_package.yaml.erb')).result(binding) + pypi_setup_file = ERB.new(read_fixture('package_managers/pypi', 'setup.py.erb')).result(binding) + commit.project = project - commit.commit_message = 'Add .gitlab-ci.yml' + commit.commit_message = 'Add files' commit.add_files([{ file_path: '.gitlab-ci.yml', - content: - <<~YAML - image: python:latest - stages: - - run - - install - - run: - stage: run - script: - - pip install twine - - python setup.py sdist bdist_wheel - - "TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token python -m twine upload --repository-url #{gitlab_address_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/pypi dist/*" - tags: - - "runner-for-#{project.name}" - install: - stage: install - script: - - "pip install #{package.name} --no-deps --index-url #{uri.scheme}://#{personal_access_token}:#{personal_access_token}@#{gitlab_host_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple --trusted-host #{gitlab_host_with_port}" - tags: - - "runner-for-#{project.name}" - - YAML + content: pypi_yaml }, { file_path: 'setup.py', - content: - <<~EOF - import setuptools - - setuptools.setup( - name="#{package.name}", - version="0.0.1", - author="Example Author", - author_email="author@example.com", - description="A small example package", - packages=setuptools.find_packages(), - classifiers=[ - "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - ], - python_requires='>=3.6', - ) - EOF - + content: pypi_setup_file }]) end end diff --git a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb index 062d2b49deb..9b5c958e442 100644 --- a/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb @@ -48,30 +48,16 @@ module QA Support::Retrier.retry_on_exception(max_attempts: 3, sleep_interval: 2) do Resource::Repository::Commit.fabricate_via_api! do |commit| + rubygem_upload_yaml = ERB.new(read_fixture('package_managers/rubygems', 'rubygems_upload_package.yaml.erb')).result(binding) + rubygem_package_gemspec = ERB.new(read_fixture('package_managers/rubygems', 'package.gemspec.erb')).result(binding) + commit.project = project commit.commit_message = 'Add package files' commit.add_files( [ { file_path: '.gitlab-ci.yml', - content: - <<~YAML - image: ruby - - test_package: - stage: deploy - before_script: - - mkdir ~/.gem - - echo "---" > ~/.gem/credentials - - | - echo "#{gitlab_address_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/rubygems: '${CI_JOB_TOKEN}'" >> ~/.gem/credentials - - chmod 0600 ~/.gem/credentials - script: - - gem build #{package.name} - - gem push #{package.name}-0.0.1.gem --host #{gitlab_address_with_port}/api/v4/projects/${CI_PROJECT_ID}/packages/rubygems - tags: - - "runner-for-#{project.name}" - YAML + content: rubygem_upload_yaml }, { file_path: 'lib/hello_gem.rb', @@ -86,49 +72,7 @@ module QA }, { file_path: "#{package.name}.gemspec", - content: - <<~RUBY - # frozen_string_literal: true - - Gem::Specification.new do |s| - s.name = '#{package.name}' - s.authors = ['Tanuki Steve', 'Hal 9000'] - s.author = 'Tanuki Steve' - s.version = '0.0.1' - s.date = '2011-09-29' - s.summary = 'this is a test package' - s.files = ['lib/hello_gem.rb'] - s.require_paths = ['lib'] - - s.description = 'A test package for GitLab.' - s.email = 'tanuki@not_real.com' - s.homepage = 'https://gitlab.com/ruby-co/my-package' - s.license = 'MIT' - - s.metadata = { - 'bug_tracker_uri' => 'https://gitlab.com/ruby-co/my-package/issues', - 'changelog_uri' => 'https://gitlab.com/ruby-co/my-package/CHANGELOG.md', - 'documentation_uri' => 'https://gitlab.com/ruby-co/my-package/docs', - 'mailing_list_uri' => 'https://gitlab.com/ruby-co/my-package/mailme', - 'source_code_uri' => 'https://gitlab.com/ruby-co/my-package' - } - - s.bindir = 'bin' - s.platform = Gem::Platform::RUBY - s.post_install_message = 'Installed, thank you!' - s.rdoc_options = ['--main'] - s.required_ruby_version = '>= 2.7.0' - s.required_rubygems_version = '>= 1.8.11' - s.requirements = 'A high powered server or calculator' - s.rubygems_version = '1.8.09' - - s.add_dependency 'dependency_1', '~> 1.2.3' - s.add_dependency 'dependency_2', '3.0.0' - s.add_dependency 'dependency_3', '>= 1.0.0' - s.add_dependency 'dependency_4' - end - - RUBY + content: rubygem_package_gemspec } ] ) diff --git a/qa/qa/support/formatters/test_stats_formatter.rb b/qa/qa/support/formatters/test_stats_formatter.rb index 3d076b341b0..a53aa6e64f4 100644 --- a/qa/qa/support/formatters/test_stats_formatter.rb +++ b/qa/qa/support/formatters/test_stats_formatter.rb @@ -14,40 +14,38 @@ module QA return log(:warn, 'Missing QA_INFLUXDB_URL, skipping metrics export!') unless influxdb_url return log(:warn, 'Missing QA_INFLUXDB_TOKEN, skipping metrics export!') unless influxdb_token - data = notification.examples.map { |example| test_stats(example) }.compact - influx_client.create_write_api.write(data: data) - log(:info, "Pushed #{data.length} entries to influxdb") - rescue StandardError => e - log(:error, "Failed to push data to influxdb, error: #{e}") + push_test_stats(notification.examples) + push_fabrication_stats end private - # InfluxDb client + # Push test execution stats to influxdb # - # @return [InfluxDB2::Client] - def influx_client - @influx_client ||= InfluxDB2::Client.new( - influxdb_url, - influxdb_token, - bucket: 'e2e-test-stats', - org: 'gitlab-qa', - precision: InfluxDB2::WritePrecision::NANOSECOND - ) - end + # @param [Array<RSpec::Core::Example>] examples + # @return [void] + def push_test_stats(examples) + data = examples.map { |example| test_stats(example) }.compact - # InfluxDb instance url - # - # @return [String] - def influxdb_url - @influxdb_url ||= env('QA_INFLUXDB_URL') + influx_client.write(data: data) + log(:debug, "Pushed #{data.length} test execution entries to influxdb") + rescue StandardError => e + log(:error, "Failed to push test execution stats to influxdb, error: #{e}") end - # Influxdb token + # Push resource fabrication stats to influxdb # - # @return [String] - def influxdb_token - @influxdb_token ||= env('QA_INFLUXDB_TOKEN') + # @return [void] + def push_fabrication_stats + data = Tools::TestResourceDataProcessor.resources.flat_map do |resource, values| + values.map { |v| fabrication_stats(resource: resource, **v) } + end + return if data.empty? + + influx_client.write(data: data) + log(:debug, "Pushed #{data.length} resource fabrication entries to influxdb") + rescue StandardError => e + log(:error, "Failed to push fabrication stats to influxdb, error: #{e}") end # Transform example to influxdb compatible metrics data @@ -93,6 +91,33 @@ module QA nil end + # Resource fabrication data point + # + # @param [String] resource + # @param [String] info + # @param [Symbol] fabrication_method + # @param [Symbol] http_method + # @param [Integer] fabrication_time + # @return [Hash] + def fabrication_stats(resource:, info:, fabrication_method:, http_method:, fabrication_time:, **) + { + name: 'fabrication-stats', + time: time, + tags: { + resource: resource, + fabrication_method: fabrication_method, + http_method: http_method, + run_type: env('QA_RUN_TYPE') || run_type, + merge_request: merge_request + }, + fields: { + fabrication_time: fabrication_time, + info: info, + job_url: QA::Runtime::Env.ci_job_url + } + } + end + # Project name # # @return [String] @@ -150,7 +175,7 @@ module QA # @param [String] message # @return [void] def log(level, message) - QA::Runtime::Logger.public_send(level, "influxdb exporter: #{message}") + QA::Runtime::Logger.public_send(level, "[influxdb exporter]: #{message}") end # Return non empty environment variable value @@ -170,6 +195,33 @@ module QA def devops_stage(file_path) file_path.match(%r{\d{1,2}_(\w+)/})&.captures&.first end + + # InfluxDb client + # + # @return [InfluxDB2::WriteApi] + def influx_client + @influx_client ||= InfluxDB2::Client.new( + influxdb_url, + influxdb_token, + bucket: 'e2e-test-stats', + org: 'gitlab-qa', + precision: InfluxDB2::WritePrecision::NANOSECOND + ).create_write_api + end + + # InfluxDb instance url + # + # @return [String] + def influxdb_url + @influxdb_url ||= env('QA_INFLUXDB_URL') + end + + # Influxdb token + # + # @return [String] + def influxdb_token + @influxdb_token ||= env('QA_INFLUXDB_TOKEN') + end end end end diff --git a/qa/qa/tools/test_resource_data_processor.rb b/qa/qa/tools/test_resource_data_processor.rb index 78fb6ef6cd0..adcb380980b 100644 --- a/qa/qa/tools/test_resource_data_processor.rb +++ b/qa/qa/tools/test_resource_data_processor.rb @@ -6,60 +6,80 @@ module QA module Tools class TestResourceDataProcessor - @resources ||= Hash.new { |hsh, key| hsh[key] = [] } + include Singleton + + def initialize + @resources = Hash.new { |hsh, key| hsh[key] = [] } + end class << self - # Ignoring rspec-mocks, sandbox, user and fork resources - # TODO: Will need to figure out which user resources can be collected, ignore for now - # - # Collecting resources created in E2E tests - # Data is a Hash of resources with keys as resource type (group, project, issue, etc.) - # Each type contains an array of resource object (hash) of the same type - # E.g: { "QA::Resource::Project": [ { info: 'foo', api_path: '/foo'}, {...} ] } - def collect(resource, info) - return if resource.api_response.nil? || - resource.is_a?(RSpec::Mocks::Double) || - resource.is_a?(Resource::Sandbox) || - resource.is_a?(Resource::User) || - resource.is_a?(Resource::Fork) + delegate :collect, :write_to_file, :resources, to: :instance + end - api_path = if resource.respond_to?(:api_delete_path) - resource.api_delete_path.gsub('%2F', '/') - elsif resource.respond_to?(:api_get_path) - resource.api_get_path.gsub('%2F', '/') - else - 'Cannot find resource API path' - end + # @return [Hash<String, Array>] + attr_reader :resources - type = resource.class.name + # Collecting resources created in E2E tests + # Data is a Hash of resources with keys as resource type (group, project, issue, etc.) + # Each type contains an array of resource object (hash) of the same type + # E.g: { "QA::Resource::Project": [ { info: 'foo', api_path: '/foo'}, {...} ] } + # + # @param [QA::Resource::Base] resource fabricated resource + # @param [String] info resource info + # @param [Symbol] method fabrication method, api or browser_ui + # @param [Integer] time fabrication time + # @return [Hash] + def collect(resource:, info:, fabrication_method:, fabrication_time:) + api_path = resource_api_path(resource) + type = resource.class.name - @resources[type] << { info: info, api_path: api_path } - end + resources[type] << { + info: info, + api_path: api_path, + fabrication_method: fabrication_method, + fabrication_time: fabrication_time, + http_method: resource.api_fabrication_http_method + } + end + + # If JSON file exists and not empty, read and load file content + # Merge what is saved in @resources into the content from file + # Overwrite file content with the new data hash + # Otherwise create file and write data hash to file for the first time + # + # @return [void] + def write_to_file + return if resources.empty? - # If JSON file exists and not empty, read and load file content - # Merge what is saved in @resources into the content from file - # Overwrite file content with the new data hash - # Otherwise create file and write data hash to file for the first time - def write_to_file - return if @resources.empty? + file = Pathname.new(Runtime::Env.test_resources_created_filepath) + FileUtils.mkdir_p(file.dirname) - file = Runtime::Env.test_resources_created_filepath - FileUtils.mkdir_p('tmp/') - FileUtils.touch(file) - data = nil + data = resources.deep_dup + # merge existing json if present + JSON.parse(File.read(file)).deep_merge!(data) { |key, val, other_val| val + other_val } if file.exist? + + File.write(file, JSON.pretty_generate(data)) + end - if File.zero?(file) - data = @resources - else - data = JSON.parse(File.read(file)) + private - @resources.each_pair do |key, val| - data[key].nil? ? data[key] = val : val.each { |item| data[key] << item } - end - end + # Determine resource api path or return default value + # Some resources fabricated via UI can raise no attribute error + # + # @param [QA::Resource::Base] resource + # @return [String] + def resource_api_path(resource) + default = 'Cannot find resource API path' - File.open(file, 'w') { |f| f.write(JSON.pretty_generate(data.each_value(&:uniq!))) } + if resource.respond_to?(:api_delete_path) + resource.api_delete_path.gsub('%2F', '/') + elsif resource.respond_to?(:api_get_path) + resource.api_get_path.gsub('%2F', '/') + else + default end + rescue QA::Resource::Base::NoValueError + default end end end diff --git a/qa/spec/resource/base_spec.rb b/qa/spec/resource/base_spec.rb index 2dd25f983bf..3b288514ad1 100644 --- a/qa/spec/resource/base_spec.rb +++ b/qa/spec/resource/base_spec.rb @@ -7,6 +7,11 @@ RSpec.describe QA::Resource::Base do let(:location) { 'http://location' } let(:log_regex) { %r{==> Built a MyResource with username 'qa' via #{method} in [\d.\-e]+ seconds+} } + before do + allow(QA::Tools::TestResourceDataProcessor).to receive(:collect) + allow(QA::Tools::TestResourceDataProcessor).to receive(:write_to_file) + end + shared_context 'with fabrication context' do subject do Class.new(described_class) do diff --git a/qa/spec/support/formatters/test_stats_formatter_spec.rb b/qa/spec/support/formatters/test_stats_formatter_spec.rb index 4dcb40a0c44..480ae99dbe0 100644 --- a/qa/spec/support/formatters/test_stats_formatter_spec.rb +++ b/qa/spec/support/formatters/test_stats_formatter_spec.rb @@ -22,6 +22,7 @@ describe QA::Support::Formatters::TestStatsFormatter do let(:file_path) { "./qa/specs/features/#{stage}/subfolder/some_spec.rb" } let(:ui_fabrication) { 0 } let(:api_fabrication) { 0 } + let(:fabrication_resources) { {} } let(:influx_client_args) do { @@ -88,6 +89,7 @@ describe QA::Support::Formatters::TestStatsFormatter do before do allow(InfluxDB2::Client).to receive(:new).with(url, token, **influx_client_args) { influx_client } + allow(QA::Tools::TestResourceDataProcessor).to receive(:resources) { fabrication_resources } end context "without influxdb variables configured" do @@ -135,6 +137,7 @@ describe QA::Support::Formatters::TestStatsFormatter do it('spec', :reliable, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/1234') {} end + expect(influx_write_api).to have_received(:write).once expect(influx_write_api).to have_received(:write).with(data: [data]) end end @@ -147,6 +150,7 @@ describe QA::Support::Formatters::TestStatsFormatter do it('spec', :quarantine, testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/1234') {} end + expect(influx_write_api).to have_received(:write).once expect(influx_write_api).to have_received(:write).with(data: [data]) end end @@ -162,6 +166,7 @@ describe QA::Support::Formatters::TestStatsFormatter do it 'exports data to influxdb with correct run type' do run_spec + expect(influx_write_api).to have_received(:write).once expect(influx_write_api).to have_received(:write).with(data: [data]) end end @@ -179,6 +184,7 @@ describe QA::Support::Formatters::TestStatsFormatter do it 'exports data to influxdb with correct run type' do run_spec + expect(influx_write_api).to have_received(:write).once expect(influx_write_api).to have_received(:write).with(data: [data]) end end @@ -195,8 +201,48 @@ describe QA::Support::Formatters::TestStatsFormatter do it 'exports data to influxdb with fabrication times' do run_spec + expect(influx_write_api).to have_received(:write).once expect(influx_write_api).to have_received(:write).with(data: [data]) end end + + context 'with fabrication resources' do + let(:fabrication_resources) do + { + 'QA::Resource::Project' => [{ + info: "with id '1'", + api_path: '/project', + fabrication_method: :api, + fabrication_time: 1, + http_method: :post + }] + } + end + + let(:fabrication_data) do + { + name: 'fabrication-stats', + time: DateTime.strptime(ci_timestamp).to_time, + tags: { + resource: 'QA::Resource::Project', + fabrication_method: :api, + http_method: :post, + run_type: run_type, + merge_request: "false" + }, + fields: { + fabrication_time: 1, + info: "with id '1'", + job_url: ci_job_url + } + } + end + + it 'exports fabrication stats data to influxdb' do + run_spec + + expect(influx_write_api).to have_received(:write).with(data: [fabrication_data]) + end + end end end diff --git a/qa/spec/support/shared_contexts/packages_registry_shared_context.rb b/qa/spec/support/shared_contexts/packages_registry_shared_context.rb index 348176d264b..73a6c2bd99e 100644 --- a/qa/spec/support/shared_contexts/packages_registry_shared_context.rb +++ b/qa/spec/support/shared_contexts/packages_registry_shared_context.rb @@ -32,7 +32,7 @@ module QA runner.name = "qa-runner-#{Time.now.to_i}" runner.tags = ["runner-for-#{package_project.group.name}"] runner.executor = :docker - runner.token = package_project.group.runners_token + runner.token = package_project.group.reload!.runners_token end end diff --git a/qa/spec/tools/test_resources_data_processor_spec.rb b/qa/spec/tools/test_resources_data_processor_spec.rb index 6a8c0fd06a4..ca823a1c381 100644 --- a/qa/spec/tools/test_resources_data_processor_spec.rb +++ b/qa/spec/tools/test_resources_data_processor_spec.rb @@ -1,33 +1,52 @@ # frozen_string_literal: true RSpec.describe QA::Tools::TestResourceDataProcessor do + include QA::Support::Helpers::StubEnv + + subject(:processor) { Class.new(described_class).instance } + let(:info) { 'information' } - let(:api_path) { '/foo' } - let(:result) { [{ info: info, api_path: api_path }] } + let(:api_response) { {} } + let(:method) { :api } + let(:time) { 2 } + let(:api_path) { resource.api_delete_path } + let(:resource) { QA::Resource::Project.init { |project| project.id = 1 } } - describe '.collect' do - context 'when resource is not restricted' do - let(:resource) { instance_double(QA::Resource::Project, api_delete_path: '/foo', api_response: 'foo') } + let(:result) do + { + 'QA::Resource::Project' => [{ + info: info, + api_path: api_path, + fabrication_method: method, + fabrication_time: time, + http_method: :post + }] + } + end - it 'collects resource' do - expect(described_class.collect(resource, info)).to eq(result) - end + before do + processor.collect(resource: resource, info: info, fabrication_method: method, fabrication_time: time) + end + + describe '.collect' do + it 'collects and stores resource' do + expect(processor.resources).to eq(result) end + end + + describe '.write_to_file' do + let(:resources_file) { Pathname.new(Faker::File.file_name(dir: 'tmp', ext: 'json')) } - context 'when resource api response is nil' do - let(:resource) { double(QA::Resource::Project, api_delete_path: '/foo', api_response: nil) } + before do + stub_env('QA_TEST_RESOURCES_CREATED_FILEPATH', resources_file) - it 'does not collect resource' do - expect(described_class.collect(resource, info)).to eq(nil) - end + allow(File).to receive(:write) end - context 'when resource is restricted' do - let(:resource) { double(QA::Resource::Sandbox, api_delete_path: '/foo', api_response: 'foo') } + it 'writes applicable resources to file' do + processor.write_to_file - it 'does not collect resource' do - expect(described_class.collect(resource, info)).to eq(nil) - end + expect(File).to have_received(:write).with(resources_file, JSON.pretty_generate(result)) end end end diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb index 112dc9e01d8..e7ff8c23a8c 100644 --- a/spec/features/admin/dashboard_spec.rb +++ b/spec/features/admin/dashboard_spec.rb @@ -53,4 +53,14 @@ RSpec.describe 'admin visits dashboard' do expect(page).to have_content('Active users 71') end end + + describe 'Version check', :js do + it 'shows badge on CE' do + visit admin_root_path + + page.within('.admin-dashboard') do + expect(find('.badge')).to have_content('Up to date') + end + end + end end diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb index a88eca5cbcc..4bff8d12204 100644 --- a/spec/features/issues/gfm_autocomplete_spec.rb +++ b/spec/features/issues/gfm_autocomplete_spec.rb @@ -6,10 +6,7 @@ RSpec.describe 'GFM autocomplete', :js do let_it_be(:user) { create(:user, name: '💃speciąl someone💃', username: 'someone.special') } let_it_be(:user2) { create(:user, name: 'Marge Simpson', username: 'msimpson') } - let_it_be(:group) { create(:group, name: 'Ancestor') } - let_it_be(:child_group) { create(:group, parent: group, name: 'My group') } - let_it_be(:project) { create(:project, group: child_group) } - + let_it_be(:project) { create(:project) } let_it_be(:issue) { create(:issue, project: project, assignees: [user]) } let_it_be(:label) { create(:label, project: project, title: 'special+') } let_it_be(:label_scoped) { create(:label, project: project, title: 'scoped::label') } @@ -27,670 +24,361 @@ RSpec.describe 'GFM autocomplete', :js do project.add_maintainer(user2) end - describe 'when tribute_autocomplete feature flag is off' do - describe 'new issue page' do - before do - stub_feature_flags(tribute_autocomplete: false) - - sign_in(user) - visit new_project_issue_path(project) + describe 'new issue page' do + before do + sign_in(user) + visit new_project_issue_path(project) - wait_for_requests - end + wait_for_requests + end - it 'allows quick actions' do - fill_in 'Description', with: '/' + it 'allows quick actions' do + fill_in 'Description', with: '/' - expect(find_autocomplete_menu).to be_visible - end + expect(find_autocomplete_menu).to be_visible end + end - describe 'issue description' do - let(:issue_to_edit) { create(:issue, project: project) } + describe 'issue description' do + let(:issue_to_edit) { create(:issue, project: project) } - before do - stub_feature_flags(tribute_autocomplete: false) + before do + sign_in(user) + visit project_issue_path(project, issue_to_edit) - sign_in(user) - visit project_issue_path(project, issue_to_edit) + wait_for_requests + end - wait_for_requests - end + it 'updates with GFM reference' do + click_button 'Edit title and description' - it 'updates with GFM reference' do - click_button 'Edit title and description' + wait_for_requests - wait_for_requests + fill_in 'Description', with: "@#{user.name[0...3]}" - fill_in 'Description', with: "@#{user.name[0...3]}" + wait_for_requests - wait_for_requests + find_highlighted_autocomplete_item.click - find_highlighted_autocomplete_item.click - - click_button 'Save changes' + click_button 'Save changes' - wait_for_requests + wait_for_requests - expect(find('.description')).to have_text(user.to_reference) - end + expect(find('.description')).to have_text(user.to_reference) + end - it 'allows quick actions' do - click_button 'Edit title and description' + it 'allows quick actions' do + click_button 'Edit title and description' - fill_in 'Description', with: '/' + fill_in 'Description', with: '/' - expect(find_autocomplete_menu).to be_visible - end + expect(find_autocomplete_menu).to be_visible end + end - describe 'issue comment' do - before do - stub_feature_flags(tribute_autocomplete: false) - - sign_in(user) - visit project_issue_path(project, issue) + describe 'issue comment' do + before do + sign_in(user) + visit project_issue_path(project, issue) - wait_for_requests - end + wait_for_requests + end - describe 'triggering autocomplete' do - it 'only opens autocomplete menu when trigger character is after whitespace', :aggregate_failures do - fill_in 'Comment', with: 'testing@' - expect(page).not_to have_css('.atwho-view') + describe 'triggering autocomplete' do + it 'only opens autocomplete menu when trigger character is after whitespace', :aggregate_failures do + fill_in 'Comment', with: 'testing@' + expect(page).not_to have_css('.atwho-view') - fill_in 'Comment', with: '@@' - expect(page).not_to have_css('.atwho-view') + fill_in 'Comment', with: '@@' + expect(page).not_to have_css('.atwho-view') - fill_in 'Comment', with: "@#{user.username[0..2]}!" - expect(page).not_to have_css('.atwho-view') + fill_in 'Comment', with: "@#{user.username[0..2]}!" + expect(page).not_to have_css('.atwho-view') - fill_in 'Comment', with: "hello:#{user.username[0..2]}" - expect(page).not_to have_css('.atwho-view') + fill_in 'Comment', with: "hello:#{user.username[0..2]}" + expect(page).not_to have_css('.atwho-view') - fill_in 'Comment', with: '7:' - expect(page).not_to have_css('.atwho-view') + fill_in 'Comment', with: '7:' + expect(page).not_to have_css('.atwho-view') - fill_in 'Comment', with: 'w:' - expect(page).not_to have_css('.atwho-view') + fill_in 'Comment', with: 'w:' + expect(page).not_to have_css('.atwho-view') - fill_in 'Comment', with: 'Ё:' - expect(page).not_to have_css('.atwho-view') + fill_in 'Comment', with: 'Ё:' + expect(page).not_to have_css('.atwho-view') - fill_in 'Comment', with: "test\n\n@" - expect(find_autocomplete_menu).to be_visible - end + fill_in 'Comment', with: "test\n\n@" + expect(find_autocomplete_menu).to be_visible end + end - context 'xss checks' do - it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do - issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' - create(:issue, project: project, title: issue_xss_title) - - fill_in 'Comment', with: '#' - - wait_for_requests - - expect(find_autocomplete_menu).to have_text(issue_xss_title) - end - - it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do - fill_in 'Comment', with: '@ev' - - wait_for_requests - - expect(find_highlighted_autocomplete_item).to have_text(user_xss.username) - end - - it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do - milestone_xss_title = 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' - create(:milestone, project: project, title: milestone_xss_title) - - fill_in 'Comment', with: '%' - - wait_for_requests - - expect(find_autocomplete_menu).to have_text('alert milestone') - end + context 'xss checks' do + it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do + issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' + create(:issue, project: project, title: issue_xss_title) - it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do - fill_in 'Comment', with: '~' + fill_in 'Comment', with: '#' - wait_for_requests + wait_for_requests - expect(find_autocomplete_menu).to have_text('alert label') - end + expect(find_autocomplete_menu).to have_text(issue_xss_title) end - describe 'autocomplete highlighting' do - it 'auto-selects the first item when there is a query, and only for assignees with no query', :aggregate_failures do - fill_in 'Comment', with: ':' - wait_for_requests - expect(find_autocomplete_menu).not_to have_css('.cur') + it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do + fill_in 'Comment', with: '@ev' - fill_in 'Comment', with: ':1' - wait_for_requests - expect(find_autocomplete_menu).to have_css('.cur:first-of-type') + wait_for_requests - fill_in 'Comment', with: '@' - wait_for_requests - expect(find_autocomplete_menu).to have_css('.cur:first-of-type') - end + expect(find_highlighted_autocomplete_item).to have_text(user_xss.username) end - describe 'assignees' do - it 'does not wrap with quotes for assignee values' do - fill_in 'Comment', with: "@#{user.username}" - - find_highlighted_autocomplete_item.click - - expect(find_field('Comment').value).to have_text("@#{user.username}") - end - - it 'includes items for assignee dropdowns with non-ASCII characters in name' do - fill_in 'Comment', with: "@#{user.name[0...8]}" - - wait_for_requests - - expect(find_autocomplete_menu).to have_text(user.name) - end - - it 'searches across full name for assignees' do - fill_in 'Comment', with: '@speciąlsome' + it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do + milestone_xss_title = 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' + create(:milestone, project: project, title: milestone_xss_title) - wait_for_requests + fill_in 'Comment', with: '%' - expect(find_highlighted_autocomplete_item).to have_text(user.name) - end - - it 'shows names that start with the query as the top result' do - fill_in 'Comment', with: '@mar' - - wait_for_requests - - expect(find_highlighted_autocomplete_item).to have_text(user2.name) - end - - it 'shows usernames that start with the query as the top result' do - fill_in 'Comment', with: '@msi' - - wait_for_requests - - expect(find_highlighted_autocomplete_item).to have_text(user2.name) - end - - # Regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/321925 - it 'shows username when pasting then pressing Enter' do - fill_in 'Comment', with: "@#{user.username}\n" - - expect(find_field('Comment').value).to have_text "@#{user.username}" - end - - it 'does not show `@undefined` when pressing `@` then Enter' do - fill_in 'Comment', with: "@\n" - - expect(find_field('Comment').value).to have_text '@' - expect(find_field('Comment').value).not_to have_text '@undefined' - end - - context 'when /assign quick action is selected' do - it 'triggers user autocomplete and lists users who are currently not assigned to the issue' do - fill_in 'Comment', with: '/as' - - find_highlighted_autocomplete_item.click + wait_for_requests - expect(find_autocomplete_menu).not_to have_text(user.username) - expect(find_autocomplete_menu).to have_text(user2.username) - end - end + expect(find_autocomplete_menu).to have_text('alert milestone') end - context 'if a selected value has special characters' do - it 'wraps the result in double quotes' do - fill_in 'Comment', with: "~#{label.title[0..2]}" - - find_highlighted_autocomplete_item.click - - expect(find_field('Comment').value).to have_text("~\"#{label.title}\"") - end - - it 'doesn\'t wrap for emoji values' do - fill_in 'Comment', with: ':cartwheel_' + it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do + fill_in 'Comment', with: '~' - find_highlighted_autocomplete_item.click - - expect(find_field('Comment').value).to have_text('cartwheel_tone1') - end - end - - context 'quick actions' do - it 'does not limit quick actions autocomplete list to 5' do - fill_in 'Comment', with: '/' + wait_for_requests - expect(find_autocomplete_menu).to have_css('li', minimum: 6) - end + expect(find_autocomplete_menu).to have_text('alert label') end + end - context 'labels' do - it 'allows colons when autocompleting scoped labels' do - fill_in 'Comment', with: '~scoped:' - - wait_for_requests - - expect(find_autocomplete_menu).to have_text('scoped::label') - end - - it 'allows spaces when autocompleting multi-word labels' do - fill_in 'Comment', with: '~Accepting merge' - - wait_for_requests - - expect(find_autocomplete_menu).to have_text('Accepting merge requests') - end - - it 'only autocompletes the last label' do - fill_in 'Comment', with: '~scoped:: foo bar ~Accepting merge' + describe 'autocomplete highlighting' do + it 'auto-selects the first item when there is a query, and only for assignees with no query', :aggregate_failures do + fill_in 'Comment', with: ':' + wait_for_requests + expect(find_autocomplete_menu).not_to have_css('.cur') - wait_for_requests + fill_in 'Comment', with: ':1' + wait_for_requests + expect(find_autocomplete_menu).to have_css('.cur:first-of-type') - expect(find_autocomplete_menu).to have_text('Accepting merge requests') - end + fill_in 'Comment', with: '@' + wait_for_requests + expect(find_autocomplete_menu).to have_css('.cur:first-of-type') + end + end - it 'does not autocomplete labels if no tilde is typed' do - fill_in 'Comment', with: 'Accepting merge' + describe 'assignees' do + it 'does not wrap with quotes for assignee values' do + fill_in 'Comment', with: "@#{user.username}" - wait_for_requests + find_highlighted_autocomplete_item.click - expect(page).not_to have_css('.atwho-view') - end + expect(find_field('Comment').value).to have_text("@#{user.username}") end - context 'when other notes are destroyed' do - let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } + it 'includes items for assignee dropdowns with non-ASCII characters in name' do + fill_in 'Comment', with: "@#{user.name[0...8]}" - # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729 - it 'keeps autocomplete key listeners' do - note = find_field('Comment') + wait_for_requests - start_comment_with_emoji(note, '.atwho-view li') + expect(find_autocomplete_menu).to have_text(user.name) + end - start_and_cancel_discussion + it 'searches across full name for assignees' do + fill_in 'Comment', with: '@speciąlsome' - note.fill_in(with: '') - start_comment_with_emoji(note, '.atwho-view li') - note.native.send_keys(:enter) + wait_for_requests - expect(note.value).to eql('Hello :100: ') - end + expect(find_highlighted_autocomplete_item).to have_text(user.name) end - shared_examples 'autocomplete suggestions' do - it 'suggests objects correctly' do - fill_in 'Comment', with: object.class.reference_prefix + it 'shows names that start with the query as the top result' do + fill_in 'Comment', with: '@mar' - find_autocomplete_menu.find('li').click + wait_for_requests - expect(find_field('Comment').value).to have_text(expected_body) - end + expect(find_highlighted_autocomplete_item).to have_text(user2.name) end - context 'issues' do - let(:object) { issue } - let(:expected_body) { object.to_reference } - - it_behaves_like 'autocomplete suggestions' - end + it 'shows usernames that start with the query as the top result' do + fill_in 'Comment', with: '@msi' - context 'merge requests' do - let(:object) { create(:merge_request, source_project: project) } - let(:expected_body) { object.to_reference } + wait_for_requests - it_behaves_like 'autocomplete suggestions' + expect(find_highlighted_autocomplete_item).to have_text(user2.name) end - context 'project snippets' do - let!(:object) { snippet } - let(:expected_body) { object.to_reference } + # Regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/321925 + it 'shows username when pasting then pressing Enter' do + fill_in 'Comment', with: "@#{user.username}\n" - it_behaves_like 'autocomplete suggestions' + expect(find_field('Comment').value).to have_text "@#{user.username}" end - context 'milestone' do - let_it_be(:milestone_expired) { create(:milestone, project: project, due_date: 5.days.ago) } - let_it_be(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') } - let_it_be(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) } - let_it_be(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) } - let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) } - - before do - fill_in 'Comment', with: '/milestone %' + it 'does not show `@undefined` when pressing `@` then Enter' do + fill_in 'Comment', with: "@\n" - wait_for_requests - end + expect(find_field('Comment').value).to have_text '@' + expect(find_field('Comment').value).not_to have_text '@undefined' + end - it 'shows milestons list in the autocomplete menu' do - page.within(find_autocomplete_menu) do - expect(page).to have_selector('li', count: 5) - end - end + context 'when /assign quick action is selected' do + it 'triggers user autocomplete and lists users who are currently not assigned to the issue' do + fill_in 'Comment', with: '/as' - it 'shows expired milestone at the bottom of the list' do - page.within(find_autocomplete_menu) do - expect(page.find('li:last-child')).to have_content milestone_expired.title - end - end + find_highlighted_autocomplete_item.click - it 'shows milestone due earliest at the top of the list' do - page.within(find_autocomplete_menu) do - aggregate_failures do - expect(page.all('li')[0]).to have_content milestone3.title - expect(page.all('li')[1]).to have_content milestone2.title - expect(page.all('li')[2]).to have_content milestone1.title - expect(page.all('li')[3]).to have_content milestone_no_duedate.title - end - end + expect(find_autocomplete_menu).not_to have_text(user.username) + expect(find_autocomplete_menu).to have_text(user2.username) end end end - end - describe 'when tribute_autocomplete feature flag is on' do - describe 'issue description' do - let(:issue_to_edit) { create(:issue, project: project) } + context 'if a selected value has special characters' do + it 'wraps the result in double quotes' do + fill_in 'Comment', with: "~#{label.title[0..2]}" - before do - stub_feature_flags(tribute_autocomplete: true) - - sign_in(user) - visit project_issue_path(project, issue_to_edit) + find_highlighted_autocomplete_item.click - wait_for_requests + expect(find_field('Comment').value).to have_text("~\"#{label.title}\"") end - it 'updates with GFM reference' do - click_button 'Edit title and description' - - wait_for_requests - - fill_in 'Description', with: "@#{user.name[0...3]}" - - wait_for_requests - - find_highlighted_tribute_autocomplete_menu.click + it 'doesn\'t wrap for emoji values' do + fill_in 'Comment', with: ':cartwheel_' - click_button 'Save changes' - - wait_for_requests + find_highlighted_autocomplete_item.click - expect(find('.description')).to have_text(user.to_reference) + expect(find_field('Comment').value).to have_text('cartwheel_tone1') end end - describe 'issue comment' do - before do - stub_feature_flags(tribute_autocomplete: true) + context 'quick actions' do + it 'does not limit quick actions autocomplete list to 5' do + fill_in 'Comment', with: '/' - sign_in(user) - visit project_issue_path(project, issue) - - wait_for_requests + expect(find_autocomplete_menu).to have_css('li', minimum: 6) end + end - describe 'triggering autocomplete' do - it 'only opens autocomplete menu when trigger character is after whitespace', :aggregate_failures do - fill_in 'Comment', with: 'testing@' - expect(page).not_to have_css('.tribute-container') - - fill_in 'Comment', with: "hello:#{user.username[0..2]}" - expect(page).not_to have_css('.tribute-container') - - fill_in 'Comment', with: '7:' - expect(page).not_to have_css('.tribute-container') - - fill_in 'Comment', with: 'w:' - expect(page).not_to have_css('.tribute-container') + context 'labels' do + it 'allows colons when autocompleting scoped labels' do + fill_in 'Comment', with: '~scoped:' - fill_in 'Comment', with: 'Ё:' - expect(page).not_to have_css('.tribute-container') + wait_for_requests - fill_in 'Comment', with: "test\n\n@" - expect(find_tribute_autocomplete_menu).to be_visible - end + expect(find_autocomplete_menu).to have_text('scoped::label') end - context 'xss checks' do - it 'opens autocomplete menu for Issues when field starts with text with item escaping HTML characters' do - issue_xss_title = 'This will execute alert<img src=x onerror=alert(2)<img src=x onerror=alert(1)>' - create(:issue, project: project, title: issue_xss_title) - - fill_in 'Comment', with: '#' - - wait_for_requests - - expect(find_tribute_autocomplete_menu).to have_text(issue_xss_title) - end - - it 'opens autocomplete menu for Username when field starts with text with item escaping HTML characters' do - fill_in 'Comment', with: '@ev' - - wait_for_requests - - expect(find_tribute_autocomplete_menu).to have_text(user_xss.username) - end - - it 'opens autocomplete menu for Milestone when field starts with text with item escaping HTML characters' do - milestone_xss_title = 'alert milestone <img src=x onerror="alert(\'Hello xss\');" a' - create(:milestone, project: project, title: milestone_xss_title) + it 'allows spaces when autocompleting multi-word labels' do + fill_in 'Comment', with: '~Accepting merge' - fill_in 'Comment', with: '%' - - wait_for_requests - - expect(find_tribute_autocomplete_menu).to have_text('alert milestone') - end - - it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do - fill_in 'Comment', with: '~' - - wait_for_requests - - expect(find_tribute_autocomplete_menu).to have_text('alert label') - end - end - - describe 'autocomplete highlighting' do - it 'auto-selects the first item with query', :aggregate_failures do - fill_in 'Comment', with: ':1' - wait_for_requests - expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type') + wait_for_requests - fill_in 'Comment', with: '@' - wait_for_requests - expect(find_tribute_autocomplete_menu).to have_css('.highlight:first-of-type') - end + expect(find_autocomplete_menu).to have_text('Accepting merge requests') end - describe 'assignees' do - it 'does not wrap with quotes for assignee values' do - fill_in 'Comment', with: "@#{user.username[0..2]}" - - find_highlighted_tribute_autocomplete_menu.click - - expect(find_field('Comment').value).to have_text("@#{user.username}") - end + it 'only autocompletes the last label' do + fill_in 'Comment', with: '~scoped:: foo bar ~Accepting merge' - it 'includes items for assignee dropdowns with non-ASCII characters in name' do - fill_in 'Comment', with: "@#{user.name[0...8]}" - - wait_for_requests - - expect(find_tribute_autocomplete_menu).to have_text(user.name) - end - - context 'when autocompleting for groups' do - it 'shows the group when searching for the name of the group' do - fill_in 'Comment', with: '@mygroup' - - expect(find_tribute_autocomplete_menu).to have_text('My group') - end + wait_for_requests - it 'does not show the group when searching for the name of the parent of the group' do - fill_in 'Comment', with: '@ancestor' + expect(find_autocomplete_menu).to have_text('Accepting merge requests') + end - expect(find_tribute_autocomplete_menu).not_to have_text('My group') - end - end + it 'does not autocomplete labels if no tilde is typed' do + fill_in 'Comment', with: 'Accepting merge' - context 'when /assign quick action is selected' do - it 'lists users who are currently not assigned to the issue' do - note = find_field('Comment') - note.native.send_keys('/assign ') - # The `/assign` ajax response might replace the one by `@` below causing a failed test - # so we need to wait for the `/assign` ajax request to finish first - wait_for_requests - note.native.send_keys('@') - wait_for_requests - - expect(find_tribute_autocomplete_menu).not_to have_text(user.username) - expect(find_tribute_autocomplete_menu).to have_text(user2.username) - end + wait_for_requests - it 'lists users who are currently not assigned to the issue when using /assign on the second line' do - note = find_field('Comment') - note.native.send_keys('/assign @user2') - note.native.send_keys(:enter) - note.native.send_keys('/assign ') - # The `/assign` ajax response might replace the one by `@` below causing a failed test - # so we need to wait for the `/assign` ajax request to finish first - wait_for_requests - note.native.send_keys('@') - wait_for_requests - - expect(find_tribute_autocomplete_menu).not_to have_text(user.username) - expect(find_tribute_autocomplete_menu).to have_text(user2.username) - end - end + expect(page).not_to have_css('.atwho-view') end + end - context 'if a selected value has special characters' do - it 'wraps the result in double quotes' do - fill_in 'Comment', with: "~#{label.title[0..2]}" + context 'when other notes are destroyed' do + let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } - find_highlighted_tribute_autocomplete_menu.click + # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729 + it 'keeps autocomplete key listeners' do + note = find_field('Comment') - expect(find_field('Comment').value).to have_text("~\"#{label.title}\"") - end + start_comment_with_emoji(note, '.atwho-view li') - it 'does not wrap for emoji values' do - fill_in 'Comment', with: ':cartwheel_' + start_and_cancel_discussion - find_highlighted_tribute_autocomplete_menu.click + note.fill_in(with: '') + start_comment_with_emoji(note, '.atwho-view li') + note.native.send_keys(:enter) - expect(find_field('Comment').value).to have_text('cartwheel_tone1') - end + expect(note.value).to eql('Hello :100: ') end + end - context 'quick actions' do - it 'autocompletes for quick actions' do - fill_in 'Comment', with: '/as' + shared_examples 'autocomplete suggestions' do + it 'suggests objects correctly' do + fill_in 'Comment', with: object.class.reference_prefix - find_highlighted_tribute_autocomplete_menu.click + find_autocomplete_menu.find('li').click - expect(find_field('Comment').value).to have_text('/assign') - end + expect(find_field('Comment').value).to have_text(expected_body) end + end - context 'labels' do - it 'allows colons when autocompleting scoped labels' do - fill_in 'Comment', with: '~scoped:' - - wait_for_requests - - expect(find_tribute_autocomplete_menu).to have_text('scoped::label') - end - - it 'autocompletes multi-word labels' do - fill_in 'Comment', with: '~Acceptingmerge' + context 'issues' do + let(:object) { issue } + let(:expected_body) { object.to_reference } - wait_for_requests + it_behaves_like 'autocomplete suggestions' + end - expect(find_tribute_autocomplete_menu).to have_text('Accepting merge requests') - end + context 'merge requests' do + let(:object) { create(:merge_request, source_project: project) } + let(:expected_body) { object.to_reference } - it 'only autocompletes the last label' do - fill_in 'Comment', with: '~scoped:: foo bar ~Acceptingmerge' - # Invoke autocompletion - find_field('Comment').native.send_keys(:right) + it_behaves_like 'autocomplete suggestions' + end - wait_for_requests + context 'project snippets' do + let!(:object) { snippet } + let(:expected_body) { object.to_reference } - expect(find_tribute_autocomplete_menu).to have_text('Accepting merge requests') - end + it_behaves_like 'autocomplete suggestions' + end - it 'does not autocomplete labels if no tilde is typed' do - fill_in 'Comment', with: 'Accepting' + context 'milestone' do + let_it_be(:milestone_expired) { create(:milestone, project: project, due_date: 5.days.ago) } + let_it_be(:milestone_no_duedate) { create(:milestone, project: project, title: 'Foo - No due date') } + let_it_be(:milestone1) { create(:milestone, project: project, title: 'Milestone-1', due_date: 20.days.from_now) } + let_it_be(:milestone2) { create(:milestone, project: project, title: 'Milestone-2', due_date: 15.days.from_now) } + let_it_be(:milestone3) { create(:milestone, project: project, title: 'Milestone-3', due_date: 10.days.from_now) } - wait_for_requests + before do + fill_in 'Comment', with: '/milestone %' - expect(page).not_to have_css('.tribute-container') - end + wait_for_requests end - context 'when other notes are destroyed' do - let!(:discussion) { create(:discussion_note_on_issue, noteable: issue, project: issue.project) } - - # This is meant to protect against this issue https://gitlab.com/gitlab-org/gitlab/-/issues/228729 - it 'keeps autocomplete key listeners' do - note = find_field('Comment') - - start_comment_with_emoji(note, '.tribute-container li') - - start_and_cancel_discussion - - note.fill_in(with: '') - start_comment_with_emoji(note, '.tribute-container li') - note.native.send_keys(:enter) - - expect(note.value).to eql('Hello :100: ') + it 'shows milestons list in the autocomplete menu' do + page.within(find_autocomplete_menu) do + expect(page).to have_selector('li', count: 5) end end - shared_examples 'autocomplete suggestions' do - it 'suggests objects correctly' do - fill_in 'Comment', with: object.class.reference_prefix - - find_tribute_autocomplete_menu.find('li').click - - expect(find_field('Comment').value).to have_text(expected_body) + it 'shows expired milestone at the bottom of the list' do + page.within(find_autocomplete_menu) do + expect(page.find('li:last-child')).to have_content milestone_expired.title end end - context 'issues' do - let(:object) { issue } - let(:expected_body) { object.to_reference } - - it_behaves_like 'autocomplete suggestions' - end - - context 'merge requests' do - let(:object) { create(:merge_request, source_project: project) } - let(:expected_body) { object.to_reference } - - it_behaves_like 'autocomplete suggestions' - end - - context 'project snippets' do - let!(:object) { snippet } - let(:expected_body) { object.to_reference } - - it_behaves_like 'autocomplete suggestions' - end - - context 'milestone' do - let!(:object) { create(:milestone, project: project) } - let(:expected_body) { object.to_reference } - - it_behaves_like 'autocomplete suggestions' + it 'shows milestone due earliest at the top of the list' do + page.within(find_autocomplete_menu) do + aggregate_failures do + expect(page.all('li')[0]).to have_content milestone3.title + expect(page.all('li')[1]).to have_content milestone2.title + expect(page.all('li')[2]).to have_content milestone1.title + expect(page.all('li')[3]).to have_content milestone_no_duedate.title + end + end end end end @@ -723,12 +411,4 @@ RSpec.describe 'GFM autocomplete', :js do def find_highlighted_autocomplete_item find('.atwho-view li.cur', visible: true) end - - def find_tribute_autocomplete_menu - find('.tribute-container ul', visible: true) - end - - def find_highlighted_tribute_autocomplete_menu - find('.tribute-container li.highlight', visible: true) - end end diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb index 5d03aa1fc2b..a719263f092 100644 --- a/spec/features/issues/user_comments_on_issue_spec.rb +++ b/spec/features/issues/user_comments_on_issue_spec.rb @@ -10,7 +10,6 @@ RSpec.describe "User comments on issue", :js do let(:user) { create(:user) } before do - stub_feature_flags(tribute_autocomplete: false) stub_feature_flags(sandboxed_mermaid: false) project.add_guest(user) sign_in(user) diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb index cc805e7d369..b2739454b52 100644 --- a/spec/features/participants_autocomplete_spec.rb +++ b/spec/features/participants_autocomplete_spec.rb @@ -33,31 +33,12 @@ RSpec.describe 'Member autocomplete', :js do let(:noteable) { create(:issue, author: author, project: project) } before do - stub_feature_flags(tribute_autocomplete: false) visit project_issue_path(project, noteable) end include_examples "open suggestions when typing @", 'issue' end - describe 'when tribute_autocomplete feature flag is on' do - context 'adding a new note on a Issue' do - let(:noteable) { create(:issue, author: author, project: project) } - - before do - stub_feature_flags(tribute_autocomplete: true) - visit project_issue_path(project, noteable) - - fill_in 'Comment', with: '@' - end - - it 'suggests noteable author and note author' do - expect(find_tribute_autocomplete_menu).to have_content(author.username) - expect(find_tribute_autocomplete_menu).to have_content(note.author.username) - end - end - end - context 'adding a new note on a Merge Request' do let(:noteable) do create(:merge_request, source_project: project, @@ -91,8 +72,4 @@ RSpec.describe 'Member autocomplete', :js do def find_autocomplete_menu find('.atwho-view ul', visible: true) end - - def find_tribute_autocomplete_menu - find('.tribute-container ul', visible: true) - end end diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap b/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap deleted file mode 100644 index 370b6eb01bc..00000000000 --- a/spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap +++ /dev/null @@ -1,54 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`gfm_autocomplete/utils emojis config shows the emoji name and icon in the menu item 1`] = `"raised_hands <gl-emoji data-name=\\"raised_hands\\"></gl-emoji>"`; - -exports[`gfm_autocomplete/utils issues config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context issue title <script>alert('hi')</script>"`; - -exports[`gfm_autocomplete/utils issues config shows the reference and title in the menu item within a group context 1`] = `"<small>gitlab#987654</small> Group context issue title <script>alert('hi')</script>"`; - -exports[`gfm_autocomplete/utils labels config shows the title in the menu item 1`] = ` -" - <span class=\\"dropdown-label-box\\" style=\\"background: #123456;\\"></span> - bug <script>alert('hi')</script>" -`; - -exports[`gfm_autocomplete/utils members config shows an avatar character, name, parent name, and count in the menu item for a group 1`] = ` -" - <div class=\\"gl-display-flex gl-align-items-center\\"> - <div class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-rounded-small - gl-display-flex gl-align-items-center gl-justify-content-center\\" aria-hidden=\\"true\\"> - G</div> - <div class=\\"gl-line-height-normal gl-ml-4\\"> - <div>1-1s <script>alert('hi')</script> (2)</div> - <div class=\\"gl-text-gray-700\\">GitLab Support Team</div> - </div> - - </div> - " -`; - -exports[`gfm_autocomplete/utils members config shows the avatar, name and username in the menu item for a user 1`] = ` -" - <div class=\\"gl-display-flex gl-align-items-center\\"> - <img class=\\"gl-avatar gl-avatar-s32 gl-flex-shrink-0 gl-avatar-circle\\" src=\\"/uploads/-/system/user/avatar/123456/avatar.png\\" alt=\\"\\" /> - <div class=\\"gl-line-height-normal gl-ml-4\\"> - <div>My Name <script>alert('hi')</script></div> - <div class=\\"gl-text-gray-700\\">@myusername</div> - </div> - - </div> - " -`; - -exports[`gfm_autocomplete/utils merge requests config shows the iid and title in the menu item within a project context 1`] = `"<small>123456</small> Project context merge request title <script>alert('hi')</script>"`; - -exports[`gfm_autocomplete/utils merge requests config shows the reference and title in the menu item within a group context 1`] = `"<small>gitlab!456789</small> Group context merge request title <script>alert('hi')</script>"`; - -exports[`gfm_autocomplete/utils milestones config shows the title in the menu item 1`] = `"13.2 <script>alert('hi')</script>"`; - -exports[`gfm_autocomplete/utils quick actions config shows the name, aliases, params and description in the menu item 1`] = ` -"<div>/unlabel <small>(or /remove_label)</small> <small>~label1 ~\\"label 2\\"</small></div> - <div><small><em>Remove all or specific label(s)</em></small></div>" -`; - -exports[`gfm_autocomplete/utils snippets config shows the id and title in the menu item 1`] = `"<small>123456</small> Snippet title <script>alert('hi')</script>"`; diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js b/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js deleted file mode 100644 index b4002fdf4ec..00000000000 --- a/spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js +++ /dev/null @@ -1,34 +0,0 @@ -import Tribute from '@gitlab/tributejs'; -import { shallowMount } from '@vue/test-utils'; -import GfmAutocomplete from '~/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue'; - -describe('GfmAutocomplete', () => { - let wrapper; - - describe('tribute', () => { - const mentions = '/gitlab-org/gitlab-test/-/autocomplete_sources/members?type=Issue&type_id=1'; - - beforeEach(() => { - wrapper = shallowMount(GfmAutocomplete, { - propsData: { - dataSources: { - mentions, - }, - }, - slots: { - default: ['<input/>'], - }, - }); - }); - - it('is set to tribute instance variable', () => { - expect(wrapper.vm.tribute instanceof Tribute).toBe(true); - }); - - it('contains the slot input element', () => { - wrapper.find('input').setValue('@'); - - expect(wrapper.vm.tribute.current.element).toBe(wrapper.find('input').element); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js b/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js deleted file mode 100644 index 7ec3fbd4e3b..00000000000 --- a/spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js +++ /dev/null @@ -1,427 +0,0 @@ -import { escape, last } from 'lodash'; -import { GfmAutocompleteType, tributeConfig } from '~/vue_shared/components/gfm_autocomplete/utils'; - -describe('gfm_autocomplete/utils', () => { - describe('emojis config', () => { - const emojisConfig = tributeConfig[GfmAutocompleteType.Emojis].config; - const emoji = 'raised_hands'; - - it('uses : as the trigger', () => { - expect(emojisConfig.trigger).toBe(':'); - }); - - it('searches using the emoji name', () => { - expect(emojisConfig.lookup(emoji)).toBe(emoji); - }); - - it('limits the number of rendered items to 100', () => { - expect(emojisConfig.menuItemLimit).toBe(100); - }); - - it('shows the emoji name and icon in the menu item', () => { - expect(emojisConfig.menuItemTemplate({ original: emoji })).toMatchSnapshot(); - }); - - it('inserts the emoji name on autocomplete selection', () => { - expect(emojisConfig.selectTemplate({ original: emoji })).toBe(`:${emoji}:`); - }); - }); - - describe('issues config', () => { - const issuesConfig = tributeConfig[GfmAutocompleteType.Issues].config; - const groupContextIssue = { - iid: 987654, - reference: 'gitlab#987654', - title: "Group context issue title <script>alert('hi')</script>", - }; - const projectContextIssue = { - id: null, - iid: 123456, - time_estimate: 0, - title: "Project context issue title <script>alert('hi')</script>", - }; - - it('uses # as the trigger', () => { - expect(issuesConfig.trigger).toBe('#'); - }); - - it('searches using both the iid and title', () => { - expect(issuesConfig.lookup(projectContextIssue)).toBe( - `${projectContextIssue.iid}${projectContextIssue.title}`, - ); - }); - - it('limits the number of rendered items to 100', () => { - expect(issuesConfig.menuItemLimit).toBe(100); - }); - - it('shows the reference and title in the menu item within a group context', () => { - expect(issuesConfig.menuItemTemplate({ original: groupContextIssue })).toMatchSnapshot(); - }); - - it('shows the iid and title in the menu item within a project context', () => { - expect(issuesConfig.menuItemTemplate({ original: projectContextIssue })).toMatchSnapshot(); - }); - - it('inserts the reference on autocomplete selection within a group context', () => { - expect(issuesConfig.selectTemplate({ original: groupContextIssue })).toBe( - groupContextIssue.reference, - ); - }); - - it('inserts the iid on autocomplete selection within a project context', () => { - expect(issuesConfig.selectTemplate({ original: projectContextIssue })).toBe( - `#${projectContextIssue.iid}`, - ); - }); - }); - - describe('labels config', () => { - const labelsConfig = tributeConfig[GfmAutocompleteType.Labels].config; - const labelsFilter = tributeConfig[GfmAutocompleteType.Labels].filterValues; - const label = { - color: '#123456', - textColor: '#FFFFFF', - title: `bug <script>alert('hi')</script>`, - type: 'GroupLabel', - }; - const singleWordLabel = { - color: '#456789', - textColor: '#DDD', - title: `bug`, - type: 'GroupLabel', - }; - const numericalLabel = { - color: '#abcdef', - textColor: '#AAA', - title: 123456, - type: 'ProjectLabel', - }; - - it('uses ~ as the trigger', () => { - expect(labelsConfig.trigger).toBe('~'); - }); - - it('searches using `title`', () => { - expect(labelsConfig.lookup).toBe('title'); - }); - - it('limits the number of rendered items to 100', () => { - expect(labelsConfig.menuItemLimit).toBe(100); - }); - - it('shows the title in the menu item', () => { - expect(labelsConfig.menuItemTemplate({ original: label })).toMatchSnapshot(); - }); - - it('inserts the title on autocomplete selection', () => { - expect(labelsConfig.selectTemplate({ original: singleWordLabel })).toBe( - `~${escape(singleWordLabel.title)}`, - ); - }); - - it('inserts the title enclosed with quotes on autocomplete selection when the title is numerical', () => { - expect(labelsConfig.selectTemplate({ original: numericalLabel })).toBe( - `~"${escape(numericalLabel.title)}"`, - ); - }); - - it('inserts the title enclosed with quotes on autocomplete selection when the title contains multiple words', () => { - expect(labelsConfig.selectTemplate({ original: label })).toBe(`~"${escape(label.title)}"`); - }); - - describe('filter', () => { - const collection = [label, singleWordLabel, { ...numericalLabel, set: true }]; - - describe('/label quick action', () => { - describe('when the line starts with `/label`', () => { - it('shows labels that are not currently selected', () => { - const fullText = '/label ~'; - const selectionStart = 8; - - expect(labelsFilter({ collection, fullText, selectionStart })).toEqual([ - collection[0], - collection[1], - ]); - }); - }); - - describe('when the line does not start with `/label`', () => { - it('shows all labels', () => { - const fullText = '~'; - const selectionStart = 1; - - expect(labelsFilter({ collection, fullText, selectionStart })).toEqual(collection); - }); - }); - }); - - describe('/unlabel quick action', () => { - describe('when the line starts with `/unlabel`', () => { - it('shows labels that are currently selected', () => { - const fullText = '/unlabel ~'; - const selectionStart = 10; - - expect(labelsFilter({ collection, fullText, selectionStart })).toEqual([collection[2]]); - }); - }); - - describe('when the line does not start with `/unlabel`', () => { - it('shows all labels', () => { - const fullText = '~'; - const selectionStart = 1; - - expect(labelsFilter({ collection, fullText, selectionStart })).toEqual(collection); - }); - }); - }); - }); - }); - - describe('members config', () => { - const membersConfig = tributeConfig[GfmAutocompleteType.Members].config; - const membersFilter = tributeConfig[GfmAutocompleteType.Members].filterValues; - const userMember = { - type: 'User', - username: 'myusername', - name: "My Name <script>alert('hi')</script>", - avatar_url: '/uploads/-/system/user/avatar/123456/avatar.png', - availability: null, - }; - const groupMember = { - type: 'Group', - username: 'gitlab-com/support/1-1s', - name: "GitLab.com / GitLab Support Team / 1-1s <script>alert('hi')</script>", - avatar_url: null, - count: 2, - mentionsDisabled: null, - }; - - it('uses @ as the trigger', () => { - expect(membersConfig.trigger).toBe('@'); - }); - - it('inserts the username on autocomplete selection', () => { - expect(membersConfig.fillAttr).toBe('username'); - }); - - it('searches using both the name and username for a user', () => { - expect(membersConfig.lookup(userMember)).toBe(`${userMember.name}${userMember.username}`); - }); - - it('searches using only its own name and not its ancestors for a group', () => { - expect(membersConfig.lookup(groupMember)).toBe(last(groupMember.name.split(' / '))); - }); - - it('limits the items in the autocomplete menu to 10', () => { - expect(membersConfig.menuItemLimit).toBe(10); - }); - - it('shows the avatar, name and username in the menu item for a user', () => { - expect(membersConfig.menuItemTemplate({ original: userMember })).toMatchSnapshot(); - }); - - it('shows an avatar character, name, parent name, and count in the menu item for a group', () => { - expect(membersConfig.menuItemTemplate({ original: groupMember })).toMatchSnapshot(); - }); - - describe('filter', () => { - const assignees = [userMember.username]; - const collection = [userMember, groupMember]; - - describe('/assign quick action', () => { - describe('when the line starts with `/assign`', () => { - it('shows members that are not currently selected', () => { - const fullText = '/assign @'; - const selectionStart = 9; - - expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual([ - collection[1], - ]); - }); - }); - - describe('when the line does not start with `/assign`', () => { - it('shows all labels', () => { - const fullText = '@'; - const selectionStart = 1; - - expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual( - collection, - ); - }); - }); - }); - - describe('/unassign quick action', () => { - describe('when the line starts with `/unassign`', () => { - it('shows members that are currently selected', () => { - const fullText = '/unassign @'; - const selectionStart = 11; - - expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual([ - collection[0], - ]); - }); - }); - - describe('when the line does not start with `/unassign`', () => { - it('shows all members', () => { - const fullText = '@'; - const selectionStart = 1; - - expect(membersFilter({ assignees, collection, fullText, selectionStart })).toEqual( - collection, - ); - }); - }); - }); - }); - }); - - describe('merge requests config', () => { - const mergeRequestsConfig = tributeConfig[GfmAutocompleteType.MergeRequests].config; - const groupContextMergeRequest = { - iid: 456789, - reference: 'gitlab!456789', - title: "Group context merge request title <script>alert('hi')</script>", - }; - const projectContextMergeRequest = { - id: null, - iid: 123456, - time_estimate: 0, - title: "Project context merge request title <script>alert('hi')</script>", - }; - - it('uses ! as the trigger', () => { - expect(mergeRequestsConfig.trigger).toBe('!'); - }); - - it('searches using both the iid and title', () => { - expect(mergeRequestsConfig.lookup(projectContextMergeRequest)).toBe( - `${projectContextMergeRequest.iid}${projectContextMergeRequest.title}`, - ); - }); - - it('limits the number of rendered items to 100', () => { - expect(mergeRequestsConfig.menuItemLimit).toBe(100); - }); - - it('shows the reference and title in the menu item within a group context', () => { - expect( - mergeRequestsConfig.menuItemTemplate({ original: groupContextMergeRequest }), - ).toMatchSnapshot(); - }); - - it('shows the iid and title in the menu item within a project context', () => { - expect( - mergeRequestsConfig.menuItemTemplate({ original: projectContextMergeRequest }), - ).toMatchSnapshot(); - }); - - it('inserts the reference on autocomplete selection within a group context', () => { - expect(mergeRequestsConfig.selectTemplate({ original: groupContextMergeRequest })).toBe( - groupContextMergeRequest.reference, - ); - }); - - it('inserts the iid on autocomplete selection within a project context', () => { - expect(mergeRequestsConfig.selectTemplate({ original: projectContextMergeRequest })).toBe( - `!${projectContextMergeRequest.iid}`, - ); - }); - }); - - describe('milestones config', () => { - const milestonesConfig = tributeConfig[GfmAutocompleteType.Milestones].config; - const milestone = { - id: null, - iid: 49, - title: "13.2 <script>alert('hi')</script>", - }; - - it('uses % as the trigger', () => { - expect(milestonesConfig.trigger).toBe('%'); - }); - - it('searches using the title', () => { - expect(milestonesConfig.lookup).toBe('title'); - }); - - it('limits the number of rendered items to 100', () => { - expect(milestonesConfig.menuItemLimit).toBe(100); - }); - - it('shows the title in the menu item', () => { - expect(milestonesConfig.menuItemTemplate({ original: milestone })).toMatchSnapshot(); - }); - - it('inserts the title on autocomplete selection', () => { - expect(milestonesConfig.selectTemplate({ original: milestone })).toBe( - `%"${escape(milestone.title)}"`, - ); - }); - }); - - describe('quick actions config', () => { - const quickActionsConfig = tributeConfig[GfmAutocompleteType.QuickActions].config; - const quickAction = { - name: 'unlabel', - aliases: ['remove_label'], - description: 'Remove all or specific label(s)', - warning: '', - icon: '', - params: ['~label1 ~"label 2"'], - }; - - it('uses / as the trigger', () => { - expect(quickActionsConfig.trigger).toBe('/'); - }); - - it('inserts the name on autocomplete selection', () => { - expect(quickActionsConfig.fillAttr).toBe('name'); - }); - - it('searches using both the name and aliases', () => { - expect(quickActionsConfig.lookup(quickAction)).toBe( - `${quickAction.name}${quickAction.aliases.join(', /')}`, - ); - }); - - it('limits the number of rendered items to 100', () => { - expect(quickActionsConfig.menuItemLimit).toBe(100); - }); - - it('shows the name, aliases, params and description in the menu item', () => { - expect(quickActionsConfig.menuItemTemplate({ original: quickAction })).toMatchSnapshot(); - }); - }); - - describe('snippets config', () => { - const snippetsConfig = tributeConfig[GfmAutocompleteType.Snippets].config; - const snippet = { - id: 123456, - title: "Snippet title <script>alert('hi')</script>", - }; - - it('uses $ as the trigger', () => { - expect(snippetsConfig.trigger).toBe('$'); - }); - - it('inserts the id on autocomplete selection', () => { - expect(snippetsConfig.fillAttr).toBe('id'); - }); - - it('searches using both the id and title', () => { - expect(snippetsConfig.lookup(snippet)).toBe(`${snippet.id}${snippet.title}`); - }); - - it('limits the number of rendered items to 100', () => { - expect(snippetsConfig.menuItemLimit).toBe(100); - }); - - it('shows the id and title in the menu item', () => { - expect(snippetsConfig.menuItemTemplate({ original: snippet })).toMatchSnapshot(); - }); - }); -}); diff --git a/spec/frontend/vue_shared/components/help_popover_spec.js b/spec/frontend/vue_shared/components/help_popover_spec.js index 30c6fa04032..597fb63d95c 100644 --- a/spec/frontend/vue_shared/components/help_popover_spec.js +++ b/spec/frontend/vue_shared/components/help_popover_spec.js @@ -9,59 +9,117 @@ describe('HelpPopover', () => { const findQuestionButton = () => wrapper.find(GlButton); const findPopover = () => wrapper.find(GlPopover); - const buildWrapper = (options = {}) => { + + const createComponent = ({ props, ...opts } = {}) => { wrapper = mount(HelpPopover, { propsData: { options: { title, content, - ...options, }, + ...props, }, + ...opts, }); }; - beforeEach(() => { - buildWrapper(); - }); - afterEach(() => { wrapper.destroy(); }); - it('renders a link button with an icon question', () => { - expect(findQuestionButton().props()).toMatchObject({ - icon: 'question', - variant: 'link', + describe('with title and content', () => { + beforeEach(() => { + createComponent(); }); - }); - it('renders popover that uses the question button as target', () => { - expect(findPopover().props().target()).toBe(findQuestionButton().vm.$el); - }); + it('renders a link button with an icon question', () => { + expect(findQuestionButton().props()).toMatchObject({ + icon: 'question', + variant: 'link', + }); + }); - it('allows rendering title with HTML tags', () => { - expect(findPopover().find('strong').exists()).toBe(true); - }); + it('renders popover that uses the question button as target', () => { + expect(findPopover().props().target()).toBe(findQuestionButton().vm.$el); + }); - it('allows rendering content with HTML tags', () => { - expect(findPopover().find('b').exists()).toBe(true); + it('shows title and content', () => { + expect(findPopover().html()).toContain(title); + expect(findPopover().html()).toContain(content); + }); + + it('allows rendering title with HTML tags', () => { + expect(findPopover().find('strong').exists()).toBe(true); + }); + + it('allows rendering content with HTML tags', () => { + expect(findPopover().find('b').exists()).toBe(true); + }); }); describe('without title', () => { - it('does not render title', () => { - buildWrapper({ title: null }); + beforeEach(() => { + createComponent({ + props: { + options: { + title: null, + content, + }, + }, + }); + }); + + it('does not show title', () => { + expect(findPopover().html()).not.toContain(title); + }); - expect(findPopover().find('span').exists()).toBe(false); + it('shows content', () => { + expect(findPopover().html()).toContain(content); }); }); - it('binds other popover options to the popover instance', () => { + describe('with other options', () => { const placement = 'bottom'; - wrapper.destroy(); - buildWrapper({ placement }); + beforeEach(() => { + createComponent({ + props: { + options: { + placement, + }, + }, + }); + }); + + it('options bind to the popover', () => { + expect(findPopover().props().placement).toBe(placement); + }); + }); + + describe('with custom slots', () => { + const titleSlot = '<h1>title</h1>'; + const defaultSlot = '<strong>content</strong>'; - expect(findPopover().props().placement).toBe(placement); + beforeEach(() => { + createComponent({ + slots: { + title: titleSlot, + default: defaultSlot, + }, + }); + }); + + it('shows title slot', () => { + expect(findPopover().html()).toContain(titleSlot); + }); + + it('shows default content slot', () => { + expect(findPopover().html()).toContain(defaultSlot); + }); + + it('overrides title and content from options', () => { + expect(findPopover().html()).not.toContain(title); + expect(findPopover().html()).toContain(content); + }); }); }); diff --git a/spec/graphql/resolvers/ci/project_pipeline_counts_resolver_spec.rb b/spec/graphql/resolvers/ci/project_pipeline_counts_resolver_spec.rb new file mode 100644 index 00000000000..07b4a5509b2 --- /dev/null +++ b/spec/graphql/resolvers/ci/project_pipeline_counts_resolver_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Ci::ProjectPipelineCountsResolver do + include GraphqlHelpers + + let(:current_user) { create(:user) } + + let_it_be(:project) { create(:project, :private) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) } + let_it_be(:success_pipeline) { create(:ci_pipeline, :success, project: project) } + let_it_be(:ref_pipeline) { create(:ci_pipeline, project: project, ref: 'awesome-feature') } + let_it_be(:sha_pipeline) { create(:ci_pipeline, :running, project: project, sha: 'deadbeef') } + let_it_be(:on_demand_dast_scan) { create(:ci_pipeline, :success, project: project, source: 'ondemand_dast_scan') } + + before do + project.add_developer(current_user) + end + + describe '#resolve' do + it 'counts pipelines' do + expect(resolve_pipeline_counts).to have_attributes( + all: 6, + finished: 3, + running: 1, + pending: 2 + ) + end + + it 'counts by ref' do + expect(resolve_pipeline_counts(ref: "awesome-feature")).to have_attributes( + all: 1, + finished: 0, + running: 0, + pending: 1 + ) + end + + it 'counts by sha' do + expect(resolve_pipeline_counts(sha: "deadbeef")).to have_attributes( + all: 1, + finished: 0, + running: 1, + pending: 0 + ) + end + + it 'counts by source' do + expect(resolve_pipeline_counts(source: "ondemand_dast_scan")).to have_attributes( + all: 1, + finished: 1, + running: 0, + pending: 0 + ) + end + end + + def resolve_pipeline_counts(args = {}, context = { current_user: current_user }) + resolve(described_class, obj: project, args: args, ctx: context) + end +end diff --git a/spec/graphql/types/ci/pipeline_counts_type_spec.rb b/spec/graphql/types/ci/pipeline_counts_type_spec.rb new file mode 100644 index 00000000000..7fdb286d253 --- /dev/null +++ b/spec/graphql/types/ci/pipeline_counts_type_spec.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['PipelineCounts'] do + include GraphqlHelpers + + let(:current_user) { create(:user) } + + let_it_be(:project) { create(:project, :private) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) } + let_it_be(:success_pipeline) { create(:ci_pipeline, :success, project: project) } + let_it_be(:ref_pipeline) { create(:ci_pipeline, project: project, ref: 'awesome-feature') } + let_it_be(:sha_pipeline) { create(:ci_pipeline, :running, project: project, sha: 'deadbeef') } + let_it_be(:on_demand_dast_scan) { create(:ci_pipeline, :success, project: project, source: 'ondemand_dast_scan') } + + before do + project.add_developer(current_user) + end + + specify { expect(described_class.graphql_name).to eq('PipelineCounts') } + + it 'has the expected fields' do + expected_fields = %w[ + all + finished + pending + running + ] + + expect(described_class).to include_graphql_fields(*expected_fields) + end + + shared_examples 'pipeline counts query' do |args: "", expected_counts:| + let_it_be(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + pipelineCounts#{args} { + all + finished + pending + running + } + } + } + ) + end + + subject { GitlabSchema.execute(query, context: { current_user: current_user }).as_json } + + it 'returns pipeline counts' do + actual_counts = subject.dig('data', 'project', 'pipelineCounts') + + expect(actual_counts).to eq(expected_counts) + end + end + + it_behaves_like "pipeline counts query", args: "", expected_counts: { + "all" => 6, + "finished" => 3, + "pending" => 2, + "running" => 1 + } + + it_behaves_like "pipeline counts query", args: '(ref: "awesome-feature")', expected_counts: { + "all" => 1, + "finished" => 0, + "pending" => 1, + "running" => 0 + } + + it_behaves_like "pipeline counts query", args: '(sha: "deadbeef")', expected_counts: { + "all" => 1, + "finished" => 0, + "pending" => 0, + "running" => 1 + } + + it_behaves_like "pipeline counts query", args: '(source: "ondemand_dast_scan")', expected_counts: { + "all" => 1, + "finished" => 1, + "pending" => 0, + "running" => 0 + } +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 9c2543a825f..cbdcec18261 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -25,7 +25,7 @@ RSpec.describe GitlabSchema.types['Project'] do only_allow_merge_if_pipeline_succeeds request_access_enabled only_allow_merge_if_all_discussions_are_resolved printing_merge_request_link_enabled namespace group statistics repository merge_requests merge_request issues - issue milestones pipelines removeSourceBranchAfterMerge sentryDetailedError snippets + issue milestones pipelines removeSourceBranchAfterMerge pipeline_counts sentryDetailedError snippets grafanaIntegration autocloseReferencedIssues suggestion_commit_message environments environment boards jira_import_status jira_imports services releases release alert_management_alerts alert_management_alert alert_management_alert_status_counts @@ -310,6 +310,13 @@ RSpec.describe GitlabSchema.types['Project'] do end end + describe 'pipelineCounts field' do + subject { described_class.fields['pipelineCounts'] } + + it { is_expected.to have_graphql_type(Types::Ci::PipelineCountsType) } + it { is_expected.to have_graphql_resolver(Resolvers::Ci::ProjectPipelineCountsResolver) } + end + describe 'snippets field' do subject { described_class.fields['snippets'] } diff --git a/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb b/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb index 4e172dd32f0..d9fa6b931ad 100644 --- a/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb +++ b/spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do let(:ce_temp_dir) { Dir.mktmpdir } let(:ee_temp_dir) { Dir.mktmpdir } + let(:timestamp) { Time.current.to_i } let(:generator_options) { { 'category' => 'Groups::EmailCampaignsController', 'action' => 'click' } } before do @@ -12,6 +13,10 @@ RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do stub_const("#{described_class}::EE_DIR", ee_temp_dir) end + around do |example| + freeze_time { example.run } + end + after do FileUtils.rm_rf([ce_temp_dir, ee_temp_dir]) end @@ -22,16 +27,41 @@ RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do end let(:sample_event_dir) { 'lib/generators/gitlab/snowplow_event_definition_generator' } + let(:file_name) { Dir.children(ce_temp_dir).first } it 'creates CE event definition file using the template' do sample_event = ::Gitlab::Config::Loader::Yaml.new(fixture_file(File.join(sample_event_dir, 'sample_event.yml'))).load_raw! described_class.new([], generator_options).invoke_all - event_definition_path = File.join(ce_temp_dir, 'groups__email_campaigns_controller_click.yml') + event_definition_path = File.join(ce_temp_dir, file_name) expect(::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!).to eq(sample_event) end + describe 'generated filename' do + it 'includes timestamp' do + described_class.new([], generator_options).invoke_all + + expect(file_name).to include(timestamp.to_s) + end + + it 'removes special characters' do + generator_options = { 'category' => '"`ui:[mavenpackages | t5%348()-=@ ]`"', 'action' => 'click' } + + described_class.new([], generator_options).invoke_all + + expect(file_name).to include('uimavenpackagest') + end + + it 'cuts name if longer than 100 characters' do + generator_options = { 'category' => 'a' * 100, 'action' => 'click' } + + described_class.new([], generator_options).invoke_all + + expect(file_name.length).to eq(100) + end + end + context 'event definition already exists' do before do stub_const('Gitlab::VERSION', '12.11.0-pre') @@ -44,7 +74,7 @@ RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do stub_const('Gitlab::VERSION', '13.11.0-pre') described_class.new([], generator_options.merge('force' => true)).invoke_all - event_definition_path = File.join(ce_temp_dir, 'groups__email_campaigns_controller_click.yml') + event_definition_path = File.join(ce_temp_dir, file_name) event_data = ::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw! expect(event_data).to eq(sample_event) @@ -56,13 +86,17 @@ RSpec.describe Gitlab::SnowplowEventDefinitionGenerator, :silence_stdout do end end - it 'creates EE event definition file using the template' do - sample_event = ::Gitlab::Config::Loader::Yaml.new(fixture_file(File.join(sample_event_dir, 'sample_event_ee.yml'))).load_raw! + describe 'EE' do + let(:file_name) { Dir.children(ee_temp_dir).first } - described_class.new([], generator_options.merge('ee' => true)).invoke_all + it 'creates EE event definition file using the template' do + sample_event = ::Gitlab::Config::Loader::Yaml.new(fixture_file(File.join(sample_event_dir, 'sample_event_ee.yml'))).load_raw! - event_definition_path = File.join(ee_temp_dir, 'groups__email_campaigns_controller_click.yml') - expect(::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!).to eq(sample_event) + described_class.new([], generator_options.merge('ee' => true)).invoke_all + + event_definition_path = File.join(ee_temp_dir, file_name) + expect(::Gitlab::Config::Loader::Yaml.new(File.read(event_definition_path)).load_raw!).to eq(sample_event) + end end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb index 7b69d23ce90..687bb82a8ef 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb @@ -252,7 +252,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Seed do extra_jobs = 2 non_handled_sql_queries = 2 - # 1. Ci::InstanceVariable Load => `Ci::InstanceVariable#cached_data` => already cached with `ProcessMemoryCache` + # 1. Ci::InstanceVariable Load => `Ci::InstanceVariable#cached_data` => already cached with `fetch_memory_cache` # 2. Ci::Variable Load => `Project#ci_variables_for` => already cached with `Gitlab::SafeRequestStore` extra_jobs * non_handled_sql_queries diff --git a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb index ab42545e951..c5ebd762a79 100644 --- a/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb +++ b/spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb @@ -15,7 +15,6 @@ RSpec.describe 'cross-database foreign keys' do ci_daily_build_group_report_results.group_id ci_daily_build_group_report_results.project_id ci_freeze_periods.project_id - ci_job_artifacts.project_id ci_job_token_project_scope_links.added_by_id ci_pending_builds.namespace_id ci_pending_builds.project_id diff --git a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb index 0531bccf4b4..feafedc7f4a 100644 --- a/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb +++ b/spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb @@ -4,7 +4,6 @@ require 'spec_helper' RSpec.describe Gitlab::Metrics::Exporter::WebExporter do let(:exporter) { described_class.new } - let(:readiness_probe) { exporter.send(:readiness_probe).execute } before do stub_config( @@ -25,12 +24,6 @@ RSpec.describe Gitlab::Metrics::Exporter::WebExporter do end context 'when running server', :prometheus do - it 'readiness probe returns succesful status' do - expect(readiness_probe.http_status).to eq(200) - expect(readiness_probe.json).to include(status: 'ok') - expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'ok' }]) - end - it 'initializes request metrics' do expect(Gitlab::Metrics::RailsSlis).to receive(:initialize_request_slis_if_needed!).and_call_original @@ -40,14 +33,4 @@ RSpec.describe Gitlab::Metrics::Exporter::WebExporter do expect(response.body).to include('gitlab_sli:rails_request_apdex') end end - - describe '#mark_as_not_running!' do - it 'readiness probe returns a failure status', :prometheus do - exporter.mark_as_not_running! - - expect(readiness_probe.http_status).to eq(503) - expect(readiness_probe.json).to include(status: 'failed') - expect(readiness_probe.json).to include('web_exporter' => [{ 'status': 'failed' }]) - end - end end diff --git a/spec/lib/gitlab/pipeline_scope_counts_spec.rb b/spec/lib/gitlab/pipeline_scope_counts_spec.rb new file mode 100644 index 00000000000..a9187ecfb54 --- /dev/null +++ b/spec/lib/gitlab/pipeline_scope_counts_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::PipelineScopeCounts do + let(:current_user) { create(:user) } + + let_it_be(:project) { create(:project, :private) } + let_it_be(:pipeline) { create(:ci_pipeline, project: project) } + let_it_be(:failed_pipeline) { create(:ci_pipeline, :failed, project: project) } + let_it_be(:success_pipeline) { create(:ci_pipeline, :success, project: project) } + let_it_be(:ref_pipeline) { create(:ci_pipeline, project: project, ref: 'awesome-feature') } + let_it_be(:sha_pipeline) { create(:ci_pipeline, :running, project: project, sha: 'deadbeef') } + let_it_be(:on_demand_dast_scan) { create(:ci_pipeline, :success, project: project, source: 'ondemand_dast_scan') } + + before do + project.add_developer(current_user) + end + + it 'has policy class' do + expect(described_class.declarative_policy_class).to be("Ci::ProjectPipelinesPolicy") + end + + it 'has expected attributes' do + expect(described_class.new(current_user, project, {})).to have_attributes( + all: 6, + finished: 3, + pending: 2, + running: 1 + ) + end + + describe 'with large amount of pipelines' do + it 'sets the PIPELINES_COUNT_LIMIT constant to a value of 1_000' do + expect(described_class::PIPELINES_COUNT_LIMIT).to eq(1_000) + end + + context 'when there are more records than the limit' do + before do + stub_const('Gitlab::PipelineScopeCounts::PIPELINES_COUNT_LIMIT', 3) + end + + it 'limits the found items' do + expect(described_class.new(current_user, project, {}).all).to eq(3) + end + end + end +end diff --git a/spec/lib/gitlab/process_memory_cache/helper_spec.rb b/spec/lib/gitlab/process_memory_cache/helper_spec.rb new file mode 100644 index 00000000000..bad4f61282c --- /dev/null +++ b/spec/lib/gitlab/process_memory_cache/helper_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::ProcessMemoryCache::Helper, :use_clean_rails_memory_store_caching do + let(:minimal_test_class) do + Class.new do + include Gitlab::ProcessMemoryCache::Helper + + def cached_content + fetch_memory_cache(:cached_content_instance_key) { expensive_computation } + end + + def clear_cached_content + invalidate_memory_cache(:cached_content_instance_key) + end + end + end + + before do + stub_const("MinimalTestClass", minimal_test_class) + end + + subject { MinimalTestClass.new } + + describe '.fetch_memory_cache' do + it 'memoizes the result' do + is_expected.to receive(:expensive_computation).once.and_return(1) + + 2.times do + expect(subject.cached_content).to eq(1) + end + end + + it 'resets the cache when the shared key is missing', :aggregate_failures do + allow(Rails.cache).to receive(:read).with(:cached_content_instance_key).and_return(nil) + is_expected.to receive(:expensive_computation).thrice.and_return(1, 2, 3) + + 3.times do |index| + expect(subject.cached_content).to eq(index + 1) + end + end + + it 'does not set the shared timestamp if it is already present', :redis do + subject.clear_cached_content + is_expected.to receive(:expensive_computation).once.and_return(1) + + expect { subject.cached_content }.not_to change { Rails.cache.read(:cached_content_instance_key) } + end + end + + describe '.invalidate_memory_cache' do + it 'invalidates the cache' do + is_expected.to receive(:expensive_computation).twice.and_return(1, 2) + + expect { subject.clear_cached_content }.to change { subject.cached_content } + end + end +end diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 2e8c41b410a..bd0397e0396 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -703,4 +703,11 @@ RSpec.describe Ci::JobArtifact do it_behaves_like 'it has loose foreign keys' do let(:factory_name) { :ci_job_artifact } end + + context 'loose foreign key on ci_job_artifacts.project_id' do + it_behaves_like 'cleanup by a loose foreign key' do + let!(:parent) { create(:project) } + let!(:model) { create(:ci_job_artifact, project: parent) } + end + end end diff --git a/spec/services/ci/process_sync_events_service_spec.rb b/spec/services/ci/process_sync_events_service_spec.rb index 8b7717fe4bf..3f4b2a0f7f2 100644 --- a/spec/services/ci/process_sync_events_service_spec.rb +++ b/spec/services/ci/process_sync_events_service_spec.rb @@ -62,16 +62,6 @@ RSpec.describe Ci::ProcessSyncEventsService do end end - context 'when the FF ci_namespace_project_mirrors is disabled' do - before do - stub_feature_flags(ci_namespace_project_mirrors: false) - end - - it 'does nothing' do - expect { execute }.not_to change(Projects::SyncEvent, :count) - end - end - it 'does not delete non-executed events' do new_project = create(:project) sync_event_class.delete_all diff --git a/yarn.lock b/yarn.lock index 98e9db70b62..c4320e3fbbc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -961,11 +961,6 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.2.0.tgz#95cf58d6ae634d535145159f08f5cff6241d4013" integrity sha512-mCwR3KfNPsxRoojtTjMIZwdd4FFlBh5DlR9AeodP+7+k8rILdWGYxTZbJMPNXoPbZx16R94nG8c5bR7toD4QBw== -"@gitlab/tributejs@1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@gitlab/tributejs/-/tributejs-1.0.0.tgz#672befa222aeffc83e7d799b0500a7a4418e59b8" - integrity sha512-nmKw1+hB6MHvlmPz63yPwVs1qQkycHwsKgxpEbzmky16Y6mL4EJMk3w1b8QlOAF/AIAzjCERPhe/R4MJiohbZw== - "@gitlab/ui@33.1.0": version "33.1.0" resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-33.1.0.tgz#45ac2e6362546530b5756b1973f97f74a9c920da" |