diff options
Diffstat (limited to 'app')
51 files changed, 203 insertions, 81 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) }) |