diff options
80 files changed, 414 insertions, 245 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3834af822ea..2062953c9ba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -37,11 +37,6 @@ workflow: - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME =~ /^release-tools\/\d+\.\d+\.\d+-rc\d+$/ && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME =~ /^[\d-]+-stable(-ee)?$/ && $CI_PROJECT_PATH == "gitlab-org/gitlab"' when: never # For merge requests running exclusively in Ruby 3.0 - - if: '($CI_MERGE_REQUEST_EVENT_TYPE == "merged_result" || $CI_MERGE_REQUEST_EVENT_TYPE == "merge_train") && $CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3/' - variables: - RUBY_VERSION: "3.0" - PIPELINE_NAME: 'Ruby 3 $CI_MERGE_REQUEST_EVENT_TYPE MR pipeline' - # For merge requests running exclusively in Ruby 3.0 - if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-in-ruby3/' variables: RUBY_VERSION: "3.0" diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml index b2fbd92af5f..8b7aee8de9d 100644 --- a/.gitlab/ci/rules.gitlab-ci.yml +++ b/.gitlab/ci/rules.gitlab-ci.yml @@ -197,8 +197,7 @@ - "spec/support/gitlab-git-test.git/**/*" .yaml-lint-patterns: &yaml-lint-patterns - - "*.yml" - - "**/*.yml" + - "**/*.{yml,yaml}{,.*}" .lint-pipeline-yaml-patterns: &lint-pipeline-yaml-patterns - ".gitlab-ci.yml" diff --git a/.rubocop_todo/security/io_methods.yml b/.rubocop_todo/security/io_methods.yml index 6138ca7ef3f..936bb21d5dc 100644 --- a/.rubocop_todo/security/io_methods.yml +++ b/.rubocop_todo/security/io_methods.yml @@ -4,21 +4,3 @@ Security/IoMethods: Details: grace period Exclude: - 'db/migrate/20210301200959_init_schema.rb' - - 'ee/lib/tasks/gitlab/spdx.rake' - - 'ee/spec/factories/spdx_catalogue.rb' - - 'ee/spec/lib/ee/gitlab/import_export/group/legacy_tree_saver_spec.rb' - - 'ee/spec/lib/gitlab/spdx/catalogue_spec.rb' - - 'lib/gitlab/import_export/json/legacy_reader.rb' - - 'lib/gitlab/import_export/lfs_restorer.rb' - - 'lib/tasks/gitlab/assets.rake' - - 'spec/features/projects/import_export/export_file_spec.rb' - - 'spec/lib/backup/gitaly_backup_spec.rb' - - 'spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb' - - 'spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb' - - 'spec/lib/gitlab/import_export/group/tree_restorer_spec.rb' - - 'spec/lib/gitlab/import_export/import_test_coverage_spec.rb' - - 'spec/lib/gitlab/import_export/json/legacy_writer_spec.rb' - - 'spec/lib/gitlab/import_export/lfs_saver_spec.rb' - - 'spec/lib/gitlab/import_export/project/relation_saver_spec.rb' - - 'spec/support/helpers/gitaly_setup.rb' - - 'spec/support/import_export/common_util.rb' diff --git a/.yamllint b/.yamllint index 4a9dd9c56bd..5b49a617a57 100644 --- a/.yamllint +++ b/.yamllint @@ -2,6 +2,15 @@ extends: default +yaml-files: + # defaults + - '*.yaml' + - '*.yml' + - '.yamllint' + # match more extensions + - '*.yaml.*' + - '*.yml.*' + # Ideally, we should have nothing in this ignore section. # # Please consider removing entries below by fixing them. @@ -19,6 +28,16 @@ ignore: | # Broken on purpose (for testing) spec/fixtures/lib/gitlab/metrics/dashboard/broken_yml_syntax.yml + # Dynamic YAML files have syntax errors sometimes. + *.erb + + # Vim temporary files. + *.sw[pon] + + # Zipped files (by e.g. asset pipeline) + *.gz + *.bz2 + #### Folders #### node_modules/ tmp/ diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 8cadd4a1fa8..6d1350d6745 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -38812995ea07e43b12b4151c24bf6b960a70f74d +6d9ffab522aae0f2fac5d3ff152064f56b01081d diff --git a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue index 9d8cb40b60a..661389f4059 100644 --- a/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue +++ b/app/assets/javascripts/ci_secure_files/components/secure_files_list.vue @@ -13,7 +13,7 @@ import { } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; import Api, { DEFAULT_PER_PAGE } from '~/api'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_PAYLOAD_TOO_LARGE } from '~/lib/utils/http_status'; import { __, s__, sprintf } from '~/locale'; import Tracking from '~/tracking'; import TimeagoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; @@ -145,7 +145,7 @@ export default { let message = ''; if (error?.response?.data?.message?.name) { message = this.$options.i18n.uploadErrorMessages.duplicate; - } else if (error.response.status === httpStatusCodes.PAYLOAD_TOO_LARGE) { + } else if (error.response.status === HTTP_STATUS_PAYLOAD_TOO_LARGE) { message = sprintf(this.$options.i18n.uploadErrorMessages.tooLarge, { limit: this.fileSizeLimit, }); diff --git a/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js index 91868132a5a..a510ec0847b 100644 --- a/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js +++ b/app/assets/javascripts/ide/stores/modules/terminal/actions/session_controls.js @@ -1,6 +1,6 @@ import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import httpStatus from '~/lib/utils/http_status'; +import httpStatus, { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status'; import * as terminalService from '../../../../services/terminals'; import { STARTING, STOPPING, STOPPED } from '../constants'; import * as messages from '../messages'; @@ -108,7 +108,7 @@ export const restartSession = ({ state, dispatch, rootState }) => { // We may have removed the build, in this case we'll just create a new session if ( responseStatus === httpStatus.NOT_FOUND || - responseStatus === httpStatus.UNPROCESSABLE_ENTITY + responseStatus === HTTP_STATUS_UNPROCESSABLE_ENTITY ) { dispatch('startSession'); } else { diff --git a/app/assets/javascripts/ide/stores/modules/terminal/messages.js b/app/assets/javascripts/ide/stores/modules/terminal/messages.js index ec05ca84754..fa1c7f23677 100644 --- a/app/assets/javascripts/ide/stores/modules/terminal/messages.js +++ b/app/assets/javascripts/ide/stores/modules/terminal/messages.js @@ -1,5 +1,5 @@ import { escape } from 'lodash'; -import httpStatus from '~/lib/utils/http_status'; +import httpStatus, { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status'; import { __, sprintf } from '~/locale'; export const UNEXPECTED_ERROR_CONFIG = __( @@ -28,7 +28,7 @@ export const ERROR_PERMISSION = __( ); export const configCheckError = (status, helpUrl) => { - if (status === httpStatus.UNPROCESSABLE_ENTITY) { + if (status === HTTP_STATUS_UNPROCESSABLE_ENTITY) { return sprintf( ERROR_CONFIG, { diff --git a/app/assets/javascripts/import_entities/import_projects/store/actions.js b/app/assets/javascripts/import_entities/import_projects/store/actions.js index 51ac8c273c7..e0db585eb3e 100644 --- a/app/assets/javascripts/import_entities/import_projects/store/actions.js +++ b/app/assets/javascripts/import_entities/import_projects/store/actions.js @@ -3,7 +3,7 @@ import _ from 'lodash'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_TOO_MANY_REQUESTS } from '~/lib/utils/http_status'; import Poll from '~/lib/utils/poll'; import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { visitUrl, objectToQuery } from '~/lib/utils/url_utility'; @@ -16,7 +16,7 @@ let eTagPoll; const hasRedirectInError = (e) => e?.response?.data?.error?.redirect; const redirectToUrlInError = (e) => visitUrl(e.response.data.error.redirect); -const tooManyRequests = (e) => e.response.status === httpStatusCodes.TOO_MANY_REQUESTS; +const tooManyRequests = (e) => e.response.status === HTTP_STATUS_TOO_MANY_REQUESTS; const pathWithParams = ({ path, ...params }) => { const filteredParams = Object.fromEntries( Object.entries(params).filter(([, value]) => value !== ''), diff --git a/app/assets/javascripts/lib/utils/http_status.js b/app/assets/javascripts/lib/utils/http_status.js index c5190592bb6..ec0d8d433a5 100644 --- a/app/assets/javascripts/lib/utils/http_status.js +++ b/app/assets/javascripts/lib/utils/http_status.js @@ -1,45 +1,43 @@ -/** - * exports HTTP status codes - */ +export const HTTP_STATUS_ABORTED = 0; +export const HTTP_STATUS_CREATED = 201; +export const HTTP_STATUS_ACCEPTED = 202; +export const HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION = 203; +export const HTTP_STATUS_NO_CONTENT = 204; +export const HTTP_STATUS_RESET_CONTENT = 205; +export const HTTP_STATUS_PARTIAL_CONTENT = 206; +export const HTTP_STATUS_MULTI_STATUS = 207; +export const HTTP_STATUS_ALREADY_REPORTED = 208; +export const HTTP_STATUS_IM_USED = 226; +export const HTTP_STATUS_METHOD_NOT_ALLOWED = 405; +export const HTTP_STATUS_CONFLICT = 409; +export const HTTP_STATUS_GONE = 410; +export const HTTP_STATUS_PAYLOAD_TOO_LARGE = 413; +export const HTTP_STATUS_UNPROCESSABLE_ENTITY = 422; +export const HTTP_STATUS_TOO_MANY_REQUESTS = 429; +// TODO move the rest of the status codes to primitive constants +// https://docs.gitlab.com/ee/development/fe_guide/style/javascript.html#export-constants-as-primitives const httpStatusCodes = { - ABORTED: 0, OK: 200, - CREATED: 201, - ACCEPTED: 202, - NON_AUTHORITATIVE_INFORMATION: 203, - NO_CONTENT: 204, - RESET_CONTENT: 205, - PARTIAL_CONTENT: 206, - MULTI_STATUS: 207, - ALREADY_REPORTED: 208, - IM_USED: 226, - MULTIPLE_CHOICES: 300, BAD_REQUEST: 400, UNAUTHORIZED: 401, FORBIDDEN: 403, NOT_FOUND: 404, - METHOD_NOT_ALLOWED: 405, - CONFLICT: 409, - GONE: 410, - PAYLOAD_TOO_LARGE: 413, - UNPROCESSABLE_ENTITY: 422, - TOO_MANY_REQUESTS: 429, INTERNAL_SERVER_ERROR: 500, SERVICE_UNAVAILABLE: 503, }; export const successCodes = [ httpStatusCodes.OK, - httpStatusCodes.CREATED, - httpStatusCodes.ACCEPTED, - httpStatusCodes.NON_AUTHORITATIVE_INFORMATION, - httpStatusCodes.NO_CONTENT, - httpStatusCodes.RESET_CONTENT, - httpStatusCodes.PARTIAL_CONTENT, - httpStatusCodes.MULTI_STATUS, - httpStatusCodes.ALREADY_REPORTED, - httpStatusCodes.IM_USED, + HTTP_STATUS_CREATED, + HTTP_STATUS_ACCEPTED, + HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION, + HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_RESET_CONTENT, + HTTP_STATUS_PARTIAL_CONTENT, + HTTP_STATUS_MULTI_STATUS, + HTTP_STATUS_ALREADY_REPORTED, + HTTP_STATUS_IM_USED, ]; export default httpStatusCodes; diff --git a/app/assets/javascripts/lib/utils/poll.js b/app/assets/javascripts/lib/utils/poll.js index 71782c9a4ce..73add1e37ee 100644 --- a/app/assets/javascripts/lib/utils/poll.js +++ b/app/assets/javascripts/lib/utils/poll.js @@ -1,5 +1,5 @@ import { normalizeHeaders } from './common_utils'; -import httpStatusCodes, { successCodes } from './http_status'; +import { HTTP_STATUS_ABORTED, successCodes } from './http_status'; /** * Polling utility for handling realtime updates. @@ -108,7 +108,7 @@ export default class Poll { }) .catch((error) => { notificationCallback(false); - if (error.status === httpStatusCodes.ABORTED) { + if (error.status === HTTP_STATUS_ABORTED) { return; } errorCallback(error); diff --git a/app/assets/javascripts/monitoring/requests/index.js b/app/assets/javascripts/monitoring/requests/index.js index 26fedb9c81c..8b65eec051f 100644 --- a/app/assets/javascripts/monitoring/requests/index.js +++ b/app/assets/javascripts/monitoring/requests/index.js @@ -1,13 +1,16 @@ import axios from '~/lib/utils/axios_utils'; import { backOff } from '~/lib/utils/common_utils'; -import statusCodes from '~/lib/utils/http_status'; +import statusCodes, { + HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_UNPROCESSABLE_ENTITY, +} from '~/lib/utils/http_status'; import { PROMETHEUS_TIMEOUT } from '../constants'; const cancellableBackOffRequest = (makeRequestCallback) => backOff((next, stop) => { makeRequestCallback() .then((resp) => { - if (resp.status === statusCodes.NO_CONTENT) { + if (resp.status === HTTP_STATUS_NO_CONTENT) { next(); } else { stop(resp); @@ -34,7 +37,7 @@ export const getPrometheusQueryData = (prometheusEndpoint, params, opts) => const { response = {} } = error; if ( response.status === statusCodes.BAD_REQUEST || - response.status === statusCodes.UNPROCESSABLE_ENTITY || + response.status === HTTP_STATUS_UNPROCESSABLE_ENTITY || response.status === statusCodes.SERVICE_UNAVAILABLE ) { const { data } = response; diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue index 0d7ff022f8f..2ccb9a0b514 100644 --- a/app/assets/javascripts/notes/components/comment_form.vue +++ b/app/assets/javascripts/notes/components/comment_form.vue @@ -7,7 +7,7 @@ import Autosave from '~/autosave'; import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; import { createAlert } from '~/flash'; import { badgeState } from '~/issuable/components/status_box.vue'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status'; import { capitalizeFirstCharacter, convertToCamelCase, @@ -28,8 +28,6 @@ import CommentTypeDropdown from './comment_type_dropdown.vue'; import DiscussionLockedWidget from './discussion_locked_widget.vue'; import NoteSignedOutWidget from './note_signed_out_widget.vue'; -const { UNPROCESSABLE_ENTITY } = httpStatusCodes; - export default { name: 'CommentForm', i18n: COMMENT_FORM, @@ -198,7 +196,7 @@ export default { 'toggleIssueLocalState', ]), handleSaveError({ data, status }) { - if (status === UNPROCESSABLE_ENTITY && data.errors?.commands_only?.length) { + if (status === HTTP_STATUS_UNPROCESSABLE_ENTITY && data.errors?.commands_only?.length) { this.errors = data.errors.commands_only; } else { this.errors = [this.$options.i18n.GENERIC_UNSUBMITTABLE_NETWORK]; diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 7a73278bf7b..826e7e5a3d0 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -7,7 +7,7 @@ import SafeHtml from '~/vue_shared/directives/safe_html'; import { confirmAction } from '~/lib/utils/confirm_via_gl_modal/confirm_via_gl_modal'; import { INLINE_DIFF_LINES_KEY } from '~/diffs/constants'; import { createAlert } from '~/flash'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_GONE } from '~/lib/utils/http_status'; import { ignoreWhilePending } from '~/lib/utils/ignore_while_pending'; import { truncateSha } from '~/lib/utils/text_utility'; import TimelineEntryItem from '~/vue_shared/components/notes/timeline_entry_item.vue'; @@ -338,7 +338,7 @@ export default { callback(); }) .catch((response) => { - if (response.status === httpStatusCodes.GONE) { + if (response.status === HTTP_STATUS_GONE) { this.removeNote(this.note); this.updateSuccess(); callback(); diff --git a/app/assets/javascripts/self_monitor/store/actions.js b/app/assets/javascripts/self_monitor/store/actions.js index 5b9e994290c..7198dbe8b04 100644 --- a/app/assets/javascripts/self_monitor/store/actions.js +++ b/app/assets/javascripts/self_monitor/store/actions.js @@ -1,6 +1,6 @@ import axios from '~/lib/utils/axios_utils'; import { backOff } from '~/lib/utils/common_utils'; -import statusCodes from '~/lib/utils/http_status'; +import statusCodes, { HTTP_STATUS_ACCEPTED } from '~/lib/utils/http_status'; import { __, s__ } from '~/locale'; import * as types from './mutation_types'; @@ -10,7 +10,7 @@ function backOffRequest(makeRequestCallback) { return backOff((next, stop) => { makeRequestCallback() .then((resp) => { - if (resp.status === statusCodes.ACCEPTED) { + if (resp.status === HTTP_STATUS_ACCEPTED) { next(); } else { stop(resp); @@ -31,7 +31,7 @@ export const requestCreateProject = ({ dispatch, state, commit }) => { axios .post(state.createProjectEndpoint) .then((resp) => { - if (resp.status === statusCodes.ACCEPTED) { + if (resp.status === HTTP_STATUS_ACCEPTED) { dispatch('requestCreateProjectStatus', resp.data.job_id); } }) @@ -83,7 +83,7 @@ export const requestDeleteProject = ({ dispatch, state, commit }) => { axios .delete(state.deleteProjectEndpoint) .then((resp) => { - if (resp.status === statusCodes.ACCEPTED) { + if (resp.status === HTTP_STATUS_ACCEPTED) { dispatch('requestDeleteProjectStatus', resp.data.job_id); } }) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue index 1256b3a8e52..c7d34d45f06 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment/memory_usage.vue @@ -1,7 +1,7 @@ <script> import { GlLoadingIcon, GlSprintf, GlLink } from '@gitlab/ui'; import { backOff } from '~/lib/utils/common_utils'; -import statusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status'; import { bytesToMiB } from '~/lib/utils/number_utils'; import { s__ } from '~/locale'; import MemoryGraph from '~/vue_shared/components/memory_graph.vue'; @@ -107,7 +107,7 @@ export default { backOff((next, stop) => { MRWidgetService.fetchMetrics(this.metricsUrl) .then((res) => { - if (res.status === statusCodes.NO_CONTENT) { + if (res.status === HTTP_STATUS_NO_CONTENT) { this.backOffRequestCounter += 1; /* eslint-disable no-unused-expressions */ this.backOffRequestCounter < 3 ? next() : stop(res); @@ -118,7 +118,7 @@ export default { .catch(stop); }) .then((res) => { - if (res.status === statusCodes.NO_CONTENT) { + if (res.status === HTTP_STATUS_NO_CONTENT) { return res; } diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js index 02170cc3247..394f8979a53 100644 --- a/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js +++ b/app/assets/javascripts/vue_merge_request_widget/extensions/code_quality/index.js @@ -1,7 +1,7 @@ import axios from '~/lib/utils/axios_utils'; import { EXTENSION_ICONS } from '~/vue_merge_request_widget/constants'; import { SEVERITY_ICONS_MR_WIDGET } from '~/ci/reports/codequality_report/constants'; -import httpStatusCodes from '~/lib/utils/http_status'; +import { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status'; import { parseCodeclimateMetrics } from '~/ci/reports/codequality_report/store/utils/codequality_parser'; import { capitalizeFirstCharacter } from '~/lib/utils/text_utility'; import { i18n } from './constants'; @@ -43,7 +43,7 @@ export default { return { ...response, data: { - parsingInProgress: status === httpStatusCodes.NO_CONTENT, + parsingInProgress: status === HTTP_STATUS_NO_CONTENT, resolvedErrors: parseCodeclimateMetrics(data.resolved_errors, this.blobPath.head_path), newErrors: parseCodeclimateMetrics(data.new_errors, this.blobPath.head_path), existingErrors: parseCodeclimateMetrics(data.existing_errors, this.blobPath.head_path), diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue index 763f2f338a3..14d2512ffc8 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue @@ -10,6 +10,7 @@ import { STATE_OPEN, TASK_TYPE_NAME, WORK_ITEM_TYPE_VALUE_OBJECTIVE, + WIDGET_TYPE_PROGRESS, WIDGET_TYPE_MILESTONE, WIDGET_TYPE_HIERARCHY, WIDGET_TYPE_ASSIGNEES, @@ -113,7 +114,15 @@ export default { return this.isExpanded ? __('Collapse') : __('Expand'); }, hasMetadata() { - return this.milestone || this.assignees.length > 0 || this.labels.length > 0; + return ( + this.progress !== undefined || + this.milestone !== undefined || + this.assignees.length > 0 || + this.labels.length > 0 + ); + }, + progress() { + return this.getWidgetByType(this.childItem, WIDGET_TYPE_PROGRESS)?.progress; }, milestone() { return this.getWidgetByType(this.childItem, WIDGET_TYPE_MILESTONE)?.milestone; @@ -231,6 +240,7 @@ export default { <work-item-link-child-metadata v-if="hasMetadata" :allows-scoped-labels="allowsScopedLabels" + :progress="progress" :milestone="milestone" :assignees="assignees" :labels="labels" diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child_metadata.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child_metadata.vue index 7be7e1f3496..a7660dc4f25 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child_metadata.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child_metadata.vue @@ -1,5 +1,12 @@ <script> -import { GlLabel, GlAvatar, GlAvatarLink, GlAvatarsInline, GlTooltipDirective } from '@gitlab/ui'; +import { + GlIcon, + GlLabel, + GlAvatar, + GlAvatarLink, + GlAvatarsInline, + GlTooltipDirective, +} from '@gitlab/ui'; import { s__, sprintf } from '~/locale'; import { isScopedLabel } from '~/lib/utils/common_utils'; @@ -8,6 +15,7 @@ import ItemMilestone from '~/issuable/components/issue_milestone.vue'; export default { components: { + GlIcon, GlLabel, GlAvatar, GlAvatarLink, @@ -23,6 +31,11 @@ export default { required: false, default: false, }, + progress: { + type: Number, + required: false, + default: 0, + }, milestone: { type: Object, required: false, @@ -40,6 +53,9 @@ export default { }, }, computed: { + hasProgress() { + return !(this.progress === null || this.progress === undefined); + }, assigneesCollapsedTooltip() { if (this.assignees.length > 2) { return sprintf(s__('WorkItem|%{count} more assignees'), { @@ -56,12 +72,6 @@ export default { } return ''; }, - labelsContainerClass() { - if (this.milestone || this.assignees.length) { - return 'gl-sm-ml-5'; - } - return ''; - }, }, methods: { showScopedLabel(label) { @@ -73,6 +83,16 @@ export default { <template> <div class="gl-display-flex gl-flex-wrap gl-align-items-center"> + <div + v-if="hasProgress" + v-gl-tooltip.bottom + :title="__('Progress')" + class="gl-display-flex gl-align-items-center gl-mr-5 gl-cursor-help gl-line-height-normal" + data-testid="item-progress" + > + <gl-icon name="progress" /> + <span class="gl-text-primary gl-ml-2">{{ progress }}%</span> + </div> <item-milestone v-if="milestone" :milestone="milestone" @@ -87,6 +107,7 @@ export default { badge-tooltip-prop="name" :badge-sr-only-text="assigneesCollapsedTooltip" :class="assigneesContainerClass" + class="gl-mr-5" > <template #avatar="{ avatar }"> <gl-avatar-link v-gl-tooltip target="blank" :href="avatar.webUrl" :title="avatar.name"> @@ -94,7 +115,7 @@ export default { </gl-avatar-link> </template> </gl-avatars-inline> - <div v-if="labels.length" class="gl-display-flex gl-flex-wrap" :class="labelsContainerClass"> + <div v-if="labels.length" class="gl-display-flex gl-flex-wrap"> <gl-label v-for="label in labels" :key="label.id" @@ -102,7 +123,7 @@ export default { :background-color="label.color" :description="label.description" :scoped="showScopedLabel(label)" - class="gl-mt-2 gl-sm-mt-0 gl-mr-2 gl-mb-auto gl-label-sm" + class="gl-mt-3 gl-sm-mt-0 gl-mr-2 gl-mb-auto gl-label-sm" tooltip-placement="top" /> </div> diff --git a/app/assets/javascripts/work_items/graphql/work_item_metadata_widgets.fragment.graphql b/app/assets/javascripts/work_items/graphql/work_item_metadata_widgets.fragment.graphql index baefcdaea93..6e2acba5b92 100644 --- a/app/assets/javascripts/work_items/graphql/work_item_metadata_widgets.fragment.graphql +++ b/app/assets/javascripts/work_items/graphql/work_item_metadata_widgets.fragment.graphql @@ -3,6 +3,10 @@ #import "~/work_items/graphql/milestone.fragment.graphql" fragment WorkItemMetadataWidgets on WorkItemWidget { + ... on WorkItemWidgetProgress { + type + progress + } ... on WorkItemWidgetMilestone { type milestone { diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss index 714dd932147..3f1f7fbfb7e 100644 --- a/app/assets/stylesheets/utilities.scss +++ b/app/assets/stylesheets/utilities.scss @@ -236,12 +236,6 @@ to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1709 } } -// TODO: Remove once https: //gitlab.com/gitlab-org/gitlab-ui/-/merge_requests/3198 is merged -.gl-sm-ml-5 { - @include gl-media-breakpoint-up(sm) { - @include gl-ml-5; - } -} /* End gitlab-ui#1709 */ diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index cb176a02411..d7c4540544b 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -27,7 +27,9 @@ module TodosHelper ) when Todo::UNMERGEABLE then s_('Todos|Could not merge') when Todo::MERGE_TRAIN_REMOVED then s_("Todos|Removed from Merge Train") - when Todo::MEMBER_ACCESS_REQUESTED then s_("Todos|has requested access") + when Todo::MEMBER_ACCESS_REQUESTED then format( + s_("Todos|has requested access to group %{which}"), which: _(todo.target.name) + ) end end diff --git a/app/models/todo.rb b/app/models/todo.rb index 1ff22ed22bf..32ec4accb4b 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -221,6 +221,8 @@ class Todo < ApplicationRecord def body if note.present? note.note + elsif member_access_requested? + target.full_path else target.title end @@ -258,6 +260,8 @@ class Todo < ApplicationRecord def target_reference if for_commit? target.reference_link_text + elsif member_access_requested? + target.full_path else target.to_reference end diff --git a/app/validators/json_schemas/web_hooks_url_variables.json b/app/validators/json_schemas/web_hooks_url_variables.json index ea504d114e3..27b251a059f 100644 --- a/app/validators/json_schemas/web_hooks_url_variables.json +++ b/app/validators/json_schemas/web_hooks_url_variables.json @@ -8,7 +8,7 @@ "^[A-Za-z]+[0-9]*(?:[._-][A-Za-z0-9]+)*$": { "type": "string", "minLength": 1, - "maxLength": 100 + "maxLength": 2048 } } } diff --git a/app/views/dashboard/todos/_todo.html.haml b/app/views/dashboard/todos/_todo.html.haml index 47d7105af0d..9dfeaa3d07d 100644 --- a/app/views/dashboard/todos/_todo.html.haml +++ b/app/views/dashboard/todos/_todo.html.haml @@ -14,12 +14,11 @@ %span = todo_parent_path(todo) - - unless todo.member_access_requested? - %span.todo-label - - if todo.target - = link_to todo_target_name(todo), todo_target_path(todo), class: 'todo-target-link gl-text-gray-500! gl-text-decoration-none!', :'aria-describedby' => dom_id(todo) + "_describer", :'aria-label' => todo_target_aria_label(todo) - - else - = _("(removed)") + %span.todo-label + - if todo.target + = link_to todo_target_name(todo), todo_target_path(todo), class: 'todo-target-link gl-text-gray-500! gl-text-decoration-none!', :'aria-describedby' => dom_id(todo) + "_describer", :'aria-label' => todo_target_aria_label(todo) + - else + = _("(removed)") .todo-body.gl-mb-2.gl-px-2.gl-display-flex.gl-align-items-flex-start.gl-lg-align-items-center .todo-avatar.gl-display-none.gl-sm-display-inline-block diff --git a/app/views/shared/file_hooks/_index.html.haml b/app/views/shared/file_hooks/_index.html.haml index d48e9f3d02e..16e89463a4b 100644 --- a/app/views/shared/file_hooks/_index.html.haml +++ b/app/views/shared/file_hooks/_index.html.haml @@ -11,15 +11,16 @@ .col-lg-8.gl-mb-3 - if file_hooks.any? - .card - .card-header + = render Pajamas::CardComponent.new do |c| + - c.header do = _('File Hooks (%{count})') % { count: file_hooks.count } - %ul.content-list - - file_hooks.each do |file| - %li - .monospace - = File.basename(file) - + - c.body do + %ul.content-list + - file_hooks.each do |file| + %li + .monospace + = File.basename(file) - else - .card.bg-light.text-center - .nothing-here-block= _('No file hooks found.') + = render Pajamas::CardComponent.new do |c| + - c.body do + .nothing-here-block= _('No file hooks found.') diff --git a/config/feature_flags/development/multiple_environment_approval_rules_fe.yml b/config/feature_flags/development/multiple_environment_approval_rules_fe.yml new file mode 100644 index 00000000000..c282313f409 --- /dev/null +++ b/config/feature_flags/development/multiple_environment_approval_rules_fe.yml @@ -0,0 +1,8 @@ +--- +name: multiple_environment_approval_rules_fe +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105719 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/384334 +milestone: '15.7' +type: development +group: group::release +default_enabled: false diff --git a/doc/api/todos.md b/doc/api/todos.md index 2fd120f9cbb..2bfae1972b7 100644 --- a/doc/api/todos.md +++ b/doc/api/todos.md @@ -20,14 +20,14 @@ GET /todos Parameters: -| Attribute | Type | Required | Description | -| --------- | ---- | -------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Attribute | Type | Required | Description | +| --------- | ---- | -------- |----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `action` | string | no | The action to be filtered. Can be `assigned`, `mentioned`, `build_failed`, `marked`, `approval_required`, `unmergeable`, `directly_addressed`, `merge_train_removed` or `member_access_requested`. | -| `author_id` | integer | no | The ID of an author | -| `project_id` | integer | no | The ID of a project | -| `group_id` | integer | no | The ID of a group | -| `state` | string | no | The state of the to-do item. Can be either `pending` or `done` | -| `type` | string | no | The type of to-do item. Can be either `Issue`, `MergeRequest`, `Commit`, `Epic`, `DesignManagement::Design` or `AlertManagement::Alert` | +| `author_id` | integer | no | The ID of an author | +| `project_id` | integer | no | The ID of a project | +| `group_id` | integer | no | The ID of a group | +| `state` | string | no | The state of the to-do item. Can be either `pending` or `done` | +| `type` | string | no | The type of to-do item. Can be either `Issue`, `MergeRequest`, `Commit`, `Epic`, `DesignManagement::Design`, `AlertManagement::Alert` or `Namespace` | ```shell curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/todos" diff --git a/doc/development/code_review.md b/doc/development/code_review.md index e2340e39903..30d9d671038 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -124,6 +124,8 @@ page, with these behaviors: branch name (unless their out-of-office (`OOO`) status changes, as in point 1). It removes leading `ce-` and `ee-`, and trailing `-ce` and `-ee`, so that it can be stable for backport branches. +- People whose Slack or [GitLab status](../user/profile/index.md#set-your-current-status) emoji + is Ⓜ `:m:`are only suggested as reviewers on projects they are a maintainer of. The [Roulette dashboard](https://gitlab-org.gitlab.io/gitlab-roulette/) contains: diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md index 562b4e10570..36ef1bcd834 100644 --- a/doc/development/gemfile.md +++ b/doc/development/gemfile.md @@ -47,7 +47,8 @@ This needs to be done for any new, or updated gems. We do not allow gems that are fetched from Git repositories. All gems have to be available in the RubyGems index. We want to minimize external build -dependencies and build times. +dependencies and build times. It's enforced by the RuboCop rule +[`Cop/GemFetcher`](https://gitlab.com/gitlab-org/ruby/gems/gitlab-styles/-/blob/master/lib/rubocop/cop/gem_fetcher.rb). ## Review the new dependency for quality diff --git a/doc/development/service_ping/metrics_dictionary.md b/doc/development/service_ping/metrics_dictionary.md index 25895d732e4..49f8a5ac465 100644 --- a/doc/development/service_ping/metrics_dictionary.md +++ b/doc/development/service_ping/metrics_dictionary.md @@ -122,10 +122,21 @@ which has a related schema in `/config/metrics/objects_schemas/topology_schema.j ### Metric `time_frame` -- `7d`: The metric data applies to the most recent 7-day interval. For example, the following metric counts the number of users that create epics over a 7-day interval: `ee/config/metrics/counts_7d/20210305145820_g_product_planning_epic_created_weekly.yml`. -- `28d`: The metric data applies to the most recent 28-day interval. For example, the following metric counts the number of unique users that create issues over a 28-day interval: `config/metrics/counts_28d/20210216181139_issues.yml`. -- `all`: The metric data applies for the whole time the metric has been active (all-time interval). For example, the following metric counts all users that create issues: `/config/metrics/counts_all/20210216181115_issues.yml`. -- `none`: The metric collects a type of data that's not tracked over time, such as settings and configuration information. Therefore, a time interval is not applicable. For example, `uuid` has no time interval applicable: `config/metrics/license/20210201124933_uuid.yml`. +A metric's time frame is calculated based on the `time_frame` field and the `data_source` of the metric. +For `redis_hll` metrics, the type of aggregation is also taken into consideration. In this context, the term "aggregation" refers to [chosen events data storage interval](implement.md#add-new-events), and is **NOT** related to the Aggregated Metrics feature. +For more information about the aggregation type of each feature, see the [`common.yml` file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data_counters/known_events/common.yml). Weeks run from Monday to Sunday. + +| data_source | time_frame | aggregation | Description | +|------------------------|------------|----------------|-------------------------------------------------| +| any | `none` | not applicable | A type of data that’s not tracked over time, such as settings and configuration information | +| `database` | `all` | not applicable | The whole time the metric has been active (all-time interval) | +| `database` | `7d` | not applicable | 9 days ago to 2 days ago | +| `database` | `28d` | not applicable | 30 days ago to 2 days ago | +| `redis` | `all` | not applicable | The whole time the metric has been active (all-time interval) | +| `redis_hll` | `7d` | `daily` | Most recent 7 complete days | +| `redis_hll` | `7d` | `weekly` | Most recent complete week | +| `redis_hll` | `28d` | `daily` | Most recent 28 complete days | +| `redis_hll` | `28d` | `weekly` | Most recent 4 complete weeks | ### Data category diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md index 275343f584d..07a21d8b941 100644 --- a/doc/user/project/import/github.md +++ b/doc/user/project/import/github.md @@ -61,6 +61,8 @@ GitLab. For this association to succeed, each GitHub author and assignee in the repository must have a [public-facing email address](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address) on GitHub that matches their GitLab email address (regardless of how the account was created). +If their email address from GitHub is set as their secondary email address in GitLab, it must be +confirmed. GitLab content imports that use GitHub accounts require that the GitHub public-facing email address is populated. This means all comments and contributions are properly mapped to the same user in GitLab. GitHub Enterprise does not require this diff --git a/lefthook.yml b/lefthook.yml index 1a8e8b5babb..d62a90d150e 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -28,7 +28,7 @@ pre-push: yamllint: tags: backend style files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD - glob: '*.{yml,yaml}' + glob: '*.{yml,yaml}{,.*}' run: scripts/lint-yaml.sh {files} stylelint: tags: stylesheet css style diff --git a/lib/api/entities/todo.rb b/lib/api/entities/todo.rb index 5bbbb59f565..02dfdb68af9 100644 --- a/lib/api/entities/todo.rb +++ b/lib/api/entities/todo.rb @@ -32,6 +32,7 @@ module API def todo_target_url(todo) return design_todo_target_url(todo) if todo.for_design? + return todo.access_request_url if todo.member_access_requested? target_type = todo.target_type.gsub('::', '_').underscore target_url = "#{todo.resource_parent.class.to_s.underscore}_#{target_type}_url" diff --git a/lib/gitlab/import_export/json/legacy_reader.rb b/lib/gitlab/import_export/json/legacy_reader.rb index dc80c92f507..ee360020556 100644 --- a/lib/gitlab/import_export/json/legacy_reader.rb +++ b/lib/gitlab/import_export/json/legacy_reader.rb @@ -27,7 +27,7 @@ module Gitlab end def read_hash - Gitlab::Json.parse(IO.read(@path)) + Gitlab::Json.parse(::File.read(@path)) rescue StandardError => e Gitlab::ErrorTracking.log_exception(e) raise Gitlab::ImportExport::Error, 'Incorrect JSON format' diff --git a/lib/gitlab/import_export/lfs_restorer.rb b/lib/gitlab/import_export/lfs_restorer.rb index 9931b09e9ca..83aab6d031e 100644 --- a/lib/gitlab/import_export/lfs_restorer.rb +++ b/lib/gitlab/import_export/lfs_restorer.rb @@ -71,7 +71,7 @@ module Gitlab @lfs_json ||= begin - json = IO.read(lfs_json_path) + json = File.read(lfs_json_path) Gitlab::Json.parse(json) rescue StandardError raise Gitlab::ImportExport::Error, 'Incorrect JSON format' diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake index ef294b1af1c..d8c0b1007e6 100644 --- a/lib/tasks/gitlab/assets.rake +++ b/lib/tasks/gitlab/assets.rake @@ -137,7 +137,7 @@ namespace :gitlab do File.open(gzip, 'wb+') do |f| gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION) gz.mtime = mtime - gz.write IO.binread(file) + gz.write File.binread(file) gz.close File.utime(mtime, mtime, f.path) diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2b149241e98..f392512b075 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5078,6 +5078,9 @@ msgstr "" msgid "Approvals are optional." msgstr "" +msgid "Approvals required" +msgstr "" + msgid "Approvals|Section: %section" msgstr "" @@ -22161,7 +22164,7 @@ msgstr "" msgid "Insights|Configure a custom report for insights into your group processes such as amount of issues, bugs, and merge requests per month. %{linkStart}How do I configure an insights report?%{linkEnd}" msgstr "" -msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config for more information)." +msgid "Insights|Some items are not visible beacuse the project was filtered out in the insights.yml file (see the projects.only config in the YAML file or the enabled project features (issues, merge requests) in the project settings)." msgstr "" msgid "Insights|This project is filtered out in the insights.yml file (see the projects.only config for more information)." @@ -33669,6 +33672,24 @@ msgstr "" msgid "ProtectedBranch|default" msgstr "" +msgid "ProtectedEnvironments|Allowed to deploy" +msgstr "" + +msgid "ProtectedEnvironments|An error occurred while fetching information on the selected approvers." +msgstr "" + +msgid "ProtectedEnvironments|Approval rules" +msgstr "" + +msgid "ProtectedEnvironments|Number of approvals must be between 1 and 5" +msgstr "" + +msgid "ProtectedEnvironments|Set which groups, access levels or users are required to approve." +msgstr "" + +msgid "ProtectedEnvironments|Set which groups, access levels or users that are allowed to deploy to this environment" +msgstr "" + msgid "ProtectedEnvironment|%{environment_name} will be writable for developers. Are you sure?" msgstr "" @@ -33681,6 +33702,9 @@ msgstr "" msgid "ProtectedEnvironment|Allowed to deploy to %{project} / %{environment}" msgstr "" +msgid "ProtectedEnvironment|Approvers" +msgstr "" + msgid "ProtectedEnvironment|Environment" msgstr "" @@ -33717,6 +33741,9 @@ msgstr "" msgid "ProtectedEnvironment|Select an environment" msgstr "" +msgid "ProtectedEnvironment|Select environment" +msgstr "" + msgid "ProtectedEnvironment|Select groups" msgstr "" @@ -43479,7 +43506,7 @@ msgstr "" msgid "Todos|added a to-do item" msgstr "" -msgid "Todos|has requested access" +msgid "Todos|has requested access to group %{which}" msgstr "" msgid "Todos|mentioned %{who}" diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb index c58b00eaa02..c48b1a67d90 100644 --- a/qa/qa/page/project/import/github.rb +++ b/qa/qa/page/project/import/github.rb @@ -81,7 +81,8 @@ module QA reload: false, skip_finished_loading_check_on_refresh: true ) do - is_partial_import = has_css?(:import_status_indicator, text: "Partial import") + status_selector = 'import_status_indicator' + is_partial_import = has_css?(status_selector, text: "Partial import") # Temporarily adding this for investigation purposes. This makes sure that the details section is # expanded when the screenshot is taken when the test fails. This can be removed or repurposed later @@ -92,7 +93,7 @@ module QA end end - has_element?(:import_status_indicator, text: "Complete") + has_element?(status_selector, text: "Complete") end end end diff --git a/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb b/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb index ab50e02c790..27f9bcc9675 100644 --- a/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb +++ b/qa/qa/specs/features/api/1_manage/import/import_github_repo_spec.rb @@ -4,7 +4,7 @@ module QA # https://github.com/gitlab-qa-github/import-test <- project under test # RSpec.describe 'Manage', product_group: :import do - describe 'GitHub import', :reliable do + describe 'GitHub import' do include_context 'with github import' context 'when imported via api' do diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/import/import_github_repo_spec.rb index d671f0c21d7..43a8af93e27 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/import/import_github_repo_spec.rb @@ -1,23 +1,11 @@ # frozen_string_literal: true module QA - # Spec uses real github.com, which means outage of github can actually block deployment - # Keep spec in reliable bucket but don't run in blocking pipelines - RSpec.describe 'Manage', :github, :reliable, :skip_live_env, :requires_admin, product_group: :import do + RSpec.describe 'Manage', product_group: :import do describe 'GitHub import' do - include QA::Support::Data::Github + include_context 'with github import' context 'when imported via UI' do - let(:github_repo) { "#{github_username}/import-test" } - let(:api_client) { Runtime::API::Client.as_admin } - let(:group) { Resource::Group.fabricate_via_api! { |resource| resource.api_client = api_client } } - let(:user) do - Resource::User.fabricate_via_api! do |resource| - resource.api_client = api_client - resource.hard_delete_on_api_removal = true - end - end - let(:imported_project) do Resource::ProjectImportedFromGithub.init do |project| project.import = true @@ -41,8 +29,6 @@ module QA end before do - group.add_member(user, Resource::Members::AccessLevel::MAINTAINER) - Flow::Login.sign_in(as: user) Page::Main::Menu.perform(&:go_to_create_project) Page::Project::New.perform do |project_page| @@ -51,10 +37,6 @@ module QA end end - after do - user.remove_via_api! - end - it 'imports a project', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347877' do Page::Project::Import::Github.perform do |import_page| import_page.add_personal_access_token(Runtime::Env.github_access_token) diff --git a/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb b/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb index da808548605..27d94b04cde 100644 --- a/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb +++ b/qa/qa/specs/features/shared_contexts/import/github_import_shared_context.rb @@ -1,12 +1,10 @@ # frozen_string_literal: true module QA - RSpec.shared_context "with github import", :github, :skip_live_env, :requires_admin, quarantine: { - type: :broken, - issue: "https://gitlab.com/gitlab-org/gitlab/-/issues/382166" - } do + RSpec.shared_context "with github import", :github, :import, :requires_admin, :orchestrated do include QA::Support::Data::Github + let!(:github_repo) { "#{github_username}/import-test" } let!(:api_client) { Runtime::API::Client.as_admin } let!(:group) do @@ -30,7 +28,7 @@ module QA project.name = 'imported-project' project.group = group project.github_personal_access_token = Runtime::Env.github_access_token - project.github_repository_path = "#{github_username}/import-test" + project.github_repository_path = github_repo project.api_client = user_api_client project.issue_events_import = true project.full_notes_import = true @@ -40,9 +38,5 @@ module QA before do group.add_member(user, Resource::Members::AccessLevel::MAINTAINER) end - - after do - user.remove_via_api! - end end end diff --git a/results.txt b/results.txt deleted file mode 100644 index e69de29bb2d..00000000000 --- a/results.txt +++ /dev/null diff --git a/scripts/review_apps/base-config.yaml b/scripts/review_apps/base-config.yaml index 4572596fe8b..0981aafec22 100644 --- a/scripts/review_apps/base-config.yaml +++ b/scripts/review_apps/base-config.yaml @@ -70,7 +70,7 @@ gitlab: memory: 920Mi limits: cpu: 800m - memory: 1100Mi + memory: 1380Mi sidekiq: resources: @@ -99,7 +99,7 @@ gitlab: cpu: 746m memory: 2809Mi limits: - cpu: 1119m + cpu: 1300m memory: 4214Mi minReplicas: 1 maxReplicas: 1 diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index 97a1265c46a..760367539fc 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -41,6 +41,10 @@ FactoryBot.define do action { Todo::UNMERGEABLE } end + trait :member_access_requested do + action { Todo::MEMBER_ACCESS_REQUESTED } + end + trait :pending do state { :pending } end diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb index bef3ac47c54..606bc82a7bb 100644 --- a/spec/features/dashboard/todos/todos_spec.rb +++ b/spec/features/dashboard/todos/todos_spec.rb @@ -445,4 +445,28 @@ RSpec.describe 'Dashboard Todos', feature_category: :team_planning do expect(page).to have_selector('.todos-list .todo', count: 1) end end + + context 'User has a todo for an access requested raised for group membership' do + let_it_be(:group) { create(:group, :public) } + + let_it_be(:todo) do + create(:todo, :member_access_requested, + user: user, + target: group, + author: author, + group: group) + end + + before do + group.add_owner(user) + sign_in(user) + + visit dashboard_todos_path + end + + it 'has todo present with access request content' do + expect(page).to have_selector('.todos-list .todo', count: 1) + expect(page).to have_content "#{author.name} has requested access to group #{group.name}" + end + end end diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index f8012242480..8986ce91ae3 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -53,7 +53,7 @@ RSpec.describe 'Import/Export - project export integration test', :js, feature_c project_json_path = File.join(tmpdir, 'project.json') expect(File).to exist(project_json_path) - project_hash = Gitlab::Json.parse(IO.read(project_json_path)) + project_hash = Gitlab::Json.parse(File.read(project_json_path)) sensitive_words.each do |sensitive_word| found = find_sensitive_attributes(sensitive_word, project_hash) @@ -79,7 +79,7 @@ RSpec.describe 'Import/Export - project export integration test', :js, feature_c expect(File).to exist(project_json_path) relations = [] - relations << Gitlab::Json.parse(IO.read(project_json_path)) + relations << Gitlab::Json.parse(File.read(project_json_path)) Dir.glob(File.join(tmpdir, 'tree/project', '*.ndjson')) do |rb_filename| File.foreach(rb_filename) do |line| relations << Gitlab::Json.parse(line) diff --git a/spec/frontend/__helpers__/mock_window_location_helper.js b/spec/frontend/__helpers__/mock_window_location_helper.js index a923ca661c5..de1e8c99b54 100644 --- a/spec/frontend/__helpers__/mock_window_location_helper.js +++ b/spec/frontend/__helpers__/mock_window_location_helper.js @@ -21,6 +21,12 @@ const useMockLocation = (fn) => { afterEach(() => { currentWindowLocation = origWindowLocation; }); + + return () => { + beforeEach(() => { + currentWindowLocation = origWindowLocation; + }); + }; }; /** diff --git a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js index fcefcb7cf66..62a3e07186a 100644 --- a/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js +++ b/spec/frontend/alerts_settings/components/alerts_settings_wrapper_spec.js @@ -32,7 +32,7 @@ import { } from '~/alerts_settings/utils/error_messages'; import { createAlert, VARIANT_SUCCESS } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; +import httpStatusCodes, { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status'; import { createHttpVariables, updateHttpVariables, @@ -358,7 +358,7 @@ describe('AlertsSettingsWrapper', () => { }); it('shows an error alert when integration test payload is invalid', async () => { - mock.onPost(/(.*)/).replyOnce(httpStatusCodes.UNPROCESSABLE_ENTITY); + mock.onPost(/(.*)/).replyOnce(HTTP_STATUS_UNPROCESSABLE_ENTITY); await wrapper.vm.testAlertPayload({ endpoint: '', data: '', token: '' }); expect(createAlert).toHaveBeenCalledWith({ message: INTEGRATION_PAYLOAD_TEST_ERROR }); expect(createAlert).toHaveBeenCalledTimes(1); diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js index 1f92010b771..5209d9c2d2c 100644 --- a/spec/frontend/api_spec.js +++ b/spec/frontend/api_spec.js @@ -1,7 +1,11 @@ import MockAdapter from 'axios-mock-adapter'; import Api, { DEFAULT_PER_PAGE } from '~/api'; import axios from '~/lib/utils/axios_utils'; -import httpStatus from '~/lib/utils/http_status'; +import httpStatus, { + HTTP_STATUS_ACCEPTED, + HTTP_STATUS_CREATED, + HTTP_STATUS_NO_CONTENT, +} from '~/lib/utils/http_status'; jest.mock('~/flash'); @@ -1069,7 +1073,7 @@ describe('Api', () => { describe('when the release is successfully created', () => { it('resolves the Promise', () => { - mock.onPost(expectedUrl, release).replyOnce(httpStatus.CREATED); + mock.onPost(expectedUrl, release).replyOnce(HTTP_STATUS_CREATED); return Api.createRelease(dummyProjectPath, release).then(() => { expect(mock.history.post).toHaveLength(1); @@ -1125,7 +1129,7 @@ describe('Api', () => { describe('when the Release is successfully created', () => { it('resolves the Promise', () => { - mock.onPost(expectedUrl, expectedLink).replyOnce(httpStatus.CREATED); + mock.onPost(expectedUrl, expectedLink).replyOnce(HTTP_STATUS_CREATED); return Api.createReleaseLink(dummyProjectPath, dummyTagName, expectedLink).then(() => { expect(mock.history.post).toHaveLength(1); @@ -1224,7 +1228,7 @@ describe('Api', () => { describe('when the merge request is successfully created', () => { it('resolves the Promise', () => { - mock.onPost(expectedUrl, options).replyOnce(httpStatus.CREATED); + mock.onPost(expectedUrl, options).replyOnce(HTTP_STATUS_CREATED); return Api.createProjectMergeRequest(dummyProjectPath, options).then(() => { expect(mock.history.post).toHaveLength(1); @@ -1332,7 +1336,7 @@ describe('Api', () => { describe('when the freeze period is successfully created', () => { it('resolves the Promise', () => { - mock.onPost(expectedUrl, options).replyOnce(httpStatus.CREATED, expectedResult); + mock.onPost(expectedUrl, options).replyOnce(HTTP_STATUS_CREATED, expectedResult); return Api.createFreezePeriod(projectId, options).then(({ data }) => { expect(data).toStrictEqual(expectedResult); @@ -1598,7 +1602,7 @@ describe('Api', () => { const secureFileId = 2; const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectId}/secure_files/${secureFileId}`; - mock.onDelete(expectedUrl).reply(httpStatus.NO_CONTENT, ''); + mock.onDelete(expectedUrl).reply(HTTP_STATUS_NO_CONTENT, ''); const { data } = await Api.deleteProjectSecureFile(projectId, secureFileId); expect(data).toEqual(''); }); @@ -1609,10 +1613,10 @@ describe('Api', () => { const groupId = 1; const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/groups/${groupId}/dependency_proxy/cache`; - mock.onDelete(expectedUrl).reply(httpStatus.ACCEPTED); + mock.onDelete(expectedUrl).reply(HTTP_STATUS_ACCEPTED); const { status } = await Api.deleteDependencyProxyCacheList(groupId, {}); - expect(status).toBe(httpStatus.ACCEPTED); + expect(status).toBe(HTTP_STATUS_ACCEPTED); }); }); diff --git a/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js index 553ca52f9ce..b2a25bc93ea 100644 --- a/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js +++ b/spec/frontend/captcha/captcha_modal_axios_interceptor_spec.js @@ -4,7 +4,10 @@ import { registerCaptchaModalInterceptor } from '~/captcha/captcha_modal_axios_i import UnsolvedCaptchaError from '~/captcha/unsolved_captcha_error'; import { waitForCaptchaToBeSolved } from '~/captcha/wait_for_captcha_to_be_solved'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; +import httpStatusCodes, { + HTTP_STATUS_CONFLICT, + HTTP_STATUS_METHOD_NOT_ALLOWED, +} from '~/lib/utils/http_status'; jest.mock('~/captcha/wait_for_captcha_to_be_solved'); @@ -33,7 +36,7 @@ describe('registerCaptchaModalInterceptor', () => { mock.onAny('/endpoint-with-unrelated-error').reply(404, AXIOS_RESPONSE); mock.onAny('/endpoint-with-captcha').reply((config) => { if (!supportedMethods.includes(config.method)) { - return [httpStatusCodes.METHOD_NOT_ALLOWED, { method: config.method }]; + return [HTTP_STATUS_METHOD_NOT_ALLOWED, { method: config.method }]; } const data = JSON.parse(config.data); @@ -46,7 +49,7 @@ describe('registerCaptchaModalInterceptor', () => { return [httpStatusCodes.OK, { ...data, method: config.method, CAPTCHA_SUCCESS }]; } - return [httpStatusCodes.CONFLICT, NEEDS_CAPTCHA_RESPONSE]; + return [HTTP_STATUS_CONFLICT, NEEDS_CAPTCHA_RESPONSE]; }); axios.interceptors.response.handlers = []; @@ -123,7 +126,7 @@ describe('registerCaptchaModalInterceptor', () => { await expect(() => axios[method]('/endpoint-with-captcha')).rejects.toThrow( expect.objectContaining({ response: expect.objectContaining({ - status: httpStatusCodes.METHOD_NOT_ALLOWED, + status: HTTP_STATUS_METHOD_NOT_ALLOWED, data: { method }, }), }), diff --git a/spec/frontend/feature_highlight/feature_highlight_helper_spec.js b/spec/frontend/feature_highlight/feature_highlight_helper_spec.js index 22bac3fca15..d82081041d9 100644 --- a/spec/frontend/feature_highlight/feature_highlight_helper_spec.js +++ b/spec/frontend/feature_highlight/feature_highlight_helper_spec.js @@ -2,7 +2,7 @@ import MockAdapter from 'axios-mock-adapter'; import { dismiss } from '~/feature_highlight/feature_highlight_helper'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; +import httpStatusCodes, { HTTP_STATUS_CREATED } from '~/lib/utils/http_status'; jest.mock('~/flash'); @@ -11,7 +11,7 @@ describe('feature highlight helper', () => { let mockAxios; const endpoint = '/-/callouts/dismiss'; const highlightId = '123'; - const { CREATED, INTERNAL_SERVER_ERROR } = httpStatusCodes; + const { INTERNAL_SERVER_ERROR } = httpStatusCodes; beforeEach(() => { mockAxios = new MockAdapter(axios); @@ -22,7 +22,7 @@ describe('feature highlight helper', () => { }); it('calls persistent dismissal endpoint with highlightId', async () => { - mockAxios.onPost(endpoint, { feature_name: highlightId }).replyOnce(CREATED); + mockAxios.onPost(endpoint, { feature_name: highlightId }).replyOnce(HTTP_STATUS_CREATED); await expect(dismiss(endpoint, highlightId)).resolves.toEqual(expect.anything()); }); diff --git a/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js index fc00bd075e7..8d21088bcaf 100644 --- a/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js +++ b/spec/frontend/ide/stores/modules/terminal/actions/checks_spec.js @@ -10,7 +10,7 @@ import { import * as messages from '~/ide/stores/modules/terminal/messages'; import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types'; import axios from '~/lib/utils/axios_utils'; -import httpStatus from '~/lib/utils/http_status'; +import httpStatus, { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status'; const TEST_PROJECT_PATH = 'lorem/root'; const TEST_BRANCH_ID = 'main'; @@ -78,7 +78,7 @@ describe('IDE store terminal check actions', () => { describe('receiveConfigCheckError', () => { it('handles error response', () => { - const status = httpStatus.UNPROCESSABLE_ENTITY; + const status = HTTP_STATUS_UNPROCESSABLE_ENTITY; const payload = { response: { status } }; return testAction( diff --git a/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js b/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js index f48797415df..df365442c67 100644 --- a/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js +++ b/spec/frontend/ide/stores/modules/terminal/actions/session_controls_spec.js @@ -6,7 +6,7 @@ import { STARTING, PENDING, STOPPING, STOPPED } from '~/ide/stores/modules/termi import * as messages from '~/ide/stores/modules/terminal/messages'; import * as mutationTypes from '~/ide/stores/modules/terminal/mutation_types'; import axios from '~/lib/utils/axios_utils'; -import httpStatus from '~/lib/utils/http_status'; +import httpStatus, { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status'; jest.mock('~/flash'); @@ -285,7 +285,7 @@ describe('IDE store terminal session controls actions', () => { ); }); - [httpStatus.NOT_FOUND, httpStatus.UNPROCESSABLE_ENTITY].forEach((status) => { + [httpStatus.NOT_FOUND, HTTP_STATUS_UNPROCESSABLE_ENTITY].forEach((status) => { it(`dispatches request and startSession on ${status}`, () => { mock .onPost(state.session.retryPath, { branch: rootState.currentBranchId, format: 'json' }) diff --git a/spec/frontend/ide/stores/modules/terminal/messages_spec.js b/spec/frontend/ide/stores/modules/terminal/messages_spec.js index e8f375a70b5..2a802d6b4af 100644 --- a/spec/frontend/ide/stores/modules/terminal/messages_spec.js +++ b/spec/frontend/ide/stores/modules/terminal/messages_spec.js @@ -1,7 +1,7 @@ import { escape } from 'lodash'; import { TEST_HOST } from 'spec/test_constants'; import * as messages from '~/ide/stores/modules/terminal/messages'; -import httpStatus from '~/lib/utils/http_status'; +import httpStatus, { HTTP_STATUS_UNPROCESSABLE_ENTITY } from '~/lib/utils/http_status'; import { sprintf } from '~/locale'; const TEST_HELP_URL = `${TEST_HOST}/help`; @@ -9,7 +9,7 @@ const TEST_HELP_URL = `${TEST_HOST}/help`; describe('IDE store terminal messages', () => { describe('configCheckError', () => { it('returns job error, with status UNPROCESSABLE_ENTITY', () => { - const result = messages.configCheckError(httpStatus.UNPROCESSABLE_ENTITY, TEST_HELP_URL); + const result = messages.configCheckError(HTTP_STATUS_UNPROCESSABLE_ENTITY, TEST_HELP_URL); expect(result).toBe( sprintf( diff --git a/spec/frontend/invite_members/components/invite_members_modal_spec.js b/spec/frontend/invite_members/components/invite_members_modal_spec.js index 9ef9589d54c..834e4f1577b 100644 --- a/spec/frontend/invite_members/components/invite_members_modal_spec.js +++ b/spec/frontend/invite_members/components/invite_members_modal_spec.js @@ -24,7 +24,7 @@ import { import eventHub from '~/invite_members/event_hub'; import ContentTransition from '~/vue_shared/components/content_transition.vue'; import axios from '~/lib/utils/axios_utils'; -import httpStatus from '~/lib/utils/http_status'; +import httpStatus, { HTTP_STATUS_CREATED } from '~/lib/utils/http_status'; import { getParameterValues } from '~/lib/utils/url_utility'; import { GROUPS_INVITATIONS_PATH, invitationsApiResponse } from '../mock_data/api_responses'; import { @@ -467,7 +467,7 @@ describe('InviteMembersModal', () => { describe('clearing the invalid state and message', () => { beforeEach(async () => { - mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.EMAIL_TAKEN); + mockInvitationsApi(HTTP_STATUS_CREATED, invitationsApiResponse.EMAIL_TAKEN); clickInviteButton(); @@ -526,7 +526,7 @@ describe('InviteMembersModal', () => { }); it('displays the restricted user api message for response with bad request', async () => { - mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.EMAIL_RESTRICTED); + mockInvitationsApi(HTTP_STATUS_CREATED, invitationsApiResponse.EMAIL_RESTRICTED); clickInviteButton(); @@ -539,7 +539,7 @@ describe('InviteMembersModal', () => { }); it('displays all errors when there are multiple existing users that are restricted by email', async () => { - mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.MULTIPLE_RESTRICTED); + mockInvitationsApi(HTTP_STATUS_CREATED, invitationsApiResponse.MULTIPLE_RESTRICTED); clickInviteButton(); @@ -636,7 +636,7 @@ describe('InviteMembersModal', () => { }); it('displays the restricted email error when restricted email is invited', async () => { - mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.EMAIL_RESTRICTED); + mockInvitationsApi(HTTP_STATUS_CREATED, invitationsApiResponse.EMAIL_RESTRICTED); clickInviteButton(); @@ -650,7 +650,7 @@ describe('InviteMembersModal', () => { }); it('displays all errors when there are multiple emails that return a restricted error message', async () => { - mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.MULTIPLE_RESTRICTED); + mockInvitationsApi(HTTP_STATUS_CREATED, invitationsApiResponse.MULTIPLE_RESTRICTED); clickInviteButton(); @@ -701,7 +701,7 @@ describe('InviteMembersModal', () => { createInviteMembersToGroupWrapper(); await triggerMembersTokenSelect([user3, user4, user5, user6]); - mockInvitationsApi(httpStatus.CREATED, invitationsApiResponse.EXPANDED_RESTRICTED); + mockInvitationsApi(HTTP_STATUS_CREATED, invitationsApiResponse.EXPANDED_RESTRICTED); clickInviteButton(); diff --git a/spec/frontend/lib/utils/poll_until_complete_spec.js b/spec/frontend/lib/utils/poll_until_complete_spec.js index 7509f954a84..3ce17ecfc8c 100644 --- a/spec/frontend/lib/utils/poll_until_complete_spec.js +++ b/spec/frontend/lib/utils/poll_until_complete_spec.js @@ -1,7 +1,7 @@ import AxiosMockAdapter from 'axios-mock-adapter'; import { TEST_HOST } from 'helpers/test_constants'; import axios from '~/lib/utils/axios_utils'; -import httpStatusCodes from '~/lib/utils/http_status'; +import httpStatusCodes, { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status'; import pollUntilComplete from '~/lib/utils/poll_until_complete'; const endpoint = `${TEST_HOST}/foo`; @@ -37,7 +37,7 @@ describe('pollUntilComplete', () => { beforeEach(() => { mock .onGet(endpoint) - .replyOnce(httpStatusCodes.NO_CONTENT, undefined, pollIntervalHeader) + .replyOnce(HTTP_STATUS_NO_CONTENT, undefined, pollIntervalHeader) .onGet(endpoint) .replyOnce(httpStatusCodes.OK, mockData); }); diff --git a/spec/frontend/monitoring/requests/index_spec.js b/spec/frontend/monitoring/requests/index_spec.js index 6f9af911a9f..def4bfe9443 100644 --- a/spec/frontend/monitoring/requests/index_spec.js +++ b/spec/frontend/monitoring/requests/index_spec.js @@ -2,7 +2,10 @@ import MockAdapter from 'axios-mock-adapter'; import { backoffMockImplementation } from 'helpers/backoff_helper'; import axios from '~/lib/utils/axios_utils'; import * as commonUtils from '~/lib/utils/common_utils'; -import statusCodes from '~/lib/utils/http_status'; +import statusCodes, { + HTTP_STATUS_NO_CONTENT, + HTTP_STATUS_UNPROCESSABLE_ENTITY, +} from '~/lib/utils/http_status'; import { getDashboard, getPrometheusQueryData } from '~/monitoring/requests'; import { metricsDashboardResponse } from '../fixture_data'; @@ -37,8 +40,8 @@ describe('monitoring metrics_requests', () => { }); it('returns a dashboard response after retrying twice', () => { - mock.onGet(dashboardEndpoint).replyOnce(statusCodes.NO_CONTENT); - mock.onGet(dashboardEndpoint).replyOnce(statusCodes.NO_CONTENT); + mock.onGet(dashboardEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); + mock.onGet(dashboardEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); mock.onGet(dashboardEndpoint).reply(statusCodes.OK, response); return getDashboard(dashboardEndpoint, params).then((data) => { @@ -81,8 +84,8 @@ describe('monitoring metrics_requests', () => { it('returns a dashboard response after retrying twice', () => { // Mock multiple attempts while the cache is filling up - mock.onGet(prometheusEndpoint).replyOnce(statusCodes.NO_CONTENT); - mock.onGet(prometheusEndpoint).replyOnce(statusCodes.NO_CONTENT); + mock.onGet(prometheusEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); + mock.onGet(prometheusEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); mock.onGet(prometheusEndpoint).reply(statusCodes.OK, response); // 3rd attempt return getPrometheusQueryData(prometheusEndpoint, params).then((data) => { @@ -116,8 +119,8 @@ describe('monitoring metrics_requests', () => { it('rejects after retrying twice and getting an HTTP 500 error', () => { // Mock multiple attempts while the cache is filling up and fails - mock.onGet(prometheusEndpoint).replyOnce(statusCodes.NO_CONTENT); - mock.onGet(prometheusEndpoint).replyOnce(statusCodes.NO_CONTENT); + mock.onGet(prometheusEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); + mock.onGet(prometheusEndpoint).replyOnce(HTTP_STATUS_NO_CONTENT); mock.onGet(prometheusEndpoint).reply(500, { status: 'error', error: 'An error occurred', @@ -132,7 +135,7 @@ describe('monitoring metrics_requests', () => { it.each` code | reason ${statusCodes.BAD_REQUEST} | ${'Parameters are missing or incorrect'} - ${statusCodes.UNPROCESSABLE_ENTITY} | ${"Expression can't be executed"} + ${HTTP_STATUS_UNPROCESSABLE_ENTITY} | ${"Expression can't be executed"} ${statusCodes.SERVICE_UNAVAILABLE} | ${'Query timed out or aborted'} `('rejects with details: "$reason" after getting an HTTP $code error', ({ code, reason }) => { mock.onGet(prometheusEndpoint).reply(code, { diff --git a/spec/frontend/monitoring/store/actions_spec.js b/spec/frontend/monitoring/store/actions_spec.js index ca66768c3cc..93af6526c67 100644 --- a/spec/frontend/monitoring/store/actions_spec.js +++ b/spec/frontend/monitoring/store/actions_spec.js @@ -4,7 +4,10 @@ import testAction from 'helpers/vuex_action_helper'; import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import * as commonUtils from '~/lib/utils/common_utils'; -import statusCodes from '~/lib/utils/http_status'; +import statusCodes, { + HTTP_STATUS_CREATED, + HTTP_STATUS_UNPROCESSABLE_ENTITY, +} from '~/lib/utils/http_status'; import { ENVIRONMENT_AVAILABLE_STATE } from '~/monitoring/constants'; import getAnnotations from '~/monitoring/queries/get_annotations.query.graphql'; @@ -944,7 +947,7 @@ describe('Monitoring store actions', () => { }); it('Succesful POST request resolves', async () => { - mock.onPost(state.dashboardsEndpoint).reply(statusCodes.CREATED, { + mock.onPost(state.dashboardsEndpoint).reply(HTTP_STATUS_CREATED, { dashboard: dashboardGitResponse[1], }); @@ -969,7 +972,7 @@ describe('Monitoring store actions', () => { commit_message: 'A new commit message', }); - mock.onPost(state.dashboardsEndpoint).reply(statusCodes.CREATED, { + mock.onPost(state.dashboardsEndpoint).reply(HTTP_STATUS_CREATED, { dashboard: mockCreatedDashboard, }); @@ -1133,7 +1136,7 @@ describe('Monitoring store actions', () => { mock .onPost(panelPreviewEndpoint, { panel_yaml: mockYmlContent }) - .reply(statusCodes.UNPROCESSABLE_ENTITY, { + .reply(HTTP_STATUS_UNPROCESSABLE_ENTITY, { message: mockErrorMsg, }); diff --git a/spec/frontend/self_monitor/store/actions_spec.js b/spec/frontend/self_monitor/store/actions_spec.js index 21e63533c66..65c9d2f5f01 100644 --- a/spec/frontend/self_monitor/store/actions_spec.js +++ b/spec/frontend/self_monitor/store/actions_spec.js @@ -1,7 +1,7 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; -import statusCodes from '~/lib/utils/http_status'; +import statusCodes, { HTTP_STATUS_ACCEPTED } from '~/lib/utils/http_status'; import * as actions from '~/self_monitor/store/actions'; import * as types from '~/self_monitor/store/mutation_types'; import createState from '~/self_monitor/store/state'; @@ -44,7 +44,7 @@ describe('self-monitor actions', () => { beforeEach(() => { state.createProjectEndpoint = '/create'; state.createProjectStatusEndpoint = '/create_status'; - mock.onPost(state.createProjectEndpoint).reply(statusCodes.ACCEPTED, { + mock.onPost(state.createProjectEndpoint).reply(HTTP_STATUS_ACCEPTED, { job_id: '123', }); mock.onGet(state.createProjectStatusEndpoint).reply(statusCodes.OK, { @@ -151,7 +151,7 @@ describe('self-monitor actions', () => { beforeEach(() => { state.deleteProjectEndpoint = '/delete'; state.deleteProjectStatusEndpoint = '/delete-status'; - mock.onDelete(state.deleteProjectEndpoint).reply(statusCodes.ACCEPTED, { + mock.onDelete(state.deleteProjectEndpoint).reply(HTTP_STATUS_ACCEPTED, { job_id: '456', }); mock.onGet(state.deleteProjectStatusEndpoint).reply(statusCodes.OK, { diff --git a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js index 8f07adda805..baef247b649 100644 --- a/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extensions/test_report/index_spec.js @@ -8,7 +8,7 @@ import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; -import httpStatusCodes from '~/lib/utils/http_status'; +import httpStatusCodes, { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status'; import TestCaseDetails from '~/pipelines/components/test_reports/test_case_details.vue'; import { failedReport } from 'jest/ci/reports/mock_data/mock_data'; @@ -82,7 +82,7 @@ describe('Test report extension', () => { }); it('with a 204 response, continues to display loading state', async () => { - mockApi(httpStatusCodes.NO_CONTENT, ''); + mockApi(HTTP_STATUS_NO_CONTENT, ''); createComponent(); await waitForPromises(); diff --git a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js index a86fbc1500e..f0ebbb1a82e 100644 --- a/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js +++ b/spec/frontend/vue_merge_request_widget/extentions/code_quality/index_spec.js @@ -7,7 +7,7 @@ import axios from '~/lib/utils/axios_utils'; import extensionsContainer from '~/vue_merge_request_widget/components/extensions/container'; import { registerExtension } from '~/vue_merge_request_widget/components/extensions'; import codeQualityExtension from '~/vue_merge_request_widget/extensions/code_quality'; -import httpStatusCodes from '~/lib/utils/http_status'; +import httpStatusCodes, { HTTP_STATUS_NO_CONTENT } from '~/lib/utils/http_status'; import { i18n } from '~/vue_merge_request_widget/extensions/code_quality/constants'; import { codeQualityResponseNewErrors, @@ -63,7 +63,7 @@ describe('Code Quality extension', () => { }); it('with a 204 response, continues to display loading state', async () => { - mockApi(httpStatusCodes.NO_CONTENT, ''); + mockApi(HTTP_STATUS_NO_CONTENT, ''); createComponent(); await waitForPromises(); diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_metadata_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_metadata_spec.js index 47489d4796b..6b3023173f5 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_link_child_metadata_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_metadata_spec.js @@ -1,4 +1,4 @@ -import { GlLabel, GlAvatarsInline } from '@gitlab/ui'; +import { GlIcon, GlLabel, GlAvatarsInline } from '@gitlab/ui'; import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; @@ -12,6 +12,7 @@ describe('WorkItemLinkChildMetadata', () => { const createComponent = ({ allowsScopedLabels = true, + progress = 10, milestone = mockMilestone, assignees = mockAssignees, labels = mockLabels, @@ -19,6 +20,7 @@ describe('WorkItemLinkChildMetadata', () => { wrapper = shallowMountExtended(WorkItemLinkChildMetadata, { propsData: { allowsScopedLabels, + progress, milestone, assignees, labels, @@ -30,7 +32,16 @@ describe('WorkItemLinkChildMetadata', () => { createComponent(); }); - it('renders milestone link button', () => { + it('renders item progress', () => { + const progressEl = wrapper.findByTestId('item-progress'); + + expect(progressEl.exists()).toBe(true); + expect(progressEl.attributes('title')).toBe('Progress'); + expect(progressEl.text().trim()).toBe('10%'); + expect(progressEl.findComponent(GlIcon).props('name')).toBe('progress'); + }); + + it('renders item milestone', () => { const milestoneLink = wrapper.findComponent(ItemMilestone); expect(milestoneLink.exists()).toBe(true); diff --git a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js index 73d498ad055..d8e0e80db79 100644 --- a/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js +++ b/spec/frontend/work_items/components/work_item_links/work_item_link_child_spec.js @@ -149,6 +149,7 @@ describe('WorkItemLinkChild', () => { expect(metadataEl.exists()).toBe(true); expect(metadataEl.props()).toMatchObject({ allowsScopedLabels: true, + progress: 10, milestone: mockMilestone, assignees: mockAssignees, labels: mockLabels, diff --git a/spec/frontend/work_items/mock_data.js b/spec/frontend/work_items/mock_data.js index f7301701dc3..e88b45a160b 100644 --- a/spec/frontend/work_items/mock_data.js +++ b/spec/frontend/work_items/mock_data.js @@ -969,6 +969,11 @@ export const workItemObjectiveWithChild = { __typename: 'WorkItemWidgetHierarchy', }, { + type: 'PROGRESS', + __typename: 'WorkItemWidgetProgress', + progress: 10, + }, + { type: 'MILESTONE', __typename: 'WorkItemWidgetMilestone', milestone: mockMilestone, diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb index a3679caa1d8..ca334a04fe9 100644 --- a/spec/helpers/todos_helper_spec.rb +++ b/spec/helpers/todos_helper_spec.rb @@ -14,6 +14,8 @@ RSpec.describe TodosHelper do note: 'I am note, hear me roar') end + let_it_be(:group) { create(:group, :public, name: 'Group 1') } + let_it_be(:design_todo) do create(:todo, :mentioned, user: user, @@ -37,6 +39,10 @@ RSpec.describe TodosHelper do create(:todo, target: issue) end + let_it_be(:group_todo) do + create(:todo, target: group) + end + describe '#todos_count_format' do it 'shows fuzzy count for 100 or more items' do expect(helper.todos_count_format(100)).to eq '99+' @@ -155,9 +161,7 @@ RSpec.describe TodosHelper do end context 'when a user requests access to group' do - let(:group) { create(:group, :public) } - - let(:group_access_request_todo) do + let_it_be(:group_access_request_todo) do create(:todo, target_id: group.id, target_type: group.class.polymorphic_name, @@ -358,7 +362,6 @@ RSpec.describe TodosHelper do Todo::APPROVAL_REQUIRED | false | format(s_("Todos|set %{who} as an approver"), who: _('you')) Todo::UNMERGEABLE | true | s_('Todos|Could not merge') Todo::MERGE_TRAIN_REMOVED | true | s_("Todos|Removed from Merge Train") - Todo::MEMBER_ACCESS_REQUESTED | true | s_("Todos|has requested access") end with_them do @@ -369,6 +372,18 @@ RSpec.describe TodosHelper do it { expect(helper.todo_action_name(alert_todo)).to eq(expected_action_name) } end + + context 'member access requested' do + context 'when source is group' do + it 'returns group access message' do + group_todo.action = Todo::MEMBER_ACCESS_REQUESTED + + expect(helper.todo_action_name(group_todo)).to eq( + format(s_("Todos|has requested access to group %{which}"), which: _(group.name)) + ) + end + end + end end describe '#todo_due_date' do diff --git a/spec/lib/backup/gitaly_backup_spec.rb b/spec/lib/backup/gitaly_backup_spec.rb index 6b0747735ed..7cc8ce2cbae 100644 --- a/spec/lib/backup/gitaly_backup_spec.rb +++ b/spec/lib/backup/gitaly_backup_spec.rb @@ -61,7 +61,7 @@ RSpec.describe Backup::GitalyBackup do it 'erases any existing repository backups' do existing_file = File.join(destination, 'some_existing_file') - IO.write(existing_file, "Some existing file.\n") + File.write(existing_file, "Some existing file.\n") subject.start(:create, destination, backup_id: backup_id) subject.finish! diff --git a/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb index dbd6cb243f6..a5b03974bc0 100644 --- a/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/group/legacy_tree_restorer_spec.rb @@ -77,7 +77,7 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeRestorer do let(:group) { create(:group) } let(:shared) { Gitlab::ImportExport::Shared.new(group) } let(:group_tree_restorer) { described_class.new(user: importer_user, shared: shared, group: group, group_hash: nil) } - let(:group_json) { Gitlab::Json.parse(IO.read(File.join(shared.export_path, 'group.json'))) } + let(:group_json) { Gitlab::Json.parse(File.read(File.join(shared.export_path, 'group.json'))) } shared_examples 'excluded attributes' do excluded_attributes = %w[ diff --git a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb index 31d647f883a..f5a4fc79c90 100644 --- a/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/group/legacy_tree_saver_spec.rb @@ -154,6 +154,6 @@ RSpec.describe Gitlab::ImportExport::Group::LegacyTreeSaver do end def group_json(filename) - ::JSON.parse(IO.read(filename)) + ::JSON.parse(File.read(filename)) end end diff --git a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb index c794694f08b..aa30e24296e 100644 --- a/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/group/tree_restorer_spec.rb @@ -112,7 +112,7 @@ RSpec.describe Gitlab::ImportExport::Group::TreeRestorer, feature: :subgroups do let(:shared) { Gitlab::ImportExport::Shared.new(group) } let(:group_tree_restorer) { described_class.new(user: importer_user, shared: shared, group: group) } let(:exported_file) { File.join(shared.export_path, 'tree/groups/4352.json') } - let(:group_json) { Gitlab::Json.parse(IO.read(exported_file)) } + let(:group_json) { Gitlab::Json.parse(File.read(exported_file)) } shared_examples 'excluded attributes' do excluded_attributes = %w[ diff --git a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb index b1f5574fba1..d8a4230e5da 100644 --- a/spec/lib/gitlab/import_export/import_test_coverage_spec.rb +++ b/spec/lib/gitlab/import_export/import_test_coverage_spec.rb @@ -86,7 +86,7 @@ RSpec.describe 'Test coverage of the Project Import' do end def relations_from_json(json_file) - json = Gitlab::Json.parse(IO.read(json_file)) + json = Gitlab::Json.parse(File.read(json_file)) [].tap { |res| gather_relations({ project: json }, res, []) } .map { |relation_names| relation_names.join('.') } diff --git a/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb b/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb index ed4368ba802..e8ecd98b1e1 100644 --- a/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb +++ b/spec/lib/gitlab/import_export/json/legacy_writer_spec.rb @@ -96,6 +96,6 @@ RSpec.describe Gitlab::ImportExport::Json::LegacyWriter do def subject_json subject.close - ::JSON.parse(IO.read(subject.path)) + ::JSON.parse(File.read(subject.path)) end end diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb index aa456736f78..5b6f50025ff 100644 --- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb +++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Gitlab::ImportExport::LfsSaver do let(:lfs_json_file) { File.join(shared.export_path, Gitlab::ImportExport.lfs_objects_filename) } def lfs_json - Gitlab::Json.parse(IO.read(lfs_json_file)) + Gitlab::Json.parse(File.read(lfs_json_file)) end before do diff --git a/spec/lib/gitlab/import_export/project/relation_saver_spec.rb b/spec/lib/gitlab/import_export/project/relation_saver_spec.rb index 0467b63e918..5032dd864bb 100644 --- a/spec/lib/gitlab/import_export/project/relation_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project/relation_saver_spec.rb @@ -111,7 +111,7 @@ RSpec.describe Gitlab::ImportExport::Project::RelationSaver do end def read_json(path) - Gitlab::Json.parse(IO.read(path)) + Gitlab::Json.parse(File.read(path)) end def read_ndjson(path) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index d3ef342168c..dfba0470d35 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -2816,7 +2816,7 @@ RSpec.describe Group do end describe 'has_project_with_service_desk_enabled?' do - let_it_be(:group) { create(:group, :private) } + let_it_be_with_refind(:group) { create(:group, :private) } subject { group.has_project_with_service_desk_enabled? } diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 2ffc9846e92..994d5688808 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -31,7 +31,7 @@ RSpec.describe WebHook do it { is_expected.to allow_value({ 'MY_TOKEN' => 'bar' }).for(:url_variables) } it { is_expected.to allow_value({ 'foo2' => 'bar' }).for(:url_variables) } it { is_expected.to allow_value({ 'x' => 'y' }).for(:url_variables) } - it { is_expected.to allow_value({ 'x' => ('a' * 100) }).for(:url_variables) } + it { is_expected.to allow_value({ 'x' => ('a' * 2048) }).for(:url_variables) } it { is_expected.to allow_value({ 'foo' => 'bar', 'bar' => 'baz' }).for(:url_variables) } it { is_expected.to allow_value((1..20).to_h { ["k#{_1}", 'value'] }).for(:url_variables) } it { is_expected.to allow_value({ 'MY-TOKEN' => 'bar' }).for(:url_variables) } @@ -45,7 +45,7 @@ RSpec.describe WebHook do it { is_expected.not_to allow_value({ 'bar' => :baz }).for(:url_variables) } it { is_expected.not_to allow_value({ 'bar' => nil }).for(:url_variables) } it { is_expected.not_to allow_value({ 'foo' => '' }).for(:url_variables) } - it { is_expected.not_to allow_value({ 'foo' => ('a' * 101) }).for(:url_variables) } + it { is_expected.not_to allow_value({ 'foo' => ('a' * 2049) }).for(:url_variables) } it { is_expected.not_to allow_value({ 'has spaces' => 'foo' }).for(:url_variables) } it { is_expected.not_to allow_value({ '' => 'foo' }).for(:url_variables) } it { is_expected.not_to allow_value({ '1foo' => 'foo' }).for(:url_variables) } diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 23ba0be2fbc..221f09dd87f 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -56,6 +56,15 @@ RSpec.describe Todo do expect(subject.body).to eq 'quick fix' end + + it 'returns full path of target when action is member_access_requested' do + group = create(:group) + + subject.target = group + subject.action = Todo::MEMBER_ACCESS_REQUESTED + + expect(subject.body).to eq group.full_path + end end describe '#done' do @@ -182,6 +191,17 @@ RSpec.describe Todo do expect(subject.target_reference).to eq issue.to_reference(full: false) end + + context 'when target is member access requested' do + it 'returns group full path' do + group = create(:group) + + subject.target = group + subject.action = Todo::MEMBER_ACCESS_REQUESTED + + expect(subject.target_reference).to eq group.full_path + end + end end describe '#self_added?' do diff --git a/spec/requests/api/todos_spec.rb b/spec/requests/api/todos_spec.rb index 064654dabd3..5a342f79926 100644 --- a/spec/requests/api/todos_spec.rb +++ b/spec/requests/api/todos_spec.rb @@ -15,6 +15,7 @@ RSpec.describe API::Todos, feature_category: :source_code_management do let_it_be(:work_item) { create(:work_item, :task, project: project_1) } let_it_be(:merge_request) { create(:merge_request, source_project: project_1) } let_it_be(:alert) { create(:alert_management_alert, project: project_1) } + let_it_be(:group_request_todo) { create(:todo, author: author_1, user: john_doe, target: group, action: Todo::MEMBER_ACCESS_REQUESTED) } let_it_be(:alert_todo) { create(:todo, project: project_1, author: john_doe, user: john_doe, target: alert) } let_it_be(:merge_request_todo) { create(:todo, project: project_1, author: author_2, user: john_doe, target: merge_request) } let_it_be(:pending_1) { create(:todo, :mentioned, project: project_1, author: author_1, user: john_doe, target: issue) } @@ -71,7 +72,7 @@ RSpec.describe API::Todos, feature_category: :source_code_management do expect(response).to have_gitlab_http_status(:ok) expect(response).to include_pagination_headers expect(json_response).to be_an Array - expect(json_response.length).to eq(6) + expect(json_response.length).to eq(7) expect(json_response[0]).to include( 'id' => pending_5.id, @@ -127,6 +128,17 @@ RSpec.describe API::Todos, feature_category: :source_code_management do 'title' => alert.title ) ) + + expect(json_response[6]).to include( + 'target_type' => 'Namespace', + 'action_name' => 'member_access_requested', + 'target' => hash_including( + 'id' => group.id, + 'name' => group.name, + 'full_path' => group.full_path + ), + 'target_url' => Gitlab::Routing.url_helpers.group_group_members_url(group, tab: 'access_requests') + ) end context "when current user does not have access to one of the TODO's target" do @@ -137,7 +149,7 @@ RSpec.describe API::Todos, feature_category: :source_code_management do get api('/todos', john_doe) - expect(json_response.count).to eq(6) + expect(json_response.count).to eq(7) expect(json_response.map { |t| t['id'] }).not_to include(no_access_todo.id, pending_4.id) end end @@ -231,7 +243,7 @@ RSpec.describe API::Todos, feature_category: :source_code_management do create(:on_commit_todo, project: new_todo.project, author: author_1, user: john_doe, target: merge_request_3) create(:todo, project: new_todo.project, author: author_2, user: john_doe, target: merge_request_3) - expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control1).with_threshold(5) + expect { get api('/todos', john_doe) }.not_to exceed_query_limit(control1).with_threshold(6) control2 = ActiveRecord::QueryRecorder.new { get api('/todos', john_doe) } create_issue_todo_for(john_doe) diff --git a/spec/support/helpers/gitaly_setup.rb b/spec/support/helpers/gitaly_setup.rb index 278dc79e1d0..20c104cd85c 100644 --- a/spec/support/helpers/gitaly_setup.rb +++ b/spec/support/helpers/gitaly_setup.rb @@ -205,7 +205,7 @@ module GitalySetup # This code needs to work in an environment where we cannot use bundler, # so we cannot easily use the toml-rb gem. This ad-hoc parser should be # good enough. - config_text = IO.read(toml) + config_text = File.read(toml) config_text.lines.each do |line| match_data = line.match(/^\s*(socket_path|listen_addr)\s*=\s*"([^"]*)"$/) diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb index 9da151895a7..3d7a0d29e71 100644 --- a/spec/support/import_export/common_util.rb +++ b/spec/support/import_export/common_util.rb @@ -83,7 +83,7 @@ module ImportExport path = File.join(dir_path, "#{exportable_path}.json") return unless File.exist?(path) - Gitlab::Json.parse(IO.read(path)) + Gitlab::Json.parse(File.read(path)) end def consume_relations(dir_path, exportable_path, key) @@ -101,7 +101,7 @@ module ImportExport end def project_json(filename) - Gitlab::Json.parse(IO.read(filename)) + Gitlab::Json.parse(File.read(filename)) end end end |