diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-10 21:12:35 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-11-10 21:12:35 +0300 |
commit | 6fd750c19206412cfc52b49a70b56147d839c52f (patch) | |
tree | 04607e6c9864c09dd312d6bfc3efe9cc5f81c762 /app | |
parent | 26881dd926cfac47c9603d44e8d5a504ab8c4a14 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
42 files changed, 276 insertions, 184 deletions
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue index 46726a8fa07..238f07ac22c 100644 --- a/app/assets/javascripts/diffs/components/diff_file_header.vue +++ b/app/assets/javascripts/diffs/components/diff_file_header.vue @@ -14,7 +14,6 @@ import { import { escape } from 'lodash'; import { mapActions, mapGetters, mapState } from 'vuex'; import { IdState } from 'vendor/vue-virtual-scroller'; -import { diffViewerModes } from '~/ide/constants'; import { scrollToElement } from '~/lib/utils/common_utils'; import { truncateSha } from '~/lib/utils/text_utility'; import { __, s__, sprintf } from '~/locale'; @@ -181,7 +180,7 @@ export default { return this.diffFile.renamed_file; }, isModeChanged() { - return this.diffFile.viewer.name === diffViewerModes.mode_changed; + return this.diffFile.mode_changed; }, expandDiffToFullFileTitle() { if (this.diffFile.isShowingFullFile) { diff --git a/app/assets/javascripts/experimentation/utils.js b/app/assets/javascripts/experimentation/utils.js index 1567bf14d0f..9ae6f6b3fdd 100644 --- a/app/assets/javascripts/experimentation/utils.js +++ b/app/assets/javascripts/experimentation/utils.js @@ -3,7 +3,12 @@ import { get } from 'lodash'; import { DEFAULT_VARIANT, CANDIDATE_VARIANT, TRACKING_CONTEXT_SCHEMA } from './constants'; function getExperimentsData() { - return get(window, ['gon', 'experiment'], {}); + // Pull from deprecated window.gon.experiment + const experimentsFromGon = get(window, ['gon', 'experiment'], {}); + // Pull from preferred window.gl.experiments + const experimentsFromGl = get(window, ['gl', 'experiments'], {}); + + return { ...experimentsFromGon, ...experimentsFromGl }; } function convertExperimentDataToExperimentContext(experimentData) { diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue index 9b3cd04c4e2..c0504cbb645 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/components/app.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/components/app.vue @@ -1,5 +1,6 @@ <script> -import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui'; +import { GlAlert, GlLink, GlSprintf, GlEmptyState } from '@gitlab/ui'; +import { isEmpty } from 'lodash'; import { mapState, mapMutations } from 'vuex'; import { retrieveAlert } from '~/jira_connect/subscriptions/utils'; import { SET_ALERT } from '../store/mutation_types'; @@ -13,6 +14,7 @@ export default { GlAlert, GlLink, GlSprintf, + GlEmptyState, SubscriptionsList, AddNamespaceButton, SignInButton, @@ -21,12 +23,18 @@ export default { usersPath: { default: '', }, + subscriptions: { + default: [], + }, }, computed: { ...mapState(['alert']), shouldShowAlert() { return Boolean(this.alert?.message); }, + hasSubscriptions() { + return !isEmpty(this.subscriptions); + }, userSignedIn() { return Boolean(!this.usersPath); }, @@ -66,15 +74,44 @@ export default { </template> </gl-alert> - <h2 class="gl-text-center">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2> - - <div class="jira-connect-app-body gl-my-7 gl-px-5 gl-pb-4"> - <div class="gl-display-flex gl-justify-content-end"> - <sign-in-button v-if="!userSignedIn" :users-path="usersPath" /> - <add-namespace-button v-else /> - </div> + <h2 class="gl-text-center gl-mb-7">{{ s__('JiraService|GitLab for Jira Configuration') }}</h2> + <div class="jira-connect-app-body gl-mx-auto gl-px-5 gl-mb-7"> + <template v-if="hasSubscriptions"> + <div class="gl-display-flex gl-justify-content-end"> + <sign-in-button v-if="!userSignedIn" :users-path="usersPath" /> + <add-namespace-button v-else /> + </div> - <subscriptions-list /> + <subscriptions-list /> + </template> + <template v-else> + <div v-if="!userSignedIn" class="gl-text-center"> + <p class="gl-mb-7">{{ s__('JiraService|Sign in to GitLab.com to get started.') }}</p> + <sign-in-button class="gl-mb-7" :users-path="usersPath"> + {{ __('Sign in to GitLab') }} + </sign-in-button> + <p> + {{ + s__( + 'Integrations|Note: this integration only works with accounts on GitLab.com (SaaS).', + ) + }} + </p> + </div> + <gl-empty-state + v-else + :title="s__('Integrations|No linked namespaces')" + :description=" + s__( + 'Integrations|Namespaces are the GitLab groups and subgroups you link to this Jira instance.', + ) + " + > + <template #actions> + <add-namespace-button /> + </template> + </gl-empty-state> + </template> </div> </div> </template> diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue index 7062fb370ed..33126040c16 100644 --- a/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue +++ b/app/assets/javascripts/jira_connect/subscriptions/components/subscriptions_list.vue @@ -1,5 +1,5 @@ <script> -import { GlButton, GlEmptyState, GlTable } from '@gitlab/ui'; +import { GlButton, GlTable } from '@gitlab/ui'; import { isEmpty } from 'lodash'; import { mapMutations } from 'vuex'; import { removeSubscription } from '~/jira_connect/subscriptions/api'; @@ -12,7 +12,6 @@ import GroupItemName from './group_item_name.vue'; export default { components: { GlButton, - GlEmptyState, GlTable, GroupItemName, TimeagoTooltip, @@ -44,17 +43,15 @@ export default { }, ], i18n: { - emptyTitle: s__('Integrations|No linked namespaces'), - emptyDescription: s__( - 'Integrations|Namespaces are the GitLab groups and subgroups you link to this Jira instance.', - ), unlinkError: s__('Integrations|Failed to unlink namespace. Please try again.'), }, methods: { ...mapMutations({ setAlert: SET_ALERT, }), - isEmpty, + isUnlinkButtonDisabled(item) { + return !isEmpty(item); + }, isLoadingItem(item) { return this.loadingItem === item; }, @@ -81,29 +78,22 @@ export default { </script> <template> - <div> - <gl-empty-state - v-if="isEmpty(subscriptions)" - :title="$options.i18n.emptyTitle" - :description="$options.i18n.emptyDescription" - /> - <gl-table v-else :items="subscriptions" :fields="$options.fields"> - <template #cell(name)="{ item }"> - <group-item-name :group="item.group" /> - </template> - <template #cell(created_at)="{ item }"> - <timeago-tooltip :time="item.created_at" /> - </template> - <template #cell(actions)="{ item }"> - <gl-button - :class="unlinkBtnClass(item)" - category="secondary" - :loading="isLoadingItem(item)" - :disabled="!isEmpty(loadingItem)" - @click.prevent="onClick(item)" - >{{ __('Unlink') }}</gl-button - > - </template> - </gl-table> - </div> + <gl-table :items="subscriptions" :fields="$options.fields"> + <template #cell(name)="{ item }"> + <group-item-name :group="item.group" /> + </template> + <template #cell(created_at)="{ item }"> + <timeago-tooltip :time="item.created_at" /> + </template> + <template #cell(actions)="{ item }"> + <gl-button + :class="unlinkBtnClass(item)" + category="secondary" + :loading="isLoadingItem(item)" + :disabled="isUnlinkButtonDisabled(loadingItem)" + @click.prevent="onClick(item)" + >{{ __('Unlink') }}</gl-button + > + </template> + </gl-table> </template> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue index 790870ee4c6..fa4f8b76cb9 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/work_in_progress.vue @@ -10,8 +10,8 @@ import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import eventHub from '../../event_hub'; import mergeRequestQueryVariablesMixin from '../../mixins/merge_request_query_variables'; import getStateQuery from '../../queries/get_state.query.graphql'; -import workInProgressQuery from '../../queries/states/work_in_progress.query.graphql'; -import removeWipMutation from '../../queries/toggle_wip.mutation.graphql'; +import draftQuery from '../../queries/states/draft.query.graphql'; +import removeDraftMutation from '../../queries/toggle_draft.mutation.graphql'; import StatusIcon from '../mr_widget_status_icon.vue'; export default { @@ -23,7 +23,7 @@ export default { mixins: [glFeatureFlagMixin(), mergeRequestQueryVariablesMixin], apollo: { userPermissions: { - query: workInProgressQuery, + query: draftQuery, skip() { return !this.glFeatures.mergeRequestWidgetGraphql; }, @@ -53,25 +53,25 @@ export default { }, }, methods: { - removeWipMutation() { + removeDraftMutation() { const { mergeRequestQueryVariables } = this; this.isMakingRequest = true; this.$apollo .mutate({ - mutation: removeWipMutation, + mutation: removeDraftMutation, variables: { ...mergeRequestQueryVariables, - wip: false, + draft: false, }, update( store, { data: { - mergeRequestSetWip: { + mergeRequestSetDraft: { errors, - mergeRequest: { mergeableDiscussionsState, workInProgress, title }, + mergeRequest: { mergeableDiscussionsState, draft, title }, }, }, }, @@ -91,7 +91,7 @@ export default { const data = produce(sourceData, (draftState) => { draftState.project.mergeRequest.mergeableDiscussionsState = mergeableDiscussionsState; - draftState.project.mergeRequest.workInProgress = workInProgress; + draftState.project.mergeRequest.draft = draft; draftState.project.mergeRequest.title = title; }); @@ -104,14 +104,14 @@ export default { optimisticResponse: { // eslint-disable-next-line @gitlab/require-i18n-strings __typename: 'Mutation', - mergeRequestSetWip: { + mergeRequestSetDraft: { __typename: 'MergeRequestSetWipPayload', errors: [], mergeRequest: { __typename: 'MergeRequest', mergeableDiscussionsState: true, title: this.mr.title, - workInProgress: false, + draft: false, }, }, }, @@ -119,7 +119,7 @@ export default { .then( ({ data: { - mergeRequestSetWip: { + mergeRequestSetDraft: { mergeRequest: { title }, }, }, @@ -137,9 +137,9 @@ export default { this.isMakingRequest = false; }); }, - handleRemoveWIP() { + handleRemoveDraft() { if (this.glFeatures.mergeRequestWidgetGraphql) { - this.removeWipMutation(); + this.removeDraftMutation(); } else { this.isMakingRequest = true; this.service @@ -178,8 +178,8 @@ export default { size="small" :disabled="isMakingRequest" :loading="isMakingRequest" - class="js-remove-wip gl-ml-3" - @click="handleRemoveWIP" + class="js-remove-draft gl-ml-3" + @click="handleRemoveDraft" > {{ s__('mrWidget|Mark as ready') }} </gl-button> diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/get_state.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/get_state.query.graphql index 871aa880b36..bfb1517be81 100644 --- a/app/assets/javascripts/vue_merge_request_widget/queries/get_state.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/queries/get_state.query.graphql @@ -23,7 +23,7 @@ query getState($projectPath: ID!, $iid: String!) { userPermissions { canMerge } - workInProgress + draft } } } diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/states/work_in_progress.query.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/states/draft.query.graphql index 0983c28448e..0983c28448e 100644 --- a/app/assets/javascripts/vue_merge_request_widget/queries/states/work_in_progress.query.graphql +++ b/app/assets/javascripts/vue_merge_request_widget/queries/states/draft.query.graphql diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/toggle_draft.mutation.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/toggle_draft.mutation.graphql new file mode 100644 index 00000000000..200fb1b7ca5 --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/queries/toggle_draft.mutation.graphql @@ -0,0 +1,10 @@ +mutation toggleDraftStatus($projectPath: ID!, $iid: String!, $draft: Boolean!) { + mergeRequestSetDraft(input: { projectPath: $projectPath, iid: $iid, draft: $draft }) { + mergeRequest { + mergeableDiscussionsState + title + draft + } + errors + } +} diff --git a/app/assets/javascripts/vue_merge_request_widget/queries/toggle_wip.mutation.graphql b/app/assets/javascripts/vue_merge_request_widget/queries/toggle_wip.mutation.graphql deleted file mode 100644 index cfaa198d516..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/queries/toggle_wip.mutation.graphql +++ /dev/null @@ -1,10 +0,0 @@ -mutation toggleWIPStatus($projectPath: ID!, $iid: String!, $wip: Boolean!) { - mergeRequestSetWip(input: { projectPath: $projectPath, iid: $iid, wip: $wip }) { - mergeRequest { - mergeableDiscussionsState - title - workInProgress - } - errors - } -} diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js index d52371032eb..2ae4f4da2f3 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/get_state_key.js @@ -17,8 +17,8 @@ export default function deviseState() { return stateKey.rebase; } else if (this.onlyAllowMergeIfPipelineSucceeds && this.isPipelineFailed) { return stateKey.pipelineFailed; - } else if (this.workInProgress) { - return stateKey.workInProgress; + } else if (this.draft) { + return stateKey.draft; } else if (this.hasMergeableDiscussionsState && !this.autoMergeEnabled) { return stateKey.unresolvedDiscussions; } else if (this.isPipelineBlocked) { diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js index 6628225cd46..10a2907c81a 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js @@ -164,7 +164,7 @@ export default class MergeRequestStore { this.projectArchived = data.project_archived; this.isSHAMismatch = this.sha !== data.diff_head_sha; this.shouldBeRebased = Boolean(data.should_be_rebased); - this.workInProgress = data.work_in_progress; + this.draft = data.draft; } const currentUser = data.current_user; @@ -207,7 +207,7 @@ export default class MergeRequestStore { this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled'; this.isSHAMismatch = this.sha !== mergeRequest.diffHeadSha; this.shouldBeRebased = mergeRequest.shouldBeRebased; - this.workInProgress = mergeRequest.workInProgress; + this.draft = mergeRequest.draft; this.mergeRequestState = mergeRequest.state; this.setState(); diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js index 4cb23407a74..9dfeaee905c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js +++ b/app/assets/javascripts/vue_merge_request_widget/stores/state_maps.js @@ -4,7 +4,7 @@ export const stateToComponentMap = { merging: 'mr-widget-merging', conflicts: 'mr-widget-conflicts', missingBranch: 'mr-widget-missing-branch', - workInProgress: 'mr-widget-wip', + draft: 'mr-widget-wip', readyToMerge: 'mr-widget-ready-to-merge', nothingToMerge: 'mr-widget-nothing-to-merge', notAllowedToMerge: 'mr-widget-not-allowed', @@ -24,7 +24,7 @@ export const stateToComponentMap = { export const statesToShowHelpWidget = [ 'merging', 'conflicts', - 'workInProgress', + 'draft', 'readyToMerge', 'checking', 'unresolvedDiscussions', @@ -40,7 +40,7 @@ export const stateKey = { nothingToMerge: 'nothingToMerge', checking: 'checking', conflicts: 'conflicts', - workInProgress: 'workInProgress', + draft: 'draft', pipelineFailed: 'pipelineFailed', unresolvedDiscussions: 'unresolvedDiscussions', pipelineBlocked: 'pipelineBlocked', diff --git a/app/assets/stylesheets/page_bundles/jira_connect.scss b/app/assets/stylesheets/page_bundles/jira_connect.scss index 4beb5edbe7b..9fe0490571e 100644 --- a/app/assets/stylesheets/page_bundles/jira_connect.scss +++ b/app/assets/stylesheets/page_bundles/jira_connect.scss @@ -42,8 +42,6 @@ $header-height: 40px; .jira-connect-app-body { max-width: 768px; - margin-left: auto; - margin-right: auto; } // needed for external_link diff --git a/app/controllers/groups/dependency_proxy_for_containers_controller.rb b/app/controllers/groups/dependency_proxy_for_containers_controller.rb index 8707381bb68..fc930ffebbd 100644 --- a/app/controllers/groups/dependency_proxy_for_containers_controller.rb +++ b/app/controllers/groups/dependency_proxy_for_containers_controller.rb @@ -113,8 +113,6 @@ class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy end def send_manifest(manifest, from_cache:) - # Technical debt: change to read_at https://gitlab.com/gitlab-org/gitlab/-/issues/341536 - manifest.touch response.headers[DependencyProxy::Manifest::DIGEST_HEADER] = manifest.digest response.headers['Content-Length'] = manifest.size response.headers['Docker-Distribution-Api-Version'] = DependencyProxy::DISTRIBUTION_API_VERSION diff --git a/app/graphql/mutations/merge_requests/set_wip.rb b/app/graphql/mutations/merge_requests/set_wip.rb deleted file mode 100644 index 9b6b67d4b4f..00000000000 --- a/app/graphql/mutations/merge_requests/set_wip.rb +++ /dev/null @@ -1,35 +0,0 @@ -# frozen_string_literal: true - -module Mutations - module MergeRequests - class SetWip < Base - graphql_name 'MergeRequestSetWip' - - argument :wip, - GraphQL::Types::Boolean, - required: true, - description: <<~DESC - Whether or not to set the merge request as a draft. - DESC - - def resolve(project_path:, iid:, wip: nil) - merge_request = authorized_find!(project_path: project_path, iid: iid) - project = merge_request.project - - ::MergeRequests::UpdateService.new(project: project, current_user: current_user, params: { wip_event: wip_event(merge_request, wip) }) - .execute(merge_request) - - { - merge_request: merge_request, - errors: errors_on_object(merge_request) - } - end - - private - - def wip_event(merge_request, wip) - wip ? 'wip' : 'unwip' - end - end - end -end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 004ac364487..a8a96b0ccef 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -53,9 +53,6 @@ module Types description: 'Indicates if the source branch is protected.' field :target_branch, GraphQL::Types::String, null: false, description: 'Target branch of the merge request.' - field :work_in_progress, GraphQL::Types::Boolean, method: :work_in_progress?, null: false, - deprecated: { reason: 'Use `draft`', milestone: '13.12' }, - description: 'Indicates if the merge request is a draft.' field :draft, GraphQL::Types::Boolean, method: :draft?, null: false, description: 'Indicates if the merge request is a draft.' field :merge_when_pipeline_succeeds, GraphQL::Types::Boolean, null: true, diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index 7be995a40b2..5366283e55a 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -65,9 +65,6 @@ module Types mount_mutation Mutations::MergeRequests::SetLocked mount_mutation Mutations::MergeRequests::SetMilestone mount_mutation Mutations::MergeRequests::SetSubscription - mount_mutation Mutations::MergeRequests::SetWip, - calls_gitaly: true, - deprecated: { reason: 'Use mergeRequestSetDraft', milestone: '13.12' } mount_mutation Mutations::MergeRequests::SetDraft, calls_gitaly: true mount_mutation Mutations::MergeRequests::SetAssignees mount_mutation Mutations::MergeRequests::ReviewerRereview diff --git a/app/models/concerns/ttl_expirable.rb b/app/models/concerns/ttl_expirable.rb index 00abe0a06e6..6d89521255c 100644 --- a/app/models/concerns/ttl_expirable.rb +++ b/app/models/concerns/ttl_expirable.rb @@ -5,10 +5,11 @@ module TtlExpirable included do validates :status, presence: true + default_value_for :read_at, Time.zone.now enum status: { default: 0, expired: 1, processing: 2, error: 3 } - scope :updated_before, ->(number_of_days) { where("updated_at <= ?", Time.zone.now - number_of_days.days) } + scope :read_before, ->(number_of_days) { where("read_at <= ?", Time.zone.now - number_of_days.days) } scope :active, -> { where(status: :default) } scope :lock_next_by, ->(sort) do @@ -17,4 +18,8 @@ module TtlExpirable .lock('FOR UPDATE SKIP LOCKED') end end + + def read! + self.update(read_at: Time.zone.now) + end end diff --git a/app/models/gpg_signature.rb b/app/models/gpg_signature.rb index 0c36e51120f..2775b520b2f 100644 --- a/app/models/gpg_signature.rb +++ b/app/models/gpg_signature.rb @@ -12,7 +12,8 @@ class GpgSignature < ApplicationRecord same_user_different_email: 2, other_user: 3, unverified_key: 4, - unknown_key: 5 + unknown_key: 5, + multiple_signatures: 6 } belongs_to :project diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bd68311fcc8..5a74f8392ec 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -268,7 +268,6 @@ class MergeRequest < ApplicationRecord from_fork.where('source_project_id = ? OR target_project_id = ?', project.id, project.id) end scope :merged, -> { with_state(:merged) } - scope :closed_and_merged, -> { with_states(:closed, :merged) } scope :open_and_closed, -> { with_states(:opened, :closed) } scope :drafts, -> { where(draft: true) } scope :from_source_branches, ->(branches) { where(source_branch: branches) } diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 80abb733418..14a0c38dcf0 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -497,6 +497,10 @@ class Namespace < ApplicationRecord Feature.enabled?(:block_issue_repositioning, self, type: :ops, default_enabled: :yaml) end + def project_namespace_creation_enabled? + Feature.enabled?(:create_project_namespace_on_project_create, self, default_enabled: :yaml) + end + private def expire_child_caches diff --git a/app/models/namespaces/project_namespace.rb b/app/models/namespaces/project_namespace.rb index d1806c1c088..22ec550dee2 100644 --- a/app/models/namespaces/project_namespace.rb +++ b/app/models/namespaces/project_namespace.rb @@ -4,8 +4,6 @@ module Namespaces class ProjectNamespace < Namespace has_one :project, foreign_key: :project_namespace_id, inverse_of: :project_namespace - validates :project, presence: true - def self.sti_name 'Project' end diff --git a/app/models/packages/npm.rb b/app/models/packages/npm.rb index e49199d911c..9221187d92a 100644 --- a/app/models/packages/npm.rb +++ b/app/models/packages/npm.rb @@ -9,5 +9,9 @@ module Packages package_name.match(Gitlab::Regex.npm_package_name_regex)&.captures&.first end + + def self.table_name_prefix + 'packages_npm_' + end end end diff --git a/app/models/packages/npm/metadatum.rb b/app/models/packages/npm/metadatum.rb new file mode 100644 index 00000000000..7388c4bdbd2 --- /dev/null +++ b/app/models/packages/npm/metadatum.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class Packages::Npm::Metadatum < ApplicationRecord + belongs_to :package, -> { where(package_type: :npm) }, inverse_of: :npm_metadatum + + validates :package, presence: true + # From https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object + validates :package_json, json_schema: { filename: "npm_package_json" } + validate :ensure_npm_package_type + validate :ensure_package_json_size + + private + + def ensure_npm_package_type + return if package&.npm? + + errors.add(:base, _('Package type must be NPM')) + end + + def ensure_package_json_size + return if package_json.to_s.size < 20000 + + errors.add(:package_json, _('structure is too large')) + end +end diff --git a/app/models/packages/package.rb b/app/models/packages/package.rb index ff9e7ef4cf5..f25d0299524 100644 --- a/app/models/packages/package.rb +++ b/app/models/packages/package.rb @@ -39,6 +39,7 @@ class Packages::Package < ApplicationRecord has_one :nuget_metadatum, inverse_of: :package, class_name: 'Packages::Nuget::Metadatum' has_one :composer_metadatum, inverse_of: :package, class_name: 'Packages::Composer::Metadatum' has_one :rubygems_metadatum, inverse_of: :package, class_name: 'Packages::Rubygems::Metadatum' + has_one :npm_metadatum, inverse_of: :package, class_name: 'Packages::Npm::Metadatum' has_many :build_infos, inverse_of: :package has_many :pipelines, through: :build_infos, disable_joins: -> { disable_cross_joins_to_pipelines? } has_one :debian_publication, inverse_of: :package, class_name: 'Packages::Debian::Publication' @@ -126,6 +127,7 @@ class Packages::Package < ApplicationRecord .where(Packages::Composer::Metadatum.table_name => { target_sha: target }) end scope :preload_composer, -> { preload(:composer_metadatum) } + scope :preload_npm_metadatum, -> { preload(:npm_metadatum) } scope :without_nuget_temporary_name, -> { where.not(name: Packages::Nuget::TEMPORARY_PACKAGE_NAME) } diff --git a/app/models/project.rb b/app/models/project.rb index f7f8ae1f77c..36f48b91221 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -98,7 +98,7 @@ class Project < ApplicationRecord before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } before_save :ensure_runners_token - before_save :ensure_project_namespace_in_sync + before_validation :ensure_project_namespace_in_sync after_save :update_project_statistics, if: :saved_change_to_namespace_id? @@ -147,7 +147,7 @@ class Project < ApplicationRecord belongs_to :namespace # Sync deletion via DB Trigger to ensure we do not have # a project without a project_namespace (or vice-versa) - belongs_to :project_namespace, autosave: true, class_name: 'Namespaces::ProjectNamespace', foreign_key: 'project_namespace_id', inverse_of: :project + belongs_to :project_namespace, autosave: true, class_name: 'Namespaces::ProjectNamespace', foreign_key: 'project_namespace_id' alias_method :parent, :namespace alias_attribute :parent_id, :namespace_id @@ -476,6 +476,7 @@ class Project < ApplicationRecord validates :project_feature, presence: true validates :namespace, presence: true + validates :project_namespace, presence: true, if: -> { self.namespace && self.root_namespace.project_namespace_creation_enabled? } validates :name, uniqueness: { scope: :namespace_id } validates :import_url, public_url: { schemes: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS }, ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS }, @@ -2919,12 +2920,28 @@ class Project < ApplicationRecord end def ensure_project_namespace_in_sync - if changes.keys & [:name, :path, :namespace_id, :visibility_level] && project_namespace.present? - project_namespace.name = name - project_namespace.path = path - project_namespace.parent = namespace - project_namespace.visibility_level = visibility_level - end + # create project_namespace when project is created if create_project_namespace_on_project_create FF is enabled + build_project_namespace if project_namespace_creation_enabled? + + # regardless of create_project_namespace_on_project_create FF we need + # to keep project and project namespace in sync if there is one + sync_attributes(project_namespace) if sync_project_namespace? + end + + def project_namespace_creation_enabled? + new_record? && !project_namespace && self.namespace && self.root_namespace.project_namespace_creation_enabled? + end + + def sync_project_namespace? + (changes.keys & %w(name path namespace_id namespace visibility_level shared_runners_enabled)).any? && project_namespace.present? + end + + def sync_attributes(project_namespace) + project_namespace.name = name + project_namespace.path = path + project_namespace.parent = namespace + project_namespace.shared_runners_enabled = shared_runners_enabled + project_namespace.visibility_level = visibility_level end end diff --git a/app/models/repository.rb b/app/models/repository.rb index 7b24ae35125..c7054b89255 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1091,6 +1091,13 @@ class Repository after_create true + rescue Gitlab::Git::Repository::RepositoryExists + # We do not want to call `#after_create` given that we didn't create the + # repo, but we obviously have a mismatch between what's in our exists cache + # and actual on-disk state as seen by Gitaly. Let's thus expire our caches. + expire_status_cache + + nil end def create_from_bundle(bundle_path) diff --git a/app/presenters/packages/npm/package_presenter.rb b/app/presenters/packages/npm/package_presenter.rb index b9595eb6647..9e3308c2573 100644 --- a/app/presenters/packages/npm/package_presenter.rb +++ b/app/presenters/packages/npm/package_presenter.rb @@ -5,26 +5,37 @@ module Packages class PackagePresenter include API::Helpers::RelatedResourcesHelpers + # Allowed fields are those defined in the abbreviated form + # defined here: https://github.com/npm/registry/blob/master/docs/responses/package-metadata.md#abbreviated-version-object + # except: name, version, dist, dependencies and xDependencies. Those are generated by this presenter. + PACKAGE_JSON_ALLOWED_FIELDS = %w[deprecated bin directories dist engines _hasShrinkwrap].freeze + attr_reader :name, :packages - def initialize(name, packages) + def initialize(name, packages, include_metadata: false) @name = name @packages = packages + @include_metadata = include_metadata end def versions package_versions = {} packages.each_batch do |relation| - relation.including_dependency_links - .preload_files - .each do |package| - package_file = package.package_files.last + batched_packages = relation.including_dependency_links + .preload_files + + if @include_metadata + batched_packages = batched_packages.preload_npm_metadatum + end + + batched_packages.each do |package| + package_file = package.package_files.last - next unless package_file + next unless package_file - package_versions[package.version] = build_package_version(package, package_file) - end + package_versions[package.version] = build_package_version(package, package_file) + end end package_versions @@ -41,14 +52,14 @@ module Packages end def build_package_version(package, package_file) - { + abbreviated_package_json(package).merge( name: package.name, version: package.version, dist: { shasum: package_file.file_sha1, tarball: tarball_url(package, package_file) } - }.tap do |package_version| + ).tap do |package_version| package_version.merge!(build_package_dependencies(package)) end end @@ -79,6 +90,13 @@ module Packages Packages::Tag.for_packages(packages) .preload_package end + + def abbreviated_package_json(package) + return {} unless @include_metadata + + json = package.npm_metadatum&.package_json || {} + json.slice(*PACKAGE_JSON_ALLOWED_FIELDS) + end end end end diff --git a/app/services/dependency_proxy/find_or_create_blob_service.rb b/app/services/dependency_proxy/find_or_create_blob_service.rb index 0a6db6e3d34..1b43263a3ba 100644 --- a/app/services/dependency_proxy/find_or_create_blob_service.rb +++ b/app/services/dependency_proxy/find_or_create_blob_service.rb @@ -30,8 +30,7 @@ module DependencyProxy blob.save! end - # Technical debt: change to read_at https://gitlab.com/gitlab-org/gitlab/-/issues/341536 - blob.touch if from_cache + blob.read! if from_cache success(blob: blob, from_cache: from_cache) end diff --git a/app/services/dependency_proxy/find_or_create_manifest_service.rb b/app/services/dependency_proxy/find_or_create_manifest_service.rb index 055980c431f..aeb62be9f3a 100644 --- a/app/services/dependency_proxy/find_or_create_manifest_service.rb +++ b/app/services/dependency_proxy/find_or_create_manifest_service.rb @@ -58,6 +58,8 @@ module DependencyProxy def respond(from_cache: true) if @manifest + @manifest.read! + success(manifest: @manifest, from_cache: from_cache) else error('Failed to download the manifest from the external registry', 503) diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb index 1d5d9c38432..959e105694f 100644 --- a/app/services/packages/npm/create_package_service.rb +++ b/app/services/packages/npm/create_package_service.rb @@ -21,6 +21,10 @@ module Packages ::Packages::CreateDependencyService.new(package, package_dependencies).execute ::Packages::Npm::CreateTagService.new(package, dist_tag).execute + if Feature.enabled?(:packages_npm_abbreviated_metadata, project) + package.create_npm_metadatum!(package_json: version_data) + end + package end diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index ea20510f36e..b7ed9202b01 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -152,14 +152,12 @@ module Projects deleted_count = project.commit_statuses.delete_all - if deleted_count > 0 - Gitlab::AppLogger.info( - class: 'Projects::DestroyService', - project_id: project.id, - message: 'leftover commit statuses', - orphaned_commit_status_count: deleted_count - ) - end + Gitlab::AppLogger.info( + class: 'Projects::DestroyService', + project_id: project.id, + message: 'leftover commit statuses', + orphaned_commit_status_count: deleted_count + ) end # The project can have multiple webhooks with hundreds of thousands of web_hook_logs. diff --git a/app/validators/json_schemas/npm_package_json.json b/app/validators/json_schemas/npm_package_json.json new file mode 100644 index 00000000000..01bd874d214 --- /dev/null +++ b/app/validators/json_schemas/npm_package_json.json @@ -0,0 +1,26 @@ +{ + "description": "NPM package json metadata", + "type": "object", + "properties": { + "name": { "type": "string" }, + "version": { "type": "string" }, + "dist": { + "type": "object", + "properties": { + "tarball": { "type": "string" }, + "shasum": { "type": "string" } + }, + "additionalProperties": true, + "required": [ + "tarball", + "shasum" + ] + } + }, + "additionalProperties": true, + "required": [ + "name", + "version", + "dist" + ] +} diff --git a/app/views/import/github/new.html.haml b/app/views/import/github/new.html.haml index 3f7f929f766..ef6479f8be2 100644 --- a/app/views/import/github/new.html.haml +++ b/app/views/import/github/new.html.haml @@ -23,7 +23,7 @@ = form_tag personal_access_token_import_github_path, method: :post do .form-group %label.label-bold= _('Personal Access Token') - = text_field_tag :personal_access_token, '', class: 'form-control', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }, data: { qa_selector: 'personal_access_token_field' } + = text_field_tag :personal_access_token, '', class: 'form-control gl-form-input', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }, data: { qa_selector: 'personal_access_token_field' } %span.form-text.text-muted = import_github_personal_access_token_message diff --git a/app/views/jira_connect/subscriptions/index.html.haml b/app/views/jira_connect/subscriptions/index.html.haml index cbe9a860210..be2be7288f8 100644 --- a/app/views/jira_connect/subscriptions/index.html.haml +++ b/app/views/jira_connect/subscriptions/index.html.haml @@ -9,20 +9,9 @@ = link_to _('Sign in to GitLab'), jira_connect_users_path, target: '_blank', rel: 'noopener noreferrer', class: 'js-jira-connect-sign-in' %main.jira-connect-app.gl-px-5.gl-pt-7.gl-mx-auto - - if current_user.blank? && @subscriptions.empty? - .jira-connect-app-body.gl-px-5.gl-text-center - %h2= s_('JiraService|GitLab for Jira Configuration') - %p= s_('JiraService|Sign in to GitLab.com to get started.') + .js-jira-connect-app{ data: jira_connect_app_data(@subscriptions) } - .gl-mt-7 - = external_link _('Sign in to GitLab'), jira_connect_users_path, class: "btn gl-button btn-confirm js-jira-connect-sign-in" - - .gl-mt-7 - %p= s_('Integrations|Note: this integration only works with accounts on GitLab.com (SaaS).') - - else - .js-jira-connect-app{ data: jira_connect_app_data(@subscriptions) } - - %p.jira-connect-app-body.gl-px-5.gl-mt-7.gl-font-base.gl-text-center + %p.jira-connect-app-body.gl-px-5.gl-font-base.gl-text-center.gl-mx-auto %strong= s_('Integrations|Browser limitations') - browser_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">' - firefox_link_start = browser_link_start.html_safe % { url: 'https://www.mozilla.org/en-US/firefox/' } diff --git a/app/views/layouts/_published_experiments.html.haml b/app/views/layouts/_published_experiments.html.haml new file mode 100644 index 00000000000..717e1e633d2 --- /dev/null +++ b/app/views/layouts/_published_experiments.html.haml @@ -0,0 +1,4 @@ += javascript_tag(nonce: content_security_policy_nonce) do + :plain + gl = window.gl || {}; + gl.experiments = #{raw ApplicationExperiment.published_experiments.reject { |name, data| data[:excluded] }.to_json}; diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 899bf65de48..26e3d9b3b92 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -16,4 +16,5 @@ = render 'layouts/img_loader' + = render 'layouts/published_experiments' = yield :scripts_body diff --git a/app/views/projects/commit/_multiple_signatures_signature_badge.html.haml b/app/views/projects/commit/_multiple_signatures_signature_badge.html.haml new file mode 100644 index 00000000000..74515438af2 --- /dev/null +++ b/app/views/projects/commit/_multiple_signatures_signature_badge.html.haml @@ -0,0 +1,6 @@ +- title = capture do + = html_escape(_('This commit was signed with %{strong_open}multiple%{strong_close} signatures.')) % { strong_open: '<strong>'.html_safe, strong_close: '</strong>'.html_safe } + +- locals = { signature: signature, title: title, label: _('Unverified'), css_class: 'invalid', icon: 'status_notfound_borderless' } + += render partial: 'projects/commit/signature_badge', locals: locals diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 2fbaa5812c0..86cac7c8376 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -1,5 +1,4 @@ - add_page_startup_api_call discussions_path(@issue) -- add_page_startup_api_call notes_url - @gfm_form = true diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index c3b1d22a909..ae39bd60233 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -246,6 +246,15 @@ :weight: 1 :idempotent: true :tags: [] +- :name: cronjob:clusters_integrations_check_prometheus_health + :worker_name: Clusters::Integrations::CheckPrometheusHealthWorker + :feature_category: :incident_management + :has_external_dependencies: true + :urgency: :low + :resource_boundary: :unknown + :weight: 1 + :idempotent: true + :tags: [] - :name: cronjob:container_expiration_policy :worker_name: ContainerExpirationPolicyWorker :feature_category: :container_registry @@ -1087,15 +1096,6 @@ :idempotent: :tags: - :needs_own_queue -- :name: incident_management:clusters_integrations_check_prometheus_health - :worker_name: Clusters::Integrations::CheckPrometheusHealthWorker - :feature_category: :incident_management - :has_external_dependencies: true - :urgency: :low - :resource_boundary: :unknown - :weight: 2 - :idempotent: true - :tags: [] - :name: incident_management:incident_management_add_severity_system_note :worker_name: IncidentManagement::AddSeveritySystemNoteWorker :feature_category: :incident_management diff --git a/app/workers/clusters/integrations/check_prometheus_health_worker.rb b/app/workers/clusters/integrations/check_prometheus_health_worker.rb index 0a583003e6f..0c0d86e975c 100644 --- a/app/workers/clusters/integrations/check_prometheus_health_worker.rb +++ b/app/workers/clusters/integrations/check_prometheus_health_worker.rb @@ -12,7 +12,6 @@ module Clusters include CronjobQueue # rubocop:enable Scalability/CronWorkerContext - queue_namespace :incident_management feature_category :incident_management urgency :low diff --git a/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb b/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb index fed469e6dc8..6a1de00ce80 100644 --- a/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb +++ b/app/workers/dependency_proxy/image_ttl_group_policy_worker.rb @@ -13,9 +13,8 @@ module DependencyProxy def perform DependencyProxy::ImageTtlGroupPolicy.enabled.each do |policy| - # Technical Debt: change to read_before https://gitlab.com/gitlab-org/gitlab/-/issues/341536 - qualified_blobs = policy.group.dependency_proxy_blobs.active.updated_before(policy.ttl) - qualified_manifests = policy.group.dependency_proxy_manifests.active.updated_before(policy.ttl) + qualified_blobs = policy.group.dependency_proxy_blobs.active.read_before(policy.ttl) + qualified_manifests = policy.group.dependency_proxy_manifests.active.read_before(policy.ttl) enqueue_blob_cleanup_job if expire_artifacts(qualified_blobs, DependencyProxy::Blob) enqueue_manifest_cleanup_job if expire_artifacts(qualified_manifests, DependencyProxy::Manifest) |