Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2024-09-05 06:11:06 +00:00
parent 4f8ecdd7a9
commit 8577a60062
40 changed files with 891 additions and 84 deletions

View File

@ -1 +1 @@
46b9580af93104de9b4c1d3dda81e9aaf7eb4c01
5261aae488f191a1373243d2f9ca000255477d7e

View File

@ -188,6 +188,7 @@ export default {
transformFluxResourceData(item) {
return {
name: item.metadata.name,
namespace: item.metadata.namespace,
status: fluxSyncStatus(item.status.conditions).status,
labels: item.metadata.labels,
annotations: item.metadata.annotations,
@ -354,6 +355,7 @@ export default {
<workload-details
v-if="hasSelectedItem"
:item="selectedItem"
:configuration="k8sAccessConfiguration"
@delete-pod="onDeletePod"
@flux-reconcile="onFluxReconcile"
/>

View File

@ -13,6 +13,7 @@ import k8sDeploymentsQuery from './queries/k8s_deployments.query.graphql';
import k8sNamespacesQuery from './queries/k8s_namespaces.query.graphql';
import fluxKustomizationQuery from './queries/flux_kustomization.query.graphql';
import fluxHelmReleaseQuery from './queries/flux_helm_release.query.graphql';
import k8sEventsQuery from './queries/k8s_events.query.graphql';
import { resolvers } from './resolvers';
import typeDefs from './typedefs.graphql';
import { connectionStatus } from './resolvers/kubernetes/constants';
@ -170,6 +171,17 @@ export const apolloProvider = (endpoint) => {
data: { logs: [] },
});
cache.writeQuery({
query: k8sEventsQuery,
data: {
lastTimestamp: '',
message: '',
reason: '',
source: {},
type: '',
},
});
return new VueApollo({
defaultClient,
});

View File

@ -0,0 +1,17 @@
query getK8sEvents(
$configuration: K8sDashboardConfig
$involvedObjectName: String
$namespace: String
) {
k8sEvents(
configuration: $configuration
involvedObjectName: $involvedObjectName
namespace: $namespace
) @client {
lastTimestamp
message
reason
source
type
}
}

View File

@ -165,5 +165,25 @@ export const kubernetesQueries = {
}
});
},
k8sEvents(_, { configuration, involvedObjectName, namespace }) {
const fieldSelector = `involvedObject.name=${involvedObjectName}`;
const config = new Configuration(configuration);
const coreV1Api = new CoreV1Api(config);
const eventsApi = coreV1Api.listCoreV1NamespacedEvent({ namespace, fieldSelector });
return eventsApi
.then((res) => {
const data = res?.items || [];
return data;
})
.catch(async (err) => {
try {
await handleClusterError(err);
} catch (error) {
throw new Error(error.message);
}
});
},
k8sLogs,
};

View File

