diff options
Diffstat (limited to 'app')
19 files changed, 156 insertions, 82 deletions
diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js index ee1a7633a11..21456564d3b 100644 --- a/app/assets/javascripts/pages/sessions/new/index.js +++ b/app/assets/javascripts/pages/sessions/new/index.js @@ -1,12 +1,14 @@ -import $ from 'jquery'; import initVueAlerts from '~/vue_alerts'; import NoEmojiValidator from '~/emoji/no_emoji_validator'; import { initLanguageSwitcher } from '~/language_switcher'; import LengthValidator from '~/validators/length_validator'; import mountEmailVerificationApplication from '~/sessions/new'; import { renderGFM } from '~/behaviors/markdown/render_gfm'; -import OAuthRememberMe from './oauth_remember_me'; -import preserveUrlFragment from './preserve_url_fragment'; +import { + appendUrlFragment, + appendRedirectQuery, + toggleRememberMeQuery, +} from './preserve_url_fragment'; import SigninTabsMemoizer from './signin_tabs_memoizer'; import UsernameValidator from './username_validator'; @@ -15,13 +17,9 @@ new LengthValidator(); // eslint-disable-line no-new new SigninTabsMemoizer(); // eslint-disable-line no-new new NoEmojiValidator(); // eslint-disable-line no-new -new OAuthRememberMe({ - container: $('.js-oauth-login'), -}).bindEvents(); - -// Save the URL fragment from the current window location. This will be present if the user was -// redirected to sign-in after attempting to access a protected URL that included a fragment. -preserveUrlFragment(window.location.hash); +appendUrlFragment(); +appendRedirectQuery(); +toggleRememberMeQuery(); initVueAlerts(); initLanguageSwitcher(); mountEmailVerificationApplication(); diff --git a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js deleted file mode 100644 index 3336b094560..00000000000 --- a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js +++ /dev/null @@ -1,34 +0,0 @@ -import $ from 'jquery'; -import { mergeUrlParams, removeParams } from '~/lib/utils/url_utility'; - -/** - * OAuth-based login buttons have a separate "remember me" checkbox. - * - * Toggling this checkbox adds/removes a `remember_me` parameter to the - * login buttons' parent form action, which is passed on to the omniauth callback. - */ - -export default class OAuthRememberMe { - constructor(opts = {}) { - this.container = opts.container || ''; - } - - bindEvents() { - $('#remember_me_omniauth', this.container).on('click', this.toggleRememberMe); - } - - toggleRememberMe(event) { - const rememberMe = $(event.target).is(':checked'); - - $('.js-oauth-login form', this.container).each((_, form) => { - const $form = $(form); - const href = $form.attr('action'); - - if (rememberMe) { - $form.attr('action', mergeUrlParams({ remember_me: 1 }, href)); - } else { - $form.attr('action', removeParams(['remember_me'], href)); - } - }); - } -} diff --git a/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js b/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js index 54ec3c52f62..de48a457bcd 100644 --- a/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js +++ b/app/assets/javascripts/pages/sessions/new/preserve_url_fragment.js @@ -1,32 +1,72 @@ -import { mergeUrlParams, setUrlFragment } from '~/lib/utils/url_utility'; +import { mergeUrlParams, removeParams, setUrlFragment } from '~/lib/utils/url_utility'; /** - * Ensure the given URL fragment is preserved by appending it to sign-in/sign-up form actions and - * OAuth/SAML login links. + * Append the fragment to all non-OAuth login form actions so it is preserved + * when the user is eventually redirected back to the originally requested URL. * * @param fragment {string} - url fragment to be preserved */ -export default function preserveUrlFragment(fragment = '') { - if (fragment) { - const normalFragment = fragment.replace(/^#/, ''); - - // Append the fragment to all sign-in/sign-up form actions so it is preserved when the user is - // eventually redirected back to the originally requested URL. - const forms = document.querySelectorAll('.js-non-oauth-login form'); - Array.prototype.forEach.call(forms, (form) => { - const actionWithFragment = setUrlFragment(form.getAttribute('action'), `#${normalFragment}`); - form.setAttribute('action', actionWithFragment); - }); +export function appendUrlFragment(fragment = document.location.hash) { + if (!fragment) { + return; + } + + const normalFragment = fragment.replace(/^#/, ''); + const forms = document.querySelectorAll('.js-non-oauth-login form'); + forms.forEach((form) => { + const actionWithFragment = setUrlFragment(form.getAttribute('action'), `#${normalFragment}`); + form.setAttribute('action', actionWithFragment); + }); +} + +/** + * Append a redirect_fragment query param to all OAuth login form actions. The + * redirect_fragment query param will be available in the omniauth callback upon + * successful authentication. + * + * @param {string} fragment - url fragment to be preserved + */ +export function appendRedirectQuery(fragment = document.location.hash) { + if (!fragment) { + return; + } + + const normalFragment = fragment.replace(/^#/, ''); + const oauthForms = document.querySelectorAll('.js-oauth-login form'); + oauthForms.forEach((oauthForm) => { + const newHref = mergeUrlParams( + { redirect_fragment: normalFragment }, + oauthForm.getAttribute('action'), + ); + oauthForm.setAttribute('action', newHref); + }); +} + +/** + * OAuth login buttons have a separate "remember me" checkbox. + * + * Toggling this checkbox adds/removes a `remember_me` parameter to the + * login form actions, which is passed on to the omniauth callback. + */ +export function toggleRememberMeQuery() { + const oauthForms = document.querySelectorAll('.js-oauth-login form'); + const checkbox = document.querySelector('#js-remember-me-omniauth'); + + if (oauthForms.length === 0 || !checkbox) { + return; + } + + checkbox.addEventListener('change', ({ currentTarget }) => { + oauthForms.forEach((oauthForm) => { + const href = oauthForm.getAttribute('action'); + let newHref; + if (currentTarget.checked) { + newHref = mergeUrlParams({ remember_me: '1' }, href); + } else { + newHref = removeParams(['remember_me'], href); + } - // Append a redirect_fragment query param to all oauth provider links. The redirect_fragment - // query param will be available in the omniauth callback upon successful authentication - const oauthForms = document.querySelectorAll('.js-oauth-login form'); - Array.prototype.forEach.call(oauthForms, (oauthForm) => { - const newHref = mergeUrlParams( - { redirect_fragment: normalFragment }, - oauthForm.getAttribute('action'), - ); oauthForm.setAttribute('action', newHref); }); - } + }); } diff --git a/app/assets/javascripts/repository/components/commit_info.vue b/app/assets/javascripts/repository/components/commit_info.vue index 319ce2cea84..9b6b0f1cb2a 100644 --- a/app/assets/javascripts/repository/components/commit_info.vue +++ b/app/assets/javascripts/repository/components/commit_info.vue @@ -26,6 +26,11 @@ export default { type: Object, required: true, }, + span: { + type: Number, + required: false, + default: null, + }, prevBlameLink: { type: String, required: false, @@ -43,6 +48,9 @@ export default { avatarLinkAltText() { return sprintf(__(`%{username}'s avatar`), { username: this.commit.authorName }); }, + truncateAuthorName() { + return typeof this.span === 'number' && this.span < 3; + }, }, methods: { toggleShowDescription() { @@ -102,18 +110,23 @@ export default { @click="toggleShowDescription" /> </div> - <div class="committer gl-flex-basis-full"> + <div + class="committer gl-flex-basis-full" + :class="truncateAuthorName ? 'gl-display-inline-flex' : ''" + data-testid="committer" + > <gl-link v-if="commit.author" :href="commit.author.webPath" class="commit-author-link js-user-link" + :class="truncateAuthorName ? 'gl-display-inline-block gl-text-truncate' : ''" > {{ commit.author.name }}</gl-link > <template v-else> {{ commit.authorName }} </template> - {{ $options.i18n.authored }} + {{ $options.i18n.authored }} <timeago-tooltip :time="commit.authoredDate" tooltip-placement="bottom" /> </div> <pre diff --git a/app/assets/javascripts/tracking/internal_events.js b/app/assets/javascripts/tracking/internal_events.js index 7da6da16d6f..d4469382be4 100644 --- a/app/assets/javascripts/tracking/internal_events.js +++ b/app/assets/javascripts/tracking/internal_events.js @@ -9,10 +9,13 @@ const InternalEvents = { /** * * @param {string} event + * @param {string} category - The category of the event. This is optional and + * defaults to the page name where the event was triggered. It's advised not to use + * this parameter for new events unless absolutely necessary. */ - trackEvent(event) { + trackEvent(event, category = undefined) { API.trackInternalEvent(event); - Tracking.event(undefined, event, { + Tracking.event(category, event, { context: { schema: SERVICE_PING_SCHEMA, data: { @@ -30,8 +33,8 @@ const InternalEvents = { mixin() { return { methods: { - trackEvent(event) { - InternalEvents.trackEvent(event); + trackEvent(event, category = undefined) { + InternalEvents.trackEvent(event, category); }, }, }; diff --git a/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue b/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue index e2fd4477f0a..3205ec2b73b 100644 --- a/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue +++ b/app/assets/javascripts/vue_shared/components/source_viewer/components/blame_info.vue @@ -30,6 +30,7 @@ export default { class="gl-display-flex gl-absolute gl-px-3" :style="{ top: blame.blameOffset }" :commit="blame.commit" + :span="blame.span" :prev-blame-link="blame.commitData && blame.commitData.projectBlameLink" /> </div> diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index b0519119bd8..e455682dcff 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -136,6 +136,10 @@ .commit-author-link { color: $gl-text-color; } + + .commit-author-link.gl-text-truncate { + max-width: 20ch; + } } } diff --git a/app/controllers/groups/autocomplete_sources_controller.rb b/app/controllers/groups/autocomplete_sources_controller.rb index 191720f69a0..8a3ec13f720 100644 --- a/app/controllers/groups/autocomplete_sources_controller.rb +++ b/app/controllers/groups/autocomplete_sources_controller.rb @@ -51,7 +51,7 @@ class Groups::AutocompleteSourcesController < Groups::ApplicationController # TODO https://gitlab.com/gitlab-org/gitlab/-/issues/388541 # type_id is a misnomer. QuickActions::TargetService actually requires an iid. QuickActions::TargetService - .new(nil, current_user, group: @group) + .new(container: @group, current_user: current_user) .execute(params[:type], params[:type_id]) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/controllers/projects/autocomplete_sources_controller.rb b/app/controllers/projects/autocomplete_sources_controller.rb index dc10004c62b..c496a326051 100644 --- a/app/controllers/projects/autocomplete_sources_controller.rb +++ b/app/controllers/projects/autocomplete_sources_controller.rb @@ -59,7 +59,7 @@ class Projects::AutocompleteSourcesController < Projects::ApplicationController # TODO https://gitlab.com/gitlab-org/gitlab/-/issues/388541 # type_id is a misnomer. QuickActions::TargetService actually requires an iid. QuickActions::TargetService - .new(project, current_user) + .new(container: project, current_user: current_user) .execute(target_type, params[:type_id]) end diff --git a/app/graphql/resolvers/projects/is_forked_resolver.rb b/app/graphql/resolvers/projects/is_forked_resolver.rb new file mode 100644 index 00000000000..f1413543b7c --- /dev/null +++ b/app/graphql/resolvers/projects/is_forked_resolver.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Resolvers + module Projects + class IsForkedResolver < BaseResolver + type GraphQL::Types::Boolean, null: false + + def resolve + lazy_fork_network_members = BatchLoader::GraphQL.for(object.id).batch do |ids, loader| + ForkNetworkMember.by_projects(ids) + .with_fork_network + .find_each do |fork_network_member| + loader.call(fork_network_member.project_id, fork_network_member) + end + end + + Gitlab::Graphql::Lazy.with_value(lazy_fork_network_members) do |fork_network_member| + next false if fork_network_member.nil? + + fork_network_member.fork_network.root_project_id != object.id + end + end + end + end +end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index aacd67e269e..bedcd08fcb4 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -685,6 +685,12 @@ module Types description: 'Project allows assigning multiple reviewers to a merge request.', null: false + field :is_forked, + GraphQL::Types::Boolean, + resolver: Resolvers::Projects::IsForkedResolver, + description: 'Project is forked.', + null: false + def timelog_categories object.project_namespace.timelog_categories if Feature.enabled?(:timelog_categories) end diff --git a/app/models/ci/pipeline_artifact.rb b/app/models/ci/pipeline_artifact.rb index e0e6906f211..05c9535cef1 100644 --- a/app/models/ci/pipeline_artifact.rb +++ b/app/models/ci/pipeline_artifact.rb @@ -10,6 +10,9 @@ module Ci include FileStoreMounter include Lockable include Presentable + include SafelyChangeColumnDefault + + columns_changing_default :partition_id FILE_SIZE_LIMIT = 10.megabytes.freeze EXPIRATION_DATE = 1.week.freeze diff --git a/app/models/ci/pipeline_config.rb b/app/models/ci/pipeline_config.rb index 11decd3fc66..8e992aae2c5 100644 --- a/app/models/ci/pipeline_config.rb +++ b/app/models/ci/pipeline_config.rb @@ -3,6 +3,9 @@ module Ci class PipelineConfig < Ci::ApplicationRecord include Ci::Partitionable + include SafelyChangeColumnDefault + + columns_changing_default :partition_id self.table_name = 'ci_pipelines_config' self.primary_key = :pipeline_id diff --git a/app/models/ci/pipeline_metadata.rb b/app/models/ci/pipeline_metadata.rb index 21d102374f0..39e2ef5cebb 100644 --- a/app/models/ci/pipeline_metadata.rb +++ b/app/models/ci/pipeline_metadata.rb @@ -4,6 +4,9 @@ module Ci class PipelineMetadata < Ci::ApplicationRecord include Ci::Partitionable include Importable + include SafelyChangeColumnDefault + + columns_changing_default :partition_id self.primary_key = :pipeline_id diff --git a/app/models/fork_network_member.rb b/app/models/fork_network_member.rb index f18c306cf91..023f948d5f9 100644 --- a/app/models/fork_network_member.rb +++ b/app/models/fork_network_member.rb @@ -9,6 +9,9 @@ class ForkNetworkMember < ApplicationRecord after_destroy :cleanup_fork_network + scope :by_projects, ->(ids) { where(project_id: ids) } + scope :with_fork_network, -> { joins(:fork_network).includes(:fork_network) } + private def cleanup_fork_network diff --git a/app/models/user.rb b/app/models/user.rb index ab5572e5b19..05e35b217f4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -631,6 +631,8 @@ class User < MainClusterwide::ApplicationRecord .trusted_with_spam) end + scope :preload_user_detail, -> { preload(:user_detail) } + def self.supported_keyset_orderings { id: [:asc, :desc], diff --git a/app/services/preview_markdown_service.rb b/app/services/preview_markdown_service.rb index 10aef87332a..31f79bc7164 100644 --- a/app/services/preview_markdown_service.rb +++ b/app/services/preview_markdown_service.rb @@ -55,7 +55,7 @@ class PreviewMarkdownService < BaseService def find_commands_target QuickActions::TargetService - .new(project, current_user, group: params[:group]) + .new(container: project, current_user: current_user, params: { group: params[:group] }) .execute(target_type, target_id) end diff --git a/app/services/quick_actions/target_service.rb b/app/services/quick_actions/target_service.rb index 04ae5287302..63e2c58fc55 100644 --- a/app/services/quick_actions/target_service.rb +++ b/app/services/quick_actions/target_service.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QuickActions - class TargetService < BaseService + class TargetService < BaseContainerService def execute(type, type_iid) case type&.downcase when 'workitem' @@ -19,15 +19,15 @@ module QuickActions # rubocop: disable CodeReuse/ActiveRecord def work_item(type_iid) - WorkItems::WorkItemsFinder.new(current_user, project_id: project.id).find_by(iid: type_iid) + WorkItems::WorkItemsFinder.new(current_user, **parent_params).find_by(iid: type_iid) end # rubocop: enable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord def issue(type_iid) - return project.issues.build if type_iid.nil? + return container.issues.build if type_iid.nil? - IssuesFinder.new(current_user, project_id: project.id).find_by(iid: type_iid) || project.issues.build + IssuesFinder.new(current_user, **parent_params).find_by(iid: type_iid) || container.issues.build end # rubocop: enable CodeReuse/ActiveRecord @@ -42,7 +42,11 @@ module QuickActions def commit(type_iid) project.commit(type_iid) end + + def parent_params + group_container? ? { group_id: group.id } : { project_id: project.id } + end end end -QuickActions::TargetService.prepend_mod_with('QuickActions::TargetService') +QuickActions::TargetService.prepend_mod diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 8197abcc787..5fbb20f7535 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -12,6 +12,6 @@ data: { testid: test_id_for_provider(provider) }, id: "oauth-login-#{provider}" - if render_remember_me - = render Pajamas::CheckboxTagComponent.new(name: 'remember_me_omniauth', value: nil) do |c| + = render Pajamas::CheckboxTagComponent.new(name: 'js-remember-me-omniauth', value: nil) do |c| - c.with_label do = _('Remember me') |