diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-08 18:08:00 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-08 18:08:00 +0300 |
commit | 684838d4bea13af1dac9c2f32b99985bf0f9f8e2 (patch) | |
tree | 611fcc177ba5a4fe702668c25aa68119675dbd8e | |
parent | 012f9a4b9ec4a78d9593d882b38f95e376c2cfe2 (diff) |
Add latest changes from gitlab-org/gitlab@master
106 files changed, 824 insertions, 182 deletions
diff --git a/Gemfile.checksum b/Gemfile.checksum index 38a57d2b85c..9bb151adb66 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -95,7 +95,7 @@ {"name":"crass","version":"1.0.6","platform":"ruby","checksum":"dc516022a56e7b3b156099abc81b6d2b08ea1ed12676ac7a5657617f012bd45d"}, {"name":"creole","version":"0.5.0","platform":"ruby","checksum":"951701e2d80760f156b1cb2a93471ca97c076289becc067a33b745133ed32c03"}, {"name":"crystalball","version":"0.7.0","platform":"ruby","checksum":"6e729f372a5071daec877adb40c5df4cb25fe21f350635e2a9624373fc151ef2"}, -{"name":"css_parser","version":"1.12.0","platform":"ruby","checksum":"8b7c04bca32257da0c65bd7b1fa585df5a0fd9f5197ccd78498d5598dd900784"}, +{"name":"css_parser","version":"1.14.0","platform":"ruby","checksum":"f2ce6148cd505297b07bdbe7a5db4cce5cf530071f9b732b9a23538d6cdc0113"}, {"name":"cvss-suite","version":"3.0.1","platform":"ruby","checksum":"b5ca9e9e94032a42fd0dc28c1e305378b62c949e35ed7111fc4a1d76f68ad3f9"}, {"name":"danger","version":"8.6.1","platform":"ruby","checksum":"d95eb58b41f68d3aaa9bbef697916b6b4d161a38819517c98562531be75cdfd8"}, {"name":"danger-gitlab","version":"8.0.0","platform":"ruby","checksum":"497dd7d0f6513913de651019223d8058cf494df10acbd17de92b175dfa04a3a8"}, diff --git a/Gemfile.lock b/Gemfile.lock index 9ae73215463..dd8b77c40cc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -314,7 +314,7 @@ GEM creole (0.5.0) crystalball (0.7.0) git - css_parser (1.12.0) + css_parser (1.14.0) addressable cvss-suite (3.0.1) danger (8.6.1) diff --git a/app/assets/javascripts/analytics/cycle_analytics/store/actions.js b/app/assets/javascripts/analytics/cycle_analytics/store/actions.js index 4a201e00582..3b9dd80a57e 100644 --- a/app/assets/javascripts/analytics/cycle_analytics/store/actions.js +++ b/app/assets/javascripts/analytics/cycle_analytics/store/actions.js @@ -6,7 +6,7 @@ import { getValueStreamStageCounts, } from '~/api/analytics_api'; import { normalizeHeaders, parseIntPagination } from '~/lib/utils/common_utils'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __ } from '~/locale'; import { DEFAULT_VALUE_STREAM, I18N_VSA_ERROR_STAGE_MEDIAN } from '../constants'; import * as types from './mutation_types'; diff --git a/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue b/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue index f917248cd13..3082897af76 100644 --- a/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue +++ b/app/assets/javascripts/analytics/shared/components/value_stream_metrics.vue @@ -1,7 +1,7 @@ <script> import { GlSkeletonLoader } from '@gitlab/ui'; import { isEqual, keyBy } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { sprintf, s__ } from '~/locale'; import { fetchMetricsData, removeFlash } from '../utils'; import ValueStreamsDashboardLink from './value_streams_dashboard_link.vue'; diff --git a/app/assets/javascripts/analytics/usage_trends/components/usage_counts.vue b/app/assets/javascripts/analytics/usage_trends/components/usage_counts.vue index 5651789e2c7..1fd1f91bda3 100644 --- a/app/assets/javascripts/analytics/usage_trends/components/usage_counts.vue +++ b/app/assets/javascripts/analytics/usage_trends/components/usage_counts.vue @@ -1,7 +1,7 @@ <script> import { GlSkeletonLoader } from '@gitlab/ui'; import { GlSingleStat } from '@gitlab/ui/dist/charts'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { number } from '~/lib/utils/unit_format'; import { __, s__ } from '~/locale'; import usageTrendsCountQuery from '../graphql/queries/usage_trends_count.query.graphql'; diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 1855fb9ed8c..de67e01d650 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -7,7 +7,7 @@ import { getEmojiScoreWithIntent } from '~/emoji/utils'; import { getCookie, setCookie, scrollToElement } from '~/lib/utils/common_utils'; import * as Emoji from '~/emoji'; import { dispose, fixTitle } from '~/tooltips'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from './lib/utils/axios_utils'; import { isInVueNoteablePage } from './lib/utils/dom_utils'; import { __ } from './locale'; diff --git a/app/assets/javascripts/badges/components/badge_form.vue b/app/assets/javascripts/badges/components/badge_form.vue index c95c90d5daf..1a80030c7e6 100644 --- a/app/assets/javascripts/badges/components/badge_form.vue +++ b/app/assets/javascripts/badges/components/badge_form.vue @@ -3,7 +3,7 @@ import { GlLoadingIcon, GlFormInput, GlFormGroup, GlButton } from '@gitlab/ui'; import { escape, debounce } from 'lodash'; import { mapActions, mapState } from 'vuex'; import SafeHtml from '~/vue_shared/directives/safe_html'; -import { createAlert, VARIANT_INFO } from '~/flash'; +import { createAlert, VARIANT_INFO } from '~/alert'; import { s__, sprintf } from '~/locale'; import createEmptyBadge from '../empty_badge'; import { PLACEHOLDERS } from '../constants'; diff --git a/app/assets/javascripts/badges/components/badge_settings.vue b/app/assets/javascripts/badges/components/badge_settings.vue index a7a21d65475..416e4fcecc7 100644 --- a/app/assets/javascripts/badges/components/badge_settings.vue +++ b/app/assets/javascripts/badges/components/badge_settings.vue @@ -1,7 +1,7 @@ <script> import { GlSprintf, GlModal } from '@gitlab/ui'; import { mapState, mapActions } from 'vuex'; -import { createAlert, VARIANT_INFO } from '~/flash'; +import { createAlert, VARIANT_INFO } from '~/alert'; import { __, s__ } from '~/locale'; import Badge from './badge.vue'; import BadgeForm from './badge_form.vue'; diff --git a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue index ed0481e7a48..beda251aa1e 100644 --- a/app/assets/javascripts/batch_comments/components/submit_dropdown.vue +++ b/app/assets/javascripts/batch_comments/components/submit_dropdown.vue @@ -9,7 +9,7 @@ import { GlFormCheckbox, } from '@gitlab/ui'; import { mapGetters, mapActions } from 'vuex'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import MarkdownField from '~/vue_shared/components/markdown/field.vue'; import { scrollToElement } from '~/lib/utils/common_utils'; import Autosave from '~/autosave'; diff --git a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js index 1d2ac936dff..f6eae7c0c83 100644 --- a/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js +++ b/app/assets/javascripts/batch_comments/stores/modules/batch_comments/actions.js @@ -1,5 +1,5 @@ import { isEmpty } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { scrollToElement } from '~/lib/utils/common_utils'; import { __ } from '~/locale'; import { CHANGES_TAB, DISCUSSION_TAB, SHOW_TAB } from '../../../constants'; diff --git a/app/assets/javascripts/blob/file_template_mediator.js b/app/assets/javascripts/blob/file_template_mediator.js index 2ea3c93625d..7ccb66f18a9 100644 --- a/app/assets/javascripts/blob/file_template_mediator.js +++ b/app/assets/javascripts/blob/file_template_mediator.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import Api from '~/api'; import initPopover from '~/blob/suggest_gitlab_ci_yml'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __ } from '~/locale'; import toast from '~/vue_shared/plugins/global_toast'; diff --git a/app/assets/javascripts/blob/viewer/index.js b/app/assets/javascripts/blob/viewer/index.js index 5e85e4cea38..bdaefe8383c 100644 --- a/app/assets/javascripts/blob/viewer/index.js +++ b/app/assets/javascripts/blob/viewer/index.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import { renderGFM } from '~/behaviors/markdown/render_gfm'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __ } from '~/locale'; import { REPO_BLOB_LOAD_VIEWER_START, diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js index 948834d69e5..fc8af52ffce 100644 --- a/app/assets/javascripts/boards/constants.js +++ b/app/assets/javascripts/boards/constants.js @@ -130,6 +130,7 @@ export const MilestoneFilterType = { started: 'Started', upcoming: 'Upcoming', }; +/* eslint-enable @gitlab/require-i18n-strings */ export const DraggableItemTypes = { card: 'card', diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 65816495432..e3cd43ac22f 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -1,6 +1,6 @@ import $ from 'jquery'; import { merge } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import FilesCommentButton from './files_comment_button'; diff --git a/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js b/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js index 58ddaa94d5e..370111e38b8 100644 --- a/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js +++ b/app/assets/javascripts/editor/extensions/source_editor_markdown_livepreview_ext.js @@ -1,7 +1,7 @@ import { KeyMod, KeyCode, Emitter } from 'monaco-editor'; import { debounce } from 'lodash'; import { BLOB_PREVIEW_ERROR } from '~/blob_edit/constants'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { sanitize } from '~/lib/dompurify'; import axios from '~/lib/utils/axios_utils'; import syntaxHighlight from '~/syntax_highlight'; diff --git a/app/assets/javascripts/error_tracking_settings/store/actions.js b/app/assets/javascripts/error_tracking_settings/store/actions.js index 4d6fe767f3a..368dd438f89 100644 --- a/app/assets/javascripts/error_tracking_settings/store/actions.js +++ b/app/assets/javascripts/error_tracking_settings/store/actions.js @@ -1,4 +1,4 @@ -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { refreshCurrentPage } from '~/lib/utils/url_utility'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js index a9542a9667e..e2218c1ba2e 100644 --- a/app/assets/javascripts/feature_highlight/feature_highlight_helper.js +++ b/app/assets/javascripts/feature_highlight/feature_highlight_helper.js @@ -1,4 +1,4 @@ -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/gpg_badges.js b/app/assets/javascripts/gpg_badges.js index ad339155a59..b45c98b46f6 100644 --- a/app/assets/javascripts/gpg_badges.js +++ b/app/assets/javascripts/gpg_badges.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { queryToObject } from '~/lib/utils/url_utility'; import { loadingIconForLegacyJS } from '~/loading_icon_for_legacy_js'; diff --git a/app/assets/javascripts/graphql_shared/constants.js b/app/assets/javascripts/graphql_shared/constants.js index 3c4ca4c197e..77fca45c949 100644 --- a/app/assets/javascripts/graphql_shared/constants.js +++ b/app/assets/javascripts/graphql_shared/constants.js @@ -26,3 +26,4 @@ export const TYPENAME_USER = 'User'; export const TYPENAME_VULNERABILITIES_SCANNER = 'Vulnerabilities::Scanner'; export const TYPENAME_VULNERABILITY = 'Vulnerability'; export const TYPENAME_WORK_ITEM = 'WorkItem'; +export const TYPE_USERS_SAVED_REPLY = 'Users::SavedReply'; diff --git a/app/assets/javascripts/group.js b/app/assets/javascripts/group.js index cc70d832edc..3b64606b141 100644 --- a/app/assets/javascripts/group.js +++ b/app/assets/javascripts/group.js @@ -1,6 +1,6 @@ import { debounce } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __ } from '~/locale'; import { getGroupPathAvailability } from '~/rest_api'; import axios from '~/lib/utils/axios_utils'; diff --git a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.vue b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.vue index 707e8a0645f..c6feb684795 100644 --- a/app/assets/javascripts/merge_conflicts/components/diff_file_editor.vue +++ b/app/assets/javascripts/merge_conflicts/components/diff_file_editor.vue @@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui'; import { debounce } from 'lodash'; import { mapActions } from 'vuex'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import { INTERACTIVE_RESOLVE_MODE } from '../constants'; diff --git a/app/assets/javascripts/merge_conflicts/store/actions.js b/app/assets/javascripts/merge_conflicts/store/actions.js index f84eaabf9e7..07a32a77c6a 100644 --- a/app/assets/javascripts/merge_conflicts/store/actions.js +++ b/app/assets/javascripts/merge_conflicts/store/actions.js @@ -1,5 +1,5 @@ import { setCookie } from '~/lib/utils/common_utils'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import { INTERACTIVE_RESOLVE_MODE, EDIT_RESOLVE_MODE } from '../constants'; diff --git a/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js b/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js index 97fb64f9971..54c1e37d899 100644 --- a/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js +++ b/app/assets/javascripts/pages/admin/application_settings/payload_downloader.js @@ -1,4 +1,4 @@ -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js index 1cd19fc09a8..41862789185 100644 --- a/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js +++ b/app/assets/javascripts/pages/admin/application_settings/payload_previewer.js @@ -1,4 +1,4 @@ -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/pages/admin/jobs/index/components/cancel_jobs_modal.vue b/app/assets/javascripts/pages/admin/jobs/index/components/cancel_jobs_modal.vue index d5857294617..2aff20a1928 100644 --- a/app/assets/javascripts/pages/admin/jobs/index/components/cancel_jobs_modal.vue +++ b/app/assets/javascripts/pages/admin/jobs/index/components/cancel_jobs_modal.vue @@ -1,6 +1,6 @@ <script> import { GlModal } from '@gitlab/ui'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { redirectTo } from '~/lib/utils/url_utility'; import { diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js index 2fdf3c42935..f57b6144b69 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js @@ -4,7 +4,7 @@ import $ from 'jquery'; import { getGroups } from '~/api/groups_api'; import { getProjects } from '~/api/projects_api'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { addDelimiter } from '~/lib/utils/text_utility'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/pages/groups/new/group_path_validator.js b/app/assets/javascripts/pages/groups/new/group_path_validator.js index fa111032b2e..16f4f7b7f7e 100644 --- a/app/assets/javascripts/pages/groups/new/group_path_validator.js +++ b/app/assets/javascripts/pages/groups/new/group_path_validator.js @@ -1,6 +1,6 @@ import { debounce } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __ } from '~/locale'; import InputValidator from '~/validators/input_validator'; import { getGroupPathAvailability } from '~/rest_api'; diff --git a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue index 3dcababb4fd..10aa73df470 100644 --- a/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue +++ b/app/assets/javascripts/pages/import/bulk_imports/history/components/bulk_imports_history_app.vue @@ -10,7 +10,7 @@ import { } from '@gitlab/ui'; import { s__, __ } from '~/locale'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import { joinPaths } from '~/lib/utils/url_utility'; import { getBulkImportsHistory } from '~/rest_api'; diff --git a/app/assets/javascripts/pages/import/history/components/import_error_details.vue b/app/assets/javascripts/pages/import/history/components/import_error_details.vue index 6af137cd722..9c26804f73d 100644 --- a/app/assets/javascripts/pages/import/history/components/import_error_details.vue +++ b/app/assets/javascripts/pages/import/history/components/import_error_details.vue @@ -1,7 +1,7 @@ <script> import { GlLoadingIcon } from '@gitlab/ui'; import API from '~/api'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { DEFAULT_ERROR } from '../utils/error_messages'; export default { diff --git a/app/assets/javascripts/pages/import/history/components/import_history_app.vue b/app/assets/javascripts/pages/import/history/components/import_history_app.vue index 09b1b3a9c0f..938c2be89c5 100644 --- a/app/assets/javascripts/pages/import/history/components/import_history_app.vue +++ b/app/assets/javascripts/pages/import/history/components/import_history_app.vue @@ -1,7 +1,7 @@ <script> import { GlButton, GlEmptyState, GlIcon, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui'; import { s__, __ } from '~/locale'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils'; import { getProjects } from '~/rest_api'; import ImportStatus from '~/import_entities/components/import_status.vue'; diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue index 85fe3477d7c..2cfedd78bd8 100644 --- a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue +++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue @@ -12,7 +12,7 @@ import { } from '@gitlab/ui'; import { kebabCase } from 'lodash'; import { buildApiUrl } from '~/api/api_utils'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import csrf from '~/lib/utils/csrf'; import { redirectTo } from '~/lib/utils/url_utility'; diff --git a/app/assets/javascripts/pages/projects/forks/new/components/project_namespace.vue b/app/assets/javascripts/pages/projects/forks/new/components/project_namespace.vue index 5e0c5735bc0..12ddf538775 100644 --- a/app/assets/javascripts/pages/projects/forks/new/components/project_namespace.vue +++ b/app/assets/javascripts/pages/projects/forks/new/components/project_namespace.vue @@ -1,6 +1,6 @@ <script> import { GlButton, GlButtonGroup, GlCollapsibleListbox } from '@gitlab/ui'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { MINIMUM_SEARCH_LENGTH } from '~/graphql_shared/constants'; import { s__ } from '~/locale'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; diff --git a/app/assets/javascripts/pages/projects/merge_requests/edit/index.js b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js index 406959c80ea..f8cb8b30250 100644 --- a/app/assets/javascripts/pages/projects/merge_requests/edit/index.js +++ b/app/assets/javascripts/pages/projects/merge_requests/edit/index.js @@ -1,4 +1,4 @@ -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 5773737c41b..5f15a11e708 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -4,7 +4,7 @@ import $ from 'jquery'; import { setCookie } from '~/lib/utils/common_utils'; import initClonePanel from '~/clone_panel'; import initDeprecatedJQueryDropdown from '~/deprecated_jquery_dropdown'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { serializeForm } from '~/lib/utils/forms'; import { mergeUrlParams } from '~/lib/utils/url_utility'; diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js index 1848aa70cf0..664909a9012 100644 --- a/app/assets/javascripts/pages/sessions/new/username_validator.js +++ b/app/assets/javascripts/pages/sessions/new/username_validator.js @@ -1,6 +1,6 @@ import { debounce } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; import InputValidator from '~/validators/input_validator'; diff --git a/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue index b19809aff53..8491d667213 100644 --- a/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue +++ b/app/assets/javascripts/pages/shared/wikis/components/wiki_content.vue @@ -1,7 +1,7 @@ <script> import { GlSkeletonLoader, GlAlert } from '@gitlab/ui'; import SafeHtml from '~/vue_shared/directives/safe_html'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __ } from '~/locale'; import axios from '~/lib/utils/axios_utils'; import { handleLocationHash } from '~/lib/utils/common_utils'; diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js index b52b7ed4caa..13bba06d425 100644 --- a/app/assets/javascripts/pages/users/activity_calendar.js +++ b/app/assets/javascripts/pages/users/activity_calendar.js @@ -1,7 +1,7 @@ import { select } from 'd3-selection'; import $ from 'jquery'; import { last } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import dateFormat from '~/lib/dateformat'; import axios from '~/lib/utils/axios_utils'; import { getDayName, getDayDifference } from '~/lib/utils/datetime_utility'; diff --git a/app/assets/javascripts/pages/users/show/index.js b/app/assets/javascripts/pages/users/show/index.js index f1b4e00c810..dd47baf06ec 100644 --- a/app/assets/javascripts/pages/users/show/index.js +++ b/app/assets/javascripts/pages/users/show/index.js @@ -1,5 +1,5 @@ import { s__ } from '~/locale'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; if (window.gon.features?.profileTabsVue) { import('~/profile') diff --git a/app/assets/javascripts/saved_replies/components/app.vue b/app/assets/javascripts/saved_replies/components/app.vue index db8476c44f3..e4b481f0908 100644 --- a/app/assets/javascripts/saved_replies/components/app.vue +++ b/app/assets/javascripts/saved_replies/components/app.vue @@ -17,7 +17,7 @@ export default {}; </p> </div> <div class="col-lg-8"> - <router-view /> + <keep-alive><router-view /></keep-alive> </div> </div> </template> diff --git a/app/assets/javascripts/saved_replies/components/form.vue b/app/assets/javascripts/saved_replies/components/form.vue index 8d2c3ce1685..edd1ffcbac9 100644 --- a/app/assets/javascripts/saved_replies/components/form.vue +++ b/app/assets/javascripts/saved_replies/components/form.vue @@ -5,6 +5,7 @@ import { helpPagePath } from '~/helpers/help_page_helper'; import { logError } from '~/lib/logger'; import { __ } from '~/locale'; import createSavedReplyMutation from '../queries/create_saved_reply.mutation.graphql'; +import updateSavedReplyMutation from '../queries/update_saved_reply.mutation.graphql'; export default { components: { @@ -15,14 +16,31 @@ export default { GlAlert, MarkdownField, }, + props: { + id: { + type: String, + required: false, + default: null, + }, + name: { + type: String, + required: false, + default: '', + }, + content: { + type: String, + required: false, + default: '', + }, + }, data() { return { errors: [], saving: false, showValidation: false, updateSavedReply: { - name: '', - content: '', + name: this.name, + content: this.content, }, }; }, @@ -52,8 +70,9 @@ export default { this.$apollo .mutate({ - mutation: createSavedReplyMutation, + mutation: this.id ? updateSavedReplyMutation : createSavedReplyMutation, variables: { + id: this.id, name: this.updateSavedReply.name, content: this.updateSavedReply.content, }, @@ -158,5 +177,6 @@ export default { > {{ __('Save') }} </gl-button> + <gl-button v-if="id" :to="{ path: '/' }">{{ __('Cancel') }}</gl-button> </gl-form> </template> diff --git a/app/assets/javascripts/saved_replies/components/list_item.vue b/app/assets/javascripts/saved_replies/components/list_item.vue index 774ac745925..3ad5642afc7 100644 --- a/app/assets/javascripts/saved_replies/components/list_item.vue +++ b/app/assets/javascripts/saved_replies/components/list_item.vue @@ -2,6 +2,7 @@ import { uniqueId } from 'lodash'; import { GlButton, GlModal, GlModalDirective, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; import { __ } from '~/locale'; +import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import deleteSavedReplyMutation from '../queries/delete_saved_reply.mutation.graphql'; export default { @@ -26,6 +27,11 @@ export default { modalId: uniqueId('delete-saved-reply-'), }; }, + computed: { + id() { + return getIdFromGraphQLId(this.reply.id); + }, + }, methods: { onDelete() { this.isDeleting = true; @@ -50,9 +56,18 @@ export default { <template> <li class="gl-mb-5"> <div class="gl-display-flex gl-align-items-center"> - <strong>{{ reply.name }}</strong> + <strong data-testid="saved-reply-name">{{ reply.name }}</strong> <div class="gl-ml-auto"> <gl-button + v-gl-tooltip + :to="{ name: 'edit', params: { id: id } }" + icon="pencil" + :title="__('Edit')" + :aria-label="__('Edit')" + class="gl-mr-3" + data-testid="saved-reply-edit-btn" + /> + <gl-button v-gl-modal="modalId" v-gl-tooltip icon="remove" diff --git a/app/assets/javascripts/saved_replies/pages/edit.vue b/app/assets/javascripts/saved_replies/pages/edit.vue new file mode 100644 index 00000000000..6f736015e5f --- /dev/null +++ b/app/assets/javascripts/saved_replies/pages/edit.vue @@ -0,0 +1,68 @@ +<script> +import { GlLoadingIcon } from '@gitlab/ui'; +import { fetchPolicies } from '~/lib/graphql'; +import { createAlert } from '~/flash'; +import { __ } from '~/locale'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; +import { TYPE_USERS_SAVED_REPLY } from '~/graphql_shared/constants'; +import CreateForm from '../components/form.vue'; +import getSavedReply from '../queries/get_saved_reply.query.graphql'; + +export default { + components: { + GlLoadingIcon, + CreateForm, + }, + apollo: { + savedReply: { + fetchPolicy: fetchPolicies.NETWORK_ONLY, + query: getSavedReply, + variables() { + return { + id: convertToGraphQLId(TYPE_USERS_SAVED_REPLY, this.$route.params.id), + }; + }, + update: (r) => r.currentUser.savedReply, + skip() { + return !this.$route.params.id; + }, + result({ + data: { + currentUser: { savedReply }, + }, + }) { + if (!savedReply) { + createAlert({ message: __('Unable to find saved reply') }); + this.redirectToRoot(); + } + }, + }, + }, + data() { + return { + savedReply: null, + }; + }, + methods: { + redirectToRoot() { + this.$router.push({ path: '/' }); + }, + }, +}; +</script> + +<template> + <div> + <h5 class="gl-mt-0 gl-font-lg"> + {{ __('Edit saved reply') }} + </h5> + <gl-loading-icon v-if="$apollo.queries.savedReply.loading" size="lg" /> + <create-form + v-else-if="savedReply" + :id="savedReply.id" + :name="savedReply.name" + :content="savedReply.content" + @saved="redirectToRoot" + /> + </div> +</template> diff --git a/app/assets/javascripts/saved_replies/queries/get_saved_reply.query.graphql b/app/assets/javascripts/saved_replies/queries/get_saved_reply.query.graphql new file mode 100644 index 00000000000..66f5f43af49 --- /dev/null +++ b/app/assets/javascripts/saved_replies/queries/get_saved_reply.query.graphql @@ -0,0 +1,10 @@ +query getSavedReply($id: UsersSavedReplyID!) { + currentUser { + id + savedReply(id: $id) { + id + name + content + } + } +} diff --git a/app/assets/javascripts/saved_replies/queries/update_saved_reply.mutation.graphql b/app/assets/javascripts/saved_replies/queries/update_saved_reply.mutation.graphql new file mode 100644 index 00000000000..14a47d7bc9c --- /dev/null +++ b/app/assets/javascripts/saved_replies/queries/update_saved_reply.mutation.graphql @@ -0,0 +1,10 @@ +mutation savedReplyUpdate($id: UsersSavedReplyID!, $name: String!, $content: String!) { + savedReplyMutation: savedReplyUpdate(input: { id: $id, name: $name, content: $content }) { + errors + savedReply { + id + name + content + } + } +} diff --git a/app/assets/javascripts/saved_replies/routes.js b/app/assets/javascripts/saved_replies/routes.js index bd582a5ed86..7687c6f335a 100644 --- a/app/assets/javascripts/saved_replies/routes.js +++ b/app/assets/javascripts/saved_replies/routes.js @@ -1,8 +1,15 @@ import IndexComponent from './pages/index.vue'; +import EditComponent from './pages/edit.vue'; + export default [ { path: '/', component: IndexComponent, }, + { + name: 'edit', + path: '/:id', + component: EditComponent, + }, ]; diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index ac355b861b3..96a7b5b144d 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -7,7 +7,12 @@ class Explore::GroupsController < Explore::ApplicationController urgency :low def index - user = Feature.enabled?(:generic_explore_groups, current_user, type: :experiment) ? nil : current_user + # For gitlab.com, including internal visibility groups here causes + # a major performance issue: https://gitlab.com/gitlab-org/gitlab/-/issues/358944 + # + # For self-hosted users, not including internal groups here causes + # a lack of visibility: https://gitlab.com/gitlab-org/gitlab/-/issues/389041 + user = Gitlab.com? ? nil : current_user render_group_tree GroupsFinder.new(user).execute end diff --git a/app/graphql/resolvers/ci/runner_projects_resolver.rb b/app/graphql/resolvers/ci/runner_projects_resolver.rb index 2a2d63f85de..13a493c42a5 100644 --- a/app/graphql/resolvers/ci/runner_projects_resolver.rb +++ b/app/graphql/resolvers/ci/runner_projects_resolver.rb @@ -68,9 +68,9 @@ module Resolvers def preloads super.merge({ - full_path: [:route, { namespace: [:route] }], - web_url: [:route, { namespace: [:route] }] - }) + full_path: [:route, { namespace: [:route] }], + web_url: [:route, { namespace: [:route] }] + }) end end end diff --git a/app/graphql/resolvers/ci/runner_resolver.rb b/app/graphql/resolvers/ci/runner_resolver.rb index ca94e28b2e9..4250b069d20 100644 --- a/app/graphql/resolvers/ci/runner_resolver.rb +++ b/app/graphql/resolvers/ci/runner_resolver.rb @@ -22,11 +22,15 @@ module Resolvers def find_runner(id:) runner_id = GitlabSchema.parse_gid(id, expected_type: ::Ci::Runner).model_id.to_i - preload_tag_list = lookahead.selects?(:tag_list) + key = { + preload_tag_list: lookahead.selects?(:tag_list), + preload_creator: lookahead.selects?(:created_by) + } - BatchLoader::GraphQL.for(runner_id).batch(key: { preload_tag_list: preload_tag_list }) do |ids, loader, batch| + BatchLoader::GraphQL.for(runner_id).batch(key: key) do |ids, loader, batch| results = ::Ci::Runner.id_in(ids) results = results.with_tags if batch[:key][:preload_tag_list] + results = results.with_creator if batch[:key][:preload_creator] results.each { |record| loader.call(record.id, record) } end diff --git a/app/graphql/resolvers/ci/runners_resolver.rb b/app/graphql/resolvers/ci/runners_resolver.rb index b52a4cc0ab4..735e38c1a5c 100644 --- a/app/graphql/resolvers/ci/runners_resolver.rb +++ b/app/graphql/resolvers/ci/runners_resolver.rb @@ -61,9 +61,7 @@ module Resolvers upgrade_status: params[:upgrade_status], search: params[:search], sort: params[:sort]&.to_s, - preload: { - tag_name: node_selection&.selects?(:tag_list) - } + preload: false # we'll handle preloading ourselves }.compact .merge(parent_param) end @@ -79,6 +77,31 @@ module Resolvers def parent object.respond_to?(:sync) ? object.sync : object end + + def preloads + super.merge({ + created_by: [:creator], + tag_list: [:tags] + }) + end + + def nested_preloads + { + created_by: { + creator: { + full_path: [:route], + web_path: [:route], + web_url: [:route] + } + }, + owner_project: { + owner_project: { + full_path: [:route, { namespace: [:route] }], + web_url: [:route, { namespace: [:route] }] + } + } + } + end end end end diff --git a/app/graphql/types/ci/runner_type.rb b/app/graphql/types/ci/runner_type.rb index 8284386d1af..4e4fc368f44 100644 --- a/app/graphql/types/ci/runner_type.rb +++ b/app/graphql/types/ci/runner_type.rb @@ -31,6 +31,9 @@ module Types method: :contacted_at field :created_at, Types::TimeType, null: true, description: 'Timestamp of creation of this runner.' + field :created_by, Types::UserType, null: true, + description: 'User that created this runner.', + method: :creator field :description, GraphQL::Types::String, null: true, description: 'Description of the runner.' field :edit_admin_url, GraphQL::Types::String, null: true, diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index d7643956554..5b511bce396 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -188,6 +188,7 @@ module Ci scope :order_token_expires_at_asc, -> { order(token_expires_at: :asc) } scope :order_token_expires_at_desc, -> { order(token_expires_at: :desc) } scope :with_tags, -> { preload(:tags) } + scope :with_creator, -> { preload(:creator) } validate :tag_constraints validates :access_level, presence: true @@ -437,7 +438,7 @@ module Ci ensure_runner_queue_value == value if value.present? end - def heartbeat(values) + def heartbeat(values, update_contacted_at: true) ## # We can safely ignore writes performed by a runner heartbeat. We do # not want to upgrade database connection proxy to use the primary @@ -445,7 +446,7 @@ module Ci # ::Gitlab::Database::LoadBalancing::Session.without_sticky_writes do values = values&.slice(:version, :revision, :platform, :architecture, :ip_address, :config, :executor) || {} - values[:contacted_at] = Time.current + values[:contacted_at] = Time.current if update_contacted_at if values.include?(:executor) values[:executor_type] = EXECUTOR_NAME_TO_TYPES.fetch(values.delete(:executor), :unknown) end diff --git a/app/models/ci/runner_machine.rb b/app/models/ci/runner_machine.rb index 68c3636ca9d..404816dda27 100644 --- a/app/models/ci/runner_machine.rb +++ b/app/models/ci/runner_machine.rb @@ -41,7 +41,7 @@ module Ci remove_duplicates: false).where(created_some_time_ago) end - def heartbeat(values) + def heartbeat(values, update_contacted_at: true) ## # We can safely ignore writes performed by a runner heartbeat. We do # not want to upgrade database connection proxy to use the primary @@ -49,7 +49,7 @@ module Ci # ::Gitlab::Database::LoadBalancing::Session.without_sticky_writes do values = values&.slice(:version, :revision, :platform, :architecture, :ip_address, :config, :executor) || {} - values[:contacted_at] = Time.current + values[:contacted_at] = Time.current if update_contacted_at if values.include?(:executor) values[:executor_type] = Ci::Runner::EXECUTOR_NAME_TO_TYPES.fetch(values.delete(:executor), :unknown) end diff --git a/config/feature_flags/development/ci_fix_max_includes.yml b/config/feature_flags/development/ci_fix_max_includes.yml new file mode 100644 index 00000000000..ef993f4f7ee --- /dev/null +++ b/config/feature_flags/development/ci_fix_max_includes.yml @@ -0,0 +1,8 @@ +--- +name: ci_fix_max_includes +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112963 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/390909 +milestone: '15.10' +type: development +group: group::pipeline authoring +default_enabled: false diff --git a/config/feature_flags/experiment/generic_explore_groups.yml b/config/feature_flags/experiment/generic_explore_groups.yml deleted file mode 100644 index d928dcd4189..00000000000 --- a/config/feature_flags/experiment/generic_explore_groups.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: generic_explore_groups -introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103019" -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381564 -milestone: '15.6' -type: experiment -group: group::source code -default_enabled: true diff --git a/config/routes/profile.rb b/config/routes/profile.rb index bee1a0f108e..539084ce34d 100644 --- a/config/routes/profile.rb +++ b/config/routes/profile.rb @@ -39,7 +39,7 @@ resource :profile, only: [:show, :update] do end resource :preferences, only: [:show, :update] - resources :saved_replies, only: [:index], action: :index + resources :saved_replies, only: [:index, :show], action: :index resources :keys, only: [:index, :show, :create, :destroy] do member do diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index bbb49fde7f6..05273158ec5 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -177,6 +177,8 @@ - 1 - - elastic_namespace_rollout - 1 +- - elastic_namespace_update + - 1 - - elastic_project_transfer - 1 - - email_receiver diff --git a/db/migrate/20230306145230_add_product_analytics_data_collector_host_to_application_settings.rb b/db/migrate/20230306145230_add_product_analytics_data_collector_host_to_application_settings.rb new file mode 100644 index 00000000000..4ae2479b1e6 --- /dev/null +++ b/db/migrate/20230306145230_add_product_analytics_data_collector_host_to_application_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddProductAnalyticsDataCollectorHostToApplicationSettings < Gitlab::Database::Migration[2.1] + # rubocop:disable Migration/AddLimitToTextColumns + def change + add_column :application_settings, :product_analytics_data_collector_host, :text + end + # rubocop:enable Migration/AddLimitToTextColumns +end diff --git a/db/migrate/20230307122838_add_text_limit_to_application_settings_product_analytics_data_collector_host.rb b/db/migrate/20230307122838_add_text_limit_to_application_settings_product_analytics_data_collector_host.rb new file mode 100644 index 00000000000..4f87cc9aee0 --- /dev/null +++ b/db/migrate/20230307122838_add_text_limit_to_application_settings_product_analytics_data_collector_host.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddTextLimitToApplicationSettingsProductAnalyticsDataCollectorHost < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + def up + add_text_limit :application_settings, :product_analytics_data_collector_host, 255 + end + + def down + remove_text_limit :application_settings, :product_analytics_data_collector_host + end +end diff --git a/db/post_migrate/20230303154314_add_user_type_migration_indexes.rb b/db/post_migrate/20230303154314_add_user_type_migration_indexes.rb new file mode 100644 index 00000000000..8f9e193f0eb --- /dev/null +++ b/db/post_migrate/20230303154314_add_user_type_migration_indexes.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AddUserTypeMigrationIndexes < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + BILLABLE_INDEX = 'index_users_for_active_billable_users_migration' + LAST_ACTIVITY_INDEX = 'i_users_on_last_activity_for_active_human_service_migration' + + def up + # Temporary indexes to migrate human user_type. See https://gitlab.com/gitlab-org/gitlab/-/issues/386474 + add_concurrent_index :users, :id, name: BILLABLE_INDEX, + where: "state = 'active' AND ((user_type IS NULL OR user_type = 0) OR (user_type = ANY (ARRAY[6, 4, 13]))) " \ + "AND ((user_type IS NULL OR user_type = 0) OR (user_type = ANY (ARRAY[4, 5])))" + add_concurrent_index :users, [:id, :last_activity_on], name: LAST_ACTIVITY_INDEX, + where: "((state)::text = 'active'::text) AND ((user_type IS NULL OR user_type = 0) OR (user_type = 4))" + end + + def down + remove_concurrent_index_by_name :users, BILLABLE_INDEX + remove_concurrent_index_by_name :users, LAST_ACTIVITY_INDEX + end +end diff --git a/db/schema_migrations/20230303154314 b/db/schema_migrations/20230303154314 new file mode 100644 index 00000000000..30a33a6efba --- /dev/null +++ b/db/schema_migrations/20230303154314 @@ -0,0 +1 @@ +c18a674b6df4baf6d81177df2eb4497dc73979ff71142a9ecda71ec515a588b4
\ No newline at end of file diff --git a/db/schema_migrations/20230306145230 b/db/schema_migrations/20230306145230 new file mode 100644 index 00000000000..d0fa5e5634b --- /dev/null +++ b/db/schema_migrations/20230306145230 @@ -0,0 +1 @@ +ca28b1355e5cc8c1e77c85a4d5e6a40b66767a8588068eb7e1528ba0e575f5da
\ No newline at end of file diff --git a/db/schema_migrations/20230307122838 b/db/schema_migrations/20230307122838 new file mode 100644 index 00000000000..adf5d84a474 --- /dev/null +++ b/db/schema_migrations/20230307122838 @@ -0,0 +1 @@ +5b147e92d42b7ec317106d905a3af4d1aee983bce8538c26a619ad32ad06c42e
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index ed022ff4d94..9e1094123fe 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11747,6 +11747,7 @@ CREATE TABLE application_settings ( security_policy_global_group_approvers_enabled boolean DEFAULT true NOT NULL, projects_api_rate_limit_unauthenticated integer DEFAULT 400 NOT NULL, deny_all_requests_except_allowed boolean DEFAULT false NOT NULL, + product_analytics_data_collector_host text, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), @@ -11777,6 +11778,7 @@ CREATE TABLE application_settings ( CONSTRAINT check_5bcba483c4 CHECK ((char_length(sentry_environment) <= 255)), CONSTRAINT check_718b4458ae CHECK ((char_length(personal_access_token_prefix) <= 20)), CONSTRAINT check_7227fad848 CHECK ((char_length(rate_limiting_response_text) <= 255)), + CONSTRAINT check_72c984b2a5 CHECK ((char_length(product_analytics_data_collector_host) <= 255)), CONSTRAINT check_734cc9407a CHECK ((char_length(globally_allowed_ips) <= 255)), CONSTRAINT check_7ccfe2764a CHECK ((char_length(arkose_labs_namespace) <= 255)), CONSTRAINT check_85a39b68ff CHECK ((char_length(encrypted_ci_jwt_signing_key_iv) <= 255)), @@ -28910,6 +28912,8 @@ CREATE UNIQUE INDEX i_pm_package_versions_on_package_id_and_version ON pm_packag CREATE UNIQUE INDEX i_pm_packages_purl_type_and_name ON pm_packages USING btree (purl_type, name); +CREATE INDEX i_users_on_last_activity_for_active_human_service_migration ON users USING btree (id, last_activity_on) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = 0) OR (user_type = 4))); + CREATE INDEX idx_analytics_devops_adoption_segments_on_namespace_id ON analytics_devops_adoption_segments USING btree (namespace_id); CREATE INDEX idx_analytics_devops_adoption_snapshots_finalized ON analytics_devops_adoption_snapshots USING btree (namespace_id, end_time) WHERE (recorded_at >= end_time); @@ -32152,6 +32156,8 @@ CREATE UNIQUE INDEX index_user_synced_attributes_metadata_on_user_id ON user_syn CREATE INDEX index_users_for_active_billable_users ON users USING btree (id) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[6, 4, 13]))) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[4, 5])))); +CREATE INDEX index_users_for_active_billable_users_migration ON users USING btree (id) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = 0) OR (user_type = ANY (ARRAY[6, 4, 13]))) AND ((user_type IS NULL) OR (user_type = 0) OR (user_type = ANY (ARRAY[4, 5])))); + CREATE INDEX index_users_on_accepted_term_id ON users USING btree (accepted_term_id); CREATE INDEX index_users_on_admin ON users USING btree (admin); diff --git a/doc/administration/geo/index.md b/doc/administration/geo/index.md index 3f98f1e12fe..731f16822fd 100644 --- a/doc/administration/geo/index.md +++ b/doc/administration/geo/index.md @@ -123,7 +123,8 @@ The following are required to run Geo: - Note,[PostgreSQL 12 is deprecated](../../update/deprecations.md#postgresql-12-deprecated) and is removed in GitLab 16.0. - Git 2.9 or later - Git-lfs 2.4.2 or later on the user side when using LFS -- All sites must run [the same GitLab and PostgreSQL versions](setup/database.md#postgresql-replication). +- All sites must run the same GitLab version. +- All sites must run [the same PostgreSQL versions](setup/database.md#postgresql-replication). - If using different operating system versions between Geo sites, [check OS locale data compatibility](replication/troubleshooting.md#check-os-locale-data-compatibility) across Geo sites to avoid silent corruption of database indexes. diff --git a/doc/administration/geo/setup/index.md b/doc/administration/geo/setup/index.md index c794b8ef219..022d9c00772 100644 --- a/doc/administration/geo/setup/index.md +++ b/doc/administration/geo/setup/index.md @@ -7,25 +7,22 @@ type: howto # Setting up Geo **(PREMIUM SELF)** -These instructions assume you have a working instance of GitLab. They guide you through: +## Prerequisites -1. Making your existing instance the **primary** site. -1. Adding **secondary** sites. +- Two (or more) independently working GitLab sites: + - One GitLab site serves as the Geo **primary** site. Use the [GitLab reference architectures documentation](../../reference_architectures/index.md) to set this up. You can use different reference architecture sizes for each Geo site. If you already have a working GitLab instance that is in-use, it can be used as a **primary** site. + - The second GitLab site serves as the Geo **secondary** site. Use the [GitLab reference architectures documentation](../../reference_architectures/index.md) to set this up. It's a good idea to sign in and test it. However, be aware that **all of the data on the secondary are lost** as part of the process of replicating from the **primary** site. -You must use a [GitLab Premium](https://about.gitlab.com/pricing/) license or higher, -but you only need one license for all the sites. + NOTE: + Geo supports multiple secondaries. You can follow the same steps and make any changes accordingly. -WARNING: -The steps below should be followed in the order they appear. **Make sure the GitLab version is the same on all sites. Do not create an account or sign in to the new secondary.** +- Ensure the **primary** site has a [GitLab Premium](https://about.gitlab.com/pricing/) license or higher to unlock Geo. You only need one license for all the sites. +- Confirm the [requirements for running Geo](../index.md#requirements-for-running-geo) are met by all sites. For example, sites must use the same GitLab version, and sites must be able to communicate with each other over certain ports. ## Using Omnibus GitLab If you installed GitLab using the Omnibus packages (highly recommended): -1. Confirm the [requirements for running Geo](../index.md#requirements-for-running-geo) are met. -1. [Install GitLab Enterprise Edition](https://about.gitlab.com/install/) on the nodes that serve as the **secondary** site. **Do not create an account or sign in** to the new **secondary** site. The **GitLab version must match** across primary and secondary sites. -1. [Add the GitLab License](../../../user/admin_area/license.md) on the **primary** site to unlock Geo. The license must be for [GitLab Premium](https://about.gitlab.com/pricing/) or higher. -1. [Confirm network connectivity](../index.md#firewall-rules) between the **primary** and **secondary** site. 1. [Set up the database replication](database.md) (`primary (read-write) <-> secondary (read-only)` topology). 1. [Configure fast lookup of authorized SSH keys in the database](../../operations/fast_ssh_key_lookup.md). This step is required and needs to be done on **both** the **primary** and **secondary** sites. 1. [Configure GitLab](../replication/configuration.md) to set the **primary** and **secondary** sites. @@ -34,6 +31,10 @@ If you installed GitLab using the Omnibus packages (highly recommended): 1. Optional: [Configure Geo secondary proxying](../secondary_proxy/index.md) to use a single, unified URL for all Geo sites. This step is recommended to accelerate most read requests while transparently proxying writes to the primary Geo site. 1. Follow the [Using a Geo Site](../replication/usage.md) guide. +## Using GitLab Charts + +[Configure the GitLab chart with GitLab Geo](https://docs.gitlab.com/charts/advanced/geo/). + ## Post-installation documentation After installing GitLab on the **secondary** sites and performing the initial configuration, see the [following documentation for post-installation information](../index.md#post-installation-documentation). diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 6817e43a510..12c5d40b4be 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -11768,6 +11768,7 @@ CI/CD variables for a project. | <a id="cirunnerarchitecturename"></a>`architectureName` | [`String`](#string) | Architecture provided by the the runner. | | <a id="cirunnercontactedat"></a>`contactedAt` | [`Time`](#time) | Timestamp of last contact from this runner. | | <a id="cirunnercreatedat"></a>`createdAt` | [`Time`](#time) | Timestamp of creation of this runner. | +| <a id="cirunnercreatedby"></a>`createdBy` | [`UserCore`](#usercore) | User that created this runner. | | <a id="cirunnerdescription"></a>`description` | [`String`](#string) | Description of the runner. | | <a id="cirunnereditadminurl"></a>`editAdminUrl` | [`String`](#string) | Admin form URL of the runner. Only available for administrators. | | <a id="cirunnerephemeralauthenticationtoken"></a>`ephemeralAuthenticationToken` **{warning-solid}** | [`String`](#string) | **Introduced** in 15.9. This feature is in Alpha. It can be changed or removed at any time. Ephemeral authentication token used for runner machine registration. Only available for the creator of the runner for a limited time during registration. | diff --git a/doc/api/rest/index.md b/doc/api/rest/index.md index 0f1f74b9bd9..ca335bfc12e 100644 --- a/doc/api/rest/index.md +++ b/doc/api/rest/index.md @@ -318,7 +318,7 @@ The following table shows the possible return codes for API requests. | `400 Bad Request` | A required attribute of the API request is missing. For example, the title of an issue is not given. | | `401 Unauthorized` | The user isn't authenticated. A valid [user token](#authentication) is necessary. | | `403 Forbidden` | The request isn't allowed. For example, the user isn't allowed to delete a project. | -| `404 Not Found` | A resource couldn't be accessed. For example, an ID for a resource couldn't be found. | +| `404 Not Found` | A resource couldn't be accessed. For example, an ID for a resource couldn't be found, or the user isn't authorized to access the resource. | | `405 Method Not Allowed` | The request isn't supported. | | `409 Conflict` | A conflicting resource already exists. For example, creating a project with a name that already exists. | | `412 Precondition Failed`| The request was denied. This can happen if the `If-Unmodified-Since` header is provided when trying to delete a resource, which was modified in between. | diff --git a/doc/topics/git/partial_clone.md b/doc/topics/git/partial_clone.md index f5f11b17675..de0547ae49d 100644 --- a/doc/topics/git/partial_clone.md +++ b/doc/topics/git/partial_clone.md @@ -94,9 +94,7 @@ Updating files: 100% (28/28), done. $ cd www-gitlab-com -$ git sparse-checkout init --cone - -$ git sparse-checkout add data +$ git sparse-checkout set data --cone remote: Enumerating objects: 301, done. remote: Counting objects: 100% (301/301), done. remote: Compressing objects: 100% (292/292), done. diff --git a/doc/tutorials/index.md b/doc/tutorials/index.md index c1b538bafbe..7a94583ae69 100644 --- a/doc/tutorials/index.md +++ b/doc/tutorials/index.md @@ -20,6 +20,7 @@ and running quickly. | <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Use GitLab for DevOps](https://www.youtube.com/watch?v=7q9Y1Cv-ib0) (12m 34s) | Use GitLab through the entire DevOps lifecycle, from planning to monitoring. | **{star}** | | [Use Markdown at GitLab](../user/markdown.md) | GitLab Flavored Markdown (GLFM) is used in many areas of GitLab, for example, in merge requests. | **{star}** | | <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [Learn GitLab project walkthrough](https://www.youtube.com/watch?v=-oaI2WEKdI4&list=PL05JrBw4t0KofkHq4GZJ05FnNGa11PQ4d) (59m 2s) | Step through the tutorial-style issues in the **Learn GitLab** project. If you don't have this project, download [the export file](https://gitlab.com/gitlab-org/gitlab/-/blob/master/vendor/project_templates/learn_gitlab_ultimate.tar.gz) and [import it to a new project](../user/project/settings/import_export.md#import-a-project-and-its-data). | | +| <i class="fa fa-youtube-play youtube" aria-hidden="true"></i> [GitLab Continuous Delivery overview](https://www.youtube.com/watch?v=g-gO93PMZvk&list=PLFGfElNsQthYDx0A_FaNNfUm9NHsK6zED&index=134) (17m 2s) | Learn how to use GitLab features to continuously build, test, and deploy iterative code changes. | | | [Productivity tips](https://about.gitlab.com/blog/2021/02/18/improve-your-gitlab-productivity-with-these-10-tips/) | Get tips to help make you a productive GitLab user. | | ## Use Git diff --git a/doc/user/permissions.md b/doc/user/permissions.md index a18e72539ea..549cd63ef1f 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -271,8 +271,7 @@ More details about the permissions for some project-level features follow. | View and download artifacts | ✓ (1) | ✓ (2) | ✓ | ✓ | ✓ | ✓ | | View [environments](../ci/environments/index.md) | ✓ (3) | ✓ (3) | ✓ | ✓ | ✓ | ✓ | | View job logs and job details page | ✓ (1) | ✓ (2) | ✓ | ✓ | ✓ | ✓ | -| View pipeline details page | ✓ (1) | ✓ (2) | ✓ | ✓ | ✓ | ✓ | -| View pipelines page | ✓ (1) | ✓ (2) | ✓ | ✓ | ✓ | ✓ | +| View pipelines and pipeline details pages | ✓ (1) | ✓ (2) | ✓ | ✓ | ✓ | ✓ | | View pipelines tab in MR | ✓ (3) | ✓ (3) | ✓ | ✓ | ✓ | ✓ | | [View vulnerabilities in a pipeline](application_security/vulnerability_report/pipeline.md#view-vulnerabilities-in-a-pipeline) | | ✓ (2) | ✓ | ✓ | ✓ | ✓ | | View and download project-level [Secure Files](../api/secure_files.md) | | | | ✓ | ✓ | ✓ | diff --git a/lib/api/ci/helpers/runner.rb b/lib/api/ci/helpers/runner.rb index 96f5265ce23..833ce5e32fa 100644 --- a/lib/api/ci/helpers/runner.rb +++ b/lib/api/ci/helpers/runner.rb @@ -12,13 +12,13 @@ module API JOB_TOKEN_PARAM = :token LEGACY_SYSTEM_XID = '<legacy>' - def authenticate_runner! + def authenticate_runner!(update_contacted_at: true) track_runner_authentication forbidden! unless current_runner runner_details = get_runner_details_from_request - current_runner.heartbeat(runner_details) - current_runner_machine&.heartbeat(runner_details) + current_runner.heartbeat(runner_details, update_contacted_at: update_contacted_at) + current_runner_machine&.heartbeat(runner_details, update_contacted_at: update_contacted_at) end def get_runner_details_from_request diff --git a/lib/api/ci/runner.rb b/lib/api/ci/runner.rb index 1c81db39bb1..0e67fb762a9 100644 --- a/lib/api/ci/runner.rb +++ b/lib/api/ci/runner.rb @@ -85,7 +85,11 @@ module API optional :system_id, type: String, desc: %q(The runner's system identifier) end post '/verify', urgency: :low, feature_category: :runner do - authenticate_runner! + # For runners that were created in the UI, we want to update the contacted_at value + # only when it starts polling for jobs + registering_created_runner = params[:token].start_with?(::Ci::Runner::CREATED_RUNNER_TOKEN_PREFIX) + + authenticate_runner!(update_contacted_at: !registering_created_runner) status 200 present current_runner, with: Entities::Ci::RunnerRegistrationDetails diff --git a/lib/gitlab/ci/config/external/context.rb b/lib/gitlab/ci/config/external/context.rb index 881b131c252..156109a084d 100644 --- a/lib/gitlab/ci/config/external/context.rb +++ b/lib/gitlab/ci/config/external/context.rb @@ -92,10 +92,11 @@ module Gitlab expandset.map(&:metadata) end - # Some ProjectConfig sources inject an `include` into the config content. We use this - # method to exclude that `include` from the calculation of the total included files. - def contains_internal_include? - !!pipeline_config&.contains_internal_include? + # Some Ci::ProjectConfig sources prepend the config content with an "internal" `include`, which becomes + # the first included file. When running a pipeline, we pass pipeline_config into the context of the first + # included file, which we use in this method to determine if the file is an "internal" one. + def internal_include? + !!pipeline_config&.internal_include_prepended? end protected diff --git a/lib/gitlab/ci/config/external/mapper/verifier.rb b/lib/gitlab/ci/config/external/mapper/verifier.rb index 6c2e52958da..7284d2a7e01 100644 --- a/lib/gitlab/ci/config/external/mapper/verifier.rb +++ b/lib/gitlab/ci/config/external/mapper/verifier.rb @@ -9,12 +9,20 @@ module Gitlab class Verifier < Base private + # rubocop: disable Metrics/CyclomaticComplexity def process_without_instrumentation(files) if ::Feature.disabled?(:ci_batch_project_includes_context, context.project) return legacy_process_without_instrumentation(files) end files.each do |file| + if YamlProcessor::FeatureFlags.enabled?(:ci_fix_max_includes) + # When running a pipeline, some Ci::ProjectConfig sources prepend the config content with an + # "internal" `include`. We use this condition to exclude that `include` from the included file set. + context.expandset << file unless context.internal_include? + verify_max_includes! + end + verify_execution_time! file.validate_location! @@ -31,19 +39,26 @@ module Gitlab # We do not combine the loops because we need to load the content of all files via `BatchLoader`. files.each do |file| # rubocop:disable Style/CombinableLoops - # Checking the max includes will be changed with https://gitlab.com/gitlab-org/gitlab/-/issues/367150 - verify_max_includes! + verify_max_includes! unless YamlProcessor::FeatureFlags.enabled?(:ci_fix_max_includes) verify_execution_time! file.validate_content! if file.valid? file.load_and_validate_expanded_hash! if file.valid? - context.expandset << file + context.expandset << file unless YamlProcessor::FeatureFlags.enabled?(:ci_fix_max_includes) end end + # rubocop: enable Metrics/CyclomaticComplexity def legacy_process_without_instrumentation(files) files.each do |file| + if YamlProcessor::FeatureFlags.enabled?(:ci_fix_max_includes) + # When running a pipeline, some Ci::ProjectConfig sources prepend the config content with an + # "internal" `include`. We use this condition to exclude that `include` from the included file set. + context.expandset << file unless context.internal_include? + verify_max_includes! + end + verify_execution_time! file.validate_location! @@ -54,19 +69,22 @@ module Gitlab # We do not combine the loops because we need to load the content of all files before continuing # to call `BatchLoader` for all locations. files.each do |file| # rubocop:disable Style/CombinableLoops - # Checking the max includes will be changed with https://gitlab.com/gitlab-org/gitlab/-/issues/367150 - verify_max_includes! + verify_max_includes! unless YamlProcessor::FeatureFlags.enabled?(:ci_fix_max_includes) verify_execution_time! file.validate_content! if file.valid? file.load_and_validate_expanded_hash! if file.valid? - context.expandset << file + context.expandset << file unless YamlProcessor::FeatureFlags.enabled?(:ci_fix_max_includes) end end def verify_max_includes! - return if context.expandset.count < context.max_includes + if YamlProcessor::FeatureFlags.enabled?(:ci_fix_max_includes) + return if context.expandset.count <= context.max_includes + else + return if context.expandset.count < context.max_includes # rubocop:disable Style/IfInsideElse + end raise Mapper::TooManyIncludesError, "Maximum of #{context.max_includes} nested includes are allowed!" end diff --git a/lib/gitlab/ci/project_config.rb b/lib/gitlab/ci/project_config.rb index e69efb85a93..00b2ad58428 100644 --- a/lib/gitlab/ci/project_config.rb +++ b/lib/gitlab/ci/project_config.rb @@ -26,7 +26,7 @@ module Gitlab end delegate :content, :source, to: :@config, allow_nil: true - delegate :contains_internal_include?, to: :@config + delegate :internal_include_prepended?, to: :@config def exists? !!@config&.exists? diff --git a/lib/gitlab/ci/project_config/auto_devops.rb b/lib/gitlab/ci/project_config/auto_devops.rb index 70f8c7b9bb3..c5f010ebaea 100644 --- a/lib/gitlab/ci/project_config/auto_devops.rb +++ b/lib/gitlab/ci/project_config/auto_devops.rb @@ -13,7 +13,7 @@ module Gitlab end end - def contains_internal_include? + def internal_include_prepended? true end diff --git a/lib/gitlab/ci/project_config/external_project.rb b/lib/gitlab/ci/project_config/external_project.rb index b2982551458..0afdab23886 100644 --- a/lib/gitlab/ci/project_config/external_project.rb +++ b/lib/gitlab/ci/project_config/external_project.rb @@ -17,7 +17,7 @@ module Gitlab end end - def contains_internal_include? + def internal_include_prepended? true end diff --git a/lib/gitlab/ci/project_config/remote.rb b/lib/gitlab/ci/project_config/remote.rb index 17d5a9ee68f..19cbf8e9c1e 100644 --- a/lib/gitlab/ci/project_config/remote.rb +++ b/lib/gitlab/ci/project_config/remote.rb @@ -12,7 +12,7 @@ module Gitlab end end - def contains_internal_include? + def internal_include_prepended? true end diff --git a/lib/gitlab/ci/project_config/repository.rb b/lib/gitlab/ci/project_config/repository.rb index c975023d35e..272425fd546 100644 --- a/lib/gitlab/ci/project_config/repository.rb +++ b/lib/gitlab/ci/project_config/repository.rb @@ -12,7 +12,7 @@ module Gitlab end end - def contains_internal_include? + def internal_include_prepended? true end diff --git a/lib/gitlab/ci/project_config/source.rb b/lib/gitlab/ci/project_config/source.rb index 30edf1b552a..9a4a6394fa1 100644 --- a/lib/gitlab/ci/project_config/source.rb +++ b/lib/gitlab/ci/project_config/source.rb @@ -24,8 +24,8 @@ module Gitlab raise NotImplementedError end - # Indicates if we are internally injecting an 'include' into the content - def contains_internal_include? + # Indicates if we are prepending the content with an "internal" `include` + def internal_include_prepended? false end diff --git a/lib/gitlab/email/html_to_markdown_parser.rb b/lib/gitlab/email/html_to_markdown_parser.rb index 42dd012308b..5dd3725cc3e 100644 --- a/lib/gitlab/email/html_to_markdown_parser.rb +++ b/lib/gitlab/email/html_to_markdown_parser.rb @@ -5,25 +5,46 @@ require 'nokogiri' module Gitlab module Email class HtmlToMarkdownParser < Html2Text - ADDITIONAL_TAGS = %w[em strong img details].freeze - IMG_ATTRS = %w[alt src].freeze + extend Gitlab::Utils::Override + # List of tags to be converted by Markdown. + # + # All attributes are removed except for the defined ones. + # + # <tag> => [<attribute to keep>, ...] + ALLOWED_TAG_ATTRIBUTES = { + 'em' => [], + 'strong' => [], + 'details' => [], + 'img' => %w[alt src] + }.freeze + private_constant :ALLOWED_TAG_ATTRIBUTES + + # This redefinition can be removed once https://github.com/soundasleep/html2text_ruby/pull/30 + # is merged and released. def self.convert(html) html = fix_newlines(replace_entities(html)) doc = Nokogiri::HTML(html) - HtmlToMarkdownParser.new(doc).convert + new(doc).convert end + private + + override :iterate_over def iterate_over(node) - return super unless ADDITIONAL_TAGS.include?(node.name) + allowed_attributes = ALLOWED_TAG_ATTRIBUTES[node.name] + return super unless allowed_attributes - if node.name == 'img' - node.keys.each { |key| node.remove_attribute(key) unless IMG_ATTRS.include?(key) } # rubocop:disable Style/HashEachMethods - end + remove_attributes(node, allowed_attributes) Kramdown::Document.new(node.to_html, input: 'html').to_commonmark end + + def remove_attributes(node, allowed_attributes) + to_remove = (node.keys - allowed_attributes) + to_remove.each { |key| node.remove_attribute(key) } + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index cf5a22b70a4..0d921596def 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -15409,6 +15409,9 @@ msgstr "" msgid "Edit public deploy key" msgstr "" +msgid "Edit saved reply" +msgstr "" + msgid "Edit sidebar" msgstr "" @@ -45924,6 +45927,9 @@ msgstr "" msgid "Unable to find Jira project to import data from." msgstr "" +msgid "Unable to find saved reply" +msgstr "" + msgid "Unable to fully load the default commit message. You can still apply this suggestion and the commit message will be correct." msgstr "" diff --git a/package.json b/package.json index d01e5e40e63..a993fcafa15 100644 --- a/package.json +++ b/package.json @@ -122,7 +122,7 @@ "dateformat": "^5.0.1", "deckar01-task_list": "^2.3.1", "diff": "^3.4.0", - "dompurify": "^2.4.4", + "dompurify": "^2.4.5", "dropzone": "^4.2.0", "editorconfig": "^0.15.3", "emoji-regex": "^10.0.0", diff --git a/spec/controllers/explore/groups_controller_spec.rb b/spec/controllers/explore/groups_controller_spec.rb index a3bd8102462..76bd94fd681 100644 --- a/spec/controllers/explore/groups_controller_spec.rb +++ b/spec/controllers/explore/groups_controller_spec.rb @@ -41,9 +41,9 @@ RSpec.describe Explore::GroupsController do it_behaves_like 'explore groups' - context 'generic_explore_groups flag is disabled' do + context 'gitlab.com' do before do - stub_feature_flags(generic_explore_groups: false) + allow(Gitlab).to receive(:com?).and_return(true) end it_behaves_like 'explore groups' diff --git a/spec/features/profiles/user_updates_saved_reply_spec.rb b/spec/features/profiles/user_updates_saved_reply_spec.rb new file mode 100644 index 00000000000..e341076ed0a --- /dev/null +++ b/spec/features/profiles/user_updates_saved_reply_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Profile > Saved replies > User updated saved reply', :js, + feature_category: :user_profile do + let_it_be(:user) { create(:user) } + let_it_be(:saved_reply) { create(:saved_reply, user: user) } + + before do + sign_in(user) + + visit profile_saved_replies_path + + wait_for_requests + end + + it 'shows the user a list of their saved replies' do + find('[data-testid="saved-reply-edit-btn"]').click + find('[data-testid="saved-reply-name-input"]').set('test') + + click_button 'Save' + + wait_for_requests + + expect(page).to have_selector('[data-testid="saved-reply-name"]', text: 'test') + end +end diff --git a/spec/fixtures/lib/gitlab/email/basic.html b/spec/fixtures/lib/gitlab/email/basic.html index 807b23c46e3..8c2c4c116b8 100644 --- a/spec/fixtures/lib/gitlab/email/basic.html +++ b/spec/fixtures/lib/gitlab/email/basic.html @@ -7,17 +7,17 @@ Even though it has whitespace and newlines, the e-mail converter will handle it correctly. - <p><em>Even</em> mismatched tags.</p> + <p><em class="class" style="color:red" title="strong">Even</em> mismatched tags.</p> <div>A div</div> <div>Another div</div> - <div>A div<div><strong>within</strong> a div</div></div> + <div>A div<div><strong class="class" style="color:red" title="strong">within</strong> a div</div></div> <p>Another line<br />Yet another line</p> <a href="http://foo.com">A link</a> - <p><details><summary>One</summary>Some details</details></p> + <p><details class="class" style="color:red" title="strong"><summary>One</summary>Some details</details></p> <p><details><summary>Two</summary>Some details</details></p> diff --git a/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap b/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap index 154ce2bd089..204afc744e7 100644 --- a/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap +++ b/spec/frontend/saved_replies/components/__snapshots__/list_item_spec.js.snap @@ -7,7 +7,9 @@ exports[`Saved replies list item component renders list item 1`] = ` <div class="gl-display-flex gl-align-items-center" > - <strong> + <strong + data-testid="saved-reply-name" + > test </strong> @@ -15,6 +17,19 @@ exports[`Saved replies list item component renders list item 1`] = ` class="gl-ml-auto" > <gl-button-stub + aria-label="Edit" + buttontextclasses="" + category="primary" + class="gl-mr-3" + data-testid="saved-reply-edit-btn" + icon="pencil" + size="medium" + title="Edit" + to="[object Object]" + variant="default" + /> + + <gl-button-stub aria-label="Delete" buttontextclasses="" category="secondary" diff --git a/spec/frontend/saved_replies/components/form_spec.js b/spec/frontend/saved_replies/components/form_spec.js index 693703ca572..adeda498e6f 100644 --- a/spec/frontend/saved_replies/components/form_spec.js +++ b/spec/frontend/saved_replies/components/form_spec.js @@ -8,24 +8,33 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import Form from '~/saved_replies/components/form.vue'; import createSavedReplyMutation from '~/saved_replies/queries/create_saved_reply.mutation.graphql'; +import updateSavedReplyMutation from '~/saved_replies/queries/update_saved_reply.mutation.graphql'; let wrapper; let createSavedReplyResponseSpy; +let updateSavedReplyResponseSpy; function createMockApolloProvider(response) { Vue.use(VueApollo); createSavedReplyResponseSpy = jest.fn().mockResolvedValue(response); + updateSavedReplyResponseSpy = jest.fn().mockResolvedValue(response); - const requestHandlers = [[createSavedReplyMutation, createSavedReplyResponseSpy]]; + const requestHandlers = [ + [createSavedReplyMutation, createSavedReplyResponseSpy], + [updateSavedReplyMutation, updateSavedReplyResponseSpy], + ]; return createMockApollo(requestHandlers); } -function createComponent(response = createdSavedReplyResponse) { +function createComponent(id = null, response = createdSavedReplyResponse) { const mockApollo = createMockApolloProvider(response); return mount(Form, { + propsData: { + id, + }, apolloProvider: mockApollo, }); } @@ -52,6 +61,7 @@ describe('Saved replies form component', () => { await waitForPromises(); expect(createSavedReplyResponseSpy).toHaveBeenCalledWith({ + id: null, content: 'Test content', name: 'Test', }); @@ -72,7 +82,7 @@ describe('Saved replies form component', () => { ${findSavedReplyNameFormGroup} | ${findSavedReplyContentInput} | ${'name'} ${findSavedReplyContentFormGroup} | ${findSavedReplyNameInput} | ${'content'} `('shows errors for empty $fieldName input', async ({ findFormGroup, findInput }) => { - wrapper = createComponent(createdSavedReplyErrorResponse); + wrapper = createComponent(null, createdSavedReplyErrorResponse); findInput().setValue('Test'); findSavedReplyFrom().trigger('submit'); @@ -83,7 +93,7 @@ describe('Saved replies form component', () => { }); it('displays errors when mutation fails', async () => { - wrapper = createComponent(createdSavedReplyErrorResponse); + wrapper = createComponent(null, createdSavedReplyErrorResponse); findSavedReplyNameInput().setValue('Test'); findSavedReplyContentInput().setValue('Test content'); @@ -113,4 +123,22 @@ describe('Saved replies form component', () => { expect(findSubmitBtn().props('loading')).toBe(false); }); }); + + describe('updates saved reply', () => { + it('calls apollo mutation', async () => { + wrapper = createComponent('1'); + + findSavedReplyNameInput().setValue('Test'); + findSavedReplyContentInput().setValue('Test content'); + findSavedReplyFrom().trigger('submit'); + + await waitForPromises(); + + expect(updateSavedReplyResponseSpy).toHaveBeenCalledWith({ + id: '1', + content: 'Test content', + name: 'Test', + }); + }); + }); }); diff --git a/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb b/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb index 5d06db904d5..ff343f3f43d 100644 --- a/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/group_runners_resolver_spec.rb @@ -78,7 +78,7 @@ RSpec.describe Resolvers::Ci::GroupRunnersResolver, feature_category: :runner_fl status_status: 'active', type_type: :group_type, tag_name: ['active_runner'], - preload: { tag_name: false }, + preload: false, search: 'abc', sort: 'contacted_asc', membership: :descendants, diff --git a/spec/graphql/resolvers/ci/project_runners_resolver_spec.rb b/spec/graphql/resolvers/ci/project_runners_resolver_spec.rb index 4cc00ced104..83435db2ea7 100644 --- a/spec/graphql/resolvers/ci/project_runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/project_runners_resolver_spec.rb @@ -67,7 +67,7 @@ RSpec.describe Resolvers::Ci::ProjectRunnersResolver, feature_category: :runner_ status_status: 'active', type_type: :group_type, tag_name: ['active_runner'], - preload: { tag_name: false }, + preload: false, search: 'abc', sort: 'contacted_asc', project: project diff --git a/spec/graphql/resolvers/ci/runners_resolver_spec.rb b/spec/graphql/resolvers/ci/runners_resolver_spec.rb index d6da8222234..e4620b96cae 100644 --- a/spec/graphql/resolvers/ci/runners_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/runners_resolver_spec.rb @@ -83,7 +83,7 @@ RSpec.describe Resolvers::Ci::RunnersResolver, feature_category: :runner_fleet d upgrade_status: 'recommended', type_type: :instance_type, tag_name: ['active_runner'], - preload: { tag_name: false }, + preload: false, search: 'abc', sort: 'contacted_asc' } @@ -108,7 +108,7 @@ RSpec.describe Resolvers::Ci::RunnersResolver, feature_category: :runner_fleet d let(:expected_params) do { active: false, - preload: { tag_name: false } + preload: false } end @@ -128,7 +128,7 @@ RSpec.describe Resolvers::Ci::RunnersResolver, feature_category: :runner_fleet d let(:expected_params) do { active: false, - preload: { tag_name: false } + preload: false } end @@ -146,9 +146,7 @@ RSpec.describe Resolvers::Ci::RunnersResolver, feature_category: :runner_fleet d end let(:expected_params) do - { - preload: { tag_name: false } - } + { preload: false } end it 'calls RunnersFinder with expected arguments' do diff --git a/spec/graphql/types/ci/runner_type_spec.rb b/spec/graphql/types/ci/runner_type_spec.rb index 3195d2cabb8..28c251d2ec2 100644 --- a/spec/graphql/types/ci/runner_type_spec.rb +++ b/spec/graphql/types/ci/runner_type_spec.rb @@ -9,7 +9,7 @@ RSpec.describe GitlabSchema.types['CiRunner'], feature_category: :runner do it 'contains attributes related to a runner' do expected_fields = %w[ - id description created_at contacted_at maximum_timeout access_level active paused status + id description created_by created_at contacted_at maximum_timeout access_level active paused status version short_sha revision locked run_untagged ip_address runner_type tag_list project_count job_count admin_url edit_admin_url register_admin_url user_permissions executor_name architecture_name platform_name maintenance_note maintenance_note_html groups projects jobs token_expires_at diff --git a/spec/lib/error_tracking/collector/payload_validator_spec.rb b/spec/lib/error_tracking/collector/payload_validator_spec.rb index 94708f63bf4..96ad66e9b58 100644 --- a/spec/lib/error_tracking/collector/payload_validator_spec.rb +++ b/spec/lib/error_tracking/collector/payload_validator_spec.rb @@ -24,7 +24,7 @@ RSpec.describe ErrorTracking::Collector::PayloadValidator do end with_them do - let(:payload) { Gitlab::Json.parse(fixture_file(event_fixture)) } + let(:payload) { Gitlab::Json.parse(File.read(event_fixture)) } it_behaves_like 'valid payload' end diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb index f1640822c6b..fc68d3d62a2 100644 --- a/spec/lib/gitlab/ci/config/external/context_spec.rb +++ b/spec/lib/gitlab/ci/config/external/context_spec.rb @@ -7,7 +7,16 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin let(:user) { double('User') } let(:sha) { '12345' } let(:variables) { Gitlab::Ci::Variables::Collection.new([{ 'key' => 'a', 'value' => 'b' }]) } - let(:attributes) { { project: project, user: user, sha: sha, variables: variables } } + let(:pipeline_config) { instance_double(Gitlab::Ci::ProjectConfig) } + let(:attributes) do + { + project: project, + user: user, + sha: sha, + variables: variables, + pipeline_config: pipeline_config + } + end subject(:subject) { described_class.new(**attributes) } @@ -20,6 +29,7 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } it { expect(subject.variables_hash).to include('a' => 'b') } + it { expect(subject.pipeline_config).to eq(pipeline_config) } end context 'without values' do @@ -31,6 +41,7 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin it { expect(subject.execution_deadline).to eq(0) } it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) } it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) } + it { expect(subject.pipeline_config).to be_nil } end end @@ -144,27 +155,24 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin it { expect(subject.sentry_payload).to match(a_hash_including(:project, :user)) } end - describe '#contains_internal_include?' do + describe '#internal_include?' do context 'when pipeline_config is provided' do - let(:pipeline_config) { instance_double(Gitlab::Ci::ProjectConfig) } - let(:attributes) do - { project: project, user: user, sha: sha, variables: variables, pipeline_config: pipeline_config } - end - where(:value) { [true, false] } with_them do - it 'returns the value of .contains_internal_include?' do - allow(pipeline_config).to receive(:contains_internal_include?).and_return(value) + it 'returns the value of .internal_include_prepended?' do + allow(pipeline_config).to receive(:internal_include_prepended?).and_return(value) - expect(subject.contains_internal_include?).to eq(value) + expect(subject.internal_include?).to eq(value) end end end context 'when pipeline_config is not provided' do + let(:pipeline_config) { nil } + it 'returns false' do - expect(subject.contains_internal_include?).to eq(false) + expect(subject.internal_include?).to eq(false) end end end diff --git a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb index 478b26e97cd..056a06337af 100644 --- a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb +++ b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb @@ -209,7 +209,30 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category: end end - context 'when total file count exceeds max_includes' do + describe 'max includes detection' do + shared_examples 'verifies max includes' do + context 'when total file count is equal to max_includes' do + before do + allow(context).to receive(:max_includes).and_return(expected_total_file_count) + end + + it 'adds the expected number of files to expandset' do + expect { process }.not_to raise_error + expect(context.expandset.count).to eq(expected_total_file_count) + end + end + + context 'when total file count exceeds max_includes' do + before do + allow(context).to receive(:max_includes).and_return(expected_total_file_count - 1) + end + + it 'raises error' do + expect { process }.to raise_error(expected_error_class) + end + end + end + context 'when files are nested' do let(:files) do [ @@ -217,9 +240,21 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category: ] end - it 'raises Processor::IncludeError' do - allow(context).to receive(:max_includes).and_return(1) - expect { process }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError) + let(:expected_total_file_count) { 4 } # Includes nested_configs.yml + 3 nested files + let(:expected_error_class) { Gitlab::Ci::Config::External::Processor::IncludeError } + + it_behaves_like 'verifies max includes' + + context 'when duplicate files are included' do + let(:expected_total_file_count) { 8 } # 2 x (Includes nested_configs.yml + 3 nested files) + let(:files) do + [ + Gitlab::Ci::Config::External::File::Local.new({ local: 'nested_configs.yml' }, context), + Gitlab::Ci::Config::External::File::Local.new({ local: 'nested_configs.yml' }, context) + ] + end + + it_behaves_like 'verifies max includes' end end @@ -231,24 +266,163 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category: ] end - it 'raises Mapper::TooManyIncludesError' do - allow(context).to receive(:max_includes).and_return(1) - expect { process }.to raise_error(Gitlab::Ci::Config::External::Mapper::TooManyIncludesError) + let(:expected_total_file_count) { files.count } + let(:expected_error_class) { Gitlab::Ci::Config::External::Mapper::TooManyIncludesError } + + it_behaves_like 'verifies max includes' + + context 'when duplicate files are included' do + let(:files) do + [ + Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context), + Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file2.yml' }, context), + Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file2.yml' }, context) + ] + end + + let(:expected_total_file_count) { files.count } + + it_behaves_like 'verifies max includes' end end - context 'when files are duplicates' do + context 'when there is a circular include' do + let(:project_files) do + { + 'myfolder/file1.yml' => <<~YAML + include: myfolder/file1.yml + YAML + } + end + let(:files) do [ - Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context), - Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context), Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context) ] end + before do + allow(context).to receive(:max_includes).and_return(10) + end + it 'raises error' do - allow(context).to receive(:max_includes).and_return(2) - expect { process }.to raise_error(Gitlab::Ci::Config::External::Mapper::TooManyIncludesError) + expect { process }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError) + end + end + + context 'when a file is an internal include' do + let(:project_files) do + { + 'myfolder/file1.yml' => <<~YAML, + my_build: + script: echo Hello World + YAML + '.internal-include.yml' => <<~YAML + include: + - local: myfolder/file1.yml + YAML + } + end + + let(:files) do + [ + Gitlab::Ci::Config::External::File::Local.new({ local: '.internal-include.yml' }, context) + ] + end + + let(:total_file_count) { 2 } # Includes .internal-include.yml + myfolder/file1.yml + let(:pipeline_config) { instance_double(Gitlab::Ci::ProjectConfig) } + + let(:context) do + Gitlab::Ci::Config::External::Context.new( + project: project, + user: user, + sha: project.commit.id, + pipeline_config: pipeline_config + ) + end + + before do + allow(pipeline_config).to receive(:internal_include_prepended?).and_return(true) + allow(context).to receive(:max_includes).and_return(1) + end + + context 'when total file count excluding internal include is equal to max_includes' do + it 'does not add the internal include to expandset' do + expect { process }.not_to raise_error + expect(context.expandset.count).to eq(total_file_count - 1) + expect(context.expandset.first.location).to eq('myfolder/file1.yml') + end + end + + context 'when total file count excluding internal include exceeds max_includes' do + let(:project_files) do + { + 'myfolder/file1.yml' => <<~YAML, + my_build: + script: echo Hello World + YAML + '.internal-include.yml' => <<~YAML + include: + - local: myfolder/file1.yml + - local: myfolder/file1.yml + YAML + } + end + + it 'raises error' do + expect { process }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError) + end + end + end + end + + context 'when FF ci_fix_max_includes is disabled' do + before do + stub_feature_flags(ci_fix_max_includes: false) + end + + context 'when total file count exceeds max_includes' do + context 'when files are nested' do + let(:files) do + [ + Gitlab::Ci::Config::External::File::Local.new({ local: 'nested_configs.yml' }, context) + ] + end + + it 'raises Processor::IncludeError' do + allow(context).to receive(:max_includes).and_return(1) + expect { process }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError) + end + end + + context 'when files are not nested' do + let(:files) do + [ + Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context), + Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file2.yml' }, context) + ] + end + + it 'raises Mapper::TooManyIncludesError' do + allow(context).to receive(:max_includes).and_return(1) + expect { process }.to raise_error(Gitlab::Ci::Config::External::Mapper::TooManyIncludesError) + end + end + + context 'when files are duplicates' do + let(:files) do + [ + Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context), + Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context), + Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context) + ] + end + + it 'raises error' do + allow(context).to receive(:max_includes).and_return(2) + expect { process }.to raise_error(Gitlab::Ci::Config::External::Mapper::TooManyIncludesError) + end end end end diff --git a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb index 434acfb5274..a9a52972294 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/config/content_spec.rb @@ -26,7 +26,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: : expect(pipeline.config_source).to eq 'bridge_source' expect(command.config_content).to eq 'the-yaml' - expect(command.pipeline_config.contains_internal_include?).to eq(false) + expect(command.pipeline_config.internal_include_prepended?).to eq(false) end end @@ -53,7 +53,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: : expect(pipeline.config_source).to eq 'repository_source' expect(pipeline.pipeline_config.content).to eq(config_content_result) expect(command.config_content).to eq(config_content_result) - expect(command.pipeline_config.contains_internal_include?).to eq(true) + expect(command.pipeline_config.internal_include_prepended?).to eq(true) end end @@ -73,7 +73,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: : expect(pipeline.config_source).to eq 'remote_source' expect(pipeline.pipeline_config.content).to eq(config_content_result) expect(command.config_content).to eq(config_content_result) - expect(command.pipeline_config.contains_internal_include?).to eq(true) + expect(command.pipeline_config.internal_include_prepended?).to eq(true) end end @@ -94,7 +94,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: : expect(pipeline.config_source).to eq 'external_project_source' expect(pipeline.pipeline_config.content).to eq(config_content_result) expect(command.config_content).to eq(config_content_result) - expect(command.pipeline_config.contains_internal_include?).to eq(true) + expect(command.pipeline_config.internal_include_prepended?).to eq(true) end context 'when path specifies a refname' do @@ -115,7 +115,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: : expect(pipeline.config_source).to eq 'external_project_source' expect(pipeline.pipeline_config.content).to eq(config_content_result) expect(command.config_content).to eq(config_content_result) - expect(command.pipeline_config.contains_internal_include?).to eq(true) + expect(command.pipeline_config.internal_include_prepended?).to eq(true) end end end @@ -143,7 +143,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: : expect(pipeline.config_source).to eq 'repository_source' expect(pipeline.pipeline_config.content).to eq(config_content_result) expect(command.config_content).to eq(config_content_result) - expect(command.pipeline_config.contains_internal_include?).to eq(true) + expect(command.pipeline_config.internal_include_prepended?).to eq(true) end end @@ -167,7 +167,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: : expect(pipeline.config_source).to eq 'auto_devops_source' expect(pipeline.pipeline_config.content).to eq(config_content_result) expect(command.config_content).to eq(config_content_result) - expect(command.pipeline_config.contains_internal_include?).to eq(true) + expect(command.pipeline_config.internal_include_prepended?).to eq(true) end end @@ -188,7 +188,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Config::Content, feature_category: : expect(pipeline.config_source).to eq 'parameter_source' expect(pipeline.pipeline_config.content).to eq(content) expect(command.config_content).to eq(content) - expect(command.pipeline_config.contains_internal_include?).to eq(false) + expect(command.pipeline_config.internal_include_prepended?).to eq(false) end end diff --git a/spec/lib/gitlab/ci/project_config/repository_spec.rb b/spec/lib/gitlab/ci/project_config/repository_spec.rb index b31a9099348..e8a997a7e43 100644 --- a/spec/lib/gitlab/ci/project_config/repository_spec.rb +++ b/spec/lib/gitlab/ci/project_config/repository_spec.rb @@ -45,8 +45,8 @@ RSpec.describe Gitlab::Ci::ProjectConfig::Repository, feature_category: :continu it { is_expected.to eq(:repository_source) } end - describe '#contains_internal_include?' do - subject { config.contains_internal_include? } + describe '#internal_include_prepended?' do + subject { config.internal_include_prepended? } it { is_expected.to eq(true) } end diff --git a/spec/lib/gitlab/ci/project_config/source_spec.rb b/spec/lib/gitlab/ci/project_config/source_spec.rb index 5248cf080e8..eefabe1babb 100644 --- a/spec/lib/gitlab/ci/project_config/source_spec.rb +++ b/spec/lib/gitlab/ci/project_config/source_spec.rb @@ -21,9 +21,9 @@ RSpec.describe Gitlab::Ci::ProjectConfig::Source, feature_category: :continuous_ it { expect { source }.to raise_error(NotImplementedError) } end - describe '#contains_internal_include?' do - subject(:contains_internal_include) { custom_config.contains_internal_include? } + describe '#internal_include_prepended?' do + subject(:internal_include_prepended) { custom_config.internal_include_prepended? } - it { expect(contains_internal_include).to eq(false) } + it { expect(internal_include_prepended).to eq(false) } end end diff --git a/spec/lib/gitlab/email/html_to_markdown_parser_spec.rb b/spec/lib/gitlab/email/html_to_markdown_parser_spec.rb index fe585d47d59..59c488739dc 100644 --- a/spec/lib/gitlab/email/html_to_markdown_parser_spec.rb +++ b/spec/lib/gitlab/email/html_to_markdown_parser_spec.rb @@ -1,17 +1,21 @@ # frozen_string_literal: true -require 'spec_helper' +require 'kramdown' +require 'html2text' +require 'fast_spec_helper' +require 'support/helpers/fixture_helpers' RSpec.describe Gitlab::Email::HtmlToMarkdownParser, feature_category: :service_desk do + include FixtureHelpers + subject { described_class.convert(html) } describe '.convert' do let(:html) { fixture_file("lib/gitlab/email/basic.html") } it 'parses html correctly' do - expect(subject) - .to eq( - <<-BODY.strip_heredoc.chomp + expect(subject).to eq( + <<~BODY.chomp Hello, World! This is some e-mail content. Even though it has whitespace and newlines, the e-mail converter will handle it correctly. *Even* mismatched tags. diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index a3d1dcc79ee..344ca97c80f 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -2030,4 +2030,13 @@ RSpec.describe Ci::Runner, type: :model, feature_category: :runner do end end end + + describe '.with_creator' do + subject { described_class.with_creator } + + let!(:user) { create(:admin) } + let!(:runner) { create(:ci_runner, creator: user) } + + it { is_expected.to contain_exactly(runner) } + end end diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb index bb32cae6b1f..d612c8f6e8b 100644 --- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb +++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb @@ -341,12 +341,13 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting, feature_category: :er describe '#update_issue' do let(:result) { subject.update_issue(**opts) } - let(:opts) { { issue_id: 1, params: {} } } + let(:issue_id) { 1 } + let(:opts) { { issue_id: issue_id, params: {} } } before do allow(subject).to receive(:sentry_client).and_return(sentry_client) allow(sentry_client).to receive(:issue_details) - .with({ issue_id: 1 }) + .with({ issue_id: issue_id }) .and_return(Gitlab::ErrorTracking::DetailedError.new(project_id: sentry_project_id)) end @@ -419,6 +420,25 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting, feature_category: :er end end end + + describe 'passing parameters to sentry client' do + include SentryClientHelpers + + let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0' } + let(:sentry_request_url) { "#{sentry_url}/issues/#{issue_id}/" } + let(:token) { 'test-token' } + let(:sentry_client) { ErrorTracking::SentryClient.new(sentry_url, token) } + + before do + stub_sentry_request(sentry_request_url, :put, body: true) + + allow(sentry_client).to receive(:update_issue).and_call_original + end + + it 'returns the successful response' do + expect(result).to eq(updated: true) + end + end end describe 'slugs' do diff --git a/spec/requests/api/ci/runner/runners_verify_post_spec.rb b/spec/requests/api/ci/runner/runners_verify_post_spec.rb index a6a1ad947aa..1b7dfe7706c 100644 --- a/spec/requests/api/ci/runner/runners_verify_post_spec.rb +++ b/spec/requests/api/ci/runner/runners_verify_post_spec.rb @@ -17,7 +17,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego end describe '/api/v4/runners' do - describe 'POST /api/v4/runners/verify' do + describe 'POST /api/v4/runners/verify', :freeze_time do let_it_be_with_reload(:runner) { create(:ci_runner, token_expires_at: 3.days.from_now) } let(:params) {} @@ -50,6 +50,30 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego stub_feature_flags(create_runner_machine: true) end + context 'with glrt-prefixed token' do + let_it_be(:registration_token) { 'glrt-abcdefg123456' } + let_it_be(:registration_type) { :authenticated_user } + let_it_be(:runner) do + create(:ci_runner, registration_type: registration_type, + token: registration_token, token_expires_at: 3.days.from_now) + end + + it 'verifies Runner credentials' do + verify + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response).to eq({ + 'id' => runner.id, + 'token' => runner.token, + 'token_expires_at' => runner.token_expires_at.iso8601(3) + }) + end + + it 'does not update contacted_at' do + expect { verify }.not_to change { runner.reload.contacted_at }.from(nil) + end + end + it 'verifies Runner credentials' do verify @@ -61,6 +85,10 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state, feature_catego }) end + it 'updates contacted_at' do + expect { verify }.to change { runner.reload.contacted_at }.from(nil).to(Time.current) + end + context 'with non-expiring runner token' do before do runner.update!(token_expires_at: nil) diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb index 7c970c2b548..0039a32155b 100644 --- a/spec/requests/api/graphql/ci/runner_spec.rb +++ b/spec/requests/api/graphql/ci/runner_spec.rb @@ -6,11 +6,13 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do include GraphqlHelpers let_it_be(:user) { create(:user, :admin) } + let_it_be(:another_admin) { create(:user, :admin) } let_it_be(:group) { create(:group) } let_it_be(:active_instance_runner) do create(:ci_runner, :instance, description: 'Runner 1', + creator: user, contacted_at: 2.hours.ago, active: true, version: 'adfe156', @@ -28,6 +30,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do let_it_be(:inactive_instance_runner) do create(:ci_runner, :instance, description: 'Runner 2', + creator: another_admin, contacted_at: 1.day.ago, active: false, version: 'adfe157', @@ -77,6 +80,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do expect(runner_data).to match a_graphql_entity_for( runner, description: runner.description, + created_by: runner.creator ? a_graphql_entity_for(runner.creator) : nil, created_at: runner.created_at&.iso8601, contacted_at: runner.contacted_at&.iso8601, version: runner.version, @@ -118,6 +122,23 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do ) expect(runner_data['tagList']).to match_array runner.tag_list end + + it 'does not execute more queries per runner', :aggregate_failures do + # warm-up license cache and so on: + personal_access_token = create(:personal_access_token, user: user) + args = { current_user: user, token: { personal_access_token: personal_access_token } } + post_graphql(query, **args) + expect(graphql_data_at(:runner)).not_to be_nil + + personal_access_token = create(:personal_access_token, user: another_admin) + args = { current_user: another_admin, token: { personal_access_token: personal_access_token } } + control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) } + + create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: another_admin) + create(:ci_runner, :project, version: '14.0.1', projects: [project1], tag_list: %w[tag3 tag8], creator: another_admin) + + expect { post_graphql(query, **args) }.not_to exceed_query_limit(control) + end end shared_examples 'retrieval with no admin url' do @@ -648,6 +669,12 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do <<~SINGLE runner(id: "#{runner.to_global_id}") { #{all_graphql_fields_for('CiRunner', excluded: excluded_fields)} + createdBy { + id + username + webPath + webUrl + } groups { nodes { id @@ -678,7 +705,7 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do let(:active_group_runner2) { create(:ci_runner, :group) } # Exclude fields that are already hardcoded above - let(:excluded_fields) { %w[jobs groups projects ownerProject] } + let(:excluded_fields) { %w[createdBy jobs groups projects ownerProject] } let(:single_query) do <<~QUERY @@ -711,6 +738,8 @@ RSpec.describe 'Query.runner(id)', feature_category: :runner_fleet do control = ActiveRecord::QueryRecorder.new { post_graphql(single_query, **args) } + personal_access_token = create(:personal_access_token, user: another_admin) + args = { current_user: another_admin, token: { personal_access_token: personal_access_token } } expect { post_graphql(double_query, **args) }.not_to exceed_query_limit(control) expect(graphql_data.count).to eq 6 diff --git a/spec/requests/api/graphql/ci/runners_spec.rb b/spec/requests/api/graphql/ci/runners_spec.rb index 75d8609dc38..c8706ae9698 100644 --- a/spec/requests/api/graphql/ci/runners_spec.rb +++ b/spec/requests/api/graphql/ci/runners_spec.rb @@ -11,16 +11,24 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do let_it_be(:instance_runner) { create(:ci_runner, :instance, version: 'abc', revision: '123', description: 'Instance runner', ip_address: '127.0.0.1') } let_it_be(:project_runner) { create(:ci_runner, :project, active: false, version: 'def', revision: '456', description: 'Project runner', projects: [project], ip_address: '127.0.0.1') } - let(:runners_graphql_data) { graphql_data['runners'] } + let(:runners_graphql_data) { graphql_data_at(:runners) } let(:params) { {} } let(:fields) do <<~QUERY nodes { - #{all_graphql_fields_for('CiRunner', excluded: %w[ownerProject])} + #{all_graphql_fields_for('CiRunner', excluded: %w[createdBy ownerProject])} + createdBy { + username + webPath + webUrl + } ownerProject { id + path + fullPath + webUrl } } QUERY @@ -50,6 +58,25 @@ RSpec.describe 'Query.runners', feature_category: :runner_fleet do it 'returns expected runner' do expect(runners_graphql_data['nodes']).to contain_exactly(a_graphql_entity_for(expected_runner)) end + + it 'does not execute more queries per runner', :aggregate_failures do + # warm-up license cache and so on: + personal_access_token = create(:personal_access_token, user: current_user) + args = { current_user: current_user, token: { personal_access_token: personal_access_token } } + post_graphql(query, **args) + expect(graphql_data_at(:runners, :nodes)).not_to be_empty + + admin2 = create(:admin) + personal_access_token = create(:personal_access_token, user: admin2) + args = { current_user: admin2, token: { personal_access_token: personal_access_token } } + control = ActiveRecord::QueryRecorder.new { post_graphql(query, **args) } + + create(:ci_runner, :instance, version: '14.0.0', tag_list: %w[tag5 tag6], creator: admin2) + create(:ci_runner, :project, version: '14.0.1', projects: [project], tag_list: %w[tag3 tag8], + creator: current_user) + + expect { post_graphql(query, **args) }.not_to exceed_query_limit(control) + end end context 'runner_type is INSTANCE_TYPE and status is ACTIVE' do diff --git a/spec/support/helpers/fixture_helpers.rb b/spec/support/helpers/fixture_helpers.rb index 7b3b8ae5f7a..eafdecb2e3d 100644 --- a/spec/support/helpers/fixture_helpers.rb +++ b/spec/support/helpers/fixture_helpers.rb @@ -8,6 +8,6 @@ module FixtureHelpers end def expand_fixture_path(filename, dir: '') - File.expand_path(Rails.root.join(dir, 'spec', 'fixtures', filename)) + File.expand_path(rails_root_join(dir, 'spec', 'fixtures', filename)) end end diff --git a/yarn.lock b/yarn.lock index 3f42a34b62d..7adc8295ebb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5217,10 +5217,10 @@ dompurify@2.3.8: resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.8.tgz#224fe9ae57d7ebd9a1ae1ac18c1c1ca3f532226f" integrity sha512-eVhaWoVibIzqdGYjwsBWodIQIaXFSB+cKDf4cfxLMsK0xiud6SE+/WCVx/Xw/UwQsa4cS3T2eITcdtmTg2UKcw== -dompurify@^2.4.4: - version "2.4.4" - resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.4.tgz#c17803931dd524e1b68e0e940a84567f9498f4bd" - integrity sha512-1e2SpqHiRx4DPvmRuXU5J0di3iQACwJM+mFGE2HAkkK7Tbnfk9WcghcAmyWc9CRrjyRRUpmuhPUH6LphQQR3EQ== +dompurify@^2.4.4, dompurify@^2.4.5: + version "2.4.5" + resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.5.tgz#0e89a27601f0bad978f9a924e7a05d5d2cccdd87" + integrity sha512-jggCCd+8Iqp4Tsz0nIvpcb22InKEBrGz5dw3EQJMs8HPJDsKbFIO3STYtAvCfDx26Muevn1MHVI0XxjgFfmiSA== domutils@^2.5.2, domutils@^2.6.0: version "2.6.0" |