@ -118,6 +118,14 @@ type K8sLogsData {
error: JSON
}
type K8sEvent {
lastTimestamp: String
message: String
reason: String
source: JSON
type: String
}
extend type Query {
environmentApp(page: Int, scope: String): LocalEnvironmentApp
folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder
@ -137,6 +145,11 @@ extend type Query {
fluxHelmRelease(configuration: LocalConfiguration, fluxResourcePath: String): LocalFluxHelmRelease
k8sDeployments(configuration: LocalConfiguration, namespace: String): [LocalK8sDeployment]
k8sLogs(configuration: LocalConfiguration, namespace: String, podName: String): [K8sLogsData]
k8sEvents(
configuration: LocalConfiguration
namespace: String
involvedObjectName: String
): [K8sEvent]
}
input ResourceTypeParam {

View File

@ -72,8 +72,8 @@ export default {
};
</script>
<template>
<div ref="milestoneDetails" class="issue-milestone-details">
<gl-icon :size="16" class="flex-shrink-0 gl-mr-2" name="milestone" />
<div ref="milestoneDetails" class="issue-milestone-details gl-flex gl-max-w-15 gl-gap-2">
<gl-icon :size="16" class="flex-shrink-0" name="milestone" />
<span class="milestone-title gl-inline-block gl-truncate">{{ milestone.title }}</span>
<gl-tooltip :target="() => $refs.milestoneDetails" placement="bottom" class="js-item-milestone">
<span class="gl-font-bold">{{ __('Milestone') }}</span> <br />

View File

@ -0,0 +1,72 @@
<script>
import { GlIcon, GlBadge, GlSprintf, GlTooltipDirective as GlTooltip } from '@gitlab/ui';
import {
differenceInMilliseconds,
stringifyTime,
parseSeconds,
localeDateFormat,
} from '~/lib/utils/datetime_utility';
import { s__ } from '~/locale';
export default {
i18n: {
source: s__('KubernetesDashboard|Source: %{source}'),
justNow: s__('Timeago|just now'),
},
components: {
GlIcon,
GlBadge,
GlSprintf,
},
directives: {
GlTooltip,
},
props: {
event: {
type: Object,
required: true,
validator: (item) =>
['lastTimestamp', 'message', 'reason', 'source', 'type'].every((key) => item[key]),
},
},
computed: {
timeAgo() {
const milliseconds = differenceInMilliseconds(new Date(this.event.lastTimestamp));
const seconds = parseSeconds(milliseconds / 1000);
const timeAgo = stringifyTime(seconds);
return timeAgo === '0m' ? this.$options.i18n.justNow : stringifyTime(seconds);
},
tooltipText() {
return localeDateFormat.asDateTimeFull.format(this.event.lastTimestamp);
},
},
};
</script>
<template>
<li class="timeline-entry">
<div
class="gl-float-left gl-ml-4 gl-mt-3 gl-h-3 gl-w-3 gl-rounded-full gl-border-1 gl-border-solid gl-border-gray-10 gl-bg-gray-100"
></div>
<div class="gl-pl-7 gl-pr-4">
<header class="gl-mb-4 gl-flex gl-flex-wrap gl-items-center gl-gap-3">
<gl-badge> {{ event.type }} </gl-badge>
<gl-sprintf :message="$options.i18n.source">
<template #source>
<span>{{ event.source.component }}</span>
</template>
</gl-sprintf>
<span v-gl-tooltip :title="tooltipText" data-testid="event-last-timestamp">
<gl-icon name="calendar" />
<time :time="event.lastTimestamp">
{{ timeAgo }}
</time>
</span>
</header>
<p class="gl-mb-6 gl-break-words">
<strong>{{ event.reason }}: </strong>{{ event.message }}
</p>
</div>
</li>
</template>

View File

@ -1,29 +1,77 @@
<script>
import { GlBadge, GlTruncate, GlButton, GlTooltipDirective } from '@gitlab/ui';
import {
GlBadge,
GlTruncate,
GlButton,
GlTooltipDirective,
GlLoadingIcon,
GlAlert,
} from '@gitlab/ui';
import { stringify } from 'yaml';
import { s__ } from '~/locale';
import PodLogsButton from '~/environments/environment_details/components/kubernetes/pod_logs_button.vue';
import getK8sEventsQuery from '~/environments/graphql/queries/k8s_events.query.graphql';
import { WORKLOAD_STATUS_BADGE_VARIANTS, STATUS_LABELS } from '../constants';
import WorkloadDetailsItem from './workload_details_item.vue';
import K8sEventItem from './k8s_event_item.vue';
export default {
components: {
GlBadge,
GlTruncate,
GlButton,
GlLoadingIcon,
GlAlert,
WorkloadDetailsItem,
PodLogsButton,
K8sEventItem,
},
directives: {
GlTooltip: GlTooltipDirective,
},
props: {
configuration: {
type: Object,
required: false,
default: () => ({}),
},
item: {
type: Object,
required: true,
validator: (item) => ['name', 'kind', 'labels', 'annotations'].every((key) => item[key]),
},
},
data() {
return { eventsError: null, eventsLoading: false, k8sEvents: [] };
},
apollo: {
k8sEvents: {
query: getK8sEventsQuery,
fetchPolicy: 'no-cache',
notifyOnNetworkStatusChange: true,
variables() {
return {
configuration: this.configuration,
involvedObjectName: this.item.name,
namespace: this.item.namespace,
};
},
skip() {
return Boolean(!Object.keys(this.configuration).length);
},
error(err) {
this.eventsError = err.message;
},
watchLoading(isLoading) {
this.eventsLoading = isLoading;
},
update(data) {
return data?.k8sEvents?.sort(
(a, b) => new Date(b.lastTimestamp) - new Date(a.lastTimestamp),
);
},
},
},
computed: {
itemLabels() {
const { labels } = this.item;
@ -79,6 +127,8 @@ export default {
annotations: s__('KubernetesDashboard|Annotations'),
spec: s__('KubernetesDashboard|Spec'),
containers: s__('KubernetesDashboard|Containers'),
events: s__('KubernetesDashboard|Events'),
eventsEmptyText: s__('KubernetesDashboard|No events available'),
},
WORKLOAD_STATUS_BADGE_VARIANTS,
STATUS_LABELS,
@ -155,5 +205,17 @@ export default {
/>
</div>
</workload-details-item>
<workload-details-item :label="$options.i18n.events" collapsible>
<gl-loading-icon v-if="eventsLoading" inline />
<gl-alert v-else-if="eventsError" variant="danger" :dismissible="false">
{{ eventsError }}
</gl-alert>
<div v-else-if="k8sEvents.length" class="issuable-discussion">
<ul class="notes main-notes-list timeline -gl-ml-2">
<k8s-event-item v-for="(event, index) in k8sEvents" :key="index" :event="event" />
</ul>
</div>
<span v-else class="gl-text-gray-500">{{ $options.i18n.eventsEmptyText }}</span>
</workload-details-item>
</ul>
</template>

View File

@ -37,12 +37,12 @@ export default {
<template>
<div class="gl-flex gl-justify-between">
<div class="gl-flex gl-flex-wrap gl-items-center gl-gap-2">
<span class="gl-text-sm gl-text-secondary">{{ reference }}</span>
<div class="gl-flex gl-flex-wrap gl-items-center gl-gap-3 gl-text-sm gl-text-secondary">
<span>{{ reference }}</span>
<item-milestone
v-if="milestone"
:milestone="milestone"
class="gl-flex gl-max-w-15 !gl-cursor-help gl-items-center gl-text-sm gl-leading-normal !gl-text-gray-900 !gl-no-underline"
class="gl-flex gl-max-w-15 !gl-cursor-help gl-items-center gl-gap-2 gl-leading-normal !gl-no-underline"
/>
<slot name="left-metadata"></slot>
</div>

View File

@ -77,7 +77,7 @@ module Groups::GroupMembersHelper
def group_group_links_list_data(group, include_relations, search)
group_links = group_group_links(group, include_relations)
group_links = group_links.search(search) if search
group_links = group_links.search(search, include_parents: true) if search
{
members: group_group_links_serialized(group, group_links),

View File

@ -79,7 +79,7 @@ module Projects::ProjectMembersHelper
if include_relations.include?(:inherited)
group_group_links = project.group_group_links.distinct_on_shared_with_group_id_with_group_access
group_group_links = group_group_links.search(search) if search
group_group_links = group_group_links.search(search, include_parents: true) if search
members += group_group_links_serialized(project, group_group_links)
end

View File

@ -55,8 +55,8 @@ class GroupGroupLink < ApplicationRecord
alias_method :shared_from, :shared_group
def self.search(query)
joins(:shared_with_group).merge(Group.search(query))
def self.search(query, **options)
joins(:shared_with_group).merge(Group.search(query, **options))
end
def self.access_options

View File

@ -2539,6 +2539,8 @@ class User < ApplicationRecord
end
def should_delay_delete?(deleted_by)
return false if placeholder?
is_deleting_own_record = deleted_by.id == id
is_deleting_own_record &&

View File

@ -21,7 +21,7 @@ module Users
# Asynchronously destroys +user+
# Migrating the associated user records, and post-migration cleanup is
# handled by the Users::MigrateRecordsToGhostUserWorker cron worker.
# handled by the Users::MigrateRecordsToGhostUserInBatchesWorker cron worker.
#
# The operation will fail if the user is the sole owner of any groups. To
# force the groups to be destroyed, pass `delete_solo_owned_groups: true` in
@ -71,12 +71,7 @@ module Users
yield(user) if block_given?
hard_delete = options.fetch(:hard_delete, false)
Users::GhostUserMigration.create!(
user: user,
initiator_user: current_user,
hard_delete: hard_delete
)
create_ghost_user(user, options)
update_metrics
end
@ -85,6 +80,17 @@ module Users
attr_reader :scheduled_records_gauge, :lag_gauge
def create_ghost_user(user, options)
hard_delete = options.fetch(:hard_delete, false)
Users::GhostUserMigration.create!(
user: user,
initiator_user: current_user,
hard_delete: hard_delete
)
rescue ActiveRecord::RecordNotUnique
# GhostUserMigration was already created by other worker process. Do nothing
end
def update_metrics
update_scheduled_records_gauge
update_lag_gauge

View File

@ -3279,6 +3279,15 @@
:weight: 1
:idempotent: true
:tags: []
- :name: import_delete_placeholder_user
:worker_name: Import::DeletePlaceholderUserWorker
:feature_category: :importers
:has_external_dependencies: false
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent: true
:tags: []
- :name: import_issues_csv
:worker_name: ImportIssuesCsvWorker
:feature_category: :team_planning

View File

@ -0,0 +1,46 @@
# frozen_string_literal: true
module Import
class DeletePlaceholderUserWorker
include ApplicationWorker
data_consistency :delayed
idempotent!
feature_category :importers
def perform(source_user_id)
source_user = Import::SourceUser.find_by_id(source_user_id)
return if source_user.nil? || source_user.placeholder_user.nil?
return unless source_user.placeholder_user.placeholder?
if placeholder_user_referenced?(source_user)
log_placeholder_user_not_deleted(source_user)
return
end
placeholder_user = source_user.placeholder_user
placeholder_user.delete_async(
deleted_by: placeholder_user,
params: { "skip_authorization" => true }
)
end
private
def log_placeholder_user_not_deleted(source_user)
::Import::Framework::Logger.warn(
message: 'Unable to delete placeholder user because it is still referenced in other tables',
source_user_id: source_user.id
)
end
def placeholder_user_referenced?(source_user)
PlaceholderReferences::AliasResolver.models_with_columns.any? do |model, columns|
(columns & ::Gitlab::ImportExport::Base::RelationFactory::USER_REFERENCES).any? do |user_reference_column|
model.where(user_reference_column => source_user.placeholder_user_id).any? # rubocop:disable CodeReuse/ActiveRecord -- Adding a scope for all possible models would not be feasible here
end
end
end
end
end

View File

@ -27,6 +27,7 @@ module Import
return unless import_source_user_valid?
Import::ReassignPlaceholderUserRecordsService.new(import_source_user).execute
Import::DeletePlaceholderUserWorker.perform_async(import_source_user.id)
end
def perform_failure(exception, import_source_user_id)

View File

@ -411,6 +411,8 @@
- 1
- - groups_update_two_factor_requirement_for_members
- 1
- - import_delete_placeholder_user
- 1
- - import_issues_csv
- 2
- - import_load_placeholder_references

View File

@ -302,9 +302,26 @@ module.exports = {
rules: [
{
type: 'javascript/auto',
exclude: /pdfjs-dist/,
test: /\.mjs$/,
use: [],
},
{
test: /(pdfjs).*\.js?$/,
include: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
plugins: [
'@babel/plugin-transform-optional-chaining',
'@babel/plugin-transform-logical-assignment-operators',
],
...defaultJsOptions,
},
},
],
},
{
test: /(@gitlab\/web-ide).*\.js?$/,
include: /node_modules/,

View File

@ -5427,6 +5427,29 @@ Input type: `GroupMemberBulkUpdateInput`
| <a id="mutationgroupmemberbulkupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationgroupmemberbulkupdategroupmembers"></a>`groupMembers` | [`[GroupMember!]`](#groupmember) | Group members after mutation. |
### `Mutation.groupMembersExport`
DETAILS:
**Introduced** in GitLab 17.4.
**Status**: Experiment.
Input type: `GroupMembersExportInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationgroupmembersexportclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationgroupmembersexportgroupid"></a>`groupId` | [`GroupID!`](#groupid) | Global ID of the group. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationgroupmembersexportclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationgroupmembersexporterrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationgroupmembersexportmessage"></a>`message` | [`String`](#string) | Export request result message. |
### `Mutation.groupSavedReplyCreate`
DETAILS:

View File

@ -151,6 +151,12 @@ module Import
column
end
def self.models_with_columns
ALIASES.values
.map { |versions| versions[versions.keys.max] }
.map { |data| [data[:model], data[:columns].values] }
end
private_class_method def self.track_error_for_missing(model:, column: nil, version: nil)
message = "ALIASES must be extended to include #{model}"
message += ".#{column}" if column

View File

@ -31158,6 +31158,9 @@ msgstr ""
msgid "KubernetesDashboard|Deployments"
msgstr ""
msgid "KubernetesDashboard|Events"
msgstr ""
msgid "KubernetesDashboard|Failed"
msgstr ""
@ -31191,6 +31194,9 @@ msgstr ""
msgid "KubernetesDashboard|No agent selected"
msgstr ""
msgid "KubernetesDashboard|No events available"
msgstr ""
msgid "KubernetesDashboard|Pending"
msgstr ""
@ -31218,6 +31224,9 @@ msgstr ""
msgid "KubernetesDashboard|Services"
msgstr ""
msgid "KubernetesDashboard|Source: %{source}"
msgstr ""
msgid "KubernetesDashboard|Spec"
msgstr ""

View File

@ -192,7 +192,7 @@
"orderedmap": "^2.1.1",
"papaparse": "^5.3.1",
"patch-package": "6.5.1",
"pdfjs-dist": "^2.16.105",
"pdfjs-dist": "^3.11.174",
"pikaday": "^1.8.0",
"pinia": "^2.2.2",
"popper.js": "^1.16.1",

View File

@ -0,0 +1,8 @@
# frozen_string_literal: true
FactoryBot.define do
factory :issue_assignee do
assignee { association(:user) }
issue { association(:issue) }
end
end

View File

@ -339,6 +339,10 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov
expect(findWorkloadDetails().props('item')).toEqual(mockPodsTableItems[0]);
});
it('provides the agent access configuration to the drawer', () => {
expect(findWorkloadDetails().props('configuration')).toEqual(configuration);
});
it('renders a title with the selected item name', () => {
expect(findDrawer().text()).toContain(mockPodsTableItems[0].name);
});
@ -383,6 +387,7 @@ describe('~/environments/environment_details/components/kubernetes/kubernetes_ov
it('provides the resource details to the drawer', () => {
const selectedItem = {
name: fluxKustomization.metadata.name,
namespace: fluxKustomization.metadata.namespace,
status: 'reconciled',
labels: fluxKustomization.metadata.labels,
annotations: fluxKustomization.metadata.annotations,

View File

@ -22,6 +22,7 @@ import {
k8sPodsMock,
k8sServicesMock,
k8sDeploymentsMock,
k8sEventsMock,
} from 'jest/kubernetes_dashboard/graphql/mock_data';
import { k8sNamespacesMock } from '../mock_data';
import { bootstrapWatcherMock } from '../watcher_mock_helper';
@ -373,6 +374,45 @@ describe('~/frontend/environments/graphql/resolvers', () => {
);
});
describe('k8sEvents', () => {
const involvedObjectName = 'my-pod';
const mockEventsListFn = jest.fn().mockImplementation(() => {
return Promise.resolve({
items: k8sEventsMock,
});
});
const mockNamespacedEventsListFn = jest.fn().mockImplementation(mockEventsListFn);
it('should request namespaced events with the field selector from the cluster_client library if namespace is specified', async () => {
jest
.spyOn(CoreV1Api.prototype, 'listCoreV1NamespacedEvent')
.mockImplementation(mockNamespacedEventsListFn);
const events = await mockResolvers.Query.k8sEvents(null, {
configuration,
namespace,
involvedObjectName,
});
expect(mockNamespacedEventsListFn).toHaveBeenCalledWith({
namespace,
fieldSelector: `involvedObject.name=${involvedObjectName}`,
});
expect(events).toEqual(k8sEventsMock);
});
it('should throw an error if the API call fails', async () => {
jest
.spyOn(CoreV1Api.prototype, 'listCoreV1NamespacedEvent')
.mockRejectedValue(new Error('API error'));
await expect(
mockResolvers.Query.k8sEvents(null, { configuration, namespace, involvedObjectName }),
).rejects.toThrow('API error');
});
});
describe('deleteKubernetesPod', () => {
const mockPodsDeleteFn = jest.fn().mockResolvedValue({ errors: [] });
const podToDelete = 'my-pod';

View File

@ -0,0 +1,53 @@
import { shallowMount } from '@vue/test-utils';
import { GlBadge, GlSprintf } from '@gitlab/ui';
import K8sEventItem from '~/kubernetes_dashboard/components/k8s_event_item.vue';
import { useFakeDate } from 'helpers/fake_date';
describe('~/kubernetes_dashboard/components/k8s_event_item.vue', () => {
useFakeDate(2023, 4, 1, 12, 4);
let wrapper;
const event = {
type: 'normal',
source: { component: 'my-component' },
reason: 'Reason 1',
message: 'Event 1',
lastTimestamp: '2023-05-01T12:00:00Z',
};
const findTypeBadge = () => wrapper.findComponent(GlBadge);
const findLastTimestamp = () => wrapper.find('[data-testid="event-last-timestamp"]');
const createWrapper = () => {
wrapper = shallowMount(K8sEventItem, {
propsData: {
event,
},
stubs: { GlSprintf },
});
};
beforeEach(() => {
createWrapper();
});
it('renders type badge', () => {
expect(findTypeBadge().text()).toBe(event.type);
});
it('renders event source', () => {
expect(wrapper.text()).toContain(`Source: ${event.source.component}`);
});
it('renders event last timestamp tooltip in correct format', () => {
expect(findLastTimestamp().attributes('title')).toBe('May 1, 2023 at 12:00:00 PM GMT');
});
it('renders event age as text', () => {
expect(findLastTimestamp().text()).toBe('4m');
});
it('renders event reason with message', () => {
expect(wrapper.text()).toContain(`${event.reason}: ${event.message}`);
});
});

View File

@ -1,14 +1,28 @@
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import { shallowMount } from '@vue/test-utils';
import { GlBadge, GlTruncate, GlButton } from '@gitlab/ui';
import { GlBadge, GlTruncate, GlButton, GlLoadingIcon, GlAlert } from '@gitlab/ui';
import WorkloadDetails from '~/kubernetes_dashboard/components/workload_details.vue';
import WorkloadDetailsItem from '~/kubernetes_dashboard/components/workload_details_item.vue';
import { WORKLOAD_STATUS_BADGE_VARIANTS } from '~/kubernetes_dashboard/constants';
import PodLogsButton from '~/environments/environment_details/components/kubernetes/pod_logs_button.vue';
import { mockPodsTableItems } from '../graphql/mock_data';
import waitForPromises from 'helpers/wait_for_promises';
import createMockApollo from 'helpers/mock_apollo_helper';
import K8sEventItem from '~/kubernetes_dashboard/components/k8s_event_item.vue';
import { mockPodsTableItems, k8sEventsMock } from '../graphql/mock_data';
Vue.use(VueApollo);
let wrapper;
let getK8sEventsQuery;
const defaultItem = mockPodsTableItems[2];
const configuration = {
basePath: 'kas/tunnel/url',
baseOptions: {
headers: { 'GitLab-Agent-Id': '1' },
},
};
const createWrapper = (item = defaultItem) => {
wrapper = shallowMount(WorkloadDetails, {
@ -19,6 +33,26 @@ const createWrapper = (item = defaultItem) => {
});
};
const createApolloProvider = () => {
const mockResolvers = {
Query: {
k8sEvents: getK8sEventsQuery,
},
};
return createMockApollo([], mockResolvers);
};
const createWrapperWithApollo = () => {
wrapper = shallowMount(WorkloadDetails, {
propsData: {
item: defaultItem,
configuration,
},
apolloProvider: createApolloProvider(),
});
};
const findAllWorkloadDetailsItems = () => wrapper.findAllComponents(WorkloadDetailsItem);
const findWorkloadDetailsItem = (at) => findAllWorkloadDetailsItems().at(at);
const findAllBadges = () => wrapper.findAllComponents(GlBadge);
@ -27,6 +61,9 @@ const findAllPodLogsButtons = () => wrapper.findAllComponents(PodLogsButton);
const findPodLogsButton = (at) => findAllPodLogsButtons().at(at);
const findAllButtons = () => wrapper.findAllComponents(GlButton);
const findButton = (at) => findAllButtons().at(at);
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findAlert = () => wrapper.findComponent(GlAlert);
const findAllK8sEventItems = () => wrapper.findAllComponents(K8sEventItem);
describe('Workload details component', () => {
describe('when minimal fields are provided', () => {
@ -156,5 +193,70 @@ describe('Workload details component', () => {
},
);
});
describe('k8s events', () => {
describe('default', () => {
beforeEach(() => {
getK8sEventsQuery = jest.fn().mockResolvedValue([]);
createWrapperWithApollo();
});
it('renders a collapsible list item for events', () => {
expect(findWorkloadDetailsItem(6).props('label')).toBe('Events');
});
it('requests k8s events for the current item', async () => {
getK8sEventsQuery = jest.fn().mockResolvedValue([]);
createWrapperWithApollo();
await nextTick();
expect(getK8sEventsQuery).toHaveBeenCalledWith(
{},
expect.objectContaining({
configuration,
namespace: defaultItem.namespace,
involvedObjectName: defaultItem.name,
}),
expect.any(Object),
expect.any(Object),
);
});
it('renders loading icon while loading events', async () => {
getK8sEventsQuery = jest.fn().mockResolvedValue([]);
createWrapperWithApollo();
await nextTick();
expect(findLoadingIcon().exists()).toBe(true);
await waitForPromises();
expect(findLoadingIcon().exists()).toBe(false);
});
it('shows empty state message when no events are found', async () => {
getK8sEventsQuery = jest.fn().mockResolvedValue([]);
createWrapperWithApollo();
await waitForPromises();
expect(findWorkloadDetailsItem(6).text()).toBe('No events available');
});
});
it('renders error alert when the request errored', async () => {
const error = new Error('Error from the cluster_client API');
getK8sEventsQuery = jest.fn().mockRejectedValue(error);
createWrapperWithApollo();
await waitForPromises();
expect(findAlert().text()).toBe(error.message);
});
it("renders a list of k8s-event-item's for each event", async () => {
getK8sEventsQuery = jest.fn().mockResolvedValue(k8sEventsMock);
createWrapperWithApollo();
await waitForPromises();
expect(findAllK8sEventItems()).toHaveLength(k8sEventsMock.length);
});
});
});
});

View File

@ -635,3 +635,27 @@ export const mockServicesTableItems = [
kind: 'Service',
},
];
export const k8sEventsMock = [
{
type: 'normal',
source: { component: 'my-component' },
reason: 'Reason 1',
message: 'Event 1',
lastTimestamp: '2023-05-01T10:00:00Z',
},
{
type: 'normal',
source: { component: 'my-component' },
reason: 'Reason 2',
message: 'Event 2',
lastTimestamp: '2023-05-01T11:00:00Z',
},
{
type: 'normal',
source: { component: 'my-component' },
reason: 'Reason 3',
message: 'Event 3',
lastTimestamp: '2023-05-01T12:00:00Z',
},
];

View File

@ -2,7 +2,7 @@
require "spec_helper"
RSpec.describe Groups::GroupMembersHelper do
RSpec.describe Groups::GroupMembersHelper, feature_category: :groups_and_projects do
include MembersPresentation
let_it_be(:group) { create(:group) }

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe Projects::ProjectMembersHelper do
RSpec.describe Projects::ProjectMembersHelper, feature_category: :groups_and_projects do
include MembersPresentation
let_it_be(:current_user) { create(:user) }

View File

@ -204,4 +204,38 @@ RSpec.describe Import::PlaceholderReferences::AliasResolver, feature_category: :
end
end
end
describe ".models_with_columns" do
subject(:models_with_columns) { described_class.models_with_columns }
it "returns models with all their columns" do
expect(models_with_columns).to include([Approval, ["user_id"]])
end
context "when there are multiple versions for a key" do
let(:aliases) do
{
"Note" => {
1 => {
model: Note,
columns: { "author_id" => "author_id" }
},
2 => {
model: Note,
columns: { "author_id" => "user_id" }
}
}
}
end
before do
stub_const("#{described_class}::ALIASES", aliases)
end
it "only includes the last version" do
expect(models_with_columns).to include([Note, ["user_id"]])
expect(models_with_columns).not_to include([Note, ["author_id"]])
end
end
end
end

View File

@ -2,8 +2,9 @@
require 'spec_helper'
RSpec.describe GroupGroupLink do
RSpec.describe GroupGroupLink, feature_category: :groups_and_projects do
let_it_be(:group) { create(:group) }
let_it_be(:nested_group) { create(:group, parent: group) }
let_it_be(:shared_group) { create(:group) }
describe 'validation' do
@ -185,4 +186,18 @@ RSpec.describe GroupGroupLink do
it { expect(described_class.search(group.name)).to eq([group_group_link]) }
it { expect(described_class.search('not-a-group-name')).to be_empty }
end
describe 'search by parent group name without `include_parents` option' do
let_it_be(:group_group_link) { create(:group_group_link, :reporter, shared_with_group: nested_group) }
it { expect(described_class.search(group.name)).to be_empty }
it { expect(described_class.search('not-a-group-name')).to be_empty }
end
describe 'search by parent group name with `include_parents` option' do
let_it_be(:group_group_link) { create(:group_group_link, :reporter, shared_with_group: nested_group) }
it { expect(described_class.search(group.name, include_parents: true)).to eq([group_group_link]) }
it { expect(described_class.search('not-a-group-name')).to be_empty }
end
end

View File

@ -6549,13 +6549,19 @@ RSpec.describe User, feature_category: :user_profile do
context 'with possible spam contribution' do
context 'with comments' do
it_behaves_like 'schedules the record for deletion with the correct delay' do
before do
allow(user).to receive(:has_possible_spam_contributions?).and_call_original
before do
allow(user).to receive(:has_possible_spam_contributions?).and_call_original
note = create(:note_on_issue, author: user)
create(:event, :commented, target: note, author: user)
end
note = create(:note_on_issue, author: user)
create(:event, :commented, target: note, author: user)
end
it_behaves_like 'schedules the record for deletion with the correct delay'
context 'when user is a placeholder' do
let(:user) { create(:user, :placeholder, note: "existing note") }
it_behaves_like 'schedules user for deletion without delay'
end
end

View File

@ -112,7 +112,9 @@ RSpec.describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shar
mr = MergeRequest.find(merge_request.id)
threads << Thread.new do
described_class.new(mr).execute(retry_lease: retry_lease)
Gitlab::ExclusiveLease.skipping_transaction_check do
described_class.new(mr).execute(retry_lease: retry_lease)
end
end
end

View File

@ -225,6 +225,19 @@ RSpec.describe Users::DestroyService, feature_category: :user_management do
end
end
context 'when running the service twice for a user with no personal projects' do
let!(:project) { nil }
it 'does not create a second ghost user migration and does not raise an exception' do
expect { described_class.new(user).execute(user) }
.to change { Users::GhostUserMigration.where(user: user).count }.by(1)
expect do
expect { described_class.new(user).execute(user) }.not_to raise_exception
end.not_to change { Users::GhostUserMigration.where(user: user).count }
end
end
it 'allows users to delete their own account' do
expect { described_class.new(user).execute(user) }
.to(

View File

@ -0,0 +1,85 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Import::DeletePlaceholderUserWorker, feature_category: :importers do
let_it_be(:placeholder_user) { create(:user, :placeholder) }
let_it_be(:source_user) { create(:import_source_user, placeholder_user: placeholder_user) }
let(:job_args) { source_user.id }
subject(:perform) { described_class.new.perform(*job_args) }
it_behaves_like 'an idempotent worker'
shared_examples 'deletes the placeholder user' do
it 'deletes the placeholder_user' do
expect(DeleteUserWorker).to receive(:perform_async).with(
placeholder_user.id, placeholder_user.id, { "skip_authorization" => true }
)
perform
end
end
shared_examples 'does not delete the placeholder_user and logs the issue' do
it 'does not delete the placeholder_user and logs the issue' do
expect(::Import::Framework::Logger).to receive(:warn).with(
message: 'Unable to delete placeholder user because it is still referenced in other tables',
source_user_id: source_user.id
)
expect(DeleteUserWorker).not_to receive(:perform_async)
perform
end
end
context 'when no tables reference the user' do
it_behaves_like 'deletes the placeholder user'
end
context 'when another table references the user from an author_id column' do
let!(:note) { create(:note, author: placeholder_user) }
it_behaves_like 'does not delete the placeholder_user and logs the issue'
end
context 'when another table references the user from a user_id column' do
let!(:approval) { create(:approval, user: placeholder_user) }
it_behaves_like 'does not delete the placeholder_user and logs the issue'
end
context 'when an issue_id happens to equal the placeholder user ID' do
let!(:issue_assignee) { create(:issue_assignee, issue_id: issue.id) }
let!(:issue) do
Issue.find_by_id(placeholder_user.id) || create(:issue, id: placeholder_user.id)
end
it_behaves_like 'deletes the placeholder user'
end
context 'when there is no placeholder user' do
let_it_be(:source_user) { create(:import_source_user, :completed, placeholder_user: nil) }
it 'does not delete the placeholder_user and does not log an issue' do
expect(::Import::Framework::Logger).not_to receive(:warn)
expect(DeleteUserWorker).not_to receive(:perform_async)
perform
end
end
context 'when attempting to delete a user who is not a placeholder' do
let_it_be(:user) { create(:user, :import_user) }
let_it_be(:source_user) { create(:import_source_user, placeholder_user: user) }
it 'does not delete the user' do
expect(DeleteUserWorker).not_to receive(:perform_async)
perform
end
end
end

View File

@ -62,6 +62,13 @@ RSpec.describe Import::ReassignPlaceholderUserRecordsWorker, feature_category: :
perform_multiple(job_args)
end
end
it 'queues a DeletePlaceholderUserWorker with the source user ID' do
expect(Import::DeletePlaceholderUserWorker)
.to receive(:perform_async).with(import_source_user.id)
perform_multiple(job_args)
end
end
end

200
yarn.lock
View File

@ -1857,6 +1857,21 @@
resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.3.tgz#0300943770e04231041a51bd39f0439b5c7ab4f0"
integrity sha512-nkalE/f1RvRGChwBnEIoBfSEYOXnCRdleKuv6+lePbMDrMZXeDQnqak5XDOeBgrPPyPfAdcCu/B5z+v3VhplGg==
"@mapbox/node-pre-gyp@^1.0.0":
version "1.0.11"
resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa"
integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==
dependencies:
detect-libc "^2.0.0"
https-proxy-agent "^5.0.0"
make-dir "^3.1.0"
node-fetch "^2.6.7"
nopt "^5.0.0"
npmlog "^5.0.1"
rimraf "^3.0.2"
semver "^7.3.5"
tar "^6.1.11"
"@mattiasbuelens/web-streams-adapter@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@mattiasbuelens/web-streams-adapter/-/web-streams-adapter-0.1.0.tgz#607b5a25682f4ae2741da7ba6df39302505336b3"
@ -3838,11 +3853,19 @@ apollo3-cache-persist@^0.14.1:
resolved "https://registry.yarnpkg.com/apollo3-cache-persist/-/apollo3-cache-persist-0.14.1.tgz#8f4c016b4d413aa28f68429b37c8d12524b5983b"
integrity sha512-p/jNzN/MmSd0TmY7/ts0B3qi0SdQ3w9yNLQdKqB3GGb9xATUlAum2v4hSrTeWd/DZKK2Z7Xg5kFXTH6nNVnKSQ==
aproba@^1.1.1:
"aproba@^1.0.3 || ^2.0.0", aproba@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
are-we-there-yet@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c"
integrity sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==
dependencies:
delegates "^1.0.0"
readable-stream "^3.6.0"
arg@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c"
@ -4586,6 +4609,15 @@ canvas-confetti@^1.4.0:
resolved "https://registry.yarnpkg.com/canvas-confetti/-/canvas-confetti-1.4.0.tgz#840f6db4a566f8f32abe28c00dcd82acf39c92bd"
integrity sha512-S18o4Y9PqI/uabdlT/jI3MY7XBJjNxnfapFIkjkMwpz6qNxLFZOm2b22OMf4ZYDL9lpNWI+Ih4fEMVPwO1KHFQ==
canvas@^2.11.2:
version "2.11.2"
resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860"
integrity sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==
dependencies:
"@mapbox/node-pre-gyp" "^1.0.0"
nan "^2.17.0"
simple-get "^3.0.3"
ccount@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5"
@ -4783,6 +4815,11 @@ color-name@^1.1.4, color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-support@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
colord@^2.9.3:
version "2.9.3"
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
@ -4920,6 +4957,11 @@ console-browserify@^1.1.0:
dependencies:
date-now "^0.1.4"
console-control-strings@^1.0.0, console-control-strings@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==
consolidate@^0.15.1:
version "0.15.1"
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
@ -5880,6 +5922,13 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9"
integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==
decompress-response@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-4.2.1.tgz#414023cc7a302da25ce2ec82d0d5238ccafd8986"
integrity sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==
dependencies:
mimic-response "^2.0.0"
decompress-response@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
@ -5980,6 +6029,11 @@ delegate@^3.1.2:
resolved "https://registry.yarnpkg.com/delegate/-/delegate-3.1.2.tgz#1e1bc6f5cadda6cb6cbf7e6d05d0bcdd5712aebe"
integrity sha1-HhvG9crdpstsv35tBdC83VcSrr4=
delegates@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==
depd@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359"
@ -6129,11 +6183,6 @@ domexception@^4.0.0:
dependencies:
webidl-conversions "^7.0.0"
dommatrix@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/dommatrix/-/dommatrix-1.0.3.tgz#e7c18e8d6f3abdd1fef3dd4aa74c4d2e620a0525"
integrity sha512-l32Xp/TLgWb8ReqbVJAFIvXmY7go4nTxxlWiAFyhoQw9RKEOHBZNnyGvJWqDVSPmq3Y9HlM4npqF/T6VMOXhww==
dompurify@^3.0.5, dompurify@^3.1.6:
version "3.1.6"
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.1.6.tgz#43c714a94c6a7b8801850f82e756685300a027e2"
@ -7371,6 +7420,21 @@ fuzzaldrin-plus@^0.6.0:
resolved "https://registry.yarnpkg.com/fuzzaldrin-plus/-/fuzzaldrin-plus-0.6.0.tgz#832f6489fbe876769459599c914a670ec22947ee"
integrity sha1-gy9kifvodnaUWVmckUpnDsIpR+4=
gauge@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/gauge/-/gauge-3.0.2.tgz#03bf4441c044383908bcfa0656ad91803259b395"
integrity sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==
dependencies:
aproba "^1.0.3 || ^2.0.0"
color-support "^1.1.2"
console-control-strings "^1.0.0"
has-unicode "^2.0.1"
object-assign "^4.1.1"
signal-exit "^3.0.0"
string-width "^4.2.3"
strip-ansi "^6.0.1"
wide-align "^1.1.2"
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@ -7710,6 +7774,11 @@ has-tostringtag@^1.0.0:
dependencies:
has-symbols "^1.0.2"
has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==
has-value@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f"
@ -8000,7 +8069,7 @@ https-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73"
integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=
https-proxy-agent@^5.0.1:
https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
@ -10265,6 +10334,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-response@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-2.1.0.tgz#d13763d35f613d09ec37ebb30bac0469c0ee8f43"
integrity sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==
mimic-response@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
@ -10367,10 +10441,10 @@ minipass@^3.0.0, minipass@^3.1.1:
dependencies:
yallist "^4.0.0"
"minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
version "6.0.2"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-6.0.2.tgz#542844b6c4ce95b202c0995b0a471f1229de4c81"
integrity sha512-MzWSV5nYVT7mVyWCwn2o7JH13w2TBRmmSqSRCKzTw+lmft9X4z+3wjvs06Tzijo5z4W/kahUCDpRXTF+ZrmF/w==
minipass@^5.0.0, "minipass@^5.0.0 || ^6.0.2 || ^7.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d"
integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==
minizlib@^2.1.1:
version "2.1.2"
@ -10716,6 +10790,13 @@ non-layered-tidy-tree-layout@^2.0.2:
resolved "https://registry.yarnpkg.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804"
integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==
nopt@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
dependencies:
abbrev "1"
nopt@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-6.0.0.tgz#245801d8ebf409c6df22ab9d95b65e1309cdb16d"
@ -10764,6 +10845,16 @@ npm-run-path@^4.0.1:
dependencies:
path-key "^3.0.0"
npmlog@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-5.0.1.tgz#f06678e80e29419ad67ab964e0fa69959c1eb8b0"
integrity sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==
dependencies:
are-we-there-yet "^2.0.0"
console-control-strings "^1.1.0"
gauge "^3.0.0"
set-blocking "^2.0.0"
nth-check@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
@ -11173,6 +11264,18 @@ path-type@^5.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-5.0.0.tgz#14b01ed7aea7ddf9c7c3f46181d4d04f9c785bb8"
integrity sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==
path2d-polyfill@^2.0.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/path2d-polyfill/-/path2d-polyfill-2.1.1.tgz#6098b7bf2fc24c306c6377bcd558b17ba437ea27"
integrity sha512-4Rka5lN+rY/p0CdD8+E+BFv51lFaFvJOrlOhyQ+zjzyQrzyh3ozmxd1vVGGDdIbUFSBtIZLSnspxTgPT0iJhvA==
dependencies:
path2d "0.1.1"
path2d@0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/path2d/-/path2d-0.1.1.tgz#d3c3886cd2252fb2a7830c27ea7bb9a862d937ea"
integrity sha512-/+S03c8AGsDYKKBtRDqieTJv2GlkMb0bWjnqOgtF6MkjdUQ9a8ARAtxWf9NgKLGm2+WQr6+/tqJdU8HNGsIDoA==
pbkdf2@^3.0.3:
version "3.0.14"
resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.14.tgz#a35e13c64799b06ce15320f459c230e68e73bade"
@ -11184,13 +11287,13 @@ pbkdf2@^3.0.3:
safe-buffer "^5.0.1"
sha.js "^2.4.8"
pdfjs-dist@^2.16.105:
version "2.16.105"
resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.16.105.tgz#937b9c4a918f03f3979c88209d84c1ce90122c2a"
integrity sha512-J4dn41spsAwUxCpEoVf6GVoz908IAA3mYiLmNxg8J9kfRXc2jxpbUepcP0ocp0alVNLFthTAM8DZ1RaHh8sU0A==
dependencies:
dommatrix "^1.0.3"
web-streams-polyfill "^3.2.1"
pdfjs-dist@^3.11.174:
version "3.11.174"
resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-3.11.174.tgz#5ff47b80f2d58c8dd0d74f615e7c6a7e7e704c4b"
integrity sha512-TdTZPf1trZ8/UFu5Cx/GXB7GZM30LT+wWUNfsi6Bq8ePLnb+woNKtDymI2mxZYBpMbonNFqKmiz684DIfnd8dA==
optionalDependencies:
canvas "^2.11.2"
path2d-polyfill "^2.0.1"
picocolors@^0.2.1:
version "0.2.1"
@ -11935,7 +12038,7 @@ read-pkg@^5.2.0:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.2:
readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2:
version "3.6.2"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
@ -12614,7 +12717,7 @@ sigmund@^1.0.1:
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=
signal-exit@^3.0.3, signal-exit@^3.0.7:
signal-exit@^3.0.0, signal-exit@^3.0.3, signal-exit@^3.0.7:
version "3.0.7"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
@ -12629,6 +12732,15 @@ simple-concat@^1.0.0:
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
simple-get@^3.0.3:
version "3.1.1"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-3.1.1.tgz#cc7ba77cfbe761036fbfce3d021af25fc5584d55"
integrity sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==
dependencies:
decompress-response "^4.2.0"
once "^1.3.1"
simple-concat "^1.0.0"
simple-get@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
@ -12931,16 +13043,7 @@ string-length@^4.0.1:
char-regex "^1.0.2"
strip-ansi "^6.0.0"
"string-width-cjs@npm:string-width@^4.2.0":
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^6.0.1"
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@ -12992,7 +13095,7 @@ string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@ -13006,13 +13109,6 @@ strip-ansi@^5.2.0:
dependencies:
ansi-regex "^4.1.0"
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-ansi@^7.0.1, strip-ansi@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@ -13321,14 +13417,14 @@ tar-stream@^2.1.4:
inherits "^2.0.3"
readable-stream "^3.1.1"
tar@^6.0.2:
version "6.1.11"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621"
integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==
tar@^6.0.2, tar@^6.1.11:
version "6.2.1"
resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a"
integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==
dependencies:
chownr "^2.0.0"
fs-minipass "^2.0.0"
minipass "^3.0.0"
minipass "^5.0.0"
minizlib "^2.1.1"
mkdirp "^1.0.3"
yallist "^4.0.0"
@ -14667,6 +14763,13 @@ which@^2.0.1:
dependencies:
isexe "^2.0.0"
wide-align@^1.1.2:
version "1.1.5"
resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.5.tgz#df1d4c206854369ecf3c9a4898f1b23fbd9d15d3"
integrity sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==
dependencies:
string-width "^1.0.2 || 2 || 3 || 4"
wildcard@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
@ -14692,7 +14795,7 @@ worker-loader@^3.0.8:
loader-utils "^2.0.0"
schema-utils "^3.0.0"
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@ -14710,15 +14813,6 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
dependencies:
ansi-styles "^4.0.0"
string-width "^4.1.0"
strip-ansi "^6.0.0"
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"