Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
1cfe6678a3
commit
0a56f5aadb
|
|
@ -636,14 +636,11 @@ lib/gitlab/checks/
|
|||
/doc/administration/license_file.md @lciutacu
|
||||
/doc/administration/load_balancer.md @axil @eread
|
||||
/doc/administration/logs/ @axil @eread
|
||||
/doc/administration/logs/_index.md @lciutacu
|
||||
/doc/administration/maintenance_mode/ @axil
|
||||
/doc/administration/merge_request_diffs.md @aqualls
|
||||
/doc/administration/merge_requests_approvals.md @brendan777
|
||||
/doc/administration/moderate_users.md @lciutacu
|
||||
/doc/administration/monitoring/_index.md @lciutacu
|
||||
/doc/administration/monitoring/github_imports.md @ashrafkhamis
|
||||
/doc/administration/monitoring/performance/ @lciutacu
|
||||
/doc/administration/monitoring/prometheus/_index.md @axil @eread
|
||||
/doc/administration/monitoring/prometheus/registry_exporter.md @z_painter
|
||||
/doc/administration/nfs.md @axil @eread
|
||||
|
|
@ -665,7 +662,6 @@ lib/gitlab/checks/
|
|||
/doc/administration/read_only_gitlab.md @axil @eread
|
||||
/doc/administration/redis/ @axil
|
||||
/doc/administration/reference_architectures/ @axil @eread
|
||||
/doc/administration/reply_by_email.md @lciutacu
|
||||
/doc/administration/reply_by_email_postfix_setup.md @axil @eread
|
||||
/doc/administration/reporting/ @idurham
|
||||
/doc/administration/reporting/spamcheck.md @axil @eread
|
||||
|
|
@ -687,7 +683,6 @@ lib/gitlab/checks/
|
|||
/doc/administration/settings/gitaly_timeouts.md @eread
|
||||
/doc/administration/settings/import_and_export_settings.md @ashrafkhamis
|
||||
/doc/administration/settings/import_export_rate_limits.md @ashrafkhamis
|
||||
/doc/administration/settings/incident_management_rate_limits.md @lciutacu
|
||||
/doc/administration/settings/instance_template_repository.md @brendan777
|
||||
/doc/administration/settings/jira_cloud_app.md @msedlakjakubowski
|
||||
/doc/administration/settings/jira_cloud_app_troubleshooting.md @msedlakjakubowski
|
||||
|
|
@ -725,7 +720,6 @@ lib/gitlab/checks/
|
|||
/doc/api/access_requests.md @idurham
|
||||
/doc/api/admin/ @idurham
|
||||
/doc/api/admin_sidekiq_queues.md @axil @eread
|
||||
/doc/api/alert_management_alerts.md @lciutacu
|
||||
/doc/api/api_resources.md @ashrafkhamis
|
||||
/doc/api/appearance.md @idurham
|
||||
/doc/api/applications.md @idurham
|
||||
|
|
@ -750,7 +744,6 @@ lib/gitlab/checks/
|
|||
/doc/api/epic_issues.md @msedlakjakubowski
|
||||
/doc/api/epic_links.md @msedlakjakubowski
|
||||
/doc/api/epics.md @msedlakjakubowski
|
||||
/doc/api/error_tracking.md @lciutacu
|
||||
/doc/api/external_controls.md @eread
|
||||
/doc/api/geo_nodes.md @axil
|
||||
/doc/api/geo_sites.md @axil
|
||||
|
|
@ -1015,7 +1008,6 @@ lib/gitlab/checks/
|
|||
/doc/integration/snowflake.md @eread
|
||||
/doc/integration/sourcegraph.md @brendan777
|
||||
/doc/integration/trello_power_up.md @msedlakjakubowski
|
||||
/doc/operations/ @lciutacu
|
||||
/doc/policy/ @axil @eread
|
||||
/doc/security/ @idurham
|
||||
/doc/security/asset_proxy.md @msedlakjakubowski
|
||||
|
|
@ -1023,7 +1015,8 @@ lib/gitlab/checks/
|
|||
/doc/solutions/ @jfullam @Darwinjs @sbrightwell
|
||||
/doc/solutions/integrations/servicenow.md @ashrafkhamis
|
||||
/doc/subscriptions/ @lciutacu
|
||||
/doc/subscriptions/gitlab_com/ @lyspin
|
||||
/doc/subscriptions/gitlab_com/ @lciutacu
|
||||
/doc/subscriptions/gitlab_com/compute_minutes.md @lyspin
|
||||
/doc/subscriptions/gitlab_dedicated/ @lyspin
|
||||
/doc/topics/ @msedlakjakubowski
|
||||
/doc/topics/git/ @brendan777
|
||||
|
|
@ -1051,8 +1044,6 @@ lib/gitlab/checks/
|
|||
/doc/tutorials/left_sidebar/ @sselhorn
|
||||
/doc/tutorials/merge_requests/ @aqualls
|
||||
/doc/tutorials/move_personal_project_to_group/ @phillipwells
|
||||
/doc/tutorials/observability/ @lciutacu
|
||||
/doc/tutorials/product_analytics_onboarding_website_project/ @lciutacu
|
||||
/doc/tutorials/protected_workflow/ @aqualls
|
||||
/doc/tutorials/reviews/ @aqualls
|
||||
/doc/tutorials/scan_execution_policy/ @rlehmann1
|
||||
|
|
@ -1095,7 +1086,6 @@ lib/gitlab/checks/
|
|||
/doc/user/emoji_reactions.md @msedlakjakubowski
|
||||
/doc/user/enterprise_user/ @idurham
|
||||
/doc/user/get_started/get_started_managing_code.md @brendan777
|
||||
/doc/user/get_started/get_started_monitoring.md @lciutacu
|
||||
/doc/user/get_started/get_started_planning_work.md @msedlakjakubowski
|
||||
/doc/user/get_started/get_started_projects.md @phillipwells
|
||||
/doc/user/get_started/getting_started_gitlab_duo.md @sselhorn
|
||||
|
|
|
|||
|
|
@ -583,11 +583,16 @@ rspec:artifact-collector ee remainder:
|
|||
optional: true
|
||||
- job: rspec-ee system pg16 # 16 jobs
|
||||
optional: true
|
||||
- job: rspec fail-fast # 1 job
|
||||
optional: true
|
||||
- job: rspec-ee fail-fast # 1 job
|
||||
optional: true
|
||||
rules:
|
||||
- !reference ['.rails:rules:ee-only-migration', rules]
|
||||
- !reference ['.rails:rules:ee-only-background-migration', rules]
|
||||
- !reference ['.rails:rules:ee-only-integration', rules]
|
||||
- !reference ['.rails:rules:ee-only-system', rules]
|
||||
- !reference ['.rails:rules:rspec fail-fast', rules]
|
||||
|
||||
rspec:coverage:
|
||||
extends:
|
||||
|
|
|
|||
|
|
@ -58,6 +58,20 @@ _quarantine:
|
|||
# Test jobs
|
||||
# ------------------------------------------
|
||||
|
||||
# ========== Network limiting setup ===========
|
||||
# Only run a subset of smoke suite
|
||||
airgapped:
|
||||
extends:
|
||||
- .parallel
|
||||
- .omnibus-e2e
|
||||
- .with-ignored-runtime-data
|
||||
variables:
|
||||
QA_SCENARIO: Test::Instance::Airgapped
|
||||
QA_RSPEC_TAGS: --tag smoke --tag ~github --tag ~external_api_calls --tag ~skip_live_env
|
||||
rules:
|
||||
- !reference [.rules:test:omnibus-base, rules]
|
||||
- if: $QA_SUITES =~ /Test::Instance::Airgapped/
|
||||
|
||||
# Execute smallest test suite to validate omnibus package in dependency update merge request pipelines
|
||||
health-check:
|
||||
extends:
|
||||
|
|
@ -250,6 +264,20 @@ registry:
|
|||
- !reference [.rules:test:omnibus-base, rules]
|
||||
- if: $QA_SUITES =~ /Test::Integration::Registry/
|
||||
|
||||
# ========== Relative url ===========
|
||||
# Only run a subset of smoke suite
|
||||
relative-url:
|
||||
extends:
|
||||
- .parallel
|
||||
- .omnibus-e2e
|
||||
- .with-ignored-runtime-data
|
||||
variables:
|
||||
QA_SCENARIO: Test::Instance::RelativeUrl
|
||||
QA_RSPEC_TAGS: --tag smoke --tag ~orchestrated --tag ~skip_live_env
|
||||
rules:
|
||||
- !reference [.rules:test:omnibus-base, rules]
|
||||
- if: $QA_SUITES =~ /Test::Instance::All/
|
||||
|
||||
repository-storage:
|
||||
extends:
|
||||
- .omnibus-e2e
|
||||
|
|
@ -259,6 +287,40 @@ repository-storage:
|
|||
- !reference [.rules:test:omnibus-base, rules]
|
||||
- if: $QA_SUITES =~ /Test::Instance::RepositoryStorage/
|
||||
|
||||
# ========== Object Storage with MiniO ===========
|
||||
object-storage:
|
||||
extends:
|
||||
- .omnibus-e2e
|
||||
variables:
|
||||
QA_SCENARIO: Test::Instance::Image
|
||||
QA_RSPEC_TAGS: --tag object_storage
|
||||
GITLAB_QA_OPTS: --omnibus-config object_storage
|
||||
rules:
|
||||
- !reference [.rules:test:omnibus-base, rules]
|
||||
- if: $QA_SUITES =~ /Test::Instance::ObjectStorage/
|
||||
|
||||
# ========== Object Storage with AWS ===========
|
||||
object-storage-aws:
|
||||
extends:
|
||||
- object-storage
|
||||
variables:
|
||||
AWS_S3_ACCESS_KEY: $QA_AWS_S3_ACCESS_KEY
|
||||
AWS_S3_BUCKET_NAME: $QA_AWS_S3_BUCKET_NAME
|
||||
AWS_S3_KEY_ID: $QA_AWS_S3_KEY_ID
|
||||
AWS_S3_REGION: $QA_AWS_S3_REGION
|
||||
GITLAB_QA_OPTS: --omnibus-config object_storage_aws
|
||||
|
||||
# ========== Object Storage with GCS ===========
|
||||
object-storage-gcs:
|
||||
extends:
|
||||
- object-storage
|
||||
variables:
|
||||
GCS_BUCKET_NAME: $QA_GCS_BUCKET_NAME
|
||||
GOOGLE_PROJECT: $QA_GOOGLE_PROJECT
|
||||
GOOGLE_JSON_KEY: $QA_GOOGLE_JSON_KEY
|
||||
GOOGLE_CLIENT_EMAIL: $QA_GOOGLE_CLIENT_EMAIL
|
||||
GITLAB_QA_OPTS: --omnibus-config object_storage_gcs
|
||||
|
||||
service-ping-disabled:
|
||||
extends:
|
||||
- .omnibus-e2e
|
||||
|
|
|
|||
|
|
@ -83,6 +83,5 @@ Gitlab/NoFindInWorkers:
|
|||
- 'ee/app/workers/groups/update_repository_storage_worker.rb'
|
||||
- 'ee/app/workers/namespaces/cascade_duo_features_enabled_worker.rb'
|
||||
- 'ee/app/workers/namespaces/storage_usage_export_worker.rb'
|
||||
- 'ee/app/workers/repository_update_mirror_worker.rb'
|
||||
- 'ee/app/workers/requirements_management/import_requirements_csv_worker.rb'
|
||||
- 'ee/app/workers/work_items/rolledup_dates/bulk_update_handler.rb'
|
||||
|
|
|
|||
|
|
@ -38,9 +38,7 @@ export default {
|
|||
}
|
||||
|
||||
if (!this.hasMergeRequests) {
|
||||
return __(
|
||||
"Merge requests are a place to propose changes you've made to a project and discuss those changes with others",
|
||||
);
|
||||
return __('Make a merge request to propose changes to this project.');
|
||||
}
|
||||
|
||||
if (this.isOpenTab) {
|
||||
|
|
@ -55,7 +53,7 @@ export default {
|
|||
}
|
||||
|
||||
if (!this.hasMergeRequests) {
|
||||
return __('Interested parties can even contribute by pushing commits if they want to.');
|
||||
return __('Others can contribute by pushing commits to the same branch.');
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { GlCard, GlIcon, GlLink, GlButton, GlToggle, GlAlert } from '@gitlab/ui'
|
|||
import { s__ } from '~/locale';
|
||||
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
|
||||
import SetValidityChecks from '~/security_configuration/graphql/set_validity_checks.graphql';
|
||||
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
|
||||
import { helpPagePath } from '~/helpers/help_page_helper';
|
||||
|
||||
export default {
|
||||
|
|
@ -18,8 +17,12 @@ export default {
|
|||
GlAlert,
|
||||
ManageViaMr,
|
||||
},
|
||||
mixins: [glFeatureFlagsMixin()],
|
||||
inject: ['projectFullPath', 'validityChecksEnabled', 'validityChecksAvailable'],
|
||||
inject: [
|
||||
'projectFullPath',
|
||||
'userIsProjectAdmin',
|
||||
'validityChecksEnabled',
|
||||
'validityChecksAvailable',
|
||||
],
|
||||
props: {
|
||||
feature: {
|
||||
type: Object,
|
||||
|
|
@ -63,11 +66,12 @@ export default {
|
|||
showManageViaMr() {
|
||||
return ManageViaMr.canRender(this.feature);
|
||||
},
|
||||
shouldRenderValidityChecks() {
|
||||
return this.glFeatures.validityChecks;
|
||||
},
|
||||
isToggleDisabled() {
|
||||
return !this.validityChecksAvailable || !this.pipelineSecretDetectionEnabled;
|
||||
return (
|
||||
!this.validityChecksAvailable ||
|
||||
!this.pipelineSecretDetectionEnabled ||
|
||||
!this.userIsProjectAdmin
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -161,7 +165,7 @@ export default {
|
|||
</gl-button>
|
||||
</div>
|
||||
|
||||
<div v-if="shouldRenderValidityChecks" class="gl-mt-6" data-testid="validity-checks-section">
|
||||
<div v-if="validityChecksAvailable" class="gl-mt-6" data-testid="validity-checks-section">
|
||||
<gl-alert
|
||||
v-if="shouldShowAlert"
|
||||
class="gl-mb-5"
|
||||
|
|
@ -195,13 +199,6 @@ export default {
|
|||
:disabled="isToggleDisabled"
|
||||
@change="onValidityChecksToggle"
|
||||
/>
|
||||
<span class="gl-ml-3 gl-text-sm">
|
||||
{{
|
||||
localValidityChecksEnabled
|
||||
? s__('SecurityConfiguration|Enabled')
|
||||
: s__('SecurityConfiguration|Not enabled')
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -507,15 +507,32 @@ export const getNewWorkItemSharedCache = ({
|
|||
}
|
||||
|
||||
if (widgetName === WIDGET_TYPE_HIERARCHY) {
|
||||
// Get parent value from shared widget localStorage entry.
|
||||
const cachedParent = sharedCacheWidgets[WIDGET_TYPE_HIERARCHY]?.parent || null;
|
||||
// Get parent value from type-specific localStorage entry.
|
||||
const typeSpecificParent =
|
||||
workItemTypeSpecificWidgets[WIDGET_TYPE_HIERARCHY]?.parent || null;
|
||||
|
||||
// Set fallback parent value
|
||||
let parent = workItemTypeSpecificWidgets[WIDGET_TYPE_HIERARCHY] ? typeSpecificParent : null;
|
||||
|
||||
if (cachedParent) {
|
||||
// Set parent from cached parent only if it is compatible
|
||||
// with current work item type, fall back to type-specific parent otherwise.
|
||||
const allowedParentTypes =
|
||||
widgetDefinitions.find((widget) => widget.type === WIDGET_TYPE_HIERARCHY)
|
||||
?.allowedParentTypes?.nodes || [];
|
||||
|
||||
parent = allowedParentTypes.some((type) => type.id === cachedParent.workItemType.id)
|
||||
? cachedParent
|
||||
: typeSpecificParent;
|
||||
}
|
||||
|
||||
widgets.push({
|
||||
type: 'HIERARCHY',
|
||||
hasChildren: false,
|
||||
hasParent: false,
|
||||
// We're not using `sharedCacheWidgets` for hierarchy parent as
|
||||
// each work item type can have its own allowed hierarchy parent types.
|
||||
parent: workItemTypeSpecificWidgets[WIDGET_TYPE_HIERARCHY]
|
||||
? workItemTypeSpecificWidgets[WIDGET_TYPE_HIERARCHY]?.parent || null
|
||||
: null,
|
||||
parent,
|
||||
depthLimitReachedByType: [],
|
||||
rolledUpCountsByType: [],
|
||||
children: {
|
||||
|
|
@ -540,17 +557,57 @@ export const getNewWorkItemSharedCache = ({
|
|||
}
|
||||
|
||||
if (widgetName === WIDGET_TYPE_CUSTOM_FIELDS) {
|
||||
// Get available custom fields for this work item type
|
||||
const customFieldsWidgetData = widgetDefinitions.find(
|
||||
(definition) => definition.type === WIDGET_TYPE_CUSTOM_FIELDS,
|
||||
);
|
||||
const availableCustomFieldValues = customFieldsWidgetData.customFieldValues;
|
||||
// Get custom fields with set values from shared widget localStorage entry.
|
||||
const cachedCustomFieldValues =
|
||||
sharedCacheWidgets[WIDGET_TYPE_CUSTOM_FIELDS]?.customFieldValues;
|
||||
// Get custom fields with set values from type-specific localStorage entry.
|
||||
const typeSpecificCustomFieldValues =
|
||||
workItemTypeSpecificWidgets[WIDGET_TYPE_CUSTOM_FIELDS]?.customFieldValues || [];
|
||||
|
||||
// Set fallback custom fields value.
|
||||
let customFieldValues = workItemTypeSpecificWidgets[WIDGET_TYPE_CUSTOM_FIELDS]
|
||||
? typeSpecificCustomFieldValues
|
||||
: customFieldsWidgetData?.customFieldValues ?? [];
|
||||
|
||||
if (cachedCustomFieldValues && availableCustomFieldValues) {
|
||||
// Create a merged list of custom fields and its values from shared cache & type-specific cache
|
||||
customFieldValues = availableCustomFieldValues.map((availableField) => {
|
||||
const cachedField = cachedCustomFieldValues.find(
|
||||
(cached) => cached.customField.id === availableField.customField.id,
|
||||
);
|
||||
const typeSpecificField = typeSpecificCustomFieldValues.find(
|
||||
(typeField) => typeField.customField.id === availableField.customField.id,
|
||||
);
|
||||
|
||||
// Grab appropriate field value
|
||||
let fieldValue = {};
|
||||
if (cachedField?.selectedOptions || typeSpecificField?.selectedOptions) {
|
||||
fieldValue = {
|
||||
selectedOptions: cachedField?.selectedOptions || typeSpecificField?.selectedOptions,
|
||||
};
|
||||
} else if (cachedField?.value || typeSpecificField?.value) {
|
||||
fieldValue = { value: cachedField?.value || typeSpecificField?.value };
|
||||
}
|
||||
|
||||
// Set field value only if present, return empty field otherwise
|
||||
if (Object.keys(fieldValue).length) {
|
||||
return {
|
||||
...availableField,
|
||||
...fieldValue,
|
||||
};
|
||||
}
|
||||
return { ...availableField };
|
||||
});
|
||||
}
|
||||
|
||||
widgets.push({
|
||||
type: WIDGET_TYPE_CUSTOM_FIELDS,
|
||||
// We're not using `sharedCacheWidgets` for custom fields as
|
||||
// each work item type can have its own allowed custom fields.
|
||||
customFieldValues: workItemTypeSpecificWidgets[WIDGET_TYPE_CUSTOM_FIELDS]
|
||||
? workItemTypeSpecificWidgets[WIDGET_TYPE_CUSTOM_FIELDS]?.customFieldValues || []
|
||||
: customFieldsWidgetData?.customFieldValues ?? [],
|
||||
customFieldValues,
|
||||
__typename: 'WorkItemWidgetCustomFields',
|
||||
});
|
||||
}
|
||||
|
|
@ -613,7 +670,7 @@ export const setNewWorkItemCache = async ({
|
|||
let draftDescription = '';
|
||||
|
||||
// Experimental support for shared widget data across work item types
|
||||
if (gon.features.workItemsAlpha) {
|
||||
if (gon.features.workItemsBeta) {
|
||||
const sharedCache = getNewWorkItemSharedCache({
|
||||
workItemAttributesWrapperOrder,
|
||||
widgetDefinitions,
|
||||
|
|
|
|||
|
|
@ -191,6 +191,7 @@ class Note < ApplicationRecord
|
|||
after_commit :trigger_note_subscription_update, on: :update
|
||||
after_commit :trigger_note_subscription_destroy, on: :destroy
|
||||
after_commit :broadcast_noteable_notes_changed, unless: :importing?
|
||||
after_commit :trigger_work_item_updated_subscription, on: :create, if: :system?
|
||||
|
||||
def trigger_note_subscription_create
|
||||
return unless trigger_note_subscription?
|
||||
|
|
@ -221,6 +222,13 @@ class Note < ApplicationRecord
|
|||
GraphqlTriggers.work_item_note_deleted(noteable.to_work_item_global_id, deleted_note_data)
|
||||
end
|
||||
|
||||
def trigger_work_item_updated_subscription
|
||||
return unless trigger_note_subscription?
|
||||
return unless system_note_work_item_reference?
|
||||
|
||||
GraphqlTriggers.work_item_updated(noteable)
|
||||
end
|
||||
|
||||
class << self
|
||||
extend Gitlab::Utils::Override
|
||||
|
||||
|
|
@ -835,6 +843,10 @@ class Note < ApplicationRecord
|
|||
def set_internal_flag
|
||||
self.internal = confidential if confidential
|
||||
end
|
||||
|
||||
def system_note_work_item_reference?
|
||||
note.present? && system_note_metadata&.about_relation?
|
||||
end
|
||||
end
|
||||
|
||||
Note.prepend_mod
|
||||
|
|
|
|||
|
|
@ -21,6 +21,19 @@ class SystemNoteMetadata < ApplicationRecord
|
|||
cloned
|
||||
].freeze
|
||||
|
||||
WORK_ITEMS_CROSS_REFERENCE = %w[
|
||||
branch
|
||||
commit
|
||||
cross_reference
|
||||
merge
|
||||
relate
|
||||
unrelate
|
||||
unrelate_from_parent
|
||||
unrelate_from_child
|
||||
relate_to_parent
|
||||
relate_to_child
|
||||
].freeze
|
||||
|
||||
ICON_TYPES = %w[
|
||||
commit description merge confidential visible label assignee cross_reference
|
||||
designs_added designs_modified designs_removed designs_discussion_added
|
||||
|
|
@ -45,6 +58,10 @@ class SystemNoteMetadata < ApplicationRecord
|
|||
note
|
||||
end
|
||||
|
||||
def about_relation?
|
||||
action.in?(WORK_ITEMS_CROSS_REFERENCE)
|
||||
end
|
||||
|
||||
def icon_types
|
||||
ICON_TYPES
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,8 +24,7 @@ module Projects
|
|||
secret_push_protection_available:
|
||||
Gitlab::CurrentSettings.current_application_settings.secret_push_protection_available,
|
||||
secret_push_protection_enabled: secret_push_protection_enabled,
|
||||
validity_checks_available:
|
||||
Ability.allowed?(current_user, :configure_secret_detection_validity_checks, project),
|
||||
validity_checks_available: validity_checks_available,
|
||||
validity_checks_enabled: validity_checks_enabled,
|
||||
user_is_project_admin: user_is_project_admin?,
|
||||
secret_detection_configuration_path: secret_detection_configuration_path
|
||||
|
|
@ -105,6 +104,7 @@ module Projects
|
|||
project.security_setting
|
||||
end
|
||||
|
||||
def validity_checks_available; end
|
||||
def validity_checks_enabled; end
|
||||
def container_scanning_for_registry_enabled; end
|
||||
def secret_push_protection_enabled; end
|
||||
|
|
|
|||
|
|
@ -29,14 +29,13 @@
|
|||
- else
|
||||
= render Pajamas::EmptyStateComponent.new(svg_path: 'illustrations/empty-state/empty-merge-requests-md.svg',
|
||||
empty_state_options: { data: { testid: 'issuable-empty-state' } },
|
||||
title: _("Merge requests are a place to propose changes you've made to a project and discuss those changes with others")) do |c|
|
||||
title: _("Make a merge request to propose changes to this project.")) do |c|
|
||||
|
||||
- c.with_description do
|
||||
= _("Interested parties can even contribute by pushing commits if they want to.")
|
||||
= _("Others can contribute by pushing commits to the same branch.")
|
||||
- if button_path
|
||||
.gl-mt-5= link_button_to button_text, button_path,
|
||||
title: button_text,
|
||||
id: 'new_merge_request_link',
|
||||
variant: :confirm,
|
||||
data: { testid: "new-merge-request-button", **tracking_data }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ActionDispatchRoutingRedirectPatch
|
||||
def build_response(req)
|
||||
response = super
|
||||
|
||||
uri = response.headers['Location'].to_s
|
||||
body = %(<html><body>You are being <a href="#{ERB::Util.unwrapped_html_escape(uri)}">redirected</a>.</body></html>)
|
||||
|
||||
response.body = body
|
||||
response.headers["Content-Length"] = body.length.to_s
|
||||
|
||||
response
|
||||
end
|
||||
end
|
||||
|
||||
module ActionControllerRedirectingPatch
|
||||
def redirect_to(*, **)
|
||||
super
|
||||
|
||||
uri = ERB::Util.unwrapped_html_escape(response.location)
|
||||
self.response_body = "<html><body>You are being <a href=\"#{uri}\">redirected</a>.</body></html>"
|
||||
end
|
||||
end
|
||||
|
||||
ActionController::Redirecting.prepend(ActionControllerRedirectingPatch)
|
||||
ActionDispatch::Routing::Redirect.prepend(ActionDispatchRoutingRedirectPatch)
|
||||
|
|
@ -8,14 +8,6 @@ description: https://docs.gitlab.com/ee/operations/feature_flags.html#feature-fl
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/24819
|
||||
milestone: '12.8'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: strategy_id
|
||||
table: operations_strategies
|
||||
sharding_key: project_id
|
||||
belongs_to: strategy
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
table_size: small
|
||||
desired_sharding_key_migration_job_name: BackfillOperationsScopesProjectId
|
||||
|
|
|
|||
|
|
@ -8,14 +8,6 @@ description: Join table relating packages_package_files and ci_pipelines
|
|||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44348
|
||||
milestone: '13.6'
|
||||
gitlab_schema: gitlab_main_cell
|
||||
desired_sharding_key:
|
||||
project_id:
|
||||
references: projects
|
||||
backfill_via:
|
||||
parent:
|
||||
foreign_key: package_file_id
|
||||
table: packages_package_files
|
||||
sharding_key: project_id
|
||||
belongs_to: package_file
|
||||
sharding_key:
|
||||
project_id: projects
|
||||
table_size: medium
|
||||
desired_sharding_key_migration_job_name: BackfillPackagesPackageFileBuildInfosProjectId
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddPackagesPackageFileBuildInfosProjectIdNotNull < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.2'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_not_null_constraint :packages_package_file_build_infos, :project_id
|
||||
end
|
||||
|
||||
def down
|
||||
remove_not_null_constraint :packages_package_file_build_infos, :project_id
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddOperationsScopesProjectIdNotNull < Gitlab::Database::Migration[2.3]
|
||||
milestone '18.2'
|
||||
disable_ddl_transaction!
|
||||
|
||||
def up
|
||||
add_not_null_constraint :operations_scopes, :project_id
|
||||
end
|
||||
|
||||
def down
|
||||
remove_not_null_constraint :operations_scopes, :project_id
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
78253d3d6bafd8609d2430f9a16115e044f13be5f50998d567cb09c2ca1673ab
|
||||
|
|
@ -0,0 +1 @@
|
|||
a0729cad8b034caf95885d7381af5b10530310daf6d7439dacffb0a714c6027e
|
||||
|
|
@ -18925,7 +18925,8 @@ CREATE TABLE operations_scopes (
|
|||
id bigint NOT NULL,
|
||||
strategy_id bigint NOT NULL,
|
||||
environment_scope character varying(255) NOT NULL,
|
||||
project_id bigint
|
||||
project_id bigint,
|
||||
CONSTRAINT check_722a570b84 CHECK ((project_id IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE operations_scopes_id_seq
|
||||
|
|
@ -19888,7 +19889,8 @@ CREATE TABLE packages_package_file_build_infos (
|
|||
id bigint NOT NULL,
|
||||
package_file_id bigint NOT NULL,
|
||||
pipeline_id bigint,
|
||||
project_id bigint
|
||||
project_id bigint,
|
||||
CONSTRAINT check_102fc16781 CHECK ((project_id IS NOT NULL))
|
||||
);
|
||||
|
||||
CREATE SEQUENCE packages_package_file_build_infos_id_seq
|
||||
|
|
|
|||
|
|
@ -56,3 +56,22 @@ first attempt to load balance the connection across the replica hosts.
|
|||
It looks for the next `online` replica host and yields a connection from the host's connection pool.
|
||||
A replica host is considered `online` if it is up-to-date with the primary, based on
|
||||
either the replication lag size or time. The thresholds for these requirements are configurable.
|
||||
|
||||
## Deployment Strategy
|
||||
|
||||
When rolling out changes via feature flag, consider deploying exclusively to Sidekiq pods initially to minimize risk.
|
||||
|
||||
Why Sidekiq-first deployment:
|
||||
|
||||
- Keeps the API pods stable ensuring ChatOps remains available to disable feature flags in worst case scenario.
|
||||
- Background jobs can retry automatically without any intervention.
|
||||
|
||||
Implementation example:
|
||||
|
||||
```ruby
|
||||
if feature_flag_enabled? && Gitlab::Runtime.sidekiq?
|
||||
new_changes
|
||||
else
|
||||
existing_changes
|
||||
end
|
||||
```
|
||||
|
|
|
|||
|
|
@ -58,6 +58,8 @@ Join the conversation about interesting ways to use GitLab O11y in the GitLab O1
|
|||
|
||||
## Set up a GitLab Observability instance
|
||||
|
||||
Observability data is collected in a separate application outside of your GitLab.com instance. Problems with your GitLab instance do not impact collecting or viewing your observability data and vice-versa.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have an EC2 instance or similar virtual machine with:
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ namespace :tw do
|
|||
CodeOwnerRule.new('Pipeline Authoring', '@marcel.amirault'),
|
||||
CodeOwnerRule.new('Pipeline Execution', '@lyspin'),
|
||||
CodeOwnerRule.new('Pipeline Security', '@marcel.amirault'),
|
||||
CodeOwnerRule.new('Platform Insights', '@lciutacu'),
|
||||
# CodeOwnerRule.new('Platform Insights', ''),
|
||||
CodeOwnerRule.new('Product Planning', '@msedlakjakubowski'),
|
||||
CodeOwnerRule.new('Project Management', '@msedlakjakubowski'),
|
||||
CodeOwnerRule.new('Provision', '@lciutacu'),
|
||||
|
|
@ -74,6 +74,7 @@ namespace :tw do
|
|||
# CodeOwnerRule.new('Respond', ''),
|
||||
CodeOwnerRule.new('Runner', '@rsarangadharan'),
|
||||
CodeOwnerRule.new('Hosted Runners', '@rsarangadharan'),
|
||||
CodeOwnerRule.new('Seat Management', '@lciutacu'),
|
||||
# CodeOwnerRule.new('Security Infrastructure', ''),
|
||||
CodeOwnerRule.new('Security Policies', '@rlehmann1'),
|
||||
CodeOwnerRule.new('Secret Detection', '@phillipwells'),
|
||||
|
|
@ -82,7 +83,7 @@ namespace :tw do
|
|||
CodeOwnerRule.new('Solutions Architecture', '@jfullam @Darwinjs @sbrightwell'),
|
||||
CodeOwnerRule.new('Source Code', '@brendan777'),
|
||||
CodeOwnerRule.new('Static Analysis', '@rdickenson'),
|
||||
# CodeOwnerRule.new('Subscription Management', ''),
|
||||
CodeOwnerRule.new('Subscription Management', '@lciutacu'),
|
||||
CodeOwnerRule.new('Switchboard', '@lyspin'),
|
||||
CodeOwnerRule.new('Testing', '@eread'),
|
||||
CodeOwnerRule.new('Tutorials', '@gl-docsteam'),
|
||||
|
|
|
|||
|
|
@ -33533,9 +33533,6 @@ msgstr ""
|
|||
msgid "Interactive mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Interested parties can even contribute by pushing commits if they want to."
|
||||
msgstr ""
|
||||
|
||||
msgid "Internal"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -37206,6 +37203,9 @@ msgstr ""
|
|||
msgid "Maintenance mode"
|
||||
msgstr ""
|
||||
|
||||
msgid "Make a merge request to propose changes to this project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Make adjustments to how your GitLab instance is set up."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -38691,9 +38691,6 @@ msgstr ""
|
|||
msgid "Merge requests"
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
|
||||
msgstr ""
|
||||
|
||||
msgid "Merge requests awaiting your review."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -43603,6 +43600,9 @@ msgstr ""
|
|||
msgid "Other visibility settings have been disabled by the administrator."
|
||||
msgstr ""
|
||||
|
||||
msgid "Others can contribute by pushing commits to the same branch."
|
||||
msgstr ""
|
||||
|
||||
msgid "Otherwise, click the link below to complete the process."
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -60,10 +60,10 @@
|
|||
"@gitlab/application-sdk-browser": "^0.3.4",
|
||||
"@gitlab/at.js": "1.5.7",
|
||||
"@gitlab/cluster-client": "^3.0.0",
|
||||
"@gitlab/duo-ui": "^8.22.1",
|
||||
"@gitlab/duo-ui": "^8.23.0",
|
||||
"@gitlab/favicon-overlay": "2.0.0",
|
||||
"@gitlab/fonts": "^1.3.0",
|
||||
"@gitlab/query-language-rust": "0.11.1",
|
||||
"@gitlab/query-language-rust": "0.11.3",
|
||||
"@gitlab/svgs": "3.137.0",
|
||||
"@gitlab/ui": "114.8.1",
|
||||
"@gitlab/vue-router-vue3": "npm:vue-router@4.5.1",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ module QA
|
|||
|
||||
tags "~github", "~external_api_calls", "~skip_live_env", *Specs::Runner::DEFAULT_SKIPPED_TAGS
|
||||
|
||||
pipeline_mappings test_on_omnibus_nightly: ["airgapped"]
|
||||
pipeline_mappings test_on_omnibus: %w[airgapped],
|
||||
test_on_omnibus_nightly: %w[airgapped]
|
||||
|
||||
def perform(address, *rspec_options)
|
||||
Runtime::Scenario.define(:network, 'airgapped')
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ module QA
|
|||
|
||||
pipeline_mappings test_on_cng: %w[cng-instance],
|
||||
test_on_gdk: %w[gdk-instance gdk-instance-gitaly-transactions gdk-instance-ff-inverse],
|
||||
test_on_omnibus: %w[instance git-sha256-repositories],
|
||||
test_on_omnibus: %w[instance git-sha256-repositories relative-url],
|
||||
test_on_omnibus_nightly: %w[
|
||||
instance-image-slow-network
|
||||
nplus1-instance-image
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ module QA
|
|||
class ObjectStorage < All
|
||||
tags :object_storage
|
||||
|
||||
pipeline_mappings test_on_omnibus_nightly: %w[object-storage object-storage-aws object-storage-gcs]
|
||||
pipeline_mappings test_on_omnibus: %w[object-storage object-storage-aws object-storage-gcs],
|
||||
test_on_omnibus_nightly: %w[object-storage object-storage-aws object-storage-gcs]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ module QA
|
|||
# @return [Array] scenarios that never run in test-on-omnibus pipeline
|
||||
IGNORED_SCENARIOS = [
|
||||
"QA::EE::Scenario::Test::Geo",
|
||||
"QA::Scenario::Test::Instance::Airgapped",
|
||||
"QA::Scenario::Test::Sanity::Selectors"
|
||||
].freeze
|
||||
|
||||
|
|
|
|||
|
|
@ -456,6 +456,7 @@ RSpec.describe ProjectsController, feature_category: :groups_and_projects do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:found)
|
||||
expect(response).to redirect_to(project_path(public_project, ref: 'master', path: '/.gitlab-ci.yml'))
|
||||
expect(response.body).to match(/You.are.being.+redirected/)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ describe('Merge request list app empty state component', () => {
|
|||
createComponent({ hasMergeRequests: false });
|
||||
|
||||
expect(findEmptyState().attributes('title')).toBe(
|
||||
"Merge requests are a place to propose changes you've made to a project and discuss those changes with others",
|
||||
'Make a merge request to propose changes to this project.',
|
||||
);
|
||||
expect(findEmptyState().attributes('description')).toBe(
|
||||
'Interested parties can even contribute by pushing commits if they want to.',
|
||||
'Others can contribute by pushing commits to the same branch.',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ describe('PipelineSecretDetectionFeatureCard component', () => {
|
|||
provide: {
|
||||
projectFullPath: 'group/project',
|
||||
userIsProjectAdmin: true,
|
||||
glFeatures: { validityChecks: true },
|
||||
validityChecksEnabled: false,
|
||||
validityChecksAvailable: true,
|
||||
...provide,
|
||||
|
|
@ -240,145 +239,151 @@ describe('PipelineSecretDetectionFeatureCard component', () => {
|
|||
});
|
||||
|
||||
describe('validity checks section', () => {
|
||||
beforeEach(() => {
|
||||
feature = makeFeature({ available: true });
|
||||
});
|
||||
it.each`
|
||||
validityChecksAvailable | shouldRender
|
||||
${true} | ${true}
|
||||
${false} | ${false}
|
||||
`(
|
||||
'should render $shouldRender when validityChecksAvailable=$validityChecksAvailable',
|
||||
({ validityChecksAvailable, shouldRender }) => {
|
||||
feature = makeFeature({ available: true });
|
||||
createComponent({}, { validityChecksAvailable });
|
||||
|
||||
it('is shown when feature flag is enabled', () => {
|
||||
createComponent({}, { glFeatures: { validityChecks: true } });
|
||||
expect(findValidityChecksSection().exists()).toBe(true);
|
||||
});
|
||||
expect(findValidityChecksSection().exists()).toBe(shouldRender);
|
||||
},
|
||||
);
|
||||
|
||||
it('is not shown when feature flag is disabled', () => {
|
||||
createComponent({}, { glFeatures: { validityChecks: false } });
|
||||
expect(findValidityChecksSection().exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('is not shown when feature is unavailable', () => {
|
||||
feature = makeFeature({ available: false });
|
||||
createComponent({}, { glFeatures: { validityChecks: true } });
|
||||
expect(findValidityChecksSection().exists()).toBe(false);
|
||||
});
|
||||
|
||||
describe('validity checks toggle', () => {
|
||||
it('has the correct default value when validityChecksEnabled is true', () => {
|
||||
createComponent({}, { validityChecksEnabled: true });
|
||||
const toggle = findValidityChecksToggle();
|
||||
expect(toggle.props('value')).toBe(true);
|
||||
});
|
||||
|
||||
it('has the correct default value when validityChecksEnabled is false', () => {
|
||||
createComponent({}, { validityChecksEnabled: false });
|
||||
const toggle = findValidityChecksToggle();
|
||||
expect(toggle.props('value')).toBe(false);
|
||||
});
|
||||
|
||||
it('is unlocked when validityChecksAvailable is true and pipeline secret detection is configured', () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent({}, { validityChecksAvailable: true });
|
||||
const toggle = findValidityChecksToggle();
|
||||
expect(toggle.props('disabled')).toBe(false);
|
||||
});
|
||||
|
||||
it('is locked when validityChecksAvailable is false', () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent({}, { validityChecksAvailable: false });
|
||||
const toggle = findValidityChecksToggle();
|
||||
expect(toggle.props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('is locked when pipeline secret detection is not configured', () => {
|
||||
feature = makeFeature({ available: true, configured: false });
|
||||
createComponent({}, { validityChecksAvailable: true });
|
||||
const toggle = findValidityChecksToggle();
|
||||
expect(toggle.props('disabled')).toBe(true);
|
||||
});
|
||||
|
||||
it('calls mutation on toggle change with correct payload', async () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent();
|
||||
const toggle = findValidityChecksToggle();
|
||||
expect(toggle.props('value')).toBe(false);
|
||||
toggle.vm.$emit('change', true);
|
||||
|
||||
expect(requestHandlers.setMutationHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
namespacePath: 'group/project',
|
||||
enable: true,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(toggle.props('value')).toBe(true);
|
||||
expect(wrapper.text()).toContain('Enabled');
|
||||
});
|
||||
|
||||
it('shows success toast when toggle succeeds', async () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent();
|
||||
|
||||
const toggle = findValidityChecksToggle();
|
||||
expect(toggle.props('value')).toBe(false);
|
||||
|
||||
toggle.vm.$emit('change', true);
|
||||
|
||||
expect(requestHandlers.setMutationHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
namespacePath: 'group/project',
|
||||
enable: true,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(toggle.props('value')).toBe(true);
|
||||
expect(mockToastShow).toHaveBeenCalledWith('Validity checks enabled');
|
||||
});
|
||||
|
||||
it('shows error alert when an error message is set', async () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent();
|
||||
|
||||
requestHandlers.setMutationHandler.mockReset();
|
||||
requestHandlers.setMutationHandler.mockResolvedValue({
|
||||
data: {
|
||||
setValidityChecks: {
|
||||
validityChecksEnabled: null,
|
||||
errors: ['data response with errors'],
|
||||
describe('toggle state', () => {
|
||||
it.each`
|
||||
available | configured | userIsProjectAdmin | shouldBeDisabled
|
||||
${true} | ${true} | ${true} | ${false}
|
||||
${true} | ${false} | ${true} | ${true}
|
||||
${true} | ${true} | ${false} | ${true}
|
||||
${true} | ${false} | ${false} | ${true}
|
||||
`(
|
||||
'disabled=$shouldBeDisabled when available=$available, configured=$configured, userIsProjectAdmin=$userIsProjectAdmin',
|
||||
({ available, configured, userIsProjectAdmin, shouldBeDisabled }) => {
|
||||
feature = makeFeature({ available, configured });
|
||||
createComponent(
|
||||
{},
|
||||
{
|
||||
validityChecksAvailable: true,
|
||||
userIsProjectAdmin,
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
const toggle = findValidityChecksToggle();
|
||||
toggle.vm.$emit('change', true);
|
||||
expect(findValidityChecksToggle().props('disabled')).toBe(shouldBeDisabled);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
describe('toggle value', () => {
|
||||
it.each`
|
||||
validityChecksEnabled | expectedValue
|
||||
${true} | ${true}
|
||||
${false} | ${false}
|
||||
`(
|
||||
'value is $expectedValue when validityChecksEnabled=$validityChecksEnabled',
|
||||
({ validityChecksEnabled, expectedValue }) => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent(
|
||||
{},
|
||||
{
|
||||
validityChecksAvailable: true,
|
||||
validityChecksEnabled,
|
||||
userIsProjectAdmin: true,
|
||||
},
|
||||
);
|
||||
|
||||
expect(findValidityChecksAlert().exists()).toBe(true);
|
||||
expect(findValidityChecksToggle().props('value')).toBe(expectedValue);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('calls mutation on toggle change with correct payload', async () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent();
|
||||
const toggle = findValidityChecksToggle();
|
||||
expect(toggle.props('value')).toBe(false);
|
||||
toggle.vm.$emit('change', true);
|
||||
|
||||
expect(requestHandlers.setMutationHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
namespacePath: 'group/project',
|
||||
enable: true,
|
||||
},
|
||||
});
|
||||
|
||||
it('handles GraphQL mutation errors', async () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent();
|
||||
await waitForPromises();
|
||||
|
||||
requestHandlers.setMutationHandler.mockReset();
|
||||
requestHandlers.setMutationHandler.mockRejectedValue(new Error('Network error'));
|
||||
expect(toggle.props('value')).toBe(true);
|
||||
expect(wrapper.text()).toContain('Enabled');
|
||||
});
|
||||
|
||||
const toggle = findValidityChecksToggle();
|
||||
toggle.vm.$emit('change', true);
|
||||
it('shows success toast when toggle succeeds', async () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent();
|
||||
|
||||
expect(requestHandlers.setMutationHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
namespacePath: 'group/project',
|
||||
enable: true,
|
||||
},
|
||||
});
|
||||
const toggle = findValidityChecksToggle();
|
||||
expect(toggle.props('value')).toBe(false);
|
||||
|
||||
await waitForPromises();
|
||||
toggle.vm.$emit('change', true);
|
||||
|
||||
expect(findValidityChecksAlert().exists()).toBe(true);
|
||||
expect(requestHandlers.setMutationHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
namespacePath: 'group/project',
|
||||
enable: true,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(toggle.props('value')).toBe(true);
|
||||
expect(mockToastShow).toHaveBeenCalledWith('Validity checks enabled');
|
||||
});
|
||||
|
||||
it('shows error alert when an error message is set', async () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent();
|
||||
|
||||
requestHandlers.setMutationHandler.mockReset();
|
||||
requestHandlers.setMutationHandler.mockResolvedValue({
|
||||
data: {
|
||||
setValidityChecks: {
|
||||
validityChecksEnabled: null,
|
||||
errors: ['data response with errors'],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const toggle = findValidityChecksToggle();
|
||||
toggle.vm.$emit('change', true);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findValidityChecksAlert().exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('handles GraphQL mutation errors', async () => {
|
||||
feature = makeFeature({ available: true, configured: true });
|
||||
createComponent();
|
||||
|
||||
requestHandlers.setMutationHandler.mockReset();
|
||||
requestHandlers.setMutationHandler.mockRejectedValue(new Error('Network error'));
|
||||
|
||||
const toggle = findValidityChecksToggle();
|
||||
toggle.vm.$emit('change', true);
|
||||
|
||||
expect(requestHandlers.setMutationHandler).toHaveBeenCalledWith({
|
||||
input: {
|
||||
namespacePath: 'group/project',
|
||||
enable: true,
|
||||
},
|
||||
});
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(findValidityChecksAlert().exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
updateCacheAfterCreatingNote,
|
||||
updateCountsForParent,
|
||||
} from '~/work_items/graphql/cache_utils';
|
||||
import { findHierarchyWidget, findNotesWidget, getWorkItemWidgets } from '~/work_items/utils';
|
||||
import { findHierarchyWidget, findNotesWidget } from '~/work_items/utils';
|
||||
import getWorkItemTreeQuery from '~/work_items/graphql/work_item_tree.query.graphql';
|
||||
import waitForPromises from 'helpers/wait_for_promises';
|
||||
import { apolloProvider } from '~/graphql_shared/issuable_client';
|
||||
|
|
@ -20,9 +20,7 @@ import {
|
|||
workItemResponseFactory,
|
||||
mockCreateWorkItemDraftData,
|
||||
mockNewWorkItemCache,
|
||||
mockNewWorkItemIssueCache,
|
||||
restoredDraftDataWidgets,
|
||||
restoredDraftDataWidgetsForIssue,
|
||||
restoredDraftDataWidgetsEmpty,
|
||||
} from '../mock_data';
|
||||
|
||||
|
|
@ -50,10 +48,6 @@ describe('work items graphql cache utils', () => {
|
|||
],
|
||||
},
|
||||
};
|
||||
// This looks like an odd pattern but is something we do already in several places
|
||||
// across our codebase to run tests conditionally, often to quarantine tests.
|
||||
// Here we're utilizing it skip test based on feature flag.
|
||||
const itif = (condition) => (condition ? it : it.skip);
|
||||
|
||||
beforeEach(() => {
|
||||
window.gon.features = {};
|
||||
|
|
@ -268,13 +262,6 @@ describe('work items graphql cache utils', () => {
|
|||
`autosave/new-gitlab-org-epic-draft`,
|
||||
JSON.stringify(mockCreateWorkItemDraftData),
|
||||
);
|
||||
|
||||
if (window.gon.features.workItemsAlpha) {
|
||||
localStorage.setItem(
|
||||
`autosave/new-gitlab-org-widgets-draft`,
|
||||
JSON.stringify(getWorkItemWidgets(mockCreateWorkItemDraftData)),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
|
@ -326,27 +313,6 @@ describe('work items graphql cache utils', () => {
|
|||
);
|
||||
},
|
||||
);
|
||||
|
||||
itif(workItemsAlpha)('shares widget data between work item types', async () => {
|
||||
await setNewWorkItemCache(mockNewWorkItemIssueCache);
|
||||
|
||||
await waitForPromises();
|
||||
|
||||
expect(mockWriteQuery).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
data: expect.objectContaining({
|
||||
workspace: expect.objectContaining({
|
||||
workItem: expect.objectContaining({
|
||||
// The title was originally set for Epic type in beforeEach call above
|
||||
title: mockCreateWorkItemDraftData.workspace.workItem.title,
|
||||
// The widgets data is shared
|
||||
widgets: expect.arrayContaining(restoredDraftDataWidgetsForIssue),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -7248,88 +7248,6 @@ export const mockNewWorkItemCache = {
|
|||
workItemTypeIconName: 'issue-type-epic',
|
||||
};
|
||||
|
||||
export const mockNewWorkItemIssueCache = {
|
||||
fullPath: 'gitlab-org',
|
||||
widgetDefinitions: [
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'AWARD_EMOJI',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'CURRENT_USER_TODOS',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'DESCRIPTION',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'HEALTH_STATUS',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionHierarchy',
|
||||
type: 'HIERARCHY',
|
||||
allowedChildTypes: {
|
||||
__typename: 'WorkItemTypeConnection',
|
||||
nodes: [
|
||||
{
|
||||
__typename: 'WorkItemType',
|
||||
id: 'gid://gitlab/WorkItems::Type/5',
|
||||
name: 'Task',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionLabels',
|
||||
type: 'LABELS',
|
||||
allowsScopedLabels: true,
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'LINKED_ITEMS',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'NOTES',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'NOTIFICATIONS',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'PARTICIPANTS',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'START_AND_DUE_DATE',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'STATUS',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionGeneric',
|
||||
type: 'TIME_TRACKING',
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionWeight',
|
||||
type: 'WEIGHT',
|
||||
editable: false,
|
||||
rollUp: true,
|
||||
},
|
||||
{
|
||||
__typename: 'WorkItemWidgetDefinitionCustomFields',
|
||||
type: WIDGET_TYPE_CUSTOM_FIELDS,
|
||||
},
|
||||
],
|
||||
workItemType: 'Issue',
|
||||
workItemTypeId: 'gid://gitlab/WorkItems::Type/2',
|
||||
workItemTypeIconName: 'issue-type-issue',
|
||||
};
|
||||
|
||||
export const restoredDraftDataWidgets = [
|
||||
{
|
||||
type: 'DESCRIPTION',
|
||||
|
|
@ -7450,29 +7368,6 @@ export const restoredDraftDataWidgets = [
|
|||
},
|
||||
];
|
||||
|
||||
export const restoredDraftDataWidgetsForIssue = restoredDraftDataWidgets
|
||||
// Drop any unsupported widget for Issue type
|
||||
.filter((widget) => !['COLOR'].includes(widget.type))
|
||||
// Override specific widgets for Issue type
|
||||
.map((widget) => {
|
||||
if (widget.type === 'HIERARCHY') {
|
||||
return {
|
||||
type: 'HIERARCHY',
|
||||
hasChildren: false,
|
||||
hasParent: false,
|
||||
parent: null,
|
||||
depthLimitReachedByType: [],
|
||||
rolledUpCountsByType: [],
|
||||
children: {
|
||||
nodes: [],
|
||||
__typename: 'WorkItemConnection',
|
||||
},
|
||||
__typename: 'WorkItemWidgetHierarchy',
|
||||
};
|
||||
}
|
||||
return { ...widget };
|
||||
});
|
||||
|
||||
export const restoredDraftDataWidgetsEmpty = [
|
||||
{
|
||||
type: 'DESCRIPTION',
|
||||
|
|
|
|||
|
|
@ -2118,4 +2118,62 @@ RSpec.describe Note, feature_category: :team_planning do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#trigger_work_item_updated_subscription' do
|
||||
let(:issue) { create(:issue) }
|
||||
let(:note) { build(:note, noteable: issue, system: true, project: issue.project) }
|
||||
|
||||
before do
|
||||
allow(note).to receive(:for_issue?).and_return(true)
|
||||
end
|
||||
|
||||
context 'when note contains metadata with specific action' do
|
||||
%w[branch relate cross_reference].each do |action|
|
||||
it "triggers subscription for note with '#{action}' action" do
|
||||
build(:system_note_metadata, note: note, action: action)
|
||||
|
||||
expect(GraphqlTriggers).to receive(:work_item_updated).with(issue)
|
||||
|
||||
note.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when note contains metadata with non-actionable action' do
|
||||
%w[label visible assignee].each do |action|
|
||||
it "does not trigger subscription for note with '#{action}' action" do
|
||||
build(:system_note_metadata, note: note, action: action)
|
||||
|
||||
expect(GraphqlTriggers).not_to receive(:work_item_updated)
|
||||
|
||||
note.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when noteable is not an issue' do
|
||||
let(:merge_request) { create(:merge_request) }
|
||||
let(:note) { build(:note, noteable: merge_request, system: true, note: 'merge request created', project: merge_request.project) }
|
||||
|
||||
before do
|
||||
allow(note).to receive(:for_issue?).and_return(false)
|
||||
end
|
||||
|
||||
it 'does not trigger subscription' do
|
||||
expect(GraphqlTriggers).not_to receive(:work_item_updated)
|
||||
|
||||
note.save!
|
||||
end
|
||||
end
|
||||
|
||||
context 'when noteable is not a system note' do
|
||||
let(:note) { build(:note, noteable: issue, system: false, note: 'merge request created', project: issue.project) }
|
||||
|
||||
it 'does not trigger subscription' do
|
||||
expect(GraphqlTriggers).not_to receive(:work_item_updated)
|
||||
|
||||
note.save!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -49,4 +49,39 @@ RSpec.describe SystemNoteMetadata, feature_category: :team_planning do
|
|||
it { expect(described_class.for_notes(::Note.id_in(notes))).to match_array([metadata1, metadata2]) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#about_relation?' do
|
||||
let(:note) { create(:note) }
|
||||
let(:system_note_metadata) { build(:system_note_metadata, note: note) }
|
||||
|
||||
context 'when action is in cross_reference_types_with_branch' do
|
||||
SystemNoteMetadata::WORK_ITEMS_CROSS_REFERENCE.each do |action_type|
|
||||
it "returns true for action '#{action_type}'" do
|
||||
system_note_metadata.action = action_type
|
||||
|
||||
expect(system_note_metadata.about_relation?).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when action is not in cross_reference_types_with_branch' do
|
||||
let(:non_cross_reference_actions) do
|
||||
SystemNoteMetadata::ICON_TYPES - SystemNoteMetadata::WORK_ITEMS_CROSS_REFERENCE
|
||||
end
|
||||
|
||||
it 'returns false for actions not in cross reference types' do
|
||||
non_cross_reference_actions.each do |action_type|
|
||||
system_note_metadata.action = action_type
|
||||
|
||||
expect(system_note_metadata.about_relation?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns false for custom action not in any predefined types' do
|
||||
system_note_metadata.action = 'custom_action'
|
||||
|
||||
expect(system_note_metadata.about_relation?).to be false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do
|
|||
|
||||
it "redirects to the .git suffix version" do
|
||||
expect(response).to redirect_to("/#{repository_path}.git/info/refs")
|
||||
expect(response.body).to match(/You.are.being.+redirected/)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -178,6 +179,7 @@ RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do
|
|||
|
||||
it "redirects to the .git suffix version" do
|
||||
expect(response).to redirect_to("/#{repository_path}.git/info/refs?service=#{params[:service]}")
|
||||
expect(response.body).to match(/You.are.being.+redirected/)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -190,6 +192,7 @@ RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do
|
|||
|
||||
it "redirects to the .git suffix version" do
|
||||
expect(response).to redirect_to("/#{repository_path}.git/info/refs?service=#{params[:service]}")
|
||||
expect(response.body).to match(/You.are.being.+redirected/)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -202,6 +205,7 @@ RSpec.describe 'Git HTTP requests', feature_category: :source_code_management do
|
|||
|
||||
it "redirects to the sign-in page" do
|
||||
expect(response).to redirect_to(new_user_session_path)
|
||||
expect(response.body).to match(/You.are.being.+redirected/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1171,6 +1171,7 @@ RSpec.describe MergeRequests::UpdateService, :mailer, feature_category: :code_re
|
|||
it 'triggers a workItemUpdated subscription for all affected records' do
|
||||
service = described_class.new(project: project, current_user: user, params: update_params)
|
||||
allow(service).to receive(:execute_hooks)
|
||||
allow(GraphqlTriggers).to receive(:work_item_updated).and_call_original
|
||||
|
||||
WorkItem.where(id: issues_to_notify).find_each do |work_item|
|
||||
expect(GraphqlTriggers).to receive(:work_item_updated).with(work_item).once.and_call_original
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ RSpec.describe WorkItems::ParentLinks::CreateService, feature_category: :portfol
|
|||
let(:params) { { issuable_references: [task1, task2] } }
|
||||
|
||||
it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
|
||||
let(:trigger_call_counter) { 2 }
|
||||
let(:trigger_call_counter) { 4 }
|
||||
|
||||
subject(:execute_service) { described_class.new(work_item, user, params).execute }
|
||||
end
|
||||
|
|
@ -242,6 +242,8 @@ RSpec.describe WorkItems::ParentLinks::CreateService, feature_category: :portfol
|
|||
|
||||
it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
|
||||
subject(:execute_service) { described_class.new(work_item, user, params).execute }
|
||||
|
||||
let(:trigger_call_counter) { 2 }
|
||||
end
|
||||
|
||||
it 'creates links only for non related tasks', :aggregate_failures do
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ RSpec.describe WorkItems::ParentLinks::DestroyService, feature_category: :team_p
|
|||
|
||||
it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
|
||||
subject(:execute_service) { described_class.new(parent_link, user).execute }
|
||||
|
||||
let(:trigger_call_counter) { 2 }
|
||||
end
|
||||
|
||||
it 'removes relation and creates notes', :aggregate_failures do
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ RSpec.describe WorkItems::ParentLinks::ReorderService, feature_category: :portfo
|
|||
it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
|
||||
let(:update_subject) { parent }
|
||||
let(:execute_service) { subject }
|
||||
let(:trigger_call_counter) { call_counter_nested }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -94,7 +95,9 @@ RSpec.describe WorkItems::ParentLinks::ReorderService, feature_category: :portfo
|
|||
let(:base_param) { { target_issuable: work_item } }
|
||||
|
||||
shared_examples 'updates hierarchy order without notes' do
|
||||
it_behaves_like 'processes ordered hierarchy'
|
||||
it_behaves_like 'processes ordered hierarchy' do
|
||||
let(:call_counter_nested) { 1 }
|
||||
end
|
||||
|
||||
it 'keeps relationships', :aggregate_failures do
|
||||
expect { subject }.to not_change { parent_link_class.count }
|
||||
|
|
@ -123,7 +126,9 @@ RSpec.describe WorkItems::ParentLinks::ReorderService, feature_category: :portfo
|
|||
|
||||
context 'when new parent is assigned' do
|
||||
shared_examples 'updates hierarchy order and creates notes' do
|
||||
it_behaves_like 'processes ordered hierarchy'
|
||||
it_behaves_like 'processes ordered hierarchy' do
|
||||
let(:call_counter_nested) { call_counter }
|
||||
end
|
||||
|
||||
it 'creates notes', :aggregate_failures do
|
||||
subject
|
||||
|
|
@ -139,13 +144,17 @@ RSpec.describe WorkItems::ParentLinks::ReorderService, feature_category: :portfo
|
|||
context 'when moving before adjacent work item' do
|
||||
let(:params) { base_param.merge({ adjacent_work_item: last_adjacent, relative_position: 'BEFORE' }) }
|
||||
|
||||
it_behaves_like 'updates hierarchy order and creates notes'
|
||||
it_behaves_like 'updates hierarchy order and creates notes' do
|
||||
let(:call_counter) { 2 }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when moving after adjacent work item' do
|
||||
let(:params) { base_param.merge({ adjacent_work_item: top_adjacent, relative_position: 'AFTER' }) }
|
||||
|
||||
it_behaves_like 'updates hierarchy order and creates notes'
|
||||
it_behaves_like 'updates hierarchy order and creates notes' do
|
||||
let(:call_counter) { 2 }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when previous parent was in place' do
|
||||
|
|
@ -157,13 +166,17 @@ RSpec.describe WorkItems::ParentLinks::ReorderService, feature_category: :portfo
|
|||
context 'when moving before adjacent work item' do
|
||||
let(:params) { base_param.merge({ adjacent_work_item: last_adjacent, relative_position: 'BEFORE' }) }
|
||||
|
||||
it_behaves_like 'updates hierarchy order and creates notes'
|
||||
it_behaves_like 'updates hierarchy order and creates notes' do
|
||||
let(:call_counter) { 2 }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when moving after adjacent work item' do
|
||||
let(:params) { base_param.merge({ adjacent_work_item: top_adjacent, relative_position: 'AFTER' }) }
|
||||
|
||||
it_behaves_like 'updates hierarchy order and creates notes'
|
||||
it_behaves_like 'updates hierarchy order and creates notes' do
|
||||
let(:call_counter) { 2 }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -494,7 +494,7 @@ RSpec.describe WorkItems::UpdateService, feature_category: :team_planning do
|
|||
end
|
||||
|
||||
it_behaves_like 'update service that triggers GraphQL work_item_updated subscription' do
|
||||
let(:trigger_call_counter) { 2 }
|
||||
let(:trigger_call_counter) { 3 }
|
||||
|
||||
subject(:execute_service) { update_work_item }
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ RSpec.shared_examples 'update service that triggers GraphQL work_item_updated su
|
|||
let(:trigger_call_counter) { 1 }
|
||||
|
||||
it 'triggers graphql subscription workItemUpdated' do
|
||||
allow(GraphqlTriggers).to receive(:work_item_updated).and_call_original
|
||||
|
||||
expect(GraphqlTriggers)
|
||||
.to receive(:work_item_updated)
|
||||
.with(update_subject)
|
||||
|
|
|
|||
16
yarn.lock
16
yarn.lock
|
|
@ -1389,10 +1389,10 @@
|
|||
core-js "^3.29.1"
|
||||
mitt "^3.0.1"
|
||||
|
||||
"@gitlab/duo-ui@^8.22.1":
|
||||
version "8.22.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/duo-ui/-/duo-ui-8.22.1.tgz#0c20839d23b54f8734cde09448b5fa0a95d654b1"
|
||||
integrity sha512-Qfsmf5YXRnP48B8d7E98NMZO6wQF3BqgtULAOqa9i/0BGAn9aKBNLVLoSid+pDvwu3DzpHDFvh8+MFL6vnb3Fw==
|
||||
"@gitlab/duo-ui@^8.23.0":
|
||||
version "8.23.0"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/duo-ui/-/duo-ui-8.23.0.tgz#eedfa98fa902f52b6cf7970399528f5d876d7371"
|
||||
integrity sha512-QOurIDNzSrgRg40omKvdQbhHbuBuFc5dweH2NZjpXjj/hksWGWJntUuzF6LsksucBJ912hQ8Z34+i57BNBQO8g==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "1.7.1"
|
||||
echarts "^5.3.2"
|
||||
|
|
@ -1438,10 +1438,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@gitlab/noop/-/noop-1.0.1.tgz#71a831146ee02732b4a61d2d3c11204564753454"
|
||||
integrity sha512-s++4wjMYeDvBp9IO59DBrWjy8SE/gFkjTDO5ck2W0S6Vv7OlqgErwL7pHngAnrSmTJAzyUG8wHGqo0ViS4jn5Q==
|
||||
|
||||
"@gitlab/query-language-rust@0.11.1":
|
||||
version "0.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/query-language-rust/-/query-language-rust-0.11.1.tgz#8ba31bf1469da1fc2b5c8dc2578bb60725d21e88"
|
||||
integrity sha512-JlVXPM6dccc2EwoYB8EHo4Z/vjrGVZ//4VTpp5mgWnLvJLW8cEjIKBl0rbFOWVLRIMdlVVtYrQJH1MLugyDhQg==
|
||||
"@gitlab/query-language-rust@0.11.3":
|
||||
version "0.11.3"
|
||||
resolved "https://registry.yarnpkg.com/@gitlab/query-language-rust/-/query-language-rust-0.11.3.tgz#0ced6816989a8d8a61d2326aa14afbc120fdd883"
|
||||
integrity sha512-wyhZuUj5m2mmM2oxcQnMrgYu1GaGx6LctE/MMnk4Nc878AcKMiJEXknw5vNotO/XXHMFv+YHJm22uTF1ag2qHw==
|
||||
|
||||
"@gitlab/stylelint-config@6.2.2":
|
||||
version "6.2.2"
|
||||
|
|
|
|||
Loading…
Reference in New Issue