diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-07 03:07:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-03-07 03:07:42 +0300 |
commit | defeeba1a8d6fa8784db1c50ca4ff9e8b56f539c (patch) | |
tree | da50633fb0b41bd238a1b972c69488073599fe28 | |
parent | 8ec004d6d8d92f00d0598e94ede4d31ab1e8f18e (diff) |
Add latest changes from gitlab-org/gitlab@master
77 files changed, 714 insertions, 138 deletions
diff --git a/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_filters.vue b/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_filters.vue index 17decb6b448..82bb1f77cbb 100644 --- a/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_filters.vue +++ b/app/assets/javascripts/analytics/cycle_analytics/components/value_stream_filters.vue @@ -82,9 +82,7 @@ export default { <div> <projects-dropdown-filter v-if="hasProjectFilter" - :key="groupId" class="js-projects-dropdown-filter project-select gl-mb-2 gl-lg-mb-0" - :group-id="groupId" :group-namespace="groupPath" :query-params="projectsQueryParams" :multi-select="$options.multiProjectSelect" diff --git a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue index 5bb60d91f1e..98193de4a12 100644 --- a/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue +++ b/app/assets/javascripts/analytics/shared/components/projects_dropdown_filter.vue @@ -32,11 +32,6 @@ export default { GlTruncate, }, props: { - groupId: { - type: Number, - required: false, - default: null, - }, groupNamespace: { type: String, required: true, diff --git a/app/assets/javascripts/behaviors/markdown/render_json_table.js b/app/assets/javascripts/behaviors/markdown/render_json_table.js index 4d9ac1d266b..aa0e7d38113 100644 --- a/app/assets/javascripts/behaviors/markdown/render_json_table.js +++ b/app/assets/javascripts/behaviors/markdown/render_json_table.js @@ -1,7 +1,7 @@ import { memoize } from 'lodash'; import Vue from 'vue'; import { __ } from '~/locale'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; // Async import component since we might not need it... const JSONTable = memoize(() => diff --git a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js index 66007aa9e3d..bd9e41ac0ba 100644 --- a/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js +++ b/app/assets/javascripts/behaviors/markdown/render_sandboxed_mermaid.js @@ -8,7 +8,7 @@ import { } from '~/lib/utils/url_utility'; import { darkModeEnabled } from '~/lib/utils/color_utils'; import { setAttributes, isElementVisible } from '~/lib/utils/dom_utils'; -import { createAlert, VARIANT_WARNING } from '~/flash'; +import { createAlert, VARIANT_WARNING } from '~/alert'; import { unrestrictedPages } from './constants'; // Renders diagrams and flowcharts from text using Mermaid in any element with the diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js index 32e395e4f3c..dc408f5a950 100644 --- a/app/assets/javascripts/behaviors/preview_markdown.js +++ b/app/assets/javascripts/behaviors/preview_markdown.js @@ -2,7 +2,7 @@ import $ from 'jquery'; import { renderGFM } from '~/behaviors/markdown/render_gfm'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { __ } from '~/locale'; diff --git a/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue b/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue index 57fae608efa..486baccfad0 100644 --- a/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue +++ b/app/assets/javascripts/deploy_tokens/components/new_deploy_token.vue @@ -9,7 +9,7 @@ import { GlSprintf, GlLink, } from '@gitlab/ui'; -import { createAlert, VARIANT_INFO } from '~/flash'; +import { createAlert, VARIANT_INFO } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { formatDate } from '~/lib/utils/datetime_utility'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; diff --git a/app/assets/javascripts/deprecated_notes.js b/app/assets/javascripts/deprecated_notes.js index 7503df9194b..5b398623164 100644 --- a/app/assets/javascripts/deprecated_notes.js +++ b/app/assets/javascripts/deprecated_notes.js @@ -16,7 +16,7 @@ import $ from 'jquery'; import { escape, uniqueId } from 'lodash'; import Vue from 'vue'; import { renderGFM } from '~/behaviors/markdown/render_gfm'; -import { createAlert, VARIANT_INFO } from '~/flash'; +import { createAlert, VARIANT_INFO } from '~/alert'; import { sanitize } from '~/lib/dompurify'; import '~/lib/utils/jquery_at_who'; import AjaxCache from '~/lib/utils/ajax_cache'; diff --git a/app/assets/javascripts/design_management/pages/design/index.vue b/app/assets/javascripts/design_management/pages/design/index.vue index 2780ae70cc7..c099c06df47 100644 --- a/app/assets/javascripts/design_management/pages/design/index.vue +++ b/app/assets/javascripts/design_management/pages/design/index.vue @@ -3,7 +3,7 @@ import { GlAlert } from '@gitlab/ui'; import { isNull } from 'lodash'; import Mousetrap from 'mousetrap'; import { keysFor, ISSUE_CLOSE_DESIGN } from '~/behaviors/shortcuts/keybindings'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { fetchPolicies } from '~/lib/graphql'; import { updateGlobalTodoCount } from '~/sidebar/utils'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; @@ -253,7 +253,7 @@ export default { }, onQueryError(message) { // because we redirect user to /designs (the issue page), - // we want to create these flashes on the issue page + // we want to create these alerts on the issue page createAlert({ message }); this.$router.push({ name: this.$options.DESIGNS_ROUTE_NAME }); }, diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue index 9a6b36ad2e8..9ccba88f7e6 100644 --- a/app/assets/javascripts/diffs/components/app.vue +++ b/app/assets/javascripts/diffs/components/app.vue @@ -11,7 +11,7 @@ import { MR_COMMITS_NEXT_COMMIT, MR_COMMITS_PREVIOUS_COMMIT, } from '~/behaviors/shortcuts/keybindings'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { isSingleViewStyle } from '~/helpers/diffs_helper'; import { helpPagePath } from '~/helpers/help_page_helper'; import { parseBoolean } from '~/lib/utils/common_utils'; diff --git a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue index 8fcbc4b5cce..53a55aac1ec 100644 --- a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue +++ b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue @@ -2,7 +2,7 @@ import { GlTooltipDirective, GlIcon, GlLoadingIcon } from '@gitlab/ui'; import { mapActions } from 'vuex'; import SafeHtml from '~/vue_shared/directives/safe_html'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { s__, sprintf } from '~/locale'; import { UNFOLD_COUNT, INLINE_DIFF_LINES_KEY } from '../constants'; import * as utils from '../store/utils'; diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue index 564f776edd2..c19174dda8a 100644 --- a/app/assets/javascripts/diffs/components/diff_file.vue +++ b/app/assets/javascripts/diffs/components/diff_file.vue @@ -5,7 +5,7 @@ import { mapActions, mapGetters, mapState } from 'vuex'; import SafeHtml from '~/vue_shared/directives/safe_html'; import { IdState } from 'vendor/vue-virtual-scroller'; import DiffContent from 'jh_else_ce/diffs/components/diff_content.vue'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { hasDiff } from '~/helpers/diffs_helper'; import { diffViewerErrors } from '~/ide/constants'; import { scrollToElement } from '~/lib/utils/common_utils'; diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js index 561e2a4f134..9236e14beb1 100644 --- a/app/assets/javascripts/diffs/store/actions.js +++ b/app/assets/javascripts/diffs/store/actions.js @@ -5,7 +5,7 @@ import { historyPushState, scrollToElement, } from '~/lib/utils/common_utils'; -import { createAlert, VARIANT_WARNING } from '~/flash'; +import { createAlert, VARIANT_WARNING } from '~/alert'; import { diffViewerModes } from '~/ide/constants'; import axios from '~/lib/utils/axios_utils'; diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue index 5ef6c974920..1c196e05ff0 100644 --- a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue +++ b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue @@ -15,7 +15,7 @@ import { GlTooltipDirective, } from '@gitlab/ui'; import { debounce } from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { s__, __, n__, sprintf } from '~/locale'; import { HTTP_STATUS_TOO_MANY_REQUESTS } from '~/lib/utils/http_status'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; diff --git a/app/assets/javascripts/import_entities/import_groups/services/status_poller.js b/app/assets/javascripts/import_entities/import_groups/services/status_poller.js index 6ad5e448a40..10496fce11b 100644 --- a/app/assets/javascripts/import_entities/import_groups/services/status_poller.js +++ b/app/assets/javascripts/import_entities/import_groups/services/status_poller.js @@ -1,5 +1,5 @@ import Visibility from 'visibilityjs'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import Poll from '~/lib/utils/poll'; import { s__ } from '~/locale'; diff --git a/app/assets/javascripts/import_entities/import_projects/store/actions.js b/app/assets/javascripts/import_entities/import_projects/store/actions.js index e0db585eb3e..e3c32028b13 100644 --- a/app/assets/javascripts/import_entities/import_projects/store/actions.js +++ b/app/assets/javascripts/import_entities/import_projects/store/actions.js @@ -1,6 +1,6 @@ import Visibility from 'visibilityjs'; import _ from 'lodash'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from '~/lib/utils/axios_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { HTTP_STATUS_TOO_MANY_REQUESTS } from '~/lib/utils/http_status'; @@ -141,7 +141,7 @@ const fetchImportFactory = (importPath = isRequired()) => ( }) .catch((e) => { const serverErrorMessage = e?.response?.data?.errors; - const flashMessage = serverErrorMessage + const alertMessage = serverErrorMessage ? sprintf( s__('ImportProjects|Importing the project failed: %{reason}'), { @@ -152,7 +152,7 @@ const fetchImportFactory = (importPath = isRequired()) => ( : s__('ImportProjects|Importing the project failed'); createAlert({ - message: flashMessage, + message: alertMessage, }); commit(types.RECEIVE_IMPORT_ERROR, repoId); @@ -179,7 +179,7 @@ export const cancelImportFactory = (cancelImportPath) => ({ state, commit }, { r }) .catch((e) => { const serverErrorMessage = e?.response?.data?.errors; - const flashMessage = serverErrorMessage + const alertMessage = serverErrorMessage ? sprintf( s__('ImportProjects|Cancelling project import failed: %{reason}'), { @@ -190,7 +190,7 @@ export const cancelImportFactory = (cancelImportPath) => ({ state, commit }, { r : s__('ImportProjects|Cancelling project import failed'); createAlert({ - message: flashMessage, + message: alertMessage, }); }); }; diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue index bdff2e6317c..25c82c29a29 100644 --- a/app/assets/javascripts/notes/components/note_edited_text.vue +++ b/app/assets/javascripts/notes/components/note_edited_text.vue @@ -46,14 +46,21 @@ export default { <template #actionText> {{ actionText }} </template> + <template #actionDetail> + {{ actionDetailText }} + </template> + <template #timeago> + <time-ago-tooltip :time="editedAt" tooltip-placement="bottom" /> + </template> <template #author> - <gl-link :href="editedBy.path" :data-user-id="editedBy.id" class="js-user-link author-link"> + <gl-link + :href="editedBy.path" + :data-user-id="editedBy.id" + class="js-user-link author-link gl-hover-text-decoration-underline" + > {{ editedBy.name }} </gl-link> </template> - <template #actionDetail> - {{ actionDetailText }} - </template> </gl-sprintf> <gl-sprintf v-else :message="$options.i18n.actionWithoutAuthor"> <template #actionText> @@ -62,7 +69,9 @@ export default { <template #actionDetail> {{ actionDetailText }} </template> + <template #timeago> + <time-ago-tooltip :time="editedAt" tooltip-placement="bottom" /> + </template> </gl-sprintf> - <time-ago-tooltip :time="editedAt" tooltip-placement="bottom" /> </div> </template> diff --git a/app/assets/javascripts/notes/i18n.js b/app/assets/javascripts/notes/i18n.js index 8aad3ec07cb..4bf2a8d70a7 100644 --- a/app/assets/javascripts/notes/i18n.js +++ b/app/assets/javascripts/notes/i18n.js @@ -51,6 +51,6 @@ export const COMMENT_FORM = { }; export const EDITED_TEXT = { - actionWithAuthor: __('%{actionText} by %{author} %{actionDetail}'), + actionWithAuthor: __('%{actionText} %{actionDetail} %{timeago} by %{author}'), actionWithoutAuthor: __('%{actionText} %{actionDetail}'), }; diff --git a/app/assets/javascripts/service_ping_consent.js b/app/assets/javascripts/service_ping_consent.js index 1cb4e188e54..7d6e7e81f3b 100644 --- a/app/assets/javascripts/service_ping_consent.js +++ b/app/assets/javascripts/service_ping_consent.js @@ -1,5 +1,5 @@ import $ from 'jquery'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from './lib/utils/axios_utils'; import { parseBoolean } from './lib/utils/common_utils'; import { __ } from './locale'; diff --git a/app/assets/javascripts/task_list.js b/app/assets/javascripts/task_list.js index a7760ad5d0b..bb344ade344 100644 --- a/app/assets/javascripts/task_list.js +++ b/app/assets/javascripts/task_list.js @@ -1,7 +1,7 @@ import $ from 'jquery'; import 'deckar01-task_list'; import { __ } from '~/locale'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import axios from './lib/utils/axios_utils'; export default class TaskList { diff --git a/app/assets/javascripts/token_access/components/inbound_token_access.vue b/app/assets/javascripts/token_access/components/inbound_token_access.vue index feaf9072ee2..1904846fcbc 100644 --- a/app/assets/javascripts/token_access/components/inbound_token_access.vue +++ b/app/assets/javascripts/token_access/components/inbound_token_access.vue @@ -9,7 +9,7 @@ import { GlSprintf, GlToggle, } from '@gitlab/ui'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __, s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; import inboundAddProjectCIJobTokenScopeMutation from '../graphql/mutations/inbound_add_project_ci_job_token_scope.mutation.graphql'; diff --git a/app/assets/javascripts/token_access/components/opt_in_jwt.vue b/app/assets/javascripts/token_access/components/opt_in_jwt.vue index c774f37b1e4..9485e0c3667 100644 --- a/app/assets/javascripts/token_access/components/opt_in_jwt.vue +++ b/app/assets/javascripts/token_access/components/opt_in_jwt.vue @@ -1,7 +1,7 @@ <script> import { GlLink, GlLoadingIcon, GlSprintf, GlToggle } from '@gitlab/ui'; import CodeInstruction from '~/vue_shared/components/registry/code_instruction.vue'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __, s__ } from '~/locale'; import updateOptInJwtMutation from '../graphql/mutations/update_opt_in_jwt.mutation.graphql'; import getOptInJwtSettingQuery from '../graphql/queries/get_opt_in_jwt_setting.query.graphql'; diff --git a/app/assets/javascripts/token_access/components/outbound_token_access.vue b/app/assets/javascripts/token_access/components/outbound_token_access.vue index 0deae1a1d82..d9c23c6c7f3 100644 --- a/app/assets/javascripts/token_access/components/outbound_token_access.vue +++ b/app/assets/javascripts/token_access/components/outbound_token_access.vue @@ -9,7 +9,7 @@ import { GlSprintf, GlToggle, } from '@gitlab/ui'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { __, s__ } from '~/locale'; import { helpPagePath } from '~/helpers/help_page_helper'; import addProjectCIJobTokenScopeMutation from '../graphql/mutations/add_project_ci_job_token_scope.mutation.graphql'; diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue index 3a3a846bce5..49574fe8846 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_link_child.vue @@ -2,7 +2,7 @@ import { GlButton, GlLink, GlIcon, GlTooltipDirective } from '@gitlab/ui'; import { __, s__ } from '~/locale'; -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import RichTimestampTooltip from '~/vue_shared/components/rich_timestamp_tooltip.vue'; import WorkItemLinkChildMetadata from 'ee_else_ce/work_items/components/work_item_links/work_item_link_child_metadata.vue'; diff --git a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue index 71de6867680..e233a2219fa 100644 --- a/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue +++ b/app/assets/javascripts/work_items/components/work_item_links/work_item_tree_children.vue @@ -1,5 +1,5 @@ <script> -import { createAlert } from '~/flash'; +import { createAlert } from '~/alert'; import { s__ } from '~/locale'; import updateWorkItemMutation from '../../graphql/update_work_item.mutation.graphql'; diff --git a/app/assets/stylesheets/page_bundles/issuable.scss b/app/assets/stylesheets/page_bundles/issuable.scss index f364170c99f..7321da1526d 100644 --- a/app/assets/stylesheets/page_bundles/issuable.scss +++ b/app/assets/stylesheets/page_bundles/issuable.scss @@ -92,10 +92,11 @@ color: var(--gray-500, $gray-500); display: block; margin: 16px 0 0; - font-size: 85%; + font-size: $gl-font-size-small; .author-link { - color: var(--gray-500, $gray-500); + color: var(--gray-700, $gray-700); + font-size: $gl-font-size-small; } } diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 5d37bd27302..70c2d262b72 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -3,10 +3,10 @@ class Admin::ProjectsController < Admin::ApplicationController include MembersPresentation - before_action :project, only: [:show, :transfer, :repository_check, :destroy] + before_action :project, only: [:show, :transfer, :repository_check, :destroy, :edit, :update] before_action :group, only: [:show, :transfer] - feature_category :projects, [:index, :show, :transfer, :destroy] + feature_category :projects, [:index, :show, :transfer, :destroy, :edit, :update] feature_category :source_code_management, [:repository_check] def index @@ -62,6 +62,18 @@ class Admin::ProjectsController < Admin::ApplicationController end # rubocop: enable CodeReuse/ActiveRecord + def edit; end + + def update + result = ::Projects::UpdateService.new(@project, current_user, project_params).execute + + if result[:status] == :success + redirect_to [:admin, @project], notice: format(_("Project '%{project_name}' was successfully updated."), project_name: @project.name) + else + render "edit" + end + end + def repository_check RepositoryCheck::SingleRepositoryWorker.perform_async(@project.id) # rubocop:disable CodeReuse/Worker @@ -83,6 +95,17 @@ class Admin::ProjectsController < Admin::ApplicationController def group @group ||= @project.group end + + def project_params + params.require(:project).permit(allowed_project_params) + end + + def allowed_project_params + [ + :description, + :name + ] + end end Admin::ProjectsController.prepend_mod_with('Admin::ProjectsController') diff --git a/app/controllers/profiles/personal_access_tokens_controller.rb b/app/controllers/profiles/personal_access_tokens_controller.rb index 8d5c690fbfe..4b6e2f768fa 100644 --- a/app/controllers/profiles/personal_access_tokens_controller.rb +++ b/app/controllers/profiles/personal_access_tokens_controller.rb @@ -25,7 +25,10 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController def create result = ::PersonalAccessTokens::CreateService.new( - current_user: current_user, target_user: current_user, params: personal_access_token_params + current_user: current_user, + target_user: current_user, + params: personal_access_token_params, + concatenate_errors: false ).execute @personal_access_token = result.payload[:personal_access_token] diff --git a/app/finders/abuse_reports_finder.rb b/app/finders/abuse_reports_finder.rb index 90d09a2d6ed..1f313534552 100644 --- a/app/finders/abuse_reports_finder.rb +++ b/app/finders/abuse_reports_finder.rb @@ -21,6 +21,7 @@ class AbuseReportsFinder def filter_reports filter_by_user_id + filter_by_user filter_by_status filter_by_category end @@ -42,6 +43,15 @@ class AbuseReportsFinder @reports = @reports.by_category(params[:category]) end + def filter_by_user + return unless params[:user].present? + + user_id = User.by_username(params[:user]).pick(:id) + return unless user_id + + @reports = @reports.by_user_id(user_id) + end + def filter_by_user_id return unless params[:user_id].present? diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 23a21bce456..d822a297856 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -200,7 +200,7 @@ module ApplicationHelper if !exclude_author && object.last_edited_by output << content_tag(:span, ' by ') - output << link_to_member(object.project, object.last_edited_by, avatar: false, author_class: nil) + output << link_to_member(object.project, object.last_edited_by, avatar: false, extra_class: 'gl-hover-text-decoration-underline', author_class: nil) end output diff --git a/app/helpers/routing/projects_helper.rb b/app/helpers/routing/projects_helper.rb index f4732e398f0..d632bccf43e 100644 --- a/app/helpers/routing/projects_helper.rb +++ b/app/helpers/routing/projects_helper.rb @@ -43,6 +43,11 @@ module Routing end def work_item_url(entity, *args) + # TODO: we do not have a route to access group level work items yet. + # That is to be done as part of view group level work item issue: + # see https://gitlab.com/gitlab-org/gitlab/-/work_items/393987?iid_path=true + return unless entity.project.present? + unless Feature.enabled?(:use_iid_in_work_items_path, entity.project.group) return project_work_items_url(entity.project, entity.id, *args) end diff --git a/app/models/alert_management/alert.rb b/app/models/alert_management/alert.rb index 4af9b644362..74edcf12ac2 100644 --- a/app/models/alert_management/alert.rb +++ b/app/models/alert_management/alert.rb @@ -140,7 +140,7 @@ module AlertManagement end def self.link_reference_pattern - @link_reference_pattern ||= super("alert_management", %r{(?<alert>\d+)/details(\#)?}) + @link_reference_pattern ||= compose_link_reference_pattern('alert_management', %r{(?<alert>\d+)/details(\#)?}) end def self.reference_valid?(reference) diff --git a/app/models/commit.rb b/app/models/commit.rb index 4517b3ef216..ea90b4e4dda 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -206,7 +206,8 @@ class Commit def self.link_reference_pattern @link_reference_pattern ||= - super("commit", /(?<commit>#{COMMIT_SHA_PATTERN})?(\.(?<extension>#{LINK_EXTENSION_PATTERN}))?/o) + compose_link_reference_pattern('commit', + /(?<commit>#{COMMIT_SHA_PATTERN})?(\.(?<extension>#{LINK_EXTENSION_PATTERN}))?/o) end def to_reference(from = nil, full: false) diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index 87029cb2033..90cdd267cbd 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -50,7 +50,7 @@ class CommitRange end def self.link_reference_pattern - @link_reference_pattern ||= super("compare", /(?<commit_range>#{PATTERN})/o) + @link_reference_pattern ||= compose_link_reference_pattern('compare', /(?<commit_range>#{PATTERN})/o) end # Initialize a CommitRange diff --git a/app/models/concerns/noteable.rb b/app/models/concerns/noteable.rb index 7addcf9e2ec..0333cfc5f9e 100644 --- a/app/models/concerns/noteable.rb +++ b/app/models/concerns/noteable.rb @@ -169,6 +169,7 @@ module Noteable def expire_note_etag_cache return unless discussions_rendered_on_frontend? return unless etag_caching_enabled? + return unless project.present? Gitlab::EtagCaching::Store.new.touch(note_etag_key) end diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index 9a17131c91c..5303d110078 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -76,7 +76,11 @@ module Referable true end - def link_reference_pattern(route, pattern) + def link_reference_pattern + raise NotImplementedError, "#{self} does not implement #{__method__}" + end + + def compose_link_reference_pattern(route, pattern) %r{ (?<url> #{Regexp.escape(Gitlab.config.gitlab.url)} diff --git a/app/models/design_management/design.rb b/app/models/design_management/design.rb index f9c748c299d..cb6d4e72c80 100644 --- a/app/models/design_management/design.rb +++ b/app/models/design_management/design.rb @@ -47,7 +47,7 @@ module DesignManagement # Pre-fetching scope to include the data necessary to construct a # reference using `to_reference`. - scope :for_reference, -> { includes(issue: [{ project: [:route, :namespace] }]) } + scope :for_reference, -> { includes(issue: [{ namespace: :project }, { project: [:route, :namespace] }]) } # A design can be uniquely identified by issue_id and filename # Takes one or more sets of composite IDs of the form: @@ -178,7 +178,7 @@ module DesignManagement (?<url_filename> #{valid_char}+ \. #{ext}) }x - super(path_segment, filename_pattern) + compose_link_reference_pattern(path_segment, filename_pattern) end end diff --git a/app/models/group.rb b/app/models/group.rb index 0dcc35b2a39..732e082c3af 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -7,7 +7,6 @@ class Group < Namespace include AfterCommitQueue include AccessRequestable include Avatarable - include Referable include SelectForProjectAuthorization include LoadedInGroupList include GroupDescendant @@ -242,14 +241,6 @@ class Group < Namespace end end - def reference_prefix - User.reference_prefix - end - - def reference_pattern - User.reference_pattern - end - # WARNING: This method should never be used on its own # please do make sure the number of rows you are filtering is small # enough for this query @@ -366,10 +357,6 @@ class Group < Namespace notification_settings.find { |n| n.notification_email.present? }&.notification_email end - def to_reference(_from = nil, target_project: nil, full: nil) - "#{self.class.reference_prefix}#{full_path}" - end - def web_url(only_path: nil) Gitlab::UrlBuilder.build(self, only_path: only_path) end diff --git a/app/models/issue.rb b/app/models/issue.rb index 7fdededb8aa..352aa89b4c8 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -163,15 +163,15 @@ class Issue < ApplicationRecord scope :order_closed_at_desc, -> { reorder(arel_table[:closed_at].desc.nulls_last) } scope :preload_associated_models, -> { preload(:assignees, :labels, project: :namespace) } - scope :with_web_entity_associations, -> { preload(:author, project: [:project_feature, :route, namespace: :route]) } + scope :with_web_entity_associations, -> { preload(:author, :namespace, project: [:project_feature, :route, namespace: :route]) } scope :preload_awardable, -> { preload(:award_emoji) } scope :with_alert_management_alerts, -> { joins(:alert_management_alert) } scope :with_prometheus_alert_events, -> { joins(:issues_prometheus_alert_events) } scope :with_self_managed_prometheus_alert_events, -> { joins(:issues_self_managed_prometheus_alert_events) } scope :with_api_entity_associations, -> { - preload(:timelogs, :closed_by, :assignees, :author, :labels, :issuable_severity, + preload(:timelogs, :closed_by, :assignees, :author, :labels, :issuable_severity, namespace: [{ parent: :route }, :route], milestone: { project: [:route, { namespace: :route }] }, - project: [:project_feature, :route, { namespace: :route }], + project: [:project_namespace, :project_feature, :route, { group: :route }, { namespace: :route }], duplicated_to: { project: [:project_feature] }) } scope :with_issue_type, ->(types) { where(issue_type: types) } @@ -346,7 +346,7 @@ class Issue < ApplicationRecord end def self.link_reference_pattern - @link_reference_pattern ||= super(%r{issues(?:\/incident)?}, Gitlab::Regex.issue) + @link_reference_pattern ||= compose_link_reference_pattern(%r{issues(?:\/incident)?}, Gitlab::Regex.issue) end def self.reference_valid?(reference) @@ -451,7 +451,7 @@ class Issue < ApplicationRecord def to_reference(from = nil, full: false) reference = "#{self.class.reference_prefix}#{iid}" - "#{project.to_reference_base(from, full: full)}#{reference}" + "#{namespace.to_reference_base(from, full: full)}#{reference}" end def suggested_branch_name diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index e59393f7533..dd810b924a1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -563,7 +563,7 @@ class MergeRequest < ApplicationRecord end def self.link_reference_pattern - @link_reference_pattern ||= super("merge_requests", Gitlab::Regex.merge_request) + @link_reference_pattern ||= compose_link_reference_pattern('merge_requests', Gitlab::Regex.merge_request) end def self.reference_valid?(reference) diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 8ae1fab963f..10d70eaa24e 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -114,7 +114,7 @@ class Milestone < ApplicationRecord end def self.link_reference_pattern - @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/) + @link_reference_pattern ||= compose_link_reference_pattern('milestones', /(?<milestone>\d+)/) end def self.upcoming_ids(projects, groups) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index cfe5f927e33..13466f147bd 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -16,6 +16,7 @@ class Namespace < ApplicationRecord include EachBatch include BlocksUnsafeSerialization include Ci::NamespaceSettings + include Referable # Tells ActiveRecord not to store the full class name, in order to save some space # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/69794 @@ -266,6 +267,23 @@ class Namespace < ApplicationRecord def top_most by_parent(nil) end + + def reference_prefix + User.reference_prefix + end + + def reference_pattern + User.reference_pattern + end + end + + def to_reference_base(from = nil, full: false) + return full_path if full || cross_namespace_reference?(from) + return path if cross_project_reference?(from) + end + + def to_reference(*) + "#{self.class.reference_prefix}#{full_path}" end def container_registry_namespace_path_validation @@ -619,6 +637,33 @@ class Namespace < ApplicationRecord private + def cross_namespace_reference?(from) + return false if from == self + + comparable_namespace_id = project_namespace? ? parent_id : id + + case from + when Project + from.namespace_id != comparable_namespace_id + when Namespaces::ProjectNamespace + from.parent_id != comparable_namespace_id + when Namespace + parent != from + when User + true + end + end + + # Check if a reference is being done cross-project + def cross_project_reference?(from) + case from + when Project + from.project_namespace_id != id + else + from && self != from + end + end + def update_new_emails_created_column return if namespace_settings.nil? return if namespace_settings.emails_enabled == !emails_disabled diff --git a/app/models/operations/feature_flag.rb b/app/models/operations/feature_flag.rb index 0df8c87f73f..6876af09c2c 100644 --- a/app/models/operations/feature_flag.rb +++ b/app/models/operations/feature_flag.rb @@ -72,7 +72,7 @@ module Operations end def link_reference_pattern - @link_reference_pattern ||= super("feature_flags", %r{(?<feature_flag>\d+)/edit}) + @link_reference_pattern ||= compose_link_reference_pattern('feature_flags', %r{(?<feature_flag>\d+)/edit}) end def reference_postfix diff --git a/app/models/project.rb b/app/models/project.rb index e2f5e51453d..21f81f70f18 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -3243,6 +3243,8 @@ class Project < ApplicationRecord case from when Project namespace_id != from.namespace_id + when Namespaces::ProjectNamespace + namespace_id != from.parent_id when Namespace namespace != from when User @@ -3252,9 +3254,14 @@ class Project < ApplicationRecord # Check if a reference is being done cross-project def cross_project_reference?(from) - return true if from.is_a?(Namespace) - - from && self != from + case from + when Namespaces::ProjectNamespace + project_namespace_id != from.id + when Namespace + true + else + from && self != from + end end def update_project_statistics diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 9ec685c5580..66d5c3ac801 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -183,7 +183,7 @@ class Snippet < ApplicationRecord end def link_reference_pattern - @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/) + @link_reference_pattern ||= compose_link_reference_pattern('snippets', /(?<snippet>\d+)/) end def find_by_id_and_project(id:, project:) diff --git a/app/services/groups/autocomplete_service.rb b/app/services/groups/autocomplete_service.rb index 92b05d9ac08..5b9d60495e9 100644 --- a/app/services/groups/autocomplete_service.rb +++ b/app/services/groups/autocomplete_service.rb @@ -13,7 +13,7 @@ module Groups IssuesFinder.new(current_user, finder_params) .execute .preload(project: :namespace) - .select(:iid, :title, :project_id) + .select(:iid, :title, :project_id, :namespace_id) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/services/personal_access_tokens/create_service.rb b/app/services/personal_access_tokens/create_service.rb index e2f2e220750..adb7924f35e 100644 --- a/app/services/personal_access_tokens/create_service.rb +++ b/app/services/personal_access_tokens/create_service.rb @@ -2,11 +2,12 @@ module PersonalAccessTokens class CreateService < BaseService - def initialize(current_user:, target_user:, params: {}) + def initialize(current_user:, target_user:, params: {}, concatenate_errors: true) @current_user = current_user @target_user = target_user @params = params.dup @ip_address = @params.delete(:ip_address) + @concatenate_errors = concatenate_errors end def execute @@ -19,7 +20,10 @@ module PersonalAccessTokens notification_service.access_token_created(target_user, token.name) ServiceResponse.success(payload: { personal_access_token: token }) else - ServiceResponse.error(message: token.errors.full_messages.to_sentence, payload: { personal_access_token: token }) + message = token.errors.full_messages + message = message.to_sentence if @concatenate_errors + + ServiceResponse.error(message: message, payload: { personal_access_token: token }) end end diff --git a/app/views/admin/projects/_form.html.haml b/app/views/admin/projects/_form.html.haml new file mode 100644 index 00000000000..18bef523168 --- /dev/null +++ b/app/views/admin/projects/_form.html.haml @@ -0,0 +1,23 @@ += gitlab_ui_form_for [:admin, @project] do |f| + = form_errors(@project) + = render ::Layouts::HorizontalSectionComponent.new(options: { class: 'gl-pb-3 gl-mb-6' }) do |c| + = c.title { _('Naming') } + = c.description do + = _('Update your project name and description.') + = c.body do + .form-group.gl-form-group + = f.label :name, _('Project name') + = f.text_field :name, class: 'form-control gl-form-input gl-md-form-input-md' + + .form-group.gl-form-group + = f.label :id, _('Project ID') + = f.text_field :id, class: 'form-control gl-form-input gl-md-form-input-sm', readonly: true + + .form-group.gl-form-group + = f.label :description, _('Project description (optional)') + = f.text_area :description, class: 'form-control gl-form-input gl-form-textarea gl-lg-form-input-xl', rows: 5 + + .gl-mt-5 + = f.submit _('Save changes'), pajamas_button: true + = render Pajamas::ButtonComponent.new(href: admin_project_path(@project)) do + = _('Cancel') diff --git a/app/views/admin/projects/_projects.html.haml b/app/views/admin/projects/_projects.html.haml index cf1bd2a8022..72dcd604f06 100644 --- a/app/views/admin/projects/_projects.html.haml +++ b/app/views/admin/projects/_projects.html.haml @@ -24,7 +24,7 @@ = render_if_exists 'admin/projects/archived', project: project .controls.gl-flex-shrink-0.gl-ml-5 - = render Pajamas::ButtonComponent.new(href: edit_project_path(project), button_options: { id: dom_id(project, :edit) }) do + = render Pajamas::ButtonComponent.new(href: edit_admin_namespace_project_path({ id: project.to_param, namespace_id: project.namespace.to_param }), button_options: { id: dom_id(project) }) do = _('Edit') = render Pajamas::ButtonComponent.new(variant: :danger, button_options: { class: 'delete-project-button', data: { delete_project_url: admin_project_path(project), project_name: project.name } }) do = s_('AdminProjects|Delete') diff --git a/app/views/admin/projects/edit.html.haml b/app/views/admin/projects/edit.html.haml new file mode 100644 index 00000000000..ade0f543d58 --- /dev/null +++ b/app/views/admin/projects/edit.html.haml @@ -0,0 +1,4 @@ +- page_title _("Edit"), @project.name, _("Projects") +%h1.page-title.gl-font-size-h-display= _('Edit project: %{project_name}') % { project_name: @project.name } +%hr += render 'form' diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 464027e73f4..2803bec49c3 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -6,7 +6,9 @@ %h1.page-title.gl-font-size-h-display = _('Project: %{name}') % { name: @project.full_name } - = render Pajamas::ButtonComponent.new(href: edit_project_path(@project), icon: 'pencil', button_options: { class: 'gl-float-right' }) do + = render Pajamas::ButtonComponent.new(href: edit_admin_namespace_project_path({ id: @project.to_param, namespace_id: @project.namespace.to_param }), + icon: 'pencil', + button_options: { class: 'gl-float-right'}) do = _('Edit') %hr - if @project.last_repository_check_failed? diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml index 61c9b130da8..7d10bc77126 100644 --- a/app/views/notify/merge_request_status_email.text.haml +++ b/app/views/notify/merge_request_status_email.text.haml @@ -1,4 +1,4 @@ -= sprintf(s_('Notify|Merge request %{merge_request} was %{mr_status}'), { merge_request: @merge_request.to_reference, mr_status: sanitize_name(@updated_by.name) }) += sprintf(s_('Notify|Merge request %{merge_request} was %{mr_status} by %{updated_by}'), { merge_request: @merge_request.to_reference, mr_status: @mr_status, updated_by: sanitize_name(@updated_by.name) }) = sprintf(s_('Notify|Merge request URL: %{merge_request_url}'), { merge_request_url: project_merge_request_url(@merge_request.target_project, @merge_request) }) diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 70d02609523..85c7951e1ea 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -123,6 +123,10 @@ namespace :admin do member do put :transfer post :repository_check + get :edit, action: :edit + get '/', action: :show + patch '/', action: :update + put '/', action: :update end resources :runner_projects, only: [:create, :destroy] diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 2457207823b..ed9404187ae 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -68,7 +68,7 @@ When a suitable [domain expert](#domain-experts) isn't available, you can choose To find a domain expert: -- In the Merge Request approvals widget, select [**View eligible approvers**](../user/project/merge_requests/approvals/rules.md#eligible-approvers). +- In the Merge Request approvals widget, select [View eligible approvers](../user/project/merge_requests/approvals/rules.md#eligible-approvers). This widget shows recommended and required approvals per area of the codebase. These rules are defined in [Code Owners](../user/project/merge_requests/approvals/rules.md#code-owners-as-eligible-approvers). - View the list of team members who work in the [stage or group](https://about.gitlab.com/handbook/product/categories/#devops-stages) related to the merge request. diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index 0375232334f..7227da3ce0d 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -86,6 +86,16 @@ project, the following information is listed: Projects can be edited or deleted. +To edit a project's name or description: + +1. In the Projects overview, next to the project you want to edit, select **Edit**. +1. Edit the **Project name** or **Project description**. +1. Select **Save Changes**. + +To delete a project: + +1. In the Projects overview, next to the project you want to delete, select **Delete**. + The list of projects can be sorted by: - Updated date diff --git a/doc/user/application_security/policies/img/scheduled_scan_execution_policies_diagram.png b/doc/user/application_security/policies/img/scheduled_scan_execution_policies_diagram.png Binary files differnew file mode 100644 index 00000000000..b183013ff7a --- /dev/null +++ b/doc/user/application_security/policies/img/scheduled_scan_execution_policies_diagram.png diff --git a/doc/user/application_security/policies/scan-execution-policies.md b/doc/user/application_security/policies/scan-execution-policies.md index 3b59fd46931..96048bb2308 100644 --- a/doc/user/application_security/policies/scan-execution-policies.md +++ b/doc/user/application_security/policies/scan-execution-policies.md @@ -88,7 +88,7 @@ This rule enforces the defined actions and schedules a scan on the provided date |------------|------|-----------------|-------------| | `type` | `string` | `schedule` | The rule's type. | | `branches` | `array` of `string` | `*` or the branch's name | The branch the given policy applies to (supports wildcard). This field is required if the `agents` field is not set. | -| `cadence` | `string` | CRON expression (for example, `0 0 * * *`) | A whitespace-separated string containing five fields that represents the scheduled time. | +| `cadence` | `string` | CRON expression (for example, `0 0 * * *`) | A whitespace-separated string containing five fields that represents the scheduled time. Minimum of 15 minute intervals when used together with the `branches` field. | | `agents` | `object` | | The name of the [GitLab agents](../../clusters/agent/index.md) where [Operational Container Scanning](../../clusters/agent/vulnerabilities.md) runs. The object key is the name of the Kubernetes agent configured for your project in GitLab. This field is required if the `branches` field is not set. | GitLab supports the following types of CRON syntax for the `cadence` field: @@ -99,8 +99,18 @@ GitLab supports the following types of CRON syntax for the `cadence` field: NOTE: Other elements of the [CRON syntax](https://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm) may work in the cadence field if supported by the [cron](https://github.com/robfig/cron) we are using in our implementation, however, GitLab does not officially test or support them. -NOTE: -If using the `agents` field, required for `Operational Container Scanning`, the CRON expression is evaluated in [UTC](https://www.timeanddate.com/worldclock/timezone/utc) using the system-time of the Kubernetes-agent pod. If not using the `agents` field, the CRON expression is evaluated in standard [UTC](https://www.timeanddate.com/worldclock/timezone/utc) time from GitLab.com. If you have a self-managed GitLab instance and have [changed the server time zone](../../../administration/timezone.md), the CRON expression is evaluated with the new time zone. +When using the `schedule` rule type in conjunction with the `agents` field, note the following: + +- The GitLab Agent for Kubernetes checks every 30 seconds to see if there is an applicable policy. When a policy is found, the scans are executed according to the `cadence` defined. +- The CRON expression is evaluated using the system-time of the Kubernetes-agent pod. + +When using the `schedule` rule type in conjunction with the `branches` field, note the following: + +- The cron worker runs on 15 minute intervals and starts any pipelines that were scheduled to run during the previous 15 minutes. +- Based on your rule, you might expect scheduled pipelines to run with an offset of up to 15 minutes. +- The CRON expression is evaluated in standard [UTC](https://www.timeanddate.com/worldclock/timezone/utc) time from GitLab.com. If you have a self-managed GitLab instance and have [changed the server time zone](../../../administration/timezone.md), the CRON expression is evaluated with the new time zone. + +![CRON worker diagram](img/scheduled_scan_execution_policies_diagram.png) ### `agent` schema diff --git a/doc/user/compliance/compliance_report/index.md b/doc/user/compliance/compliance_report/index.md index 6415cdeaf05..0d794b843af 100644 --- a/doc/user/compliance/compliance_report/index.md +++ b/doc/user/compliance/compliance_report/index.md @@ -77,7 +77,7 @@ From [GitLab 14.10](https://gitlab.com/groups/gitlab-org/-/epics/6870), these ar | Committers approved merge request | High | [Separation of duties](#separation-of-duties) | Committers of the merge request approved the merge request they contributed to. For more information, see [Prevent approvals by users who add commits](../../project/merge_requests/approvals/settings.md#prevent-approvals-by-users-who-add-commits). | | Fewer than two approvals | High | [Separation of duties](#separation-of-duties) | Merge request was merged with fewer than two approvals. For more information, see [Merge request approval rules](../../project/merge_requests/approvals/rules.md). | -The following are unavailable compliance violations that are tracked in [issue 346011](https://gitlab.com/gitlab-org/gitlab/-/issues/346011). +The following are unavailable compliance violations that are tracked in [epic 5237](https://gitlab.com/groups/gitlab-org/-/epics/5237). <!-- vale gitlab.SubstitutionWarning = NO --> diff --git a/lib/banzai/filter/references/design_reference_filter.rb b/lib/banzai/filter/references/design_reference_filter.rb index 01e1036dcec..16a2a2835e9 100644 --- a/lib/banzai/filter/references/design_reference_filter.rb +++ b/lib/banzai/filter/references/design_reference_filter.rb @@ -43,7 +43,7 @@ module Banzai return [] unless project.design_management_enabled? iids = identifiers.map(&:issue_iid).to_set - issues = project.issues.where(iid: iids) + issues = project.issues.where(iid: iids).includes(:project, :namespace) id_for_iid = issues.index_by(&:iid).transform_values(&:id) issue_by_id = issues.index_by(&:id) diff --git a/lib/banzai/filter/references/issue_reference_filter.rb b/lib/banzai/filter/references/issue_reference_filter.rb index b536d900a02..e186c8b1f73 100644 --- a/lib/banzai/filter/references/issue_reference_filter.rb +++ b/lib/banzai/filter/references/issue_reference_filter.rb @@ -22,7 +22,7 @@ module Banzai end def parent_records(parent, ids) - parent.issues.where(iid: ids.to_a) + parent.issues.where(iid: ids.to_a).includes(:project, :namespace) end def object_link_text_extras(issue, matches) diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb index ecf77191c13..a5862fbaac4 100644 --- a/lib/banzai/reference_parser/issue_parser.rb +++ b/lib/banzai/reference_parser/issue_parser.rb @@ -58,6 +58,7 @@ module Banzai def records_for_nodes(nodes) node_includes = [ + :namespace, :author, :assignees, { diff --git a/lib/gitlab/github_import/importer/events/cross_referenced.rb b/lib/gitlab/github_import/importer/events/cross_referenced.rb index b56ae186d3c..4fe371e5900 100644 --- a/lib/gitlab/github_import/importer/events/cross_referenced.rb +++ b/lib/gitlab/github_import/importer/events/cross_referenced.rb @@ -55,6 +55,7 @@ module Gitlab record = record_class.new(id: db_id, iid: iid) record.project = project + record.namespace = project.project_namespace if record.respond_to?(:namespace) record.readonly! record end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index e7954873267..29eb4f9dd47 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -479,6 +479,9 @@ msgstr[1] "" msgid "%{actionText} %{actionDetail}" msgstr "" +msgid "%{actionText} %{actionDetail} %{timeago} by %{author}" +msgstr "" + msgid "%{actionText} & %{openOrClose} %{noteable}" msgstr "" @@ -488,9 +491,6 @@ msgstr "" msgid "%{actionText} & reopen %{noteable}" msgstr "" -msgid "%{actionText} by %{author} %{actionDetail}" -msgstr "" - msgid "%{address} is an invalid IP address range" msgstr "" @@ -15388,6 +15388,9 @@ msgstr "" msgid "Edit merge requests" msgstr "" +msgid "Edit project: %{project_name}" +msgstr "" + msgid "Edit public deploy key" msgstr "" @@ -28037,6 +28040,9 @@ msgstr "" msgid "Namespaces to index" msgstr "" +msgid "Naming" +msgstr "" + msgid "Naming, topics, avatar" msgstr "" @@ -29138,9 +29144,6 @@ msgstr "" msgid "Notify|Merge request %{merge_request} can no longer be merged due to conflict." msgstr "" -msgid "Notify|Merge request %{merge_request} was %{mr_status}" -msgstr "" - msgid "Notify|Merge request %{merge_request} was %{mr_status} by %{updated_by}" msgstr "" @@ -41517,6 +41520,9 @@ msgstr "" msgid "StatusCheck|Service name" msgstr "" +msgid "StatusCheck|Status Check ID" +msgstr "" + msgid "StatusCheck|Status checks" msgstr "" @@ -46166,6 +46172,9 @@ msgstr "" msgid "Update your group name, description, avatar, and visibility." msgstr "" +msgid "Update your project name and description." +msgstr "" + msgid "Update your project name, topics, description, and avatar." msgstr "" diff --git a/spec/factories/work_items.rb b/spec/factories/work_items.rb index cff246d4071..3cb4d8cd8bc 100644 --- a/spec/factories/work_items.rb +++ b/spec/factories/work_items.rb @@ -37,5 +37,12 @@ FactoryBot.define do issue_type { :key_result } association :work_item_type, :default, :key_result end + + before(:create, :build) do |work_item, evaluator| + if evaluator.namespace.present? + work_item.project = nil + work_item.namespace = evaluator.namespace + end + end end end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index f08e6521184..405a254dc84 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -161,4 +161,29 @@ RSpec.describe "Admin::Projects", feature_category: :projects do expect(page).to have_current_path(dashboard_projects_path, ignore_query: true, url: false) end end + + describe 'project edit' do + it 'updates project details' do + project = create(:project, :private, name: 'Garfield', description: 'Funny Cat') + + visit edit_admin_namespace_project_path({ id: project.to_param, namespace_id: project.namespace.to_param }) + + aggregate_failures do + expect(page).to have_content(project.name) + expect(page).to have_content(project.description) + end + + fill_in 'Project name', with: 'Scooby-Doo' + fill_in 'Project description (optional)', with: 'Funny Dog' + + click_button 'Save changes' + + visit edit_admin_namespace_project_path({ id: project.to_param, namespace_id: project.namespace.to_param }) + + aggregate_failures do + expect(page).to have_content('Scooby-Doo') + expect(page).to have_content('Funny Dog') + end + end + end end diff --git a/spec/finders/abuse_reports_finder_spec.rb b/spec/finders/abuse_reports_finder_spec.rb index 297f3487227..3dec6bf3eda 100644 --- a/spec/finders/abuse_reports_finder_spec.rb +++ b/spec/finders/abuse_reports_finder_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' RSpec.describe AbuseReportsFinder, '#execute' do + let_it_be(:user1) { create(:user) } + let_it_be(:user2) { create(:user) } + let_it_be(:abuse_report_1) { create(:abuse_report, category: 'spam', user: user1) } + let_it_be(:abuse_report_2) { create(:abuse_report, :closed, category: 'phishing', user: user2) } + let(:params) { {} } - let!(:user1) { create(:user) } - let!(:user2) { create(:user) } - let!(:abuse_report_1) { create(:abuse_report, category: 'spam', user: user1, reporter: user2) } - let!(:abuse_report_2) { create(:abuse_report, :closed, category: 'phishing', user: user2) } subject { described_class.new(params).execute } @@ -25,6 +26,27 @@ RSpec.describe AbuseReportsFinder, '#execute' do end end + context 'when params[:user] is present' do + let(:params) { { user: abuse_report_1.user.username } } + + it 'returns abuse reports for the specified user' do + expect(subject).to match_array([abuse_report_1]) + end + + context 'when no user has username = params[:user]' do + before do + allow(User).to receive_message_chain(:by_username, :pick) + .with(params[:user]) + .with(:id) + .and_return(nil) + end + + it 'returns all abuse reports' do + expect(subject).to match_array([abuse_report_1, abuse_report_2]) + end + end + end + context 'when params[:status] is present' do context 'when value is "open"' do let(:params) { { status: 'open' } } diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index d5fff7f3a31..fd10b204e50 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -293,10 +293,13 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do end describe '#issuable_reference' do + let(:project_namespace) { build_stubbed(:project_namespace) } + let(:project) { build_stubbed(:project, project_namespace: project_namespace) } + context 'when show_full_reference truthy' do it 'display issuable full reference' do assign(:show_full_reference, true) - issue = build_stubbed(:issue) + issue = build_stubbed(:issue, project: project) expect(helper.issuable_reference(issue)).to eql(issue.to_reference(full: true)) end @@ -305,12 +308,10 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do context 'when show_full_reference falsey' do context 'when @group present' do it 'display issuable reference to @group' do - project = build_stubbed(:project) - assign(:show_full_reference, nil) assign(:group, project.namespace) - issue = build_stubbed(:issue) + issue = build_stubbed(:issue, project: project) expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project.namespace)) end @@ -318,13 +319,11 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do context 'when @project present' do it 'display issuable reference to @project' do - project = build_stubbed(:project) - assign(:show_full_reference, nil) assign(:group, nil) assign(:project, project) - issue = build_stubbed(:issue) + issue = build_stubbed(:issue, project: project) expect(helper.issuable_reference(issue)).to eql(issue.to_reference(project)) end @@ -333,8 +332,11 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do end describe '#issuable_project_reference' do + let(:project_namespace) { build_stubbed(:project_namespace) } + let(:project) { build_stubbed(:project, project_namespace: project_namespace) } + it 'display project name and simple reference with `#` to an issue' do - issue = build_stubbed(:issue) + issue = build_stubbed(:issue, project: project) expect(helper.issuable_project_reference(issue)).to eq("#{issue.project.full_name} ##{issue.iid}") end diff --git a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb index cc3918b9678..80061539a0b 100644 --- a/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb +++ b/spec/lib/banzai/filter/issuable_reference_expansion_filter_spec.rb @@ -202,7 +202,7 @@ RSpec.describe Banzai::Filter::IssuableReferenceExpansionFilter, feature_categor filter(link, context) end.count - expect(control_count).to eq 9 + expect(control_count).to eq 10 expect do filter("#{link} #{link2}", context) diff --git a/spec/lib/banzai/filter/references/reference_cache_spec.rb b/spec/lib/banzai/filter/references/reference_cache_spec.rb index 7307daca516..7e5ca00f118 100644 --- a/spec/lib/banzai/filter/references/reference_cache_spec.rb +++ b/spec/lib/banzai/filter/references/reference_cache_spec.rb @@ -76,8 +76,7 @@ RSpec.describe Banzai::Filter::References::ReferenceCache, feature_category: :te cache_single.load_records_per_parent end.count - expect(control_count).to eq 1 - + expect(control_count).to eq 2 # Since this is an issue filter that is not batching issue queries # across projects, we have to account for that. # 1 for original issue, 2 for second route/project, 1 for other issue diff --git a/spec/lib/gitlab/issuable_sorter_spec.rb b/spec/lib/gitlab/issuable_sorter_spec.rb index b8d0c7b0609..0d9940bab6f 100644 --- a/spec/lib/gitlab/issuable_sorter_spec.rb +++ b/spec/lib/gitlab/issuable_sorter_spec.rb @@ -4,16 +4,42 @@ require 'spec_helper' RSpec.describe Gitlab::IssuableSorter do let(:namespace1) { build_stubbed(:namespace, id: 1) } - let(:project1) { build_stubbed(:project, id: 1, namespace: namespace1) } - - let(:project2) { build_stubbed(:project, id: 2, path: "a", namespace: project1.namespace) } - let(:project3) { build_stubbed(:project, id: 3, path: "b", namespace: project1.namespace) } - let(:namespace2) { build_stubbed(:namespace, id: 2, path: "a") } let(:namespace3) { build_stubbed(:namespace, id: 3, path: "b") } - let(:project4) { build_stubbed(:project, id: 4, path: "a", namespace: namespace2) } - let(:project5) { build_stubbed(:project, id: 5, path: "b", namespace: namespace2) } - let(:project6) { build_stubbed(:project, id: 6, path: "a", namespace: namespace3) } + + let(:project1) do + build_stubbed(:project, id: 1, namespace: namespace1, project_namespace: build_stubbed(:project_namespace)) + end + + let(:project2) do + build_stubbed( + :project, id: 2, path: "a", namespace: project1.namespace, project_namespace: build_stubbed(:project_namespace) + ) + end + + let(:project3) do + build_stubbed( + :project, id: 3, path: "b", namespace: project1.namespace, project_namespace: build_stubbed(:project_namespace) + ) + end + + let(:project4) do + build_stubbed( + :project, id: 4, path: "a", namespace: namespace2, project_namespace: build_stubbed(:project_namespace) + ) + end + + let(:project5) do + build_stubbed( + :project, id: 5, path: "b", namespace: namespace2, project_namespace: build_stubbed(:project_namespace) + ) + end + + let(:project6) do + build_stubbed( + :project, id: 6, path: "a", namespace: namespace3, project_namespace: build_stubbed(:project_namespace) + ) + end let(:unsorted) { [sorted[2], sorted[3], sorted[0], sorted[1]] } diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb index 87a0a2a492c..30639f5a580 100644 --- a/spec/models/abuse_report_spec.rb +++ b/spec/models/abuse_report_spec.rb @@ -71,9 +71,8 @@ RSpec.describe AbuseReport, feature_category: :insider_threat do end describe 'scopes' do - let!(:reporter) { create(:user, username: 'reporter') } - let!(:report1) { create(:abuse_report) } - let!(:report2) { create(:abuse_report, :closed, reporter: reporter, category: 'phishing') } + let_it_be(:report1) { create(:abuse_report) } + let_it_be(:report2) { create(:abuse_report, :closed, category: 'phishing') } describe '.open' do subject(:results) { described_class.open } diff --git a/spec/models/design_management/design_spec.rb b/spec/models/design_management/design_spec.rb index 5217b2a4114..cb122fa09fc 100644 --- a/spec/models/design_management/design_spec.rb +++ b/spec/models/design_management/design_spec.rb @@ -513,11 +513,11 @@ RSpec.describe DesignManagement::Design, feature_category: :design_management do end describe '#to_reference' do + let(:filename) { 'homescreen.jpg' } let(:namespace) { build(:namespace, id: non_existing_record_id, path: 'sample-namespace') } let(:project) { build(:project, name: 'sample-project', namespace: namespace) } - let(:group) { create(:group, name: 'Group', path: 'sample-group') } + let(:group) { build(:group, name: 'Group', path: 'sample-group') } let(:issue) { build(:issue, iid: 1, project: project) } - let(:filename) { 'homescreen.jpg' } let(:design) { build(:design, filename: filename, issue: issue, project: project) } context 'when nil argument' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 6e54ac3bd3f..a61a3c5e2ab 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -580,38 +580,38 @@ RSpec.describe Issue, feature_category: :team_planning do end describe '#to_reference' do - let(:namespace) { build(:namespace, path: 'sample-namespace') } - let(:project) { build(:project, name: 'sample-project', namespace: namespace) } - let(:issue) { build(:issue, iid: 1, project: project) } + let_it_be(:namespace) { create(:namespace, path: 'sample-namespace') } + let_it_be(:project) { create(:project, name: 'sample-project', namespace: namespace) } + let_it_be(:issue) { create(:issue, project: project) } context 'when nil argument' do it 'returns issue id' do - expect(issue.to_reference).to eq "#1" + expect(issue.to_reference).to eq "##{issue.iid}" end it 'returns complete path to the issue with full: true' do - expect(issue.to_reference(full: true)).to eq 'sample-namespace/sample-project#1' + expect(issue.to_reference(full: true)).to eq "#{project.full_path}##{issue.iid}" end end context 'when argument is a project' do context 'when same project' do it 'returns issue id' do - expect(issue.to_reference(project)).to eq("#1") + expect(issue.to_reference(project)).to eq("##{issue.iid}") end it 'returns full reference with full: true' do - expect(issue.to_reference(project, full: true)).to eq 'sample-namespace/sample-project#1' + expect(issue.to_reference(project, full: true)).to eq "#{project.full_path}##{issue.iid}" end end context 'when cross-project in same namespace' do let(:another_project) do - build(:project, name: 'another-project', namespace: project.namespace) + create(:project, name: 'another-project', namespace: project.namespace) end it 'returns a cross-project reference' do - expect(issue.to_reference(another_project)).to eq "sample-project#1" + expect(issue.to_reference(another_project)).to eq "sample-project##{issue.iid}" end end @@ -620,7 +620,7 @@ RSpec.describe Issue, feature_category: :team_planning do let(:another_namespace_project) { build(:project, path: 'another-project', namespace: another_namespace) } it 'returns complete path to the issue' do - expect(issue.to_reference(another_namespace_project)).to eq 'sample-namespace/sample-project#1' + expect(issue.to_reference(another_namespace_project)).to eq "#{project.full_path}##{issue.iid}" end end end @@ -628,11 +628,11 @@ RSpec.describe Issue, feature_category: :team_planning do context 'when argument is a namespace' do context 'when same as issue' do it 'returns path to the issue with the project name' do - expect(issue.to_reference(namespace)).to eq 'sample-project#1' + expect(issue.to_reference(namespace)).to eq "sample-project##{issue.iid}" end it 'returns full reference with full: true' do - expect(issue.to_reference(namespace, full: true)).to eq 'sample-namespace/sample-project#1' + expect(issue.to_reference(namespace, full: true)).to eq "#{project.full_path}##{issue.iid}" end end @@ -640,12 +640,111 @@ RSpec.describe Issue, feature_category: :team_planning do let(:group) { build(:group, name: 'Group', path: 'sample-group') } it 'returns full path to the issue with full: true' do - expect(issue.to_reference(group)).to eq 'sample-namespace/sample-project#1' + expect(issue.to_reference(group)).to eq "#{project.full_path}##{issue.iid}" end end end end + describe '#to_reference with table syntax' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:user_namespace) { user.namespace } + + let_it_be(:parent) { create(:group) } + let_it_be(:group) { create(:group, parent: parent) } + let_it_be(:another_group) { create(:group) } + + let_it_be(:project) { create(:project, namespace: group) } + let_it_be(:project_namespace) { project.project_namespace } + let_it_be(:same_namespace_project) { create(:project, namespace: group) } + let_it_be(:same_namespace_project_namespace) { same_namespace_project.project_namespace } + + let_it_be(:another_namespace_project) { create(:project) } + let_it_be(:another_namespace_project_namespace) { another_namespace_project.project_namespace } + + let_it_be(:project_issue) { build(:issue, project: project, iid: 123) } + let_it_be(:project_issue_full_reference) { "#{project.full_path}##{project_issue.iid}" } + + let_it_be(:group_issue) { build(:issue, namespace: group, iid: 123) } + let_it_be(:group_issue_full_reference) { "#{group.full_path}##{group_issue.iid}" } + + # this one is just theoretically possible, not smth to be supported for real + let_it_be(:user_issue) { build(:issue, namespace: user_namespace, iid: 123) } + let_it_be(:user_issue_full_reference) { "#{user_namespace.full_path}##{user_issue.iid}" } + + # namespace would be group, project namespace or user namespace + where(:issue, :full, :from, :result) do + ref(:project_issue) | false | nil | lazy { "##{issue.iid}" } + ref(:project_issue) | true | nil | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:group) | lazy { "#{project.path}##{issue.iid}" } + ref(:project_issue) | true | ref(:group) | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:parent) | ref(:project_issue_full_reference) + ref(:project_issue) | true | ref(:parent) | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:project) | lazy { "##{issue.iid}" } + ref(:project_issue) | true | ref(:project) | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:project_namespace) | lazy { "##{issue.iid}" } + ref(:project_issue) | true | ref(:project_namespace) | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:same_namespace_project) | lazy { "#{project.path}##{issue.iid}" } + ref(:project_issue) | true | ref(:same_namespace_project) | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:same_namespace_project_namespace) | lazy { "#{project.path}##{issue.iid}" } + ref(:project_issue) | true | ref(:same_namespace_project_namespace) | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:another_group) | ref(:project_issue_full_reference) + ref(:project_issue) | true | ref(:another_group) | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:another_namespace_project) | ref(:project_issue_full_reference) + ref(:project_issue) | true | ref(:another_namespace_project) | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:another_namespace_project_namespace) | ref(:project_issue_full_reference) + ref(:project_issue) | true | ref(:another_namespace_project_namespace) | ref(:project_issue_full_reference) + ref(:project_issue) | false | ref(:user_namespace) | ref(:project_issue_full_reference) + ref(:project_issue) | true | ref(:user_namespace) | ref(:project_issue_full_reference) + + ref(:group_issue) | false | nil | lazy { "##{issue.iid}" } + ref(:group_issue) | true | nil | ref(:group_issue_full_reference) + ref(:group_issue) | false | ref(:user_namespace) | ref(:group_issue_full_reference) + ref(:group_issue) | true | ref(:user_namespace) | ref(:group_issue_full_reference) + ref(:group_issue) | false | ref(:group) | lazy { "##{issue.iid}" } + ref(:group_issue) | true | ref(:group) | ref(:group_issue_full_reference) + ref(:group_issue) | false | ref(:parent) | lazy { "#{group.path}##{issue.iid}" } + ref(:group_issue) | true | ref(:parent) | ref(:group_issue_full_reference) + ref(:group_issue) | false | ref(:project) | lazy { "#{group.path}##{issue.iid}" } + ref(:group_issue) | true | ref(:project) | ref(:group_issue_full_reference) + ref(:group_issue) | false | ref(:project_namespace) | lazy { "#{group.path}##{issue.iid}" } + ref(:group_issue) | true | ref(:project_namespace) | ref(:group_issue_full_reference) + ref(:group_issue) | false | ref(:another_group) | ref(:group_issue_full_reference) + ref(:group_issue) | true | ref(:another_group) | ref(:group_issue_full_reference) + ref(:group_issue) | false | ref(:another_namespace_project) | ref(:group_issue_full_reference) + ref(:group_issue) | true | ref(:another_namespace_project) | ref(:group_issue_full_reference) + ref(:group_issue) | false | ref(:another_namespace_project_namespace) | ref(:group_issue_full_reference) + ref(:group_issue) | true | ref(:another_namespace_project_namespace) | ref(:group_issue_full_reference) + + ref(:user_issue) | false | nil | lazy { "##{issue.iid}" } + ref(:user_issue) | true | nil | ref(:user_issue_full_reference) + ref(:user_issue) | false | ref(:user_namespace) | lazy { "##{issue.iid}" } + ref(:user_issue) | true | ref(:user_namespace) | ref(:user_issue_full_reference) + ref(:user_issue) | false | ref(:group) | ref(:user_issue_full_reference) + ref(:user_issue) | true | ref(:group) | ref(:user_issue_full_reference) + ref(:user_issue) | false | ref(:parent) | ref(:user_issue_full_reference) + ref(:user_issue) | true | ref(:parent) | ref(:user_issue_full_reference) + ref(:user_issue) | false | ref(:project) | ref(:user_issue_full_reference) + ref(:user_issue) | true | ref(:project) | ref(:user_issue_full_reference) + ref(:user_issue) | false | ref(:project_namespace) | ref(:user_issue_full_reference) + ref(:user_issue) | true | ref(:project_namespace) | ref(:user_issue_full_reference) + ref(:user_issue) | false | ref(:another_group) | ref(:user_issue_full_reference) + ref(:user_issue) | true | ref(:another_group) | ref(:user_issue_full_reference) + ref(:user_issue) | false | ref(:another_namespace_project) | ref(:user_issue_full_reference) + ref(:user_issue) | true | ref(:another_namespace_project) | ref(:user_issue_full_reference) + ref(:user_issue) | false | ref(:another_namespace_project_namespace) | ref(:user_issue_full_reference) + ref(:user_issue) | true | ref(:another_namespace_project_namespace) | ref(:user_issue_full_reference) + end + + with_them do + it 'returns correct reference' do + expect(issue.to_reference(from, full: full)).to eq(result) + end + end + end + describe '#assignee_or_author?' do let(:issue) { create(:issue, project: reusable_project) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 2d6ddd74dfd..0553a7d7152 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -266,6 +266,117 @@ RSpec.describe Namespace, feature_category: :subgroups do end end + describe "ReferencePatternValidation" do + subject { described_class.reference_pattern } + + it { is_expected.to match("@group1") } + it { is_expected.to match("@group1/group2/group3") } + it { is_expected.to match("@1234/1234/1234") } + it { is_expected.to match("@.q-w_e") } + end + + describe '#to_reference_base' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:user_namespace) { user.namespace } + + let_it_be(:parent) { create(:group) } + let_it_be(:group) { create(:group, parent: parent) } + let_it_be(:another_group) { create(:group) } + + let_it_be(:project) { create(:project, namespace: group) } + let_it_be(:project_namespace) { project.project_namespace } + + let_it_be(:another_namespace_project) { create(:project) } + let_it_be(:another_namespace_project_namespace) { another_namespace_project.project_namespace } + + # testing references with namespace being: group, project namespace and user namespace + where(:namespace, :full, :from, :result) do + ref(:parent) | false | nil | nil + ref(:parent) | true | nil | lazy { parent.full_path } + ref(:parent) | false | ref(:group) | lazy { parent.path } + ref(:parent) | true | ref(:group) | lazy { parent.full_path } + ref(:parent) | false | ref(:parent) | nil + ref(:parent) | true | ref(:parent) | lazy { parent.full_path } + ref(:parent) | false | ref(:project) | lazy { parent.path } + ref(:parent) | true | ref(:project) | lazy { parent.full_path } + ref(:parent) | false | ref(:project_namespace) | lazy { parent.path } + ref(:parent) | true | ref(:project_namespace) | lazy { parent.full_path } + ref(:parent) | false | ref(:another_group) | lazy { parent.full_path } + ref(:parent) | true | ref(:another_group) | lazy { parent.full_path } + ref(:parent) | false | ref(:another_namespace_project) | lazy { parent.full_path } + ref(:parent) | true | ref(:another_namespace_project) | lazy { parent.full_path } + ref(:parent) | false | ref(:another_namespace_project_namespace) | lazy { parent.full_path } + ref(:parent) | true | ref(:another_namespace_project_namespace) | lazy { parent.full_path } + ref(:parent) | false | ref(:user_namespace) | lazy { parent.full_path } + ref(:parent) | true | ref(:user_namespace) | lazy { parent.full_path } + + ref(:group) | false | nil | nil + ref(:group) | true | nil | lazy { group.full_path } + ref(:group) | false | ref(:group) | nil + ref(:group) | true | ref(:group) | lazy { group.full_path } + ref(:group) | false | ref(:parent) | lazy { group.path } + ref(:group) | true | ref(:parent) | lazy { group.full_path } + ref(:group) | false | ref(:project) | lazy { group.path } + ref(:group) | true | ref(:project) | lazy { group.full_path } + ref(:group) | false | ref(:project_namespace) | lazy { group.path } + ref(:group) | true | ref(:project_namespace) | lazy { group.full_path } + ref(:group) | false | ref(:another_group) | lazy { group.full_path } + ref(:group) | true | ref(:another_group) | lazy { group.full_path } + ref(:group) | false | ref(:another_namespace_project) | lazy { group.full_path } + ref(:group) | true | ref(:another_namespace_project) | lazy { group.full_path } + ref(:group) | false | ref(:another_namespace_project_namespace) | lazy { group.full_path } + ref(:group) | true | ref(:another_namespace_project_namespace) | lazy { group.full_path } + ref(:group) | false | ref(:user_namespace) | lazy { group.full_path } + ref(:group) | true | ref(:user_namespace) | lazy { group.full_path } + + ref(:project_namespace) | false | nil | nil + ref(:project_namespace) | true | nil | lazy { project_namespace.full_path } + ref(:project_namespace) | false | ref(:group) | lazy { project_namespace.path } + ref(:project_namespace) | true | ref(:group) | lazy { project_namespace.full_path } + ref(:project_namespace) | false | ref(:parent) | lazy { project_namespace.full_path } + ref(:project_namespace) | true | ref(:parent) | lazy { project_namespace.full_path } + ref(:project_namespace) | false | ref(:project) | nil + ref(:project_namespace) | true | ref(:project) | lazy { project_namespace.full_path } + ref(:project_namespace) | false | ref(:project_namespace) | nil + ref(:project_namespace) | true | ref(:project_namespace) | lazy { project_namespace.full_path } + ref(:project_namespace) | false | ref(:another_group) | lazy { project_namespace.full_path } + ref(:project_namespace) | true | ref(:another_group) | lazy { project_namespace.full_path } + ref(:project_namespace) | false | ref(:another_namespace_project) | lazy { project_namespace.full_path } + ref(:project_namespace) | true | ref(:another_namespace_project) | lazy { project_namespace.full_path } + ref(:project_namespace) | false | ref(:another_namespace_project_namespace) | lazy { project_namespace.full_path } + ref(:project_namespace) | true | ref(:another_namespace_project_namespace) | lazy { project_namespace.full_path } + ref(:project_namespace) | false | ref(:user_namespace) | lazy { project_namespace.full_path } + ref(:project_namespace) | true | ref(:user_namespace) | lazy { project_namespace.full_path } + + ref(:user_namespace) | false | nil | nil + ref(:user_namespace) | true | nil | lazy { user_namespace.full_path } + ref(:user_namespace) | false | ref(:user_namespace) | nil + ref(:user_namespace) | true | ref(:user_namespace) | lazy { user_namespace.full_path } + ref(:user_namespace) | false | ref(:group) | lazy { user_namespace.full_path } + ref(:user_namespace) | true | ref(:group) | lazy { user_namespace.full_path } + ref(:user_namespace) | false | ref(:parent) | lazy { user_namespace.full_path } + ref(:user_namespace) | true | ref(:parent) | lazy { user_namespace.full_path } + ref(:user_namespace) | false | ref(:project) | lazy { user_namespace.full_path } + ref(:user_namespace) | true | ref(:project) | lazy { user_namespace.full_path } + ref(:user_namespace) | false | ref(:project_namespace) | lazy { user_namespace.full_path } + ref(:user_namespace) | true | ref(:project_namespace) | lazy { user_namespace.full_path } + ref(:user_namespace) | false | ref(:another_group) | lazy { user_namespace.full_path } + ref(:user_namespace) | true | ref(:another_group) | lazy { user_namespace.full_path } + ref(:user_namespace) | false | ref(:another_namespace_project) | lazy { user_namespace.full_path } + ref(:user_namespace) | true | ref(:another_namespace_project) | lazy { user_namespace.full_path } + ref(:user_namespace) | false | ref(:another_namespace_project_namespace) | lazy { user_namespace.full_path } + ref(:user_namespace) | true | ref(:another_namespace_project_namespace) | lazy { user_namespace.full_path } + end + + with_them do + it 'returns correct path' do + expect(namespace.to_reference_base(from, full: full)).to eq(result) + end + end + end + describe 'handling STI', :aggregate_failures do let(:namespace_type) { nil } let(:parent) { nil } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d11257cbf89..e6f2d373128 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1392,6 +1392,60 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do end end + describe '#to_reference_base' do + using RSpec::Parameterized::TableSyntax + + let_it_be(:user) { create(:user) } + let_it_be(:user_namespace) { user.namespace } + + let_it_be(:parent) { create(:group) } + let_it_be(:group) { create(:group, parent: parent) } + let_it_be(:another_group) { create(:group) } + + let_it_be(:project1) { create(:project, namespace: group) } + let_it_be(:project_namespace) { project1.project_namespace } + + # different project same group + let_it_be(:project2) { create(:project, namespace: group) } + let_it_be(:project_namespace2) { project2.project_namespace } + + # different project from different group + let_it_be(:project3) { create(:project) } + let_it_be(:project_namespace3) { project3.project_namespace } + + # testing references with namespace being: group, project namespace and user namespace + where(:project, :full, :from, :result) do + ref(:project1) | false | nil | nil + ref(:project1) | true | nil | lazy { project.full_path } + ref(:project1) | false | ref(:group) | lazy { project.path } + ref(:project1) | true | ref(:group) | lazy { project.full_path } + ref(:project1) | false | ref(:parent) | lazy { project.full_path } + ref(:project1) | true | ref(:parent) | lazy { project.full_path } + ref(:project1) | false | ref(:project1) | nil + ref(:project1) | true | ref(:project1) | lazy { project.full_path } + ref(:project1) | false | ref(:project_namespace) | nil + ref(:project1) | true | ref(:project_namespace) | lazy { project.full_path } + ref(:project1) | false | ref(:project2) | lazy { project.path } + ref(:project1) | true | ref(:project2) | lazy { project.full_path } + ref(:project1) | false | ref(:project_namespace2) | lazy { project.path } + ref(:project1) | true | ref(:project_namespace2) | lazy { project.full_path } + ref(:project1) | false | ref(:another_group) | lazy { project.full_path } + ref(:project1) | true | ref(:another_group) | lazy { project.full_path } + ref(:project1) | false | ref(:project3) | lazy { project.full_path } + ref(:project1) | true | ref(:project3) | lazy { project.full_path } + ref(:project1) | false | ref(:project_namespace3) | lazy { project.full_path } + ref(:project1) | true | ref(:project_namespace3) | lazy { project.full_path } + ref(:project1) | false | ref(:user_namespace) | lazy { project.full_path } + ref(:project1) | true | ref(:user_namespace) | lazy { project.full_path } + end + + with_them do + it 'returns correct path' do + expect(project.to_reference_base(from, full: full)).to eq(result) + end + end + end + describe '#merge_method' do where(:ff, :rebase, :method) do true | true | :ff diff --git a/spec/requests/admin/projects_controller_spec.rb b/spec/requests/admin/projects_controller_spec.rb new file mode 100644 index 00000000000..5ff49a30ed8 --- /dev/null +++ b/spec/requests/admin/projects_controller_spec.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Admin::ProjectsController, :enable_admin_mode, feature_category: :projects do + let_it_be(:project) { create(:project, :public, name: 'test', description: 'test') } + let_it_be(:admin) { create(:admin) } + + describe 'PUT #update' do + let(:project_params) { {} } + let(:params) { { project: project_params } } + let(:path_params) { { namespace_id: project.namespace.to_param, id: project.to_param } } + + before do + sign_in(admin) + end + + subject do + put admin_namespace_project_path(path_params), params: params + end + + context 'when changing the name' do + let(:project_params) { { name: 'new name' } } + + it 'returns success' do + subject + + expect(response).to have_gitlab_http_status(:found) + end + + it 'changes the name' do + expect { subject }.to change { project.reload.name }.to('new name') + end + end + + context 'when changing the description' do + let(:project_params) { { description: 'new description' } } + + it 'returns success' do + subject + + expect(response).to have_gitlab_http_status(:found) + end + + it 'changes the project description' do + expect { subject }.to change { project.reload.description }.to('new description') + end + end + + context 'when changing the name to an invalid name' do + let(:project_params) { { name: 'invalid/project/name' } } + + it 'does not change the name' do + expect { subject }.not_to change { project.reload.name } + end + end + end +end diff --git a/spec/serializers/group_issuable_autocomplete_entity_spec.rb b/spec/serializers/group_issuable_autocomplete_entity_spec.rb index 86ef9dea23b..977239c67da 100644 --- a/spec/serializers/group_issuable_autocomplete_entity_spec.rb +++ b/spec/serializers/group_issuable_autocomplete_entity_spec.rb @@ -4,7 +4,8 @@ require 'spec_helper' RSpec.describe GroupIssuableAutocompleteEntity do let(:group) { build_stubbed(:group) } - let(:project) { build_stubbed(:project, group: group) } + let(:project_namespace) { build_stubbed(:project_namespace) } + let(:project) { build_stubbed(:project, group: group, project_namespace: project_namespace) } let(:issue) { build_stubbed(:issue, project: project) } describe '#represent' do diff --git a/spec/services/personal_access_tokens/create_service_spec.rb b/spec/services/personal_access_tokens/create_service_spec.rb index b8a4c8f30d2..d80be5cccce 100644 --- a/spec/services/personal_access_tokens/create_service_spec.rb +++ b/spec/services/personal_access_tokens/create_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe PersonalAccessTokens::CreateService do +RSpec.describe PersonalAccessTokens::CreateService, feature_category: :system_access do shared_examples_for 'a successfully created token' do it 'creates personal access token record' do expect(subject.success?).to be true @@ -40,7 +40,7 @@ RSpec.describe PersonalAccessTokens::CreateService do let(:current_user) { create(:user) } let(:user) { create(:user) } let(:params) { { name: 'Test token', impersonation: false, scopes: [:api], expires_at: Date.today + 1.month } } - let(:service) { described_class.new(current_user: current_user, target_user: user, params: params) } + let(:service) { described_class.new(current_user: current_user, target_user: user, params: params, concatenate_errors: false) } let(:token) { subject.payload[:personal_access_token] } context 'when current_user is an administrator' do @@ -66,5 +66,21 @@ RSpec.describe PersonalAccessTokens::CreateService do it_behaves_like 'a successfully created token' end end + + context 'when invalid scope' do + let(:params) { { name: 'Test token', impersonation: false, scopes: [:no_valid], expires_at: Date.today + 1.month } } + + context 'when concatenate_errors: true' do + let(:service) { described_class.new(current_user: user, target_user: user, params: params) } + + it { expect(subject.message).to be_an_instance_of(String) } + end + + context 'when concatenate_errors: false' do + let(:service) { described_class.new(current_user: user, target_user: user, params: params, concatenate_errors: false) } + + it { expect(subject.message).to be_an_instance_of(Array) } + end + end end end |