Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/issues/show/components/fields/description.vue5
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue5
-rw-r--r--app/assets/javascripts/pages/admin/index.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/gfm_autocomplete/gfm_autocomplete.vue106
-rw-r--r--app/assets/javascripts/vue_shared/components/gfm_autocomplete/utils.js195
-rw-r--r--app/assets/javascripts/vue_shared/components/help_popover.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue25
-rw-r--r--app/assets/stylesheets/vendors/tribute.scss41
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/graphql/resolvers/ci/project_pipeline_counts_resolver.rb28
-rw-r--r--app/graphql/types/ci/pipeline_counts_type.rb24
-rw-r--r--app/graphql/types/project_type.rb6
-rw-r--r--app/models/ci/instance_variable.rb11
-rw-r--r--app/policies/ci/project_pipelines_policy.rb7
-rw-r--r--app/services/ci/process_sync_events_service.rb2
-rw-r--r--app/views/projects/_remove.html.haml3
-rw-r--r--config/feature_flags/development/ci_decompose_for_namespace_monthly_usage_query.yml8
-rw-r--r--config/feature_flags/development/ci_namespace_project_mirrors.yml8
-rw-r--r--config/feature_flags/development/tribute_autocomplete.yml8
-rw-r--r--config/initializers/7_prometheus_metrics.rb7
-rw-r--r--config/webpack.config.js3
-rw-r--r--db/post_migrate/20220124153233_remove_projects_ci_job_artifacts_project_id_fk.rb20
-rw-r--r--db/schema_migrations/202201241532331
-rw-r--r--db/structure.sql3
-rw-r--r--doc/administration/logs.md2
-rw-r--r--doc/administration/monitoring/github_imports.md2
-rw-r--r--doc/administration/monitoring/gitlab_self_monitoring_project/index.md2
-rw-r--r--doc/administration/monitoring/index.md2
-rw-r--r--doc/administration/monitoring/ip_whitelist.md2
-rw-r--r--doc/administration/monitoring/performance/gitlab_configuration.md2
-rw-r--r--doc/administration/monitoring/performance/grafana_configuration.md2
-rw-r--r--doc/administration/monitoring/performance/index.md2
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md2
-rw-r--r--doc/administration/monitoring/performance/request_profiling.md2
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_exporter.md2
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md2
-rw-r--r--doc/administration/monitoring/prometheus/node_exporter.md2
-rw-r--r--doc/administration/monitoring/prometheus/pgbouncer_exporter.md2
-rw-r--r--doc/administration/monitoring/prometheus/postgres_exporter.md2
-rw-r--r--doc/administration/monitoring/prometheus/puma_exporter.md2
-rw-r--r--doc/administration/monitoring/prometheus/redis_exporter.md2
-rw-r--r--doc/administration/monitoring/prometheus/registry_exporter.md2
-rw-r--r--doc/api/container_registry.md4
-rw-r--r--doc/api/epics.md9
-rw-r--r--doc/api/error_tracking.md2
-rw-r--r--doc/api/graphql/reference/index.md27
-rw-r--r--doc/api/metrics_dashboard_annotations.md2
-rw-r--r--doc/api/metrics_user_starred_dashboards.md2
-rw-r--r--doc/architecture/blueprints/runner_scaling/index.md29
-rw-r--r--doc/development/distributed_tracing.md2
-rw-r--r--doc/development/documentation/styleguide/index.md11
-rw-r--r--doc/development/logging.md2
-rw-r--r--doc/development/prometheus_metrics.md2
-rw-r--r--doc/operations/error_tracking.md2
-rw-r--r--doc/operations/incident_management/alerts.md2
-rw-r--r--doc/operations/incident_management/escalation_policies.md2
-rw-r--r--doc/operations/incident_management/incidents.md2
-rw-r--r--doc/operations/incident_management/index.md2
-rw-r--r--doc/operations/incident_management/integrations.md2
-rw-r--r--doc/operations/incident_management/oncall_schedules.md2
-rw-r--r--doc/operations/incident_management/paging.md2
-rw-r--r--doc/operations/incident_management/status_page.md2
-rw-r--r--doc/operations/index.md2
-rw-r--r--doc/operations/metrics/alerts.md2
-rw-r--r--doc/operations/metrics/dashboards/default.md2
-rw-r--r--doc/operations/metrics/dashboards/develop.md2
-rw-r--r--doc/operations/metrics/dashboards/index.md2
-rw-r--r--doc/operations/metrics/dashboards/panel_types.md2
-rw-r--r--doc/operations/metrics/dashboards/settings.md2
-rw-r--r--doc/operations/metrics/dashboards/templating_variables.md2
-rw-r--r--doc/operations/metrics/dashboards/variables.md2
-rw-r--r--doc/operations/metrics/dashboards/yaml.md2
-rw-r--r--doc/operations/metrics/dashboards/yaml_number_format.md2
-rw-r--r--doc/operations/metrics/embed.md2
-rw-r--r--doc/operations/metrics/embed_grafana.md2
-rw-r--r--doc/operations/metrics/index.md2
-rw-r--r--doc/operations/tracing.md2
-rw-r--r--doc/raketasks/generate_sample_prometheus_data.md2
-rw-r--r--doc/subscriptions/gitlab_com/index.md2
-rw-r--r--doc/user/admin_area/monitoring/health_check.md2
-rw-r--r--doc/user/clusters/agent/index.md3
-rw-r--r--doc/user/infrastructure/clusters/manage/management_project_applications/elasticstack.md2
-rw-r--r--doc/user/infrastructure/clusters/manage/management_project_applications/prometheus.md2
-rw-r--r--doc/user/infrastructure/clusters/manage/management_project_applications/sentry.md2
-rw-r--r--doc/user/project/clusters/kubernetes_pod_logs.md2
-rw-r--r--doc/user/project/integrations/prometheus.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/cloudwatch.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/haproxy.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/index.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/kubernetes.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress.md2
-rw-r--r--doc/user/project/integrations/prometheus_library/nginx_ingress_vts.md2
-rw-r--r--lib/generators/gitlab/snowplow_event_definition_generator.rb7
-rw-r--r--lib/gitlab/database/gitlab_loose_foreign_keys.yml4
-rw-r--r--lib/gitlab/metrics/exporter/base_exporter.rb4
-rw-r--r--lib/gitlab/metrics/exporter/web_exporter.rb36
-rw-r--r--lib/gitlab/pipeline_scope_counts.rb41
-rw-r--r--lib/gitlab/process_memory_cache/helper.rb51
-rw-r--r--locale/gitlab.pot3
-rw-r--r--package.json1
-rw-r--r--qa/qa/fixtures/package_managers/composer/composer.json.erb13
-rw-r--r--qa/qa/fixtures/package_managers/composer/composer_upload_package.yaml.erb13
-rw-r--r--qa/qa/fixtures/package_managers/conan/conan_upload_install_package.yaml.erb12
-rw-r--r--qa/qa/fixtures/package_managers/generic/generic_upload_install_package.yaml.erb18
-rw-r--r--qa/qa/fixtures/package_managers/helm/Chart.yaml.erb6
-rw-r--r--qa/qa/fixtures/package_managers/helm/helm_install_package.yaml.erb11
-rw-r--r--qa/qa/fixtures/package_managers/helm/helm_upload_package.yaml.erb14
-rw-r--r--qa/qa/fixtures/package_managers/maven/build_install.gradle.erb28
-rw-r--r--qa/qa/fixtures/package_managers/maven/build_upload.gradle.erb27
-rw-r--r--qa/qa/fixtures/package_managers/maven/client_pom.xml.erb19
-rw-r--r--qa/qa/fixtures/package_managers/maven/gradle_install_package.yaml.erb8
-rw-r--r--qa/qa/fixtures/package_managers/maven/gradle_upload_package.yaml.erb8
-rw-r--r--qa/qa/fixtures/package_managers/maven/maven_install_package.yaml.erb8
-rw-r--r--qa/qa/fixtures/package_managers/maven/maven_upload_package.yaml.erb8
-rw-r--r--qa/qa/fixtures/package_managers/maven/package_pom.xml.erb22
-rw-r--r--qa/qa/fixtures/package_managers/maven/settings.xml.erb16
-rw-r--r--qa/qa/fixtures/package_managers/maven/settings_with_pat.xml.erb16
-rw-r--r--qa/qa/fixtures/package_managers/npm/npm_install_package_instance.yaml.erb21
-rw-r--r--qa/qa/fixtures/package_managers/npm/npm_upload_install_package_project.yaml.erb31
-rw-r--r--qa/qa/fixtures/package_managers/npm/npm_upload_package_instance.yaml.erb14
-rw-r--r--qa/qa/fixtures/package_managers/npm/package_instance.json.erb8
-rw-r--r--qa/qa/fixtures/package_managers/npm/package_project.json.erb8
-rw-r--r--qa/qa/fixtures/package_managers/nuget/nuget_install_package.yaml.erb15
-rw-r--r--qa/qa/fixtures/package_managers/nuget/nuget_upload_package.yaml.erb17
-rw-r--r--qa/qa/fixtures/package_managers/pypi/pypi_upload_install_package.yaml.erb19
-rw-r--r--qa/qa/fixtures/package_managers/pypi/setup.py.erb16
-rw-r--r--qa/qa/fixtures/package_managers/rubygems/package.gemspec.erb39
-rw-r--r--qa/qa/fixtures/package_managers/rubygems/rubygems_upload_package.yaml.erb15
-rw-r--r--qa/qa/resource/base.rb64
-rw-r--r--qa/qa/resource/fork.rb2
-rw-r--r--qa/qa/resource/group.rb6
-rw-r--r--qa/qa/resource/sandbox.rb8
-rw-r--r--qa/qa/runtime/fixtures.rb8
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/pipeline/pass_dotenv_variables_to_downstream_via_bridge_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/composer_registry_spec.rb45
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/conan_repository_spec.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/generic_repository_spec.rb31
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/helm_registry_spec.rb81
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/maven_gradle_repository_spec.rb137
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/maven_repository_spec.rb214
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_instance_level_spec.rb93
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/npm/npm_project_level_spec.rb69
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/nuget_repository_spec.rb44
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/pypi_repository_spec.rb51
-rw-r--r--qa/qa/specs/features/browser_ui/5_package/package_registry/rubygems_registry_spec.rb66
-rw-r--r--qa/qa/support/formatters/test_stats_formatter.rb104
-rw-r--r--qa/qa/tools/test_resource_data_processor.rb106
-rw-r--r--qa/spec/resource/base_spec.rb5
-rw-r--r--qa/spec/support/formatters/test_stats_formatter_spec.rb46
-rw-r--r--qa/spec/support/shared_contexts/packages_registry_shared_context.rb2
-rw-r--r--qa/spec/tools/test_resources_data_processor_spec.rb55
-rw-r--r--spec/features/admin/dashboard_spec.rb10
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb760
-rw-r--r--spec/features/issues/user_comments_on_issue_spec.rb1
-rw-r--r--spec/features/participants_autocomplete_spec.rb23
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/__snapshots__/utils_spec.js.snap54
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/gfm_autocomplete_spec.js34
-rw-r--r--spec/frontend/vue_shared/components/gfm_autocomplete/utils_spec.js427
-rw-r--r--spec/frontend/vue_shared/components/help_popover_spec.js110
-rw-r--r--spec/graphql/resolvers/ci/project_pipeline_counts_resolver_spec.rb63
-rw-r--r--spec/graphql/types/ci/pipeline_counts_type_spec.rb87
-rw-r--r--spec/graphql/types/project_type_spec.rb9
-rw-r--r--spec/lib/generators/gitlab/snowplow_event_definition_generator_spec.rb48
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/seed_spec.rb2
-rw-r--r--spec/lib/gitlab/database/no_cross_db_foreign_keys_spec.rb1
-rw-r--r--spec/lib/gitlab/metrics/exporter/web_exporter_spec.rb17
-rw-r--r--spec/lib/gitlab/pipeline_scope_counts_spec.rb48
-rw-r--r--spec/lib/gitlab/process_memory_cache/helper_spec.rb59
-rw-r--r--spec/models/ci/job_artifact_spec.rb7
-rw-r--r--spec/services/ci/process_sync_events_service_spec.rb10
-rw-r--r--yarn.lock5
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)&lt;img src=x onerror=alert(1)&gt;'
- 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 &lt;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)&lt;img src=x onerror=alert(1)&gt;'
+ 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 &lt;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)&lt;img src=x onerror=alert(1)&gt;'
- 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 &lt;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 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-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 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils labels config shows the title in the menu item 1`] = `
-"
- <span class=\\"dropdown-label-box\\" style=\\"background: #123456;\\"></span>
- bug &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"
-`;
-
-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 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt; (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 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;</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 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-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 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-exports[`gfm_autocomplete/utils milestones config shows the title in the menu item 1`] = `"13.2 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
-
-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 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;"`;
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"