From 90303b46526c9c8ec9a04945688af6720b8df28a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 23 Sep 2024 09:52:14 -0400 Subject: [PATCH 01/31] chore: Remove SENTRY_USE_CUSTOMER_DOMAINS (#77872) This setting is no longer used. Use `system:multi-region` feature flag instead. --- src/sentry/conf/server.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py index 3103d0f8e22feb..a95c5dd7f026c9 100644 --- a/src/sentry/conf/server.py +++ b/src/sentry/conf/server.py @@ -2184,10 +2184,6 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]: # This flag activates consuming GroupAttribute messages in the development environment SENTRY_USE_GROUP_ATTRIBUTES = True -# This flag activates code paths that are specific for customer domains -# Deprecated: This setting will be replaced with feature checks for system:multi-region -SENTRY_USE_CUSTOMER_DOMAINS = False - # This flag activates replay analyzer service in the development environment SENTRY_USE_REPLAY_ANALYZER_SERVICE = False From 86f7051a9e098beba6d7cf2d14dde649024e9bfd Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 23 Sep 2024 09:55:41 -0400 Subject: [PATCH 02/31] chore(relocation) Remove outbox skip condition (#77803) Revert changes made in #77787 as the stuck outbox has been cleared. --- src/sentry/receivers/outbox/control.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/sentry/receivers/outbox/control.py b/src/sentry/receivers/outbox/control.py index 1a5e94d448fdcb..46931fe263dbc5 100644 --- a/src/sentry/receivers/outbox/control.py +++ b/src/sentry/receivers/outbox/control.py @@ -140,10 +140,6 @@ def process_relocation_reply_with_export(payload: Mapping[str, Any], **kwds): except Exception: raise FileNotFoundError("Could not open SaaS -> SaaS export in proxy relocation bucket.") - # TODO(mark) remove this after the stuck outbox is cleared - if slug == "test-reloc-ct": - return - with encrypted_bytes: region_relocation_export_service.reply_with_export( relocation_uuid=payload["relocation_uuid"], From d8adb17159754dd62a65b066b950df70395c1443 Mon Sep 17 00:00:00 2001 From: Priscila Oliveira Date: Mon, 23 Sep 2024 16:15:06 +0200 Subject: [PATCH 03/31] ref(whats-new): Remove feature flag: (#77943) --- .../components/sidebar/broadcasts.spec.tsx | 22 +------------ static/app/components/sidebar/broadcasts.tsx | 31 ++----------------- 2 files changed, 4 insertions(+), 49 deletions(-) diff --git a/static/app/components/sidebar/broadcasts.spec.tsx b/static/app/components/sidebar/broadcasts.spec.tsx index de3710dd04c527..00e83e5da13de1 100644 --- a/static/app/components/sidebar/broadcasts.spec.tsx +++ b/static/app/components/sidebar/broadcasts.spec.tsx @@ -51,7 +51,7 @@ describe('Broadcasts', function () { }); it('renders a broadcast item with media content correctly', async function () { - const organization = OrganizationFixture({features: ['what-is-new-revamp']}); + const organization = OrganizationFixture(); const broadcast = BroadcastFixture({ mediaUrl: 'https://images.ctfassets.net/em6l9zw4tzag/2vWdw7ZaApWxygugalbyOC/285525e5b7c9fbfa8fb814a69ab214cd/PerformancePageSketches_hero.jpg?w=2520&h=945&q=50&fm=webp', @@ -87,24 +87,4 @@ describe('Broadcasts', function () { }) ); }); - - it('renders deprecated broadcast experience', async function () { - const organization = OrganizationFixture(); - const broadcast = BroadcastFixture(); - - renderMockRequests({orgSlug: organization.slug, broadcastsResponse: [broadcast]}); - - render( - jest.fn()} - hidePanel={jest.fn()} - organization={organization} - /> - ); - - expect(await screen.findByRole('link', {name: broadcast.cta})).toBeInTheDocument(); - }); }); diff --git a/static/app/components/sidebar/broadcasts.tsx b/static/app/components/sidebar/broadcasts.tsx index 6e38966090d261..14b1d3a43905b3 100644 --- a/static/app/components/sidebar/broadcasts.tsx +++ b/static/app/components/sidebar/broadcasts.tsx @@ -8,7 +8,6 @@ import {BroadcastPanelItem} from 'sentry/components/sidebar/broadcastPanelItem'; import SidebarItem from 'sentry/components/sidebar/sidebarItem'; import SidebarPanel from 'sentry/components/sidebar/sidebarPanel'; import SidebarPanelEmpty from 'sentry/components/sidebar/sidebarPanelEmpty'; -import SidebarPanelItem from 'sentry/components/sidebar/sidebarPanelItem'; import {IconBroadcast} from 'sentry/icons'; import {t} from 'sentry/locale'; import type {Organization} from 'sentry/types/organization'; @@ -36,22 +35,18 @@ function BroadcastSidebarContent({ collapsed, loading, broadcasts, - whatIsNewRevampFeature, hidePanel, onResetCounter, }: { broadcasts: Broadcast[]; loading: boolean; onResetCounter: () => void; - whatIsNewRevampFeature: boolean; } & Pick) { useEffect(() => { return () => { - if (whatIsNewRevampFeature) { - onResetCounter(); - } + onResetCounter(); }; - }, [onResetCounter, whatIsNewRevampFeature]); + }, [onResetCounter]); return ( {t('No recent updates from the Sentry team.')} - ) : whatIsNewRevampFeature ? ( + ) : ( broadcasts.map(item => ( )) - ) : ( - broadcasts.map(item => ( - - )) )} ); @@ -158,10 +142,6 @@ class Broadcasts extends Component { this.props.onShowPanel(); }; - get hasWhatsNewRevampFeature() { - return this.props.organization.features.includes('what-is-new-revamp'); - } - markSeen = async () => { const unseenBroadcastIds = this.unseenIds; if (unseenBroadcastIds.length === 0) { @@ -169,10 +149,6 @@ class Broadcasts extends Component { } await markBroadcastsAsSeen(this.props.api, unseenBroadcastIds); - - if (!this.hasWhatsNewRevampFeature) { - this.handleResetCounter(); - } }; get unseenIds() { @@ -215,7 +191,6 @@ class Broadcasts extends Component { broadcasts={broadcasts} collapsed={collapsed} orientation={orientation} - whatIsNewRevampFeature={this.hasWhatsNewRevampFeature} onResetCounter={this.handleResetCounter} /> )} From f8cd0857b7bc2e14e477662be49fb868e916795b Mon Sep 17 00:00:00 2001 From: Ogi <86684834+obostjancic@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:42:10 +0200 Subject: [PATCH 04/31] ref(onboarding): simplify NestJS onboarding text (#77940) --- .../gettingStartedDocs/javascript/astro.tsx | 3 +-- .../app/gettingStartedDocs/node/connect.tsx | 7 +++---- .../app/gettingStartedDocs/node/express.tsx | 7 +++---- .../app/gettingStartedDocs/node/fastify.tsx | 7 +++---- static/app/gettingStartedDocs/node/hapi.tsx | 7 +++---- static/app/gettingStartedDocs/node/koa.tsx | 7 +++---- static/app/gettingStartedDocs/node/nestjs.tsx | 20 ++++++++----------- static/app/gettingStartedDocs/node/node.tsx | 7 +++---- 8 files changed, 27 insertions(+), 38 deletions(-) diff --git a/static/app/gettingStartedDocs/javascript/astro.tsx b/static/app/gettingStartedDocs/javascript/astro.tsx index 7359868cd5b72d..1861a7360c9900 100644 --- a/static/app/gettingStartedDocs/javascript/astro.tsx +++ b/static/app/gettingStartedDocs/javascript/astro.tsx @@ -303,10 +303,9 @@ import * as Sentry from "@sentry/astro";`, }, ], additionalInfo: tct( - `Note that creating your own [code:sentry.client.config.js] file will override the default settings in your [code2:astro.config.js] file. Learn more about this [link:here].`, + `Note that creating your own [code:sentry.client.config.js] file will override the default settings in your [code:astro.config.js] file. Learn more about this [link:here].`, { code: , - code2: , link: ( ), diff --git a/static/app/gettingStartedDocs/node/connect.tsx b/static/app/gettingStartedDocs/node/connect.tsx index dc7ba29a23a1bc..d417f4b3033937 100644 --- a/static/app/gettingStartedDocs/node/connect.tsx +++ b/static/app/gettingStartedDocs/node/connect.tsx @@ -50,7 +50,7 @@ const onboarding: OnboardingConfig = { { type: StepType.CONFIGURE, description: t( - "Initialize Sentry as early as possible in your application's lifecycle. Otherwise, auto-instrumentation will not work." + "Initialize Sentry as early as possible in your application's lifecycle." ), configurations: [ { @@ -70,10 +70,9 @@ const onboarding: OnboardingConfig = { }, { description: tct( - "Make sure to import [code1:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code2:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", { - code1: , - code2: , + code: , docs: ( ), diff --git a/static/app/gettingStartedDocs/node/express.tsx b/static/app/gettingStartedDocs/node/express.tsx index 3046b314983f10..31b41073f8d15a 100644 --- a/static/app/gettingStartedDocs/node/express.tsx +++ b/static/app/gettingStartedDocs/node/express.tsx @@ -64,7 +64,7 @@ const onboarding: OnboardingConfig = { { type: StepType.CONFIGURE, description: t( - "Initialize Sentry as early as possible in your application's lifecycle. Otherwise, auto-instrumentation will not work." + "Initialize Sentry as early as possible in your application's lifecycle." ), configurations: [ { @@ -84,10 +84,9 @@ const onboarding: OnboardingConfig = { }, { description: tct( - "Make sure to import [code1:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code2:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", { - code1: , - code2: , + code: , docs: ( ), diff --git a/static/app/gettingStartedDocs/node/fastify.tsx b/static/app/gettingStartedDocs/node/fastify.tsx index 38010bbf9d2579..7d20e49c0738ec 100644 --- a/static/app/gettingStartedDocs/node/fastify.tsx +++ b/static/app/gettingStartedDocs/node/fastify.tsx @@ -53,7 +53,7 @@ const onboarding: OnboardingConfig = { { type: StepType.CONFIGURE, description: t( - "Initialize Sentry as early as possible in your application's lifecycle. Otherwise, auto-instrumentation will not work." + "Initialize Sentry as early as possible in your application's lifecycle." ), configurations: [ { @@ -73,10 +73,9 @@ const onboarding: OnboardingConfig = { }, { description: tct( - "Make sure to import [code1:instrument.js/mjs] at the top of your file. Set up the error handler. This setup is typically done in your application's entry point file, which is usually [code2:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", { - code1: , - code2: , + code: , docs: ( ), diff --git a/static/app/gettingStartedDocs/node/hapi.tsx b/static/app/gettingStartedDocs/node/hapi.tsx index 43c3baccd4935e..0fe73f422a0653 100644 --- a/static/app/gettingStartedDocs/node/hapi.tsx +++ b/static/app/gettingStartedDocs/node/hapi.tsx @@ -64,7 +64,7 @@ const onboarding: OnboardingConfig = { { type: StepType.CONFIGURE, description: t( - "Initialize Sentry as early as possible in your application's lifecycle. Otherwise, auto-instrumentation will not work." + "Initialize Sentry as early as possible in your application's lifecycle." ), configurations: [ { @@ -84,10 +84,9 @@ const onboarding: OnboardingConfig = { }, { description: tct( - "Make sure to import [code1:instrument.js/mjs] at the top of your file. Set up the error handler. This setup is typically done in your application's entry point file, which is usually [code2:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", { - code1: , - code2: , + code: , docs: ( ), diff --git a/static/app/gettingStartedDocs/node/koa.tsx b/static/app/gettingStartedDocs/node/koa.tsx index 0d9e5121ed665f..2771e0f58a3fe0 100644 --- a/static/app/gettingStartedDocs/node/koa.tsx +++ b/static/app/gettingStartedDocs/node/koa.tsx @@ -57,7 +57,7 @@ const onboarding: OnboardingConfig = { { type: StepType.CONFIGURE, description: t( - "Initialize Sentry as early as possible in your application's lifecycle. Otherwise, auto-instrumentation will not work." + "Initialize Sentry as early as possible in your application's lifecycle." ), configurations: [ { @@ -77,10 +77,9 @@ const onboarding: OnboardingConfig = { }, { description: tct( - "Make sure to import [code1:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code2:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", { - code1: , - code2: , + code: , docs: ( ), diff --git a/static/app/gettingStartedDocs/node/nestjs.tsx b/static/app/gettingStartedDocs/node/nestjs.tsx index 44cde672b51b61..cbf16773be1129 100644 --- a/static/app/gettingStartedDocs/node/nestjs.tsx +++ b/static/app/gettingStartedDocs/node/nestjs.tsx @@ -106,7 +106,7 @@ const onboarding: OnboardingConfig = { { type: StepType.CONFIGURE, description: t( - "Initialize Sentry as early as possible in your application's lifecycle. Otherwise, auto-instrumentation will not work." + "Initialize Sentry as early as possible in your application's lifecycle." ), configurations: [ { @@ -126,10 +126,9 @@ const onboarding: OnboardingConfig = { }, { description: tct( - 'Import [code1:instrument.js/mjs] in your [code2:main.ts/js] file:', + 'Make sure to import [code:instrument.js/mjs] at the top of your [code:main.ts/js] file:', { - code1: , - code2: , + code: , docs: ( ), @@ -147,9 +146,9 @@ const onboarding: OnboardingConfig = { }, { description: tct( - 'Afterwards, add the [code1:SentryModule] as a root module to your main module:', + 'Add the [code:SentryModule] as a root module to your main module:', { - code1: , + code: , docs: ( ), @@ -167,12 +166,9 @@ const onboarding: OnboardingConfig = { }, { description: tct( - 'In case you are using a global catch-all exception filter (which is either a filter registered with [code1:app.useGlobalFilters()] or a filter registered in your app module providers annotated with a [code2:@Catch()] decorator without arguments), add a [code3:@WithSentry()] decorator to the [code4:catch()] method of this global error filter. This decorator will report all unexpected errors that are received by your global error filter to Sentry:', + 'If you are using a global catch-all exception filter add a [code:@WithSentry()] decorator to the [code:catch()] method of this global error filter. This will report all unhandled errors to Sentry', { - code1: , - code2: , - code3: , - code4: , + code: , } ), code: [ @@ -187,7 +183,7 @@ const onboarding: OnboardingConfig = { }, { description: tct( - 'In case you do not have a global catch-all exception filter, add the [code:SentryGlobalFilter] to the providers of your main module. This filter will report all unhandled errors to Sentry that are not caught by any other error filter. Important: The [code:SentryGlobalFilter] needs to be registered before any other exception filters. Also note that in NestJS + GraphQL applications the [code:SentryGlobalFilter] needs to be replaced with the [code:SentryGlobalGraphQLFilter].', + 'Alternatively, add the [code:SentryGlobalFilter] (or [code:SentryGlobalGraphQLFilter] if you are using GraphQL) before any other exception filters to the providers of your main module.', { code: , } diff --git a/static/app/gettingStartedDocs/node/node.tsx b/static/app/gettingStartedDocs/node/node.tsx index 3e7be4dab8675d..0e475838d8a623 100644 --- a/static/app/gettingStartedDocs/node/node.tsx +++ b/static/app/gettingStartedDocs/node/node.tsx @@ -47,7 +47,7 @@ const onboarding: OnboardingConfig = { { type: StepType.CONFIGURE, description: t( - "Initialize Sentry as early as possible in your application's lifecycle. Otherwise, auto-instrumentation will not work." + "Initialize Sentry as early as possible in your application's lifecycle." ), configurations: [ { @@ -67,10 +67,9 @@ const onboarding: OnboardingConfig = { }, { description: tct( - "Make sure to import [code1:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code2:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", + "Make sure to import [code:instrument.js/mjs] at the top of your file. Set up the error handler after all controllers and before any other error middleware. This setup is typically done in your application's entry point file, which is usually [code:index.(js|ts)]. If you're running your application in ESM mode, or looking for alternative ways to set up Sentry, read about [docs:installation methods in our docs].", { - code1: , - code2: , + code: , docs: ( ), From 14db5e89348f7a08c0b62394571ab20e07badaa4 Mon Sep 17 00:00:00 2001 From: George Gritsouk <989898+gggritso@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:07:45 -0400 Subject: [PATCH 05/31] fix(perf): Prevents settings route from marking "Performance" in sidebar as active (#77946) There's a `/settings/projects/performance/` URL that activates the "Performance" sidebar item because it's using an inexact URL match. Omit Settings pages the same way other paths do this. Yucky but works for now! --- static/app/components/sidebar/sidebarItem.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/app/components/sidebar/sidebarItem.tsx b/static/app/components/sidebar/sidebarItem.tsx index 833d6d24d51469..ae670d5ae95eff 100644 --- a/static/app/components/sidebar/sidebarItem.tsx +++ b/static/app/components/sidebar/sidebarItem.tsx @@ -335,7 +335,9 @@ export function isItemActive( location.pathname.includes('/alerts/') && !location.pathname.startsWith('/settings/')) || (item?.label === 'Releases' && location.pathname.includes('/release-thresholds/')) || - (item?.label === 'Performance' && location.pathname.includes('/performance/')) + (item?.label === 'Performance' && + location.pathname.includes('/performance/') && + !location.pathname.startsWith('/settings/')) ); } From 5ee069a629ccccd68793cbed7931cef68dac19a4 Mon Sep 17 00:00:00 2001 From: William Mak Date: Mon, 23 Sep 2024 11:08:30 -0400 Subject: [PATCH 06/31] feat(spans): Add a way to query numeric spans (#77432) - The syntax we're going with is to extend the existing `tags[foo]` syntax to allow a comma and that the second value is one of two type strings `number` or `string`. With the possibility long term than this could be the unit instead ( with the type being then implicit ) - You can read more about this in the internal notion here: https://www.notion.so/sentry/Public-interface-for-attribute-Querying-f8c12061ca0a4e0f9cea1ed1fd3af814 - Unfortunately aliasing gets a little funky with this implementation since we can't pass `attr_str[foo] as tags[foo,string]` to clickhouse because of the comma in the alias. To alleviate this I've introduced a tag_alias_map so we can pass `tags_foo@string` to clickhouse as the alias, then just map them back in the process_results function --- src/sentry/api/event_search.py | 10 +- src/sentry/search/events/builder/base.py | 20 ++- .../search/events/builder/spans_indexed.py | 38 ++++- src/sentry/search/events/constants.py | 1 + src/sentry/testutils/cases.py | 3 + src/sentry/utils/snuba.py | 9 +- .../test_organization_events_span_indexed.py | 147 ++++++++++++++++++ 7 files changed, 220 insertions(+), 8 deletions(-) diff --git a/src/sentry/api/event_search.py b/src/sentry/api/event_search.py index 5a4634684d945a..7aba67336a779d 100644 --- a/src/sentry/api/event_search.py +++ b/src/sentry/api/event_search.py @@ -137,13 +137,15 @@ key = ~r"[a-zA-Z0-9_.-]+" quoted_key = '"' ~r"[a-zA-Z0-9_.:-]+" '"' explicit_tag_key = "tags" open_bracket search_key closed_bracket +explicit_typed_tag_key = "tags" open_bracket search_key spaces comma spaces search_type closed_bracket aggregate_key = key open_paren spaces function_args? spaces closed_paren function_args = aggregate_param (spaces comma spaces !comma aggregate_param?)* aggregate_param = quoted_aggregate_param / raw_aggregate_param raw_aggregate_param = ~r"[^()\t\n, \"]+" quoted_aggregate_param = '"' ('\\"' / ~r'[^\t\n\"]')* '"' search_key = key / quoted_key -text_key = explicit_tag_key / search_key +search_type = "number" / "string" +text_key = explicit_tag_key / explicit_typed_tag_key / search_key value = ~r"[^()\t\n ]*" quoted_value = '"' ('\\"' / ~r'[^"]')* '"' in_value = (&in_value_termination in_value_char)+ @@ -1046,6 +1048,12 @@ def visit_quoted_key(self, node, children): def visit_explicit_tag_key(self, node, children): return SearchKey(f"tags[{children[2].name}]") + def visit_explicit_typed_tag_key(self, node, children): + return SearchKey(f"tags[{children[2].name},{children[6]}]") + + def visit_search_type(self, node, children): + return node.text + def visit_aggregate_key(self, node, children): children = remove_optional_nodes(children) children = remove_space(children) diff --git a/src/sentry/search/events/builder/base.py b/src/sentry/search/events/builder/base.py index 956707eabb7f87..0e69f09db0572f 100644 --- a/src/sentry/search/events/builder/base.py +++ b/src/sentry/search/events/builder/base.py @@ -260,6 +260,11 @@ def __init__( self.prefixed_to_tag_map: dict[str, str] = {} self.tag_to_prefixed_map: dict[str, str] = {} + # Tags with their type in them can't be passed to clickhouse because of the space + # This map is so we can convert those back before the user sees the internal alias + self.typed_tag_to_alias_map: dict[str, str] = {} + self.alias_to_typed_tag_map: dict[str, str] = {} + self.requires_other_aggregates = False self.limit = self.resolve_limit(limit) self.offset = None if offset is None else Offset(offset) @@ -850,6 +855,9 @@ def resolve_orderby(self, orderby: list[str] | str | None) -> list[OrderBy]: or isinstance(resolved_orderby, AliasedExpression) ): bare_orderby = resolved_orderby.alias + # tags that are typed have a different alias because we can't pass commas down + elif bare_orderby in self.typed_tag_to_alias_map: + bare_orderby = self.typed_tag_to_alias_map[bare_orderby] for selected_column in self.columns: if isinstance(selected_column, Column) and selected_column == resolved_orderby: @@ -1529,12 +1537,14 @@ def run_query( def process_results(self, results: Any) -> EventsResponse: with sentry_sdk.start_span(op="QueryBuilder", description="process_results") as span: span.set_data("result_count", len(results.get("data", []))) - translated_columns = {} + translated_columns = self.alias_to_typed_tag_map if self.builder_config.transform_alias_to_input_format: - translated_columns = { - column: function_details.field - for column, function_details in self.function_alias_map.items() - } + translated_columns.update( + { + column: function_details.field + for column, function_details in self.function_alias_map.items() + } + ) for column in list(self.function_alias_map): translated_column = translated_columns.get(column, column) diff --git a/src/sentry/search/events/builder/spans_indexed.py b/src/sentry/search/events/builder/spans_indexed.py index 98f85003d72130..7a6f31ec3561bd 100644 --- a/src/sentry/search/events/builder/spans_indexed.py +++ b/src/sentry/search/events/builder/spans_indexed.py @@ -1,6 +1,7 @@ from sentry_relay.consts import SPAN_STATUS_CODE_TO_NAME -from snuba_sdk import Column, Function +from snuba_sdk import AliasedExpression, Column, Function +from sentry.exceptions import InvalidSearchQuery from sentry.search.events import constants from sentry.search.events.builder.base import BaseQueryBuilder from sentry.search.events.builder.discover import TimeseriesQueryBuilder, TopEventsQueryBuilder @@ -73,6 +74,41 @@ def _get_dataset_name(self) -> str: return "events_analytics_platform" return self.dataset.value + def resolve_field(self, raw_field: str, alias: bool = False) -> Column: + # try the typed regex first + if len(raw_field) <= 200: + tag_match = constants.TYPED_TAG_KEY_RE.search(raw_field) + else: + raise InvalidSearchQuery(f"{raw_field} is too long, can be a maximum of 200 characters") + field = tag_match.group("tag") if tag_match else None + field_type = tag_match.group("type") if tag_match else None + if ( + field is None + or field_type is None + or not constants.VALID_FIELD_PATTERN.match(field) + # attr field is less permissive than tags, we can't have - in them + or "-" in field + ): + return super().resolve_field(raw_field, alias) + + if field_type not in ["number", "string"]: + raise InvalidSearchQuery( + f"Unknown type for field {raw_field}, only string and number are supported" + ) + + if field_type == "string": + col = Column(f"attr_str[{field}]") + else: + col = Column(f"attr_num[{field}]") + + if alias: + field_alias = f"tags_{field}@{field_type}" + self.typed_tag_to_alias_map[raw_field] = field_alias + self.alias_to_typed_tag_map[field_alias] = raw_field + return AliasedExpression(col, field_alias) + else: + return col + class TimeseriesSpanIndexedQueryBuilder(SpansIndexedQueryBuilderMixin, TimeseriesQueryBuilder): config_class = SpansIndexedDatasetConfig diff --git a/src/sentry/search/events/constants.py b/src/sentry/search/events/constants.py index db60e15ddec53b..a88c2429fc700f 100644 --- a/src/sentry/search/events/constants.py +++ b/src/sentry/search/events/constants.py @@ -101,6 +101,7 @@ class ThresholdDict(TypedDict): } TAG_KEY_RE = re.compile(r"^(sentry_tags|tags)\[(?P.*)\]$") +TYPED_TAG_KEY_RE = re.compile(r"^(sentry_tags|tags)\[(?P.*),\s*(?P.*)\]$") # Based on general/src/protocol/tags.rs in relay VALID_FIELD_PATTERN = re.compile(r"^[a-zA-Z0-9_.:-]*$") diff --git a/src/sentry/testutils/cases.py b/src/sentry/testutils/cases.py index beffdb7c5f4069..97c39f023bfad9 100644 --- a/src/sentry/testutils/cases.py +++ b/src/sentry/testutils/cases.py @@ -3402,6 +3402,7 @@ def create_span( project: Project | None = None, start_ts: datetime | None = None, duration: int = 1000, + measurements: dict[str, Any] | None = None, ) -> dict[str, Any]: """Create span json, not required for store_span, but with no params passed should just work out of the box""" if organization is None: @@ -3440,6 +3441,8 @@ def create_span( # coerce to string for tag, value in dict(span["tags"]).items(): span["tags"][tag] = str(value) + if measurements: + span["measurements"] = measurements return span diff --git a/src/sentry/utils/snuba.py b/src/sentry/utils/snuba.py index bf70cb598739b1..82b1f61f7d4ffc 100644 --- a/src/sentry/utils/snuba.py +++ b/src/sentry/utils/snuba.py @@ -1464,7 +1464,11 @@ def _resolve_column(col): return col if isinstance(col, int) or isinstance(col, float): return col - if isinstance(col, str) and (col.startswith("tags[") or QUOTED_LITERAL_RE.match(col)): + if ( + dataset != Dataset.SpansEAP + and isinstance(col, str) + and (col.startswith("tags[") or QUOTED_LITERAL_RE.match(col)) + ): return col # Some dataset specific logic: @@ -1476,6 +1480,9 @@ def _resolve_column(col): if isinstance(col, str) and col.startswith("sentry_tags["): # Replace the first instance of sentry tags with attr str instead return col.replace("sentry_tags", "attr_str", 1) + if isinstance(col, str) and col.startswith("tags["): + # Replace the first instance of sentry tags with attr str instead + return col.replace("tags", "attr_str", 1) measurement_name = get_measurement_name(col) if measurement_name: return f"attr_num[{measurement_name}]" diff --git a/tests/snuba/api/endpoints/test_organization_events_span_indexed.py b/tests/snuba/api/endpoints/test_organization_events_span_indexed.py index 15f9cbf8655433..5aad5dafb58f94 100644 --- a/tests/snuba/api/endpoints/test_organization_events_span_indexed.py +++ b/tests/snuba/api/endpoints/test_organization_events_span_indexed.py @@ -643,3 +643,150 @@ def test_extrapolation_smoke(self): ) assert response.status_code == 200, f"error: {response.content}\naggregate: {function}" + + def test_numeric_attr_without_space(self): + self.store_spans( + [ + self.create_span( + {"description": "foo", "sentry_tags": {"status": "success", "foo": "five"}}, + measurements={"foo": {"value": 5}}, + start_ts=self.ten_mins_ago, + ), + ], + is_eap=self.is_eap, + ) + + response = self.do_request( + { + "field": ["description", "tags[foo,number]", "tags[foo,string]", "tags[foo]"], + "query": "", + "orderby": "description", + "project": self.project.id, + "dataset": self.dataset, + } + ) + + assert response.status_code == 200, response.content + assert len(response.data["data"]) == 1 + data = response.data["data"] + assert data[0]["tags[foo,number]"] == 5 + assert data[0]["tags[foo,string]"] == "five" + assert data[0]["tags[foo]"] == "five" + + def test_numeric_attr_with_spaces(self): + self.store_spans( + [ + self.create_span( + {"description": "foo", "sentry_tags": {"status": "success", "foo": "five"}}, + measurements={"foo": {"value": 5}}, + start_ts=self.ten_mins_ago, + ), + ], + is_eap=self.is_eap, + ) + + response = self.do_request( + { + "field": ["description", "tags[foo, number]", "tags[foo, string]", "tags[foo]"], + "query": "", + "orderby": "description", + "project": self.project.id, + "dataset": self.dataset, + } + ) + + assert response.status_code == 200, response.content + assert len(response.data["data"]) == 1 + data = response.data["data"] + assert data[0]["tags[foo, number]"] == 5 + assert data[0]["tags[foo, string]"] == "five" + assert data[0]["tags[foo]"] == "five" + + def test_numeric_attr_filtering(self): + self.store_spans( + [ + self.create_span( + {"description": "foo", "sentry_tags": {"status": "success", "foo": "five"}}, + measurements={"foo": {"value": 5}}, + start_ts=self.ten_mins_ago, + ), + self.create_span( + {"description": "bar", "sentry_tags": {"status": "success", "foo": "five"}}, + measurements={"foo": {"value": 8}}, + start_ts=self.ten_mins_ago, + ), + ], + is_eap=self.is_eap, + ) + + response = self.do_request( + { + "field": ["description", "tags[foo,number]"], + "query": "tags[foo,number]:5", + "orderby": "description", + "project": self.project.id, + "dataset": self.dataset, + } + ) + + assert response.status_code == 200, response.content + assert len(response.data["data"]) == 1 + data = response.data["data"] + assert data[0]["tags[foo,number]"] == 5 + assert data[0]["description"] == "foo" + + def test_long_attr_name(self): + response = self.do_request( + { + "field": ["description", "z" * 201], + "query": "", + "orderby": "description", + "project": self.project.id, + "dataset": self.dataset, + } + ) + + assert response.status_code == 400, response.content + assert "Is Too Long" in response.data["detail"].title() + + def test_numeric_attr_orderby(self): + self.store_spans( + [ + self.create_span( + {"description": "baz", "sentry_tags": {"status": "success", "foo": "five"}}, + measurements={"foo": {"value": 71}}, + start_ts=self.ten_mins_ago, + ), + self.create_span( + {"description": "foo", "sentry_tags": {"status": "success", "foo": "five"}}, + measurements={"foo": {"value": 5}}, + start_ts=self.ten_mins_ago, + ), + self.create_span( + {"description": "bar", "sentry_tags": {"status": "success", "foo": "five"}}, + measurements={"foo": {"value": 8}}, + start_ts=self.ten_mins_ago, + ), + ], + is_eap=self.is_eap, + ) + + response = self.do_request( + { + "field": ["description", "tags[foo,number]"], + "query": "", + "orderby": ["tags[foo,number]"], + "project": self.project.id, + "dataset": self.dataset, + } + ) + + assert response.status_code == 200, response.content + assert len(response.data["data"]) == 3 + data = response.data["data"] + assert data[0]["tags[foo,number]"] == 5 + assert data[0]["description"] == "foo" + assert data[1]["tags[foo,number]"] == 8 + assert data[1]["description"] == "bar" + assert data[2]["tags[foo,number]"] == 71 + assert data[2]["description"] == "baz" From 347ede1a3d4bd2807036dc321fd69f50989c0784 Mon Sep 17 00:00:00 2001 From: Ash <0Calories@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:19:31 -0400 Subject: [PATCH 07/31] feat(insights): Occlude very large queries in the hover tooltip (#77800) Extremely long queries can be overwhelming and ugly when they appear in the tooltip preview after hovering over a query description. This PR uses the `ClippedBox` component to add a max height to queries, and provides a "View full query" button to encourage the user to inspect the query in its entirety. Clicking on this button will just jump to the summary page for that query Applies to both SQL and MongoDB queries. ![image](https://github.com/user-attachments/assets/3cad00b1-21e5-4999-8ddd-f690e82136a4) --------- Co-authored-by: Dominik Buszowiecki <44422760+DominikB2014@users.noreply.github.com> --- .../common/components/fullSpanDescription.tsx | 94 ++++++++++++++++--- .../common/components/spanDescription.tsx | 2 +- .../insights/database/utils/jsonUtils.tsx | 20 +++- 3 files changed, 98 insertions(+), 18 deletions(-) diff --git a/static/app/views/insights/common/components/fullSpanDescription.tsx b/static/app/views/insights/common/components/fullSpanDescription.tsx index 70b7ff2e8b0517..e174ef47432f80 100644 --- a/static/app/views/insights/common/components/fullSpanDescription.tsx +++ b/static/app/views/insights/common/components/fullSpanDescription.tsx @@ -1,13 +1,20 @@ -import {Fragment} from 'react'; +import {Fragment, type ReactNode} from 'react'; import styled from '@emotion/styled'; +import ClippedBox from 'sentry/components/clippedBox'; import {CodeSnippet} from 'sentry/components/codeSnippet'; import LoadingIndicator from 'sentry/components/loadingIndicator'; +import {IconOpen} from 'sentry/icons'; +import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {SQLishFormatter} from 'sentry/utils/sqlish/SQLishFormatter'; +import {MutableSearch} from 'sentry/utils/tokenizeSearch'; +import {useLocation} from 'sentry/utils/useLocation'; +import {useNavigate} from 'sentry/utils/useNavigate'; +import {useSpansIndexed} from 'sentry/views/insights/common/queries/useDiscover'; import {useFullSpanFromTrace} from 'sentry/views/insights/common/queries/useFullSpanFromTrace'; import {prettyPrintJsonString} from 'sentry/views/insights/database/utils/jsonUtils'; -import {ModuleName} from 'sentry/views/insights/types'; +import {ModuleName, SpanIndexedField} from 'sentry/views/insights/types'; const formatter = new SQLishFormatter(); @@ -29,16 +36,32 @@ export function FullSpanDescription({ filters, moduleName, }: Props) { + const {data: indexedSpans, isFetching: areIndexedSpansLoading} = useSpansIndexed( + { + search: MutableSearch.fromQueryObject({'span.group': group}), + limit: 1, + fields: [ + SpanIndexedField.PROJECT_ID, + SpanIndexedField.TRANSACTION_ID, + SpanIndexedField.SPAN_DESCRIPTION, + ], + }, + 'api.starfish.span-description' + ); + const indexedSpan = indexedSpans?.[0]; + + // This is used as backup in case we don't have the necessary data available in the indexed span const { data: fullSpan, isLoading, isFetching, - } = useFullSpanFromTrace(group, [INDEXED_SPAN_SORT], Boolean(group), filters); + } = useFullSpanFromTrace(group, [INDEXED_SPAN_SORT], Boolean(indexedSpan), filters); - const description = fullSpan?.description ?? shortDescription; + const description = + indexedSpan?.['span.description'] ?? fullSpan?.description ?? shortDescription; const system = fullSpan?.data?.['db.system']; - if (isLoading && isFetching) { + if (areIndexedSpansLoading || (isLoading && isFetching)) { return ( @@ -53,24 +76,37 @@ export function FullSpanDescription({ if (moduleName === ModuleName.DB) { if (system === 'mongodb') { let stringifiedQuery = ''; + let result: ReturnType | undefined = undefined; - if (fullSpan?.sentry_tags) { - stringifiedQuery = prettyPrintJsonString(fullSpan?.sentry_tags?.description); + if (indexedSpan?.['span.description']) { + result = prettyPrintJsonString(indexedSpan?.['span.description']); } else if (description) { - stringifiedQuery = prettyPrintJsonString(description); + result = prettyPrintJsonString(description); } else if (fullSpan?.sentry_tags?.description) { - stringifiedQuery = prettyPrintJsonString(fullSpan?.sentry_tags?.description); + result = prettyPrintJsonString(fullSpan?.sentry_tags.description); + } else if (fullSpan?.sentry_tags?.description) { + result = prettyPrintJsonString(fullSpan?.sentry_tags?.description); } else { stringifiedQuery = description || fullSpan?.sentry_tags?.description || 'N/A'; } - return {stringifiedQuery}; + if (result) { + stringifiedQuery = result.prettifiedQuery; + } + + return ( + + {stringifiedQuery} + + ); } return ( - - {formatter.toString(description, {maxLineLength: LINE_LENGTH})} - + + + {formatter.toString(description, {maxLineLength: LINE_LENGTH})} + + ); } @@ -81,8 +117,40 @@ export function FullSpanDescription({ return {description}; } +type TruncatedQueryClipBoxProps = { + children: ReactNode; + group: string | undefined; +}; + +function QueryClippedBox({group, children}: TruncatedQueryClipBoxProps) { + const location = useLocation(); + const navigate = useNavigate(); + + return ( + , + onClick: () => + navigate({ + pathname: `${location.pathname}spans/span/${group}/`, + }), + }} + > + {children} + + ); +} + const LINE_LENGTH = 60; const PaddedSpinner = styled('div')` padding: 0 ${space(0.5)}; `; + +const StyledClippedBox = styled(ClippedBox)` + > div > div { + z-index: 1; + } +`; diff --git a/static/app/views/insights/common/components/spanDescription.tsx b/static/app/views/insights/common/components/spanDescription.tsx index 38c2d33b7b5894..c83a9c8692e2f5 100644 --- a/static/app/views/insights/common/components/spanDescription.tsx +++ b/static/app/views/insights/common/components/spanDescription.tsx @@ -90,7 +90,7 @@ export function DatabaseSpanDescription({ return rawDescription ?? 'N/A'; } - return prettyPrintJsonString(bestDescription); + return prettyPrintJsonString(bestDescription).prettifiedQuery; } return formatter.toString(rawDescription ?? ''); diff --git a/static/app/views/insights/database/utils/jsonUtils.tsx b/static/app/views/insights/database/utils/jsonUtils.tsx index 79c423dbc60a1d..2ee90a524b4615 100644 --- a/static/app/views/insights/database/utils/jsonUtils.tsx +++ b/static/app/views/insights/database/utils/jsonUtils.tsx @@ -9,16 +9,28 @@ export const isValidJson = (str: string) => { return true; }; -export function prettyPrintJsonString(json: string) { +export function prettyPrintJsonString(json: string): { + failed: boolean; + isTruncated: boolean; + prettifiedQuery: string; +} { try { - return JSON.stringify(JSON.parse(json), null, 4); + return { + prettifiedQuery: JSON.stringify(JSON.parse(json), null, 4), + isTruncated: false, + failed: false, + }; } catch { // Attempt to repair the JSON try { const repairedJson = jsonrepair(json); - return JSON.stringify(JSON.parse(repairedJson), null, 4); + return { + prettifiedQuery: JSON.stringify(JSON.parse(repairedJson), null, 4), + isTruncated: true, + failed: false, + }; } catch { - return json; + return {prettifiedQuery: json, isTruncated: false, failed: true}; } } } From 07396b7608ee6a7857a35641626b70027f6bc95d Mon Sep 17 00:00:00 2001 From: Michelle Zhang <56095982+michellewzhang@users.noreply.github.com> Date: Mon, 23 Sep 2024 08:56:49 -0700 Subject: [PATCH 08/31] fix(routes): track last route in routeNotFound (#77835) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### The problem In Sentry today, tons of “route not found” events are grouped together into one issue, even if the suspect routes/transactions are not related. This poses a problem because the relevant teams are not alerted to these issues, which means 1) we’re not able to triage & fix these issues as quickly, and 2) we do not have proper scope of the problem areas. See an issue example [here](https://sentry.sentry.io/issues/496412334/events/c7dce88ad52944a095d672d0d114d7da/?project=11276&referrer=replay-errors), and notice that not all the [events](https://sentry.sentry.io/issues/496412334/events/c720b6d1807849b9ae0859f9b165a344/events/?project=11276&referrer=next-event) have the same transaction. ### The goal Improve our grouping for “route not found” issues so that the events are split up into better issues — specifically, based on where the last known route was. That way, we can have more relevant issues grouped by (hopefully) the suspect problem route & triage more effectively. ### Next steps Next step would be setting the fingerprinting rules in the `javascript` project to include the new tag-based grouping rule. This is in project settings: SCR-20240919-tsyc [notion doc](https://www.notion.so/sentry/Improve-Route-not-found-issue-grouping-1068b10e4b5d80129e5dc9b84b1b5eb5?showMoveTo=true&saveParent=true) --- static/app/views/app/index.tsx | 35 ++++++++++--------- .../views/lastKnownRouteContextProvider.tsx | 31 ++++++++++++++++ static/app/views/routeNotFound.tsx | 5 ++- 3 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 static/app/views/lastKnownRouteContextProvider.tsx diff --git a/static/app/views/app/index.tsx b/static/app/views/app/index.tsx index e0efe2a18e1bda..f616a08b965de1 100644 --- a/static/app/views/app/index.tsx +++ b/static/app/views/app/index.tsx @@ -32,6 +32,7 @@ import {useLocation} from 'sentry/utils/useLocation'; import {useUser} from 'sentry/utils/useUser'; import type {InstallWizardProps} from 'sentry/views/admin/installWizard'; import {AsyncSDKIntegrationContextProvider} from 'sentry/views/app/asyncSDKIntegrationProvider'; +import LastKnownRouteContextProvider from 'sentry/views/lastKnownRouteContextProvider'; import {OrganizationContextProvider} from 'sentry/views/organizationContext'; import RouteAnalyticsContextProvider from 'sentry/views/routeAnalyticsContextProvider'; @@ -241,22 +242,24 @@ function App({children, params}: Props) { return ( - - - - - - - - - - {renderBody()} - - - - - - + + + + + + + + + + + {renderBody()} + + + + + + + ); } diff --git a/static/app/views/lastKnownRouteContextProvider.tsx b/static/app/views/lastKnownRouteContextProvider.tsx new file mode 100644 index 00000000000000..38fae90648a579 --- /dev/null +++ b/static/app/views/lastKnownRouteContextProvider.tsx @@ -0,0 +1,31 @@ +import {createContext, useContext} from 'react'; + +import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes'; +import usePrevious from 'sentry/utils/usePrevious'; +import {useRoutes} from 'sentry/utils/useRoutes'; + +interface Props { + children: React.ReactNode; +} + +export const LastKnownRouteContext = createContext(''); + +export function useLastKnownRoute() { + return useContext(LastKnownRouteContext); +} + +/** + * This provider tracks the last known route that the user has navigated to. + * This is used to better group issues when we hit "route not found" errors. + */ +export default function LastKnownRouteContextProvider({children}: Props) { + const route = useRoutes(); + const prevRoute = usePrevious(route); + const lastKnownRoute = getRouteStringFromRoutes(prevRoute); + + return ( + + {children} + + ); +} diff --git a/static/app/views/routeNotFound.tsx b/static/app/views/routeNotFound.tsx index e02dde44fbc034..c92f7ca4aecc62 100644 --- a/static/app/views/routeNotFound.tsx +++ b/static/app/views/routeNotFound.tsx @@ -8,11 +8,13 @@ import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import Sidebar from 'sentry/components/sidebar'; import {t} from 'sentry/locale'; import type {RouteComponentProps} from 'sentry/types/legacyReactRouter'; +import {useLastKnownRoute} from 'sentry/views/lastKnownRouteContextProvider'; type Props = RouteComponentProps<{}, {}>; function RouteNotFound({router, location}: Props) { const {pathname, search, hash} = location; + const lastKnownRoute = useLastKnownRoute(); const isMissingSlash = pathname[pathname.length - 1] !== '/'; @@ -27,13 +29,14 @@ function RouteNotFound({router, location}: Props) { scope.setFingerprint(['RouteNotFound']); scope.setTag('isMissingSlash', isMissingSlash); scope.setTag('pathname', pathname); + scope.setTag('lastKnownRoute', lastKnownRoute); scope.setTag( 'reactRouterVersion', window.__SENTRY_USING_REACT_ROUTER_SIX ? '6' : '3' ); Sentry.captureException(new Error('Route not found')); }); - }, [pathname, search, hash, isMissingSlash, router]); + }, [pathname, search, hash, isMissingSlash, router, lastKnownRoute]); if (isMissingSlash) { return null; From e07fbac0178949199a8c21daad702a8b93a379eb Mon Sep 17 00:00:00 2001 From: Ian Woodard <17186604+IanWoodard@users.noreply.github.com> Date: Mon, 23 Sep 2024 09:49:13 -0700 Subject: [PATCH 09/31] style(onboarding): Fix overflow for SplitContainer (#77954) Before: overflow After: image --- .../app/views/insights/common/components/modulesOnboarding.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/static/app/views/insights/common/components/modulesOnboarding.tsx b/static/app/views/insights/common/components/modulesOnboarding.tsx index 34edda6099fe14..97678326a0fa6c 100644 --- a/static/app/views/insights/common/components/modulesOnboarding.tsx +++ b/static/app/views/insights/common/components/modulesOnboarding.tsx @@ -178,6 +178,7 @@ const Header = styled('h3')` const SplitContainer = styled(Panel)` display: flex; justify-content: center; + overflow: hidden; `; const ModuleInfo = styled('div')` From 9c780e577aa926ff69bf87f580e584e2940d3aa6 Mon Sep 17 00:00:00 2001 From: Christinarlong <60594860+Christinarlong@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:07:22 -0700 Subject: [PATCH 10/31] chore(sentry_apps): Move over installation endpoints to sentry_apps (#77869) --- pyproject.toml | 1 - .../integrations/sentry_apps/__init__.py | 12 ---------- src/sentry/api/urls.py | 22 ++++++++++++++----- src/sentry/conf/server.py | 1 + .../api/endpoints}/__init__.py | 0 .../api/endpoints/installation_details.py} | 0 .../installation_external_issue_actions.py} | 0 .../installation_external_issue_details.py} | 0 .../installation_external_issues.py} | 0 .../installation_external_requests.py} | 0 .../endpoints/sentry_app_installations.py} | 5 +++++ src/sentry/sentry_apps/installations.py | 5 +++-- ...ization_sentry_app_installation_details.py | 0 ...t_organization_sentry_app_installations.py | 0 ...app_installation_external_issue_actions.py | 0 ...app_installation_external_issue_details.py | 0 ...sentry_app_installation_external_issues.py | 0 ...ntry_app_installation_external_requests.py | 0 18 files changed, 25 insertions(+), 21 deletions(-) rename src/sentry/{api/endpoints/integrations/sentry_apps/installation => sentry_apps/api/endpoints}/__init__.py (100%) rename src/sentry/{api/endpoints/integrations/sentry_apps/installation/details.py => sentry_apps/api/endpoints/installation_details.py} (100%) rename src/sentry/{api/endpoints/integrations/sentry_apps/installation/external_issue/actions.py => sentry_apps/api/endpoints/installation_external_issue_actions.py} (100%) rename src/sentry/{api/endpoints/integrations/sentry_apps/installation/external_issue/details.py => sentry_apps/api/endpoints/installation_external_issue_details.py} (100%) rename src/sentry/{api/endpoints/integrations/sentry_apps/installation/external_issue/index.py => sentry_apps/api/endpoints/installation_external_issues.py} (100%) rename src/sentry/{api/endpoints/integrations/sentry_apps/installation/external_requests.py => sentry_apps/api/endpoints/installation_external_requests.py} (100%) rename src/sentry/{api/endpoints/integrations/sentry_apps/installation/index.py => sentry_apps/api/endpoints/sentry_app_installations.py} (94%) rename tests/sentry/{ => sentry_apps}/api/endpoints/test_organization_sentry_app_installation_details.py (100%) rename tests/sentry/{ => sentry_apps}/api/endpoints/test_organization_sentry_app_installations.py (100%) rename tests/sentry/{ => sentry_apps}/api/endpoints/test_sentry_app_installation_external_issue_actions.py (100%) rename tests/sentry/{ => sentry_apps}/api/endpoints/test_sentry_app_installation_external_issue_details.py (100%) rename tests/sentry/{ => sentry_apps}/api/endpoints/test_sentry_app_installation_external_issues.py (100%) rename tests/sentry/{ => sentry_apps}/api/endpoints/test_sentry_app_installation_external_requests.py (100%) diff --git a/pyproject.toml b/pyproject.toml index de1cb687a3a0d2..fca22dbf49f5c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -138,7 +138,6 @@ module = [ "sentry.api.endpoints.index", "sentry.api.endpoints.integrations.sentry_apps.details", "sentry.api.endpoints.integrations.sentry_apps.index", - "sentry.api.endpoints.integrations.sentry_apps.installation.index", "sentry.api.endpoints.integrations.sentry_apps.internal_app_token.index", "sentry.api.endpoints.integrations.sentry_apps.publish_request", "sentry.api.endpoints.integrations.sentry_apps.requests", diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/__init__.py b/src/sentry/api/endpoints/integrations/sentry_apps/__init__.py index 140934cc533b6e..a6a8d05d8ed9dc 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/__init__.py +++ b/src/sentry/api/endpoints/integrations/sentry_apps/__init__.py @@ -3,12 +3,6 @@ from .details import SentryAppDetailsEndpoint from .features import SentryAppFeaturesEndpoint from .index import SentryAppsEndpoint -from .installation.details import SentryAppInstallationDetailsEndpoint -from .installation.external_issue.actions import SentryAppInstallationExternalIssueActionsEndpoint -from .installation.external_issue.details import SentryAppInstallationExternalIssueDetailsEndpoint -from .installation.external_issue.index import SentryAppInstallationExternalIssuesEndpoint -from .installation.external_requests import SentryAppInstallationExternalRequestsEndpoint -from .installation.index import SentryAppInstallationsEndpoint from .interaction import SentryAppInteractionEndpoint from .internal_app_token.details import SentryInternalAppTokenDetailsEndpoint from .internal_app_token.index import SentryInternalAppTokensEndpoint @@ -26,12 +20,6 @@ "SentryAppComponentsEndpoint", "SentryAppDetailsEndpoint", "SentryAppFeaturesEndpoint", - "SentryAppInstallationDetailsEndpoint", - "SentryAppInstallationExternalIssueActionsEndpoint", - "SentryAppInstallationExternalIssueDetailsEndpoint", - "SentryAppInstallationExternalIssuesEndpoint", - "SentryAppInstallationExternalRequestsEndpoint", - "SentryAppInstallationsEndpoint", "SentryAppInteractionEndpoint", "SentryAppPublishRequestEndpoint", "SentryAppRequestsEndpoint", diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index 7ed0ee60551096..483654466288ed 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -273,6 +273,22 @@ from sentry.scim.endpoints.members import OrganizationSCIMMemberDetails, OrganizationSCIMMemberIndex from sentry.scim.endpoints.schemas import OrganizationSCIMSchemaIndex from sentry.scim.endpoints.teams import OrganizationSCIMTeamDetails, OrganizationSCIMTeamIndex +from sentry.sentry_apps.api.endpoints.installation_details import ( + SentryAppInstallationDetailsEndpoint, +) +from sentry.sentry_apps.api.endpoints.installation_external_issue_actions import ( + SentryAppInstallationExternalIssueActionsEndpoint, +) +from sentry.sentry_apps.api.endpoints.installation_external_issue_details import ( + SentryAppInstallationExternalIssueDetailsEndpoint, +) +from sentry.sentry_apps.api.endpoints.installation_external_issues import ( + SentryAppInstallationExternalIssuesEndpoint, +) +from sentry.sentry_apps.api.endpoints.installation_external_requests import ( + SentryAppInstallationExternalRequestsEndpoint, +) +from sentry.sentry_apps.api.endpoints.sentry_app_installations import SentryAppInstallationsEndpoint from sentry.uptime.endpoints.project_uptime_alert_details import ProjectUptimeAlertDetailsEndpoint from sentry.uptime.endpoints.project_uptime_alert_index import ProjectUptimeAlertIndexEndpoint from sentry.users.api.endpoints.authenticator_index import AuthenticatorIndexEndpoint @@ -368,12 +384,6 @@ SentryAppComponentsEndpoint, SentryAppDetailsEndpoint, SentryAppFeaturesEndpoint, - SentryAppInstallationDetailsEndpoint, - SentryAppInstallationExternalIssueActionsEndpoint, - SentryAppInstallationExternalIssueDetailsEndpoint, - SentryAppInstallationExternalIssuesEndpoint, - SentryAppInstallationExternalRequestsEndpoint, - SentryAppInstallationsEndpoint, SentryAppInteractionEndpoint, SentryAppPublishRequestEndpoint, SentryAppRequestsEndpoint, diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py index a95c5dd7f026c9..d64382f29eca46 100644 --- a/src/sentry/conf/server.py +++ b/src/sentry/conf/server.py @@ -2967,6 +2967,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]: "sentry.issues.endpoints", "sentry.integrations.api.endpoints", "sentry.users.api.endpoints", + "sentry.sentry_apps.api.endpoints", ) SENTRY_MAIL_ADAPTER_BACKEND = "sentry.mail.adapter.MailAdapter" diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/installation/__init__.py b/src/sentry/sentry_apps/api/endpoints/__init__.py similarity index 100% rename from src/sentry/api/endpoints/integrations/sentry_apps/installation/__init__.py rename to src/sentry/sentry_apps/api/endpoints/__init__.py diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/installation/details.py b/src/sentry/sentry_apps/api/endpoints/installation_details.py similarity index 100% rename from src/sentry/api/endpoints/integrations/sentry_apps/installation/details.py rename to src/sentry/sentry_apps/api/endpoints/installation_details.py diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/installation/external_issue/actions.py b/src/sentry/sentry_apps/api/endpoints/installation_external_issue_actions.py similarity index 100% rename from src/sentry/api/endpoints/integrations/sentry_apps/installation/external_issue/actions.py rename to src/sentry/sentry_apps/api/endpoints/installation_external_issue_actions.py diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/installation/external_issue/details.py b/src/sentry/sentry_apps/api/endpoints/installation_external_issue_details.py similarity index 100% rename from src/sentry/api/endpoints/integrations/sentry_apps/installation/external_issue/details.py rename to src/sentry/sentry_apps/api/endpoints/installation_external_issue_details.py diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/installation/external_issue/index.py b/src/sentry/sentry_apps/api/endpoints/installation_external_issues.py similarity index 100% rename from src/sentry/api/endpoints/integrations/sentry_apps/installation/external_issue/index.py rename to src/sentry/sentry_apps/api/endpoints/installation_external_issues.py diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/installation/external_requests.py b/src/sentry/sentry_apps/api/endpoints/installation_external_requests.py similarity index 100% rename from src/sentry/api/endpoints/integrations/sentry_apps/installation/external_requests.py rename to src/sentry/sentry_apps/api/endpoints/installation_external_requests.py diff --git a/src/sentry/api/endpoints/integrations/sentry_apps/installation/index.py b/src/sentry/sentry_apps/api/endpoints/sentry_app_installations.py similarity index 94% rename from src/sentry/api/endpoints/integrations/sentry_apps/installation/index.py rename to src/sentry/sentry_apps/api/endpoints/sentry_app_installations.py index 5e2be9dfecbdfa..6c82deffaf59c1 100644 --- a/src/sentry/api/endpoints/integrations/sentry_apps/installation/index.py +++ b/src/sentry/sentry_apps/api/endpoints/sentry_app_installations.py @@ -17,6 +17,8 @@ from sentry.sentry_apps.installations import SentryAppInstallationCreator from sentry.sentry_apps.models.sentry_app import SentryApp from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation +from sentry.users.models.user import User +from sentry.users.services.user.model import RpcUser class SentryAppInstallationsSerializer(serializers.Serializer): @@ -90,6 +92,9 @@ def post(self, request: Request, organization) -> Response: sentry_app__slug=slug, organization_id=organization.id ) except SentryAppInstallation.DoesNotExist: + assert isinstance( + request.user, (User, RpcUser) + ), "user must be authenticated to create a SentryAppInstallation" install = SentryAppInstallationCreator( organization_id=organization.id, slug=slug, notify=True ).run(user=request.user, request=request) diff --git a/src/sentry/sentry_apps/installations.py b/src/sentry/sentry_apps/installations.py index 777f91b6bffe30..9cf122a680c301 100644 --- a/src/sentry/sentry_apps/installations.py +++ b/src/sentry/sentry_apps/installations.py @@ -19,6 +19,7 @@ from sentry.sentry_apps.services.hook import hook_service from sentry.tasks.sentry_apps import installation_webhook from sentry.users.models.user import User +from sentry.users.services.user.model import RpcUser from sentry.utils import metrics @@ -100,7 +101,7 @@ class SentryAppInstallationCreator: slug: str notify: bool = True - def run(self, *, user: User, request: HttpRequest | None) -> SentryAppInstallation: + def run(self, *, user: User | RpcUser, request: HttpRequest | None) -> SentryAppInstallation: metrics.incr("sentry_apps.installation.attempt") with transaction.atomic(router.db_for_write(ApiGrant)): api_grant = self._create_api_grant() @@ -157,7 +158,7 @@ def audit(self, request: HttpRequest | None) -> None: data={"sentry_app": self.sentry_app.name}, ) - def record_analytics(self, user: User) -> None: + def record_analytics(self, user: User | RpcUser) -> None: analytics.record( "sentry_app.installed", user_id=user.id, diff --git a/tests/sentry/api/endpoints/test_organization_sentry_app_installation_details.py b/tests/sentry/sentry_apps/api/endpoints/test_organization_sentry_app_installation_details.py similarity index 100% rename from tests/sentry/api/endpoints/test_organization_sentry_app_installation_details.py rename to tests/sentry/sentry_apps/api/endpoints/test_organization_sentry_app_installation_details.py diff --git a/tests/sentry/api/endpoints/test_organization_sentry_app_installations.py b/tests/sentry/sentry_apps/api/endpoints/test_organization_sentry_app_installations.py similarity index 100% rename from tests/sentry/api/endpoints/test_organization_sentry_app_installations.py rename to tests/sentry/sentry_apps/api/endpoints/test_organization_sentry_app_installations.py diff --git a/tests/sentry/api/endpoints/test_sentry_app_installation_external_issue_actions.py b/tests/sentry/sentry_apps/api/endpoints/test_sentry_app_installation_external_issue_actions.py similarity index 100% rename from tests/sentry/api/endpoints/test_sentry_app_installation_external_issue_actions.py rename to tests/sentry/sentry_apps/api/endpoints/test_sentry_app_installation_external_issue_actions.py diff --git a/tests/sentry/api/endpoints/test_sentry_app_installation_external_issue_details.py b/tests/sentry/sentry_apps/api/endpoints/test_sentry_app_installation_external_issue_details.py similarity index 100% rename from tests/sentry/api/endpoints/test_sentry_app_installation_external_issue_details.py rename to tests/sentry/sentry_apps/api/endpoints/test_sentry_app_installation_external_issue_details.py diff --git a/tests/sentry/api/endpoints/test_sentry_app_installation_external_issues.py b/tests/sentry/sentry_apps/api/endpoints/test_sentry_app_installation_external_issues.py similarity index 100% rename from tests/sentry/api/endpoints/test_sentry_app_installation_external_issues.py rename to tests/sentry/sentry_apps/api/endpoints/test_sentry_app_installation_external_issues.py diff --git a/tests/sentry/api/endpoints/test_sentry_app_installation_external_requests.py b/tests/sentry/sentry_apps/api/endpoints/test_sentry_app_installation_external_requests.py similarity index 100% rename from tests/sentry/api/endpoints/test_sentry_app_installation_external_requests.py rename to tests/sentry/sentry_apps/api/endpoints/test_sentry_app_installation_external_requests.py From 172313975f8e7386af3f710defb047c85ddbecf9 Mon Sep 17 00:00:00 2001 From: Michael Sun <55160142+MichaelSun48@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:10:24 -0700 Subject: [PATCH 11/31] feat(issue-views): Improve tab switch animations (#77890) Makes the tab switch animation not absolute trash by: 1. Keeping text static (by only animating it's change in position and not it's change in width) 2. Slightly delaying when the ellipsis menu animates in (otherwise, as a consequence of keeping the text static, the text will appear behind the ellipsis menu for a moment when switching to that tab) before: https://github.com/user-attachments/assets/c9608738-ea73-4434-ab60-22bbd6d71e94 after: https://github.com/user-attachments/assets/41d95acb-15bf-4b30-b7cb-799b471f3afa (these are both in local dev envs, so the prod animations should be a bit smoother) --- .../groupSearchViewTabs/draggableTabBar.tsx | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx b/static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx index 1954c6f8cc8670..6337d25ab819ce 100644 --- a/static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx +++ b/static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx @@ -3,6 +3,7 @@ import 'intersection-observer'; // polyfill import {useCallback, useContext, useEffect, useState} from 'react'; import styled from '@emotion/styled'; import type {Node} from '@react-types/shared'; +import {motion} from 'framer-motion'; import { DraggableTabList, @@ -387,24 +388,34 @@ export function DraggableTabBar({ disabled={tab.key === editingTabKey} > - setEditingTabKey(isEditing ? tab.key : null)} - onChange={newLabel => handleOnTabRenamed(newLabel.trim(), tab.key)} - tabKey={tab.key} - /> + + setEditingTabKey(isEditing ? tab.key : null)} + onChange={newLabel => handleOnTabRenamed(newLabel.trim(), tab.key)} + tabKey={tab.key} + /> + {/* If tablistState isn't initialized, we want to load the elipsis menu for the initial tab, that way it won't load in a second later and cause the tabs to shift and animate on load. */} {((tabListState && tabListState?.selectedKey === tab.key) || (!tabListState && tab.key === initialTabKey)) && ( - + + + )} From db5b5f9529d111d7468c99b3cd0936ae94f04a6c Mon Sep 17 00:00:00 2001 From: Michael Sun <55160142+MichaelSun48@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:21:19 -0700 Subject: [PATCH 12/31] feat(issue-views): Make search suggestion list dividers disappear on hover (#77886) https://github.com/user-attachments/assets/f603ec48-91d6-4ddc-bbef-69e153e59709 --- static/app/views/issueList/addViewPage.tsx | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/static/app/views/issueList/addViewPage.tsx b/static/app/views/issueList/addViewPage.tsx index e74adfdc674e1d..d5d8cfc736811b 100644 --- a/static/app/views/issueList/addViewPage.tsx +++ b/static/app/views/issueList/addViewPage.tsx @@ -243,6 +243,20 @@ const QueryWrapper = styled('div')` overflow: hidden; `; +const SuggestionList = styled('ul')` + display: flex; + flex-direction: column; + padding: 0; + + li:has(+ li:hover) { + border-bottom: 1px solid transparent; + } + + li:hover { + border-bottom: 1px solid transparent; + } +`; + const Suggestion = styled('li')` position: relative; display: inline-grid; @@ -261,12 +275,6 @@ const Suggestion = styled('li')` } `; -const SuggestionList = styled('ul')` - display: flex; - flex-direction: column; - padding: 0; -`; - const Banner = styled('div')` position: relative; display: flex; From 048900a9d385b13343eea0e26cf0ac0bc9ca5bf8 Mon Sep 17 00:00:00 2001 From: Matt Duncan <14761+mrduncan@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:22:37 -0700 Subject: [PATCH 13/31] chore(issues): Move issues endpoints to issues (#77885) This moves a few more issues-specific endpoints from the general sentry.api.endpoints to the more specific sentry.issues.endpoints. --- src/sentry/api/urls.py | 4 ++-- src/sentry/issues/endpoints/__init__.py | 4 ++++ src/sentry/{api => issues}/endpoints/organization_eventid.py | 0 src/sentry/{api => issues}/endpoints/organization_shortid.py | 0 .../{api => issues}/endpoints/test_organization_shortid.py | 0 5 files changed, 6 insertions(+), 2 deletions(-) rename src/sentry/{api => issues}/endpoints/organization_eventid.py (100%) rename src/sentry/{api => issues}/endpoints/organization_shortid.py (100%) rename tests/sentry/{api => issues}/endpoints/test_organization_shortid.py (100%) diff --git a/src/sentry/api/urls.py b/src/sentry/api/urls.py index 483654466288ed..b7803b5bd5fad5 100644 --- a/src/sentry/api/urls.py +++ b/src/sentry/api/urls.py @@ -169,6 +169,7 @@ ) from sentry.issues.endpoints import ( ActionableItemsEndpoint, + EventIdLookupEndpoint, EventJsonEndpoint, GroupActivitiesEndpoint, GroupDetailsEndpoint, @@ -191,6 +192,7 @@ ProjectGroupStatsEndpoint, ProjectStacktraceLinkEndpoint, SharedGroupDetailsEndpoint, + ShortIdLookupEndpoint, SourceMapDebugEndpoint, TeamGroupsOldEndpoint, ) @@ -438,7 +440,6 @@ from .endpoints.organization_details import OrganizationDetailsEndpoint from .endpoints.organization_environments import OrganizationEnvironmentsEndpoint from .endpoints.organization_event_details import OrganizationEventDetailsEndpoint -from .endpoints.organization_eventid import EventIdLookupEndpoint from .endpoints.organization_events import OrganizationEventsEndpoint from .endpoints.organization_events_facets import OrganizationEventsFacetsEndpoint from .endpoints.organization_events_facets_performance import ( @@ -542,7 +543,6 @@ ) from .endpoints.organization_search_details import OrganizationSearchDetailsEndpoint from .endpoints.organization_sessions import OrganizationSessionsEndpoint -from .endpoints.organization_shortid import ShortIdLookupEndpoint from .endpoints.organization_slugs import SlugsUpdateEndpoint from .endpoints.organization_spans_fields import ( OrganizationSpansFieldsEndpoint, diff --git a/src/sentry/issues/endpoints/__init__.py b/src/sentry/issues/endpoints/__init__.py index d862ff8eaa96ea..50e5e852f05c39 100644 --- a/src/sentry/issues/endpoints/__init__.py +++ b/src/sentry/issues/endpoints/__init__.py @@ -9,11 +9,13 @@ from .group_participants import GroupParticipantsEndpoint from .group_similar_issues import GroupSimilarIssuesEndpoint from .group_similar_issues_embeddings import GroupSimilarIssuesEmbeddingsEndpoint +from .organization_eventid import EventIdLookupEndpoint from .organization_group_index import OrganizationGroupIndexEndpoint from .organization_group_index_stats import OrganizationGroupIndexStatsEndpoint from .organization_group_search_views import OrganizationGroupSearchViewsEndpoint from .organization_release_previous_commits import OrganizationReleasePreviousCommitsEndpoint from .organization_searches import OrganizationSearchesEndpoint +from .organization_shortid import ShortIdLookupEndpoint from .project_event_details import EventJsonEndpoint, ProjectEventDetailsEndpoint from .project_events import ProjectEventsEndpoint from .project_group_index import ProjectGroupIndexEndpoint @@ -25,6 +27,7 @@ __all__ = ( "ActionableItemsEndpoint", + "EventIdLookupEndpoint", "EventJsonEndpoint", "GroupActivitiesEndpoint", "GroupDetailsEndpoint", @@ -47,6 +50,7 @@ "ProjectGroupStatsEndpoint", "ProjectStacktraceLinkEndpoint", "SharedGroupDetailsEndpoint", + "ShortIdLookupEndpoint", "SourceMapDebugEndpoint", "TeamGroupsOldEndpoint", ) diff --git a/src/sentry/api/endpoints/organization_eventid.py b/src/sentry/issues/endpoints/organization_eventid.py similarity index 100% rename from src/sentry/api/endpoints/organization_eventid.py rename to src/sentry/issues/endpoints/organization_eventid.py diff --git a/src/sentry/api/endpoints/organization_shortid.py b/src/sentry/issues/endpoints/organization_shortid.py similarity index 100% rename from src/sentry/api/endpoints/organization_shortid.py rename to src/sentry/issues/endpoints/organization_shortid.py diff --git a/tests/sentry/api/endpoints/test_organization_shortid.py b/tests/sentry/issues/endpoints/test_organization_shortid.py similarity index 100% rename from tests/sentry/api/endpoints/test_organization_shortid.py rename to tests/sentry/issues/endpoints/test_organization_shortid.py From e8d991bd02ea736b7be83d235025de231a25733c Mon Sep 17 00:00:00 2001 From: Michael Sun <55160142+MichaelSun48@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:40:30 -0700 Subject: [PATCH 14/31] feat(issue-views): Add character limits on tab titles (#77883) Adds "soft" and hard limits to the number of characters in the tab titles to prevent any weird behavior. The hard limit is 128 characters since this is the max character length enforced by the database schema. The soft character limit is approximately 50 characters, and a tooltip showing the full title will appear on hover if the tab title exceeds the maximum width. https://github.com/user-attachments/assets/72f47938-5ecf-4ed3-820b-9302a4260cec --- .../groupSearchViewTabs/editableTabTitle.tsx | 58 ++++++++++++------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx b/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx index 4913509c2bbc31..2e0d7a734c8b8c 100644 --- a/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx +++ b/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx @@ -4,6 +4,7 @@ import styled from '@emotion/styled'; import {GrowingInput} from 'sentry/components/growingInput'; import {TabsContext} from 'sentry/components/tabs'; +import {Tooltip} from 'sentry/components/tooltip'; interface EditableTabTitleProps { isEditing: boolean; @@ -84,31 +85,45 @@ function EditableTabTitle({ setInputValue(e.target.value); }; - return isSelected ? ( - isSelected && setIsEditing(true)} - onBlur={handleOnBlur} - ref={inputRef} - style={memoizedStyles} - isEditing={isEditing} - onFocus={e => e.target.select()} - onPointerDown={e => { - e.stopPropagation(); - }} - onMouseDown={e => { - e.stopPropagation(); - }} - /> - ) : ( -
{label}
+ return ( + + {isSelected ? ( + isSelected && setIsEditing(true)} + onBlur={handleOnBlur} + ref={inputRef} + style={memoizedStyles} + isEditing={isEditing} + onFocus={e => e.target.select()} + onPointerDown={e => { + e.stopPropagation(); + }} + onMouseDown={e => { + e.stopPropagation(); + }} + maxLength={128} + /> + ) : ( + {label} + )} + ); } export default EditableTabTitle; +const UnselectedTabTitle = styled('div')` + height: 20px; + /* The max width is slightly smaller than the GrowingInput since the text in the growing input is bolded */ + max-width: 310px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + const StyledGrowingInput = styled(GrowingInput)<{ isEditing: boolean; }>` @@ -119,8 +134,11 @@ const StyledGrowingInput = styled(GrowingInput)<{ min-height: 0px; height: 20px; border-radius: 0px; + text-overflow: ellipsis; cursor: ${p => (p.isEditing ? 'text' : 'pointer')}; + ${p => !p.isEditing && `max-width: 325px;`} + &, &:focus, &:active, From 5e3cf47df3ff27359438222adab483a60441b083 Mon Sep 17 00:00:00 2001 From: Michael Sun <55160142+MichaelSun48@users.noreply.github.com> Date: Mon, 23 Sep 2024 10:41:43 -0700 Subject: [PATCH 15/31] fix(issue-views): Fixes unsaved changes not detected on reload (#77892) Fixes a uncommon bug where reloading the page on a tab where the query is not the tab's persistent query would not retrigger the unsaved changes icon.
Before (notice how after the page refresh, the unsaved changes indicator is gone) https://github.com/user-attachments/assets/2f6f471f-27b0-431a-8a1a-357df65c2881
After https://github.com/user-attachments/assets/87c4b6a6-008e-4f65-b0b6-c6d891b54fb6
--- static/app/views/issueList/customViewsHeader.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/static/app/views/issueList/customViewsHeader.tsx b/static/app/views/issueList/customViewsHeader.tsx index 3fd9088d83c6ed..632060f3f497cd 100644 --- a/static/app/views/issueList/customViewsHeader.tsx +++ b/static/app/views/issueList/customViewsHeader.tsx @@ -237,7 +237,7 @@ function CustomViewsIssueListHeaderTabsContent({ setDraggableTabs( draggableTabs.map(tab => - tab.key === selectedTab!.key + tab.key === selectedTab.key ? { ...tab, unsavedChanges, @@ -289,7 +289,7 @@ function CustomViewsIssueListHeaderTabsContent({ return; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [navigate, organization.slug, query, sort, viewId]); + }, [navigate, organization.slug, query, sort, viewId, tabListState]); // Update local tabs when new views are received from mutation request useEffect(() => { From 587ccc6c9b832a747f7733e6e905a5fc8a379cba Mon Sep 17 00:00:00 2001 From: Evan Purkhiser Date: Mon, 23 Sep 2024 13:45:35 -0400 Subject: [PATCH 16/31] style(js): Enable useBlockStatements (#77960) We lost this when enabling some biome features and disbling eslint feature https://github.com/getsentry/eslint-config-sentry/commit/24d720842939eb18548bcab2ff99027738c03cb5#diff-8240c7fa00318e050d9ba1a3b01538434fb10dd1ac1a8d111a62613e3f647be2L123 --- biome.json | 11 +++++-- .../events/autofix/autofixRootCause.tsx | 8 +++-- .../events/autofix/autofixSteps.tsx | 11 +++++-- .../modals/recoveryOptionsModal.tsx | 4 ++- .../flamegraph/continuousFlamegraph.tsx | 4 ++- .../profiling/flamegraph/flamegraphChart.tsx | 4 ++- .../profiling/profilingOnboardingSidebar.tsx | 4 ++- .../tokens/filter/valueCombobox.tsx | 3 +- .../app/components/smartSearchBar/index.tsx | 4 ++- .../profiling/profile/continuousProfile.tsx | 4 ++- static/app/utils/profiling/units/units.ts | 4 ++- .../app/utils/useDispatchingReducer.spec.tsx | 8 +++-- .../yAxisStep/yAxisSelector/index.tsx | 12 +++++-- .../widgetBuilder/widgetBuilder.tsx | 3 +- .../common/queries/useHasFirstSpan.tsx | 4 ++- .../http/components/charts/durationChart.tsx | 4 ++- .../performance/newTraceDetails/guards.tsx | 8 +++-- .../performance/newTraceDetails/trace.tsx | 30 ++++++++++++----- .../traceDrawer/traceDrawer.tsx | 12 +++++-- .../newTraceDetails/traceModels/traceTree.tsx | 32 ++++++++++++++----- .../traceRenderers/virtualizedViewManager.tsx | 32 ++++++++++++++----- .../traceSearch/traceSearchEvaluator.tsx | 4 ++- .../traceState/traceSearch.tsx | 8 +++-- .../transactionProfiles/content.tsx | 4 ++- .../landing/slowestFunctionsTable.tsx | 8 +++-- .../integrationButton.tsx | 4 ++- 26 files changed, 174 insertions(+), 60 deletions(-) diff --git a/biome.json b/biome.json index 030f704518b143..a28089f817376f 100644 --- a/biome.json +++ b/biome.json @@ -84,7 +84,8 @@ "useNodejsImportProtocol": "error", "useLiteralEnumMembers": "error", "useEnumInitializers": "error", - "useAsConstAssertion": "error" + "useAsConstAssertion": "error", + "useBlockStatements": "error" } } }, @@ -100,8 +101,12 @@ ] }, "css": { - "formatter": { "enabled": false }, - "linter": { "enabled": false } + "formatter": { + "enabled": false + }, + "linter": { + "enabled": false + } }, "formatter": { "enabled": true, diff --git a/static/app/components/events/autofix/autofixRootCause.tsx b/static/app/components/events/autofix/autofixRootCause.tsx index fdc4b387244840..520ef1a2424fb4 100644 --- a/static/app/components/events/autofix/autofixRootCause.tsx +++ b/static/app/components/events/autofix/autofixRootCause.tsx @@ -217,11 +217,15 @@ export function SuggestedFixSnippet({ icon?: React.ReactNode; }) { function getSourceLink() { - if (!repos) return undefined; + if (!repos) { + return undefined; + } const repo = repos.find( r => r.name === snippet.repo_name && r.provider === 'integrations:github' ); - if (!repo) return undefined; + if (!repo) { + return undefined; + } return `${repo.url}/blob/${repo.default_branch}/${snippet.file_path}`; } const extension = getFileExtension(snippet.file_path); diff --git a/static/app/components/events/autofix/autofixSteps.tsx b/static/app/components/events/autofix/autofixSteps.tsx index d2f91ac2aae450..47645575f8b2a3 100644 --- a/static/app/components/events/autofix/autofixSteps.tsx +++ b/static/app/components/events/autofix/autofixSteps.tsx @@ -119,10 +119,11 @@ function useInView(ref: HTMLElement | null) { setInView(entry.isIntersecting); }); - if (!ref) + if (!ref) { return () => { observer.disconnect(); }; + } observer.observe(ref); return () => { @@ -143,9 +144,13 @@ export function AutofixSteps({data, groupId, runId, onRetry}: AutofixStepsProps) if (text.length > 0) { handleSelectFix({customRootCause: text}); } else { - if (!steps) return; + if (!steps) { + return; + } const step = steps[steps.length - 1]; - if (step.type !== AutofixStepType.ROOT_CAUSE_ANALYSIS) return; + if (step.type !== AutofixStepType.ROOT_CAUSE_ANALYSIS) { + return; + } const cause = step.causes[0]; const id = cause.id; handleSelectFix({causeId: id}); diff --git a/static/app/components/modals/recoveryOptionsModal.tsx b/static/app/components/modals/recoveryOptionsModal.tsx index d6435a9bbec895..a3782ca4e1cf55 100644 --- a/static/app/components/modals/recoveryOptionsModal.tsx +++ b/static/app/components/modals/recoveryOptionsModal.tsx @@ -48,7 +48,9 @@ function RecoveryOptionsModal({ setSkipSms(true); }; - if (isPending) return ; + if (isPending) { + return ; + } if (isError) { return ( diff --git a/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx b/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx index 8dd56bc5e31991..e2fc2b790a2466 100644 --- a/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx +++ b/static/app/components/profiling/flamegraph/continuousFlamegraph.tsx @@ -236,7 +236,9 @@ export function ContinuousFlamegraph(): ReactElement { const start: number | null = useMemo(() => { const qs = new URLSearchParams(window.location.search); const startedAt = qs.get('start'); - if (!startedAt) return null; + if (!startedAt) { + return null; + } const sinceEpoch = new Date(startedAt).getTime(); return isNaN(sinceEpoch) ? null : sinceEpoch; diff --git a/static/app/components/profiling/flamegraph/flamegraphChart.tsx b/static/app/components/profiling/flamegraph/flamegraphChart.tsx index 39ff7a42f19182..334b9e21bdfcce 100644 --- a/static/app/components/profiling/flamegraph/flamegraphChart.tsx +++ b/static/app/components/profiling/flamegraph/flamegraphChart.tsx @@ -266,7 +266,9 @@ export function FlamegraphChart({ ); const isInsufficientDuration = useMemo(() => { - if (!chart) return false; + if (!chart) { + return false; + } return formatTo(chart?.configSpace.width, configViewUnit, 'millisecond') < 200; }, [chart, configViewUnit]); diff --git a/static/app/components/profiling/profilingOnboardingSidebar.tsx b/static/app/components/profiling/profilingOnboardingSidebar.tsx index 0d10e022e4687a..5dbecc27b2849c 100644 --- a/static/app/components/profiling/profilingOnboardingSidebar.tsx +++ b/static/app/components/profiling/profilingOnboardingSidebar.tsx @@ -70,7 +70,9 @@ function ProfilingOnboarding(props: CommonSidebarProps) { ); useEffect(() => { - if (currentProject) return; + if (currentProject) { + return; + } // we'll only ever select an unsupportedProject if they do not have a supported project in their organization if (supportedProjects.length === 0 && unsupportedProjects.length > 0) { diff --git a/static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx b/static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx index 93314c5ec985c8..a225d06c60e761 100644 --- a/static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx +++ b/static/app/components/searchQueryBuilder/tokens/filter/valueCombobox.tsx @@ -761,7 +761,7 @@ export function SearchQueryBuilderValueCombobox({ const customMenu: CustomComboboxMenu> | undefined = useMemo(() => { - if (!showDatePicker) + if (!showDatePicker) { return function (props) { return ( ); }; + } return function (props) { return ( diff --git a/static/app/components/smartSearchBar/index.tsx b/static/app/components/smartSearchBar/index.tsx index e6d9da499ed97a..d5ce77a3ccc96f 100644 --- a/static/app/components/smartSearchBar/index.tsx +++ b/static/app/components/smartSearchBar/index.tsx @@ -110,7 +110,9 @@ function isMultiProject(projectIds: number[] | Readonly) { * - [-1] (All Projects) * - [a, b, ...] (two or more projects) */ - if (projectIds === undefined) return false; + if (projectIds === undefined) { + return false; + } return ( projectIds.length === 0 || (projectIds.length === 1 && projectIds[0] === -1) || diff --git a/static/app/utils/profiling/profile/continuousProfile.tsx b/static/app/utils/profiling/profile/continuousProfile.tsx index d1100a10d398e9..db9f1ff374ce26 100644 --- a/static/app/utils/profiling/profile/continuousProfile.tsx +++ b/static/app/utils/profiling/profile/continuousProfile.tsx @@ -46,7 +46,9 @@ export class ContinuousProfile extends Profile { for (let j = stack.length - 1; j >= 0; j--) { frame = resolveFrame(stack[j]); - if (frame) resolvedStack[size++] = frame; + if (frame) { + resolvedStack[size++] = frame; + } } profile.appendSample( diff --git a/static/app/utils/profiling/units/units.ts b/static/app/utils/profiling/units/units.ts index f33d4f236faab1..8901cb3d1eaf82 100644 --- a/static/app/utils/profiling/units/units.ts +++ b/static/app/utils/profiling/units/units.ts @@ -48,7 +48,9 @@ const durationMappings: Record = { export function assertValidProfilingUnit( unit: string ): asserts unit is ProfilingFormatterUnit { - if (unit in durationMappings) return; + if (unit in durationMappings) { + return; + } throw new Error(`Invalid profiling unit: ${unit}`); } diff --git a/static/app/utils/useDispatchingReducer.spec.tsx b/static/app/utils/useDispatchingReducer.spec.tsx index b5f721aa75d1e2..f506c8d2f226e5 100644 --- a/static/app/utils/useDispatchingReducer.spec.tsx +++ b/static/app/utils/useDispatchingReducer.spec.tsx @@ -123,11 +123,15 @@ describe('useDispatchingReducer', () => { it('supports combined reducer', () => { function reducerA(state: Record, action: string) { - if (action !== 'a') return state; + if (action !== 'a') { + return state; + } return {...state, [action]: 1}; } function reducerB(state: Record, action: string) { - if (action !== 'b') return state; + if (action !== 'b') { + return state; + } return {...state, [action]: 1}; } diff --git a/static/app/views/dashboards/widgetBuilder/buildSteps/yAxisStep/yAxisSelector/index.tsx b/static/app/views/dashboards/widgetBuilder/buildSteps/yAxisStep/yAxisSelector/index.tsx index 0bf9a32f4eaa25..2c5128750c0bf5 100644 --- a/static/app/views/dashboards/widgetBuilder/buildSteps/yAxisStep/yAxisSelector/index.tsx +++ b/static/app/views/dashboards/widgetBuilder/buildSteps/yAxisStep/yAxisSelector/index.tsx @@ -63,7 +63,9 @@ export function YAxisSelector({ displayType === DisplayType.BIG_NUMBER ) { onChange(newAggregates, newAggregates.length - 1); - } else onChange(newAggregates); + } else { + onChange(newAggregates); + } } function handleAddEquation(event: React.MouseEvent) { @@ -79,7 +81,9 @@ export function YAxisSelector({ ) { const newSelectedAggregate = newAggregates.length - 1; onChange(newAggregates, newSelectedAggregate); - } else onChange(newAggregates); + } else { + onChange(newAggregates); + } } function handleRemoveQueryField(event: React.MouseEvent, fieldIndex: number) { @@ -93,7 +97,9 @@ export function YAxisSelector({ ) { const newSelectedAggregate = newAggregates.length - 1; onChange(newAggregates, newSelectedAggregate); - } else onChange(newAggregates); + } else { + onChange(newAggregates); + } } function handleChangeQueryField(value: QueryFieldValue, fieldIndex: number) { diff --git a/static/app/views/dashboards/widgetBuilder/widgetBuilder.tsx b/static/app/views/dashboards/widgetBuilder/widgetBuilder.tsx index 88b755d09c9749..b89da0894634a5 100644 --- a/static/app/views/dashboards/widgetBuilder/widgetBuilder.tsx +++ b/static/app/views/dashboards/widgetBuilder/widgetBuilder.tsx @@ -714,8 +714,9 @@ function WidgetBuilder({ if ( organization.features.includes('dashboards-bignumber-equations') && defined(newSelectedAggregate) - ) + ) { newQueries[0].selectedAggregate = newSelectedAggregate; + } set(newState, 'queries', newQueries); set(newState, 'userHasModified', true); diff --git a/static/app/views/insights/common/queries/useHasFirstSpan.tsx b/static/app/views/insights/common/queries/useHasFirstSpan.tsx index ddbc804dbcbc6c..5552d7c2218b7b 100644 --- a/static/app/views/insights/common/queries/useHasFirstSpan.tsx +++ b/static/app/views/insights/common/queries/useHasFirstSpan.tsx @@ -38,7 +38,9 @@ export function useHasFirstSpan(module: ModuleName, projects?: Project[]): boole const pageFilters = usePageFilters(); // Unsupported modules. Remove MOBILE_UI from this list once released. - if ((excludedModuleNames as readonly ModuleName[]).includes(module)) return false; + if ((excludedModuleNames as readonly ModuleName[]).includes(module)) { + return false; + } if (projects) { return projects.some(p => p[modulePropertyMap[module]] === true); diff --git a/static/app/views/insights/http/components/charts/durationChart.tsx b/static/app/views/insights/http/components/charts/durationChart.tsx index 51c90369667a5c..dd96c94e1e520d 100644 --- a/static/app/views/insights/http/components/charts/durationChart.tsx +++ b/static/app/views/insights/http/components/charts/durationChart.tsx @@ -35,7 +35,9 @@ export function DurationChart({ // TODO: This is duplicated from `DurationChart` in `SampleList`. Resolve the duplication const handleChartHighlight: EChartHighlightHandler = function (event) { // ignore mouse hovering over the chart legend - if (!event.batch) return; + if (!event.batch) { + return; + } // TODO: Gross hack. Even though `scatterPlot` is a separate prop, it's just an array of `Series` that gets appended to the main series. To find the point that was hovered, we re-construct the correct series order. It would have been cleaner to just pass the scatter plot as its own, single series const allSeries = [...series, ...(scatterPlot ?? [])]; diff --git a/static/app/views/performance/newTraceDetails/guards.tsx b/static/app/views/performance/newTraceDetails/guards.tsx index fca34e08b0d04e..c39cef67db8aac 100644 --- a/static/app/views/performance/newTraceDetails/guards.tsx +++ b/static/app/views/performance/newTraceDetails/guards.tsx @@ -67,8 +67,12 @@ export function isTraceNode( } export function shouldAddMissingInstrumentationSpan(sdk: string | undefined): boolean { - if (!sdk) return true; - if (sdk.length < 'sentry.javascript.'.length) return true; + if (!sdk) { + return true; + } + if (sdk.length < 'sentry.javascript.'.length) { + return true; + } switch (sdk.toLowerCase()) { case 'sentry.javascript.browser': diff --git a/static/app/views/performance/newTraceDetails/trace.tsx b/static/app/views/performance/newTraceDetails/trace.tsx index d9764b9af140e6..b5158a39f6167e 100644 --- a/static/app/views/performance/newTraceDetails/trace.tsx +++ b/static/app/views/performance/newTraceDetails/trace.tsx @@ -135,8 +135,12 @@ function maybeFocusRow( node: TraceTreeNode, previouslyFocusedNodeRef: React.MutableRefObject | null> ) { - if (!ref) return; - if (node === previouslyFocusedNodeRef.current) return; + if (!ref) { + return; + } + if (node === previouslyFocusedNodeRef.current) { + return; + } previouslyFocusedNodeRef.current = node; ref.focus(); } @@ -399,11 +403,17 @@ export function Trace({ }); } if (event.key === 'ArrowLeft') { - if (node.zoomedIn) onNodeZoomIn(event, node, false); - else if (node.expanded) onNodeExpand(event, node, false); + if (node.zoomedIn) { + onNodeZoomIn(event, node, false); + } else if (node.expanded) { + onNodeExpand(event, node, false); + } } else if (event.key === 'ArrowRight') { - if (!node.expanded) onNodeExpand(event, node, true); - else if (node.expanded && node.canFetch) onNodeZoomIn(event, node, true); + if (!node.expanded) { + onNodeExpand(event, node, true); + } else if (node.expanded && node.canFetch) { + onNodeZoomIn(event, node, true); + } } }, [manager, onNodeExpand, onNodeZoomIn, traceDispatch] @@ -1456,12 +1466,16 @@ interface BackgroundPatternsProps { function BackgroundPatterns(props: BackgroundPatternsProps) { const performance_issues = useMemo(() => { - if (!props.performance_issues.size) return []; + if (!props.performance_issues.size) { + return []; + } return [...props.performance_issues]; }, [props.performance_issues]); const errors = useMemo(() => { - if (!props.errors.size) return []; + if (!props.errors.size) { + return []; + } return [...props.errors]; }, [props.errors]); diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/traceDrawer.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/traceDrawer.tsx index ad30aa62a9b3b1..4ffae77c9c519b 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/traceDrawer.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/traceDrawer.tsx @@ -122,7 +122,9 @@ export function TraceDrawer(props: TraceDrawerProps) { const resizeEndRef = useRef<{id: number} | null>(null); const onResize = useCallback( (size: number, min: number, user?: boolean, minimized?: boolean) => { - if (!props.traceGridRef) return; + if (!props.traceGridRef) { + return; + } // When we resize the layout in x axis, we need to update the physical space // of the virtualized view manager to make sure a redrawing is correctly triggered. @@ -159,7 +161,9 @@ export function TraceDrawer(props: TraceDrawerProps) { const drawerWidth = size / width; const drawerHeight = size / height; - if (resizeEndRef.current) cancelAnimationTimeout(resizeEndRef.current); + if (resizeEndRef.current) { + cancelAnimationTimeout(resizeEndRef.current); + } resizeEndRef.current = requestAnimationTimeout(() => { if (traceStateRef.current.preferences.drawer.minimized) { return; @@ -318,7 +322,9 @@ export function TraceDrawer(props: TraceDrawerProps) { const initializedRef = useRef(false); useLayoutEffect(() => { - if (initializedRef.current) return; + if (initializedRef.current) { + return; + } if (traceState.preferences.drawer.minimized && props.traceGridRef) { if (traceStateRef.current.preferences.layout === 'drawer bottom') { props.traceGridRef.style.gridTemplateColumns = `1fr`; diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx index df2065dd0ba16f..50cd137d2eeb67 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx @@ -379,7 +379,9 @@ function shouldCollapseNodeByDefault(node: TraceTreeNode) { } function startTimestamp(node: TraceTreeNode) { - if (node.space) return node.space[0]; + if (node.space) { + return node.space[0]; + } if (isTraceNode(node)) { return 0; @@ -741,7 +743,9 @@ export class TraceTree { }) ); - if (cancelled) return; + if (cancelled) { + return; + } const updatedData = results.reduce( (acc, result) => { @@ -2690,12 +2694,24 @@ export function traceNodeAnalyticsName(node: TraceTreeNode) if (isAutogroupedNode(node)) { return isParentAutogroupedNode(node) ? 'parent autogroup' : 'sibling autogroup'; } - if (isSpanNode(node)) return 'span'; - if (isTransactionNode(node)) return 'transaction'; - if (isMissingInstrumentationNode(node)) return 'missing instrumentation'; - if (isRootNode(node)) return 'root'; - if (isTraceNode(node)) return 'trace'; - if (isTraceErrorNode(node)) return 'error'; + if (isSpanNode(node)) { + return 'span'; + } + if (isTransactionNode(node)) { + return 'transaction'; + } + if (isMissingInstrumentationNode(node)) { + return 'missing instrumentation'; + } + if (isRootNode(node)) { + return 'root'; + } + if (isTraceNode(node)) { + return 'trace'; + } + if (isTraceErrorNode(node)) { + return 'error'; + } return 'unknown'; } diff --git a/static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx b/static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx index 99452e570446af..e15b44874fa032 100644 --- a/static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx +++ b/static/app/views/performance/newTraceDetails/traceRenderers/virtualizedViewManager.tsx @@ -701,7 +701,9 @@ export class VirtualizedViewManager { } syncResetZoomButton() { - if (!this.reset_zoom_button) return; + if (!this.reset_zoom_button) { + return; + } this.reset_zoom_button.disabled = this.view.trace_view.width === this.view.trace_space.width; } @@ -1372,14 +1374,18 @@ export class VirtualizedViewManager { } hideSpanArrow(span_arrow: this['span_arrows'][0]) { - if (!span_arrow) return; + if (!span_arrow) { + return; + } span_arrow.ref.className = 'TraceArrow'; span_arrow.visible = false; span_arrow.ref.style.opacity = '0'; } drawSpanBar(span_bar: this['span_bars'][0]) { - if (!span_bar) return; + if (!span_bar) { + return; + } const span_transform = this.computeSpanCSSMatrixTransform(span_bar?.space); span_bar.ref.style.transform = `matrix(${span_transform.join(',')}`; @@ -1392,7 +1398,9 @@ export class VirtualizedViewManager { } drawSpanText(span_text: this['span_text'][0], node: TraceTreeNode | undefined) { - if (!span_text) return; + if (!span_text) { + return; + } const [inside, text_transform] = this.computeSpanTextPlacement( node!, @@ -1412,7 +1420,9 @@ export class VirtualizedViewManager { } drawSpanArrow(span_arrow: this['span_arrows'][0], visible: boolean, position: 0 | 1) { - if (!span_arrow) return; + if (!span_arrow) { + return; + } if (visible !== span_arrow.visible) { span_arrow.visible = visible; @@ -1521,7 +1531,9 @@ export class VirtualizedViewManager { container: HTMLElement | null, options: {list_width: number; span_list_width: number} ) { - if (!container) return; + if (!container) { + return; + } if (this.last_list_column_width !== options.list_width) { container.style.setProperty( @@ -1747,8 +1759,12 @@ export class VirtualizedList { // Jest does not implement scroll updates, however since we have the // middleware to handle scroll updates, we can dispatch a scroll event ourselves function dispatchJestScrollUpdate(container: HTMLElement) { - if (!container) return; - if (process.env.NODE_ENV !== 'test') return; + if (!container) { + return; + } + if (process.env.NODE_ENV !== 'test') { + return; + } // since we do not tightly control how browsers handle event dispatching, dispatch it async window.requestAnimationFrame(() => { container.dispatchEvent(new CustomEvent('scroll')); diff --git a/static/app/views/performance/newTraceDetails/traceSearch/traceSearchEvaluator.tsx b/static/app/views/performance/newTraceDetails/traceSearch/traceSearchEvaluator.tsx index 0253c3d54fe7b5..a088f85c06ff0b 100644 --- a/static/app/views/performance/newTraceDetails/traceSearch/traceSearchEvaluator.tsx +++ b/static/app/views/performance/newTraceDetails/traceSearch/traceSearchEvaluator.tsx @@ -365,7 +365,9 @@ function evaluateValueDate( if (typeof value === 'string') { value = new Date(value).getTime(); - if (isNaN(value)) return false; + if (isNaN(value)) { + return false; + } } const query = token.parsed.value.getTime(); diff --git a/static/app/views/performance/newTraceDetails/traceState/traceSearch.tsx b/static/app/views/performance/newTraceDetails/traceState/traceSearch.tsx index d2ca8a4ab66624..b35482e067fdfa 100644 --- a/static/app/views/performance/newTraceDetails/traceState/traceSearch.tsx +++ b/static/app/views/performance/newTraceDetails/traceState/traceSearch.tsx @@ -100,7 +100,9 @@ export function traceSearchReducer( node: state.results[0].value, }; } - if (!state.results) return state; + if (!state.results) { + return state; + } let next = state.resultIteratorIndex + 1; if (next > state.results.length - 1) { @@ -127,7 +129,9 @@ export function traceSearchReducer( node: state.results[state.results.length - 1].value, }; } - if (!state.results) return state; + if (!state.results) { + return state; + } let previous = state.resultIteratorIndex - 1; if (previous < 0) { diff --git a/static/app/views/performance/transactionSummary/transactionProfiles/content.tsx b/static/app/views/performance/transactionSummary/transactionProfiles/content.tsx index c34274f61cdfb3..bece6335fee96d 100644 --- a/static/app/views/performance/transactionSummary/transactionProfiles/content.tsx +++ b/static/app/views/performance/transactionSummary/transactionProfiles/content.tsx @@ -78,7 +78,9 @@ export function TransactionProfilesContent(props: TransactionProfilesContentProp function isEmpty(resp: Profiling.Schema) { const profile = resp.profiles[0]; - if (!profile) return true; + if (!profile) { + return true; + } if ( resp.profiles.length === 1 && isSampledProfile(profile) && diff --git a/static/app/views/profiling/landing/slowestFunctionsTable.tsx b/static/app/views/profiling/landing/slowestFunctionsTable.tsx index ce9adb6d678964..5281c90e03a274 100644 --- a/static/app/views/profiling/landing/slowestFunctionsTable.tsx +++ b/static/app/views/profiling/landing/slowestFunctionsTable.tsx @@ -283,7 +283,9 @@ function SlowestFunctionsProjectBadge(props: SlowestFunctionsProjectBadgeProps) for (const example of props.examples) { if ('project_id' in example) { const project = props.projectsLookupTable[example.project_id]; - if (project) projects.push(project); + if (project) { + projects.push(project); + } } } @@ -339,7 +341,9 @@ function SlowestFunctionTimeSeries(props: SlowestFunctionTimeSeriesProps) { }); const series: Series[] = useMemo(() => { - if (!metrics.isFetched) return []; + if (!metrics.isFetched) { + return []; + } const serie: Series = { seriesName: props.function.name, diff --git a/static/app/views/settings/organizationIntegrations/integrationButton.tsx b/static/app/views/settings/organizationIntegrations/integrationButton.tsx index dd0f6cd5abb399..c80256d131a5f6 100644 --- a/static/app/views/settings/organizationIntegrations/integrationButton.tsx +++ b/static/app/views/settings/organizationIntegrations/integrationButton.tsx @@ -33,7 +33,9 @@ function IntegrationButton({ const organization = useOrganization(); const {provider, type, installStatus, analyticsParams, modalParams} = useContext(IntegrationContext) ?? {}; - if (!provider || !type) return null; + if (!provider || !type) { + return null; + } const {metadata} = provider; if (!userHasAccess) { From e525c0d52738377f34cda8002833a7086fa5b470 Mon Sep 17 00:00:00 2001 From: Evan Purkhiser Date: Mon, 23 Sep 2024 13:58:34 -0400 Subject: [PATCH 17/31] fix(uptime): Use correct feature flag for wizard (#77959) --- static/app/views/alerts/wizard/index.spec.tsx | 2 +- static/app/views/alerts/wizard/options.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/static/app/views/alerts/wizard/index.spec.tsx b/static/app/views/alerts/wizard/index.spec.tsx index af0bf4feed087a..d039917dcd7ac1 100644 --- a/static/app/views/alerts/wizard/index.spec.tsx +++ b/static/app/views/alerts/wizard/index.spec.tsx @@ -112,7 +112,7 @@ describe('AlertWizard', () => { 'incidents', 'performance-view', 'crash-rate-alerts', - 'organizations:uptime-display-wizard-create', + 'uptime-display-wizard-create', ], access: ['org:write', 'alerts:write'], }, diff --git a/static/app/views/alerts/wizard/options.tsx b/static/app/views/alerts/wizard/options.tsx index 7eef2fdb46516f..c7c5016c0c8e9b 100644 --- a/static/app/views/alerts/wizard/options.tsx +++ b/static/app/views/alerts/wizard/options.tsx @@ -133,7 +133,8 @@ export const getAlertWizardCategories = (org: Organization) => { options: ['llm_tokens', 'llm_cost'], }); } - if (org.features.includes('organizations:uptime-display-wizard-create')) { + + if (org.features.includes('uptime-display-wizard-create')) { result.push({ categoryHeading: t('Uptime'), options: ['uptime_monitor'], From 84813e623fefb9772fc91ea5dc3a70eade082d8d Mon Sep 17 00:00:00 2001 From: getsentry-bot Date: Mon, 23 Sep 2024 18:06:31 +0000 Subject: [PATCH 18/31] Revert "feat(issue-views): Add character limits on tab titles (#77883)" This reverts commit e8d991bd02ea736b7be83d235025de231a25733c. Co-authored-by: MichaelSun48 <55160142+MichaelSun48@users.noreply.github.com> --- .../groupSearchViewTabs/editableTabTitle.tsx | 58 +++++++------------ 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx b/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx index 2e0d7a734c8b8c..4913509c2bbc31 100644 --- a/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx +++ b/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx @@ -4,7 +4,6 @@ import styled from '@emotion/styled'; import {GrowingInput} from 'sentry/components/growingInput'; import {TabsContext} from 'sentry/components/tabs'; -import {Tooltip} from 'sentry/components/tooltip'; interface EditableTabTitleProps { isEditing: boolean; @@ -85,45 +84,31 @@ function EditableTabTitle({ setInputValue(e.target.value); }; - return ( - - {isSelected ? ( - isSelected && setIsEditing(true)} - onBlur={handleOnBlur} - ref={inputRef} - style={memoizedStyles} - isEditing={isEditing} - onFocus={e => e.target.select()} - onPointerDown={e => { - e.stopPropagation(); - }} - onMouseDown={e => { - e.stopPropagation(); - }} - maxLength={128} - /> - ) : ( - {label} - )} - + return isSelected ? ( + isSelected && setIsEditing(true)} + onBlur={handleOnBlur} + ref={inputRef} + style={memoizedStyles} + isEditing={isEditing} + onFocus={e => e.target.select()} + onPointerDown={e => { + e.stopPropagation(); + }} + onMouseDown={e => { + e.stopPropagation(); + }} + /> + ) : ( +
{label}
); } export default EditableTabTitle; -const UnselectedTabTitle = styled('div')` - height: 20px; - /* The max width is slightly smaller than the GrowingInput since the text in the growing input is bolded */ - max-width: 310px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -`; - const StyledGrowingInput = styled(GrowingInput)<{ isEditing: boolean; }>` @@ -134,11 +119,8 @@ const StyledGrowingInput = styled(GrowingInput)<{ min-height: 0px; height: 20px; border-radius: 0px; - text-overflow: ellipsis; cursor: ${p => (p.isEditing ? 'text' : 'pointer')}; - ${p => !p.isEditing && `max-width: 325px;`} - &, &:focus, &:active, From 924726fca94d9090b749dea91c5ae594f7a5a95a Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Mon, 23 Sep 2024 11:35:20 -0700 Subject: [PATCH 19/31] test(ui): Remove useOrganization mocks (#77963) --- .../mobile/screens/components/screensOverview.spec.tsx | 4 ---- .../screens/components/screensOverviewTable.spec.tsx | 8 ++++---- .../mobile/screens/views/screensLandingPage.spec.tsx | 7 ++----- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/static/app/views/insights/mobile/screens/components/screensOverview.spec.tsx b/static/app/views/insights/mobile/screens/components/screensOverview.spec.tsx index cf3eb93a95ba64..657f25e658915a 100644 --- a/static/app/views/insights/mobile/screens/components/screensOverview.spec.tsx +++ b/static/app/views/insights/mobile/screens/components/screensOverview.spec.tsx @@ -5,12 +5,10 @@ import {ProjectFixture} from 'sentry-fixture/project'; import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary'; import {useLocation} from 'sentry/utils/useLocation'; -import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject'; import {ScreensOverview} from 'sentry/views/insights/mobile/screens/components/screensOverview'; -jest.mock('sentry/utils/useOrganization'); jest.mock('sentry/views/insights/mobile/common/queries/useCrossPlatformProject'); jest.mock('sentry/utils/usePageFilters'); jest.mock('sentry/utils/useLocation'); @@ -56,8 +54,6 @@ describe('ScreensOverview', () => { selectedPlatform: 'Android', }); - jest.mocked(useOrganization).mockReturnValue(organization); - it('renders search bar and table', async () => { MockApiClient.addMockResponse({ url: `/organizations/${organization.slug}/events/`, diff --git a/static/app/views/insights/mobile/screens/components/screensOverviewTable.spec.tsx b/static/app/views/insights/mobile/screens/components/screensOverviewTable.spec.tsx index 4626b072b0c596..b9d4d9c526a939 100644 --- a/static/app/views/insights/mobile/screens/components/screensOverviewTable.spec.tsx +++ b/static/app/views/insights/mobile/screens/components/screensOverviewTable.spec.tsx @@ -6,11 +6,9 @@ import {render, screen} from 'sentry-test/reactTestingLibrary'; import EventView from 'sentry/utils/discover/eventView'; import {useLocation} from 'sentry/utils/useLocation'; -import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import ScreensOverviewTable from 'sentry/views/insights/mobile/screens/components/screensOverviewTable'; -jest.mock('sentry/utils/useOrganization'); jest.mock('sentry/utils/useLocation'); jest.mock('sentry/views/insights/common/utils/useModuleURL'); jest.mock('sentry/utils/usePageFilters'); @@ -33,7 +31,6 @@ describe('ScreensOverviewTable', () => { state: undefined, } as Location; - jest.mocked(useOrganization).mockReturnValue(organization); jest.mocked(useLocation).mockReturnValue(location); jest.mocked(usePageFilters).mockReturnValue({ isReady: true, @@ -76,7 +73,10 @@ describe('ScreensOverviewTable', () => { eventView={mockEventView} isLoading={false} pageLinks="" - /> + />, + { + organization, + } ); // headers diff --git a/static/app/views/insights/mobile/screens/views/screensLandingPage.spec.tsx b/static/app/views/insights/mobile/screens/views/screensLandingPage.spec.tsx index 5a6912d958f882..7dbbef8bba6a20 100644 --- a/static/app/views/insights/mobile/screens/views/screensLandingPage.spec.tsx +++ b/static/app/views/insights/mobile/screens/views/screensLandingPage.spec.tsx @@ -6,7 +6,6 @@ import {render, screen, waitFor, within} from 'sentry-test/reactTestingLibrary'; import {t} from 'sentry/locale'; import {useLocation} from 'sentry/utils/useLocation'; -import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import useCrossPlatformProject from 'sentry/views/insights/mobile/common/queries/useCrossPlatformProject'; import {MODULE_FEATURE} from 'sentry/views/insights/mobile/screens/settings'; @@ -15,7 +14,6 @@ import {ScreensLandingPage} from 'sentry/views/insights/mobile/screens/views/scr jest.mock('sentry/utils/usePageFilters'); jest.mock('sentry/utils/useLocation'); jest.mock('sentry/views/insights/mobile/common/queries/useCrossPlatformProject'); -jest.mock('sentry/utils/useOrganization'); describe('Screens Landing Page', function () { const organization = OrganizationFixture({ @@ -23,7 +21,6 @@ describe('Screens Landing Page', function () { }); const project = ProjectFixture({platform: 'react-native'}); - jest.mocked(useOrganization).mockReturnValue(organization); jest.mocked(useLocation).mockReturnValue({ action: 'PUSH', hash: '', @@ -200,7 +197,7 @@ describe('Screens Landing Page', function () { it('shows no content if permission is missing', async function () { organization.features = []; - render(); + render(, {organization}); expect( await screen.findByText(t("You don't have access to this feature")) ).toBeInTheDocument(); @@ -208,7 +205,7 @@ describe('Screens Landing Page', function () { it('shows content if permission is there', async function () { organization.features = [MODULE_FEATURE]; - render(); + render(, {organization}); expect(await screen.findAllByText(t('Mobile Screens'))).toHaveLength(2); }); }); From 9206ba718ebe1d3372da7d11a7ec3ccecdc68a95 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Mon, 23 Sep 2024 11:42:36 -0700 Subject: [PATCH 20/31] feat(issues): Consolidate group tags hooks (#77821) --- static/app/actionCreators/group.tsx | 43 +---------- .../app/components/group/tagFacets/index.tsx | 51 ++----------- static/app/routes.tsx | 4 +- .../app/views/issueDetails/groupSidebar.tsx | 6 -- .../groupTagsTab.spec.tsx} | 8 +-- .../groupTagsTab.tsx} | 11 ++- .../issueDetails/groupTags/useGroupTags.tsx | 72 +++++++++++++++++++ .../issueDetails/streamline/eventSearch.tsx | 9 +-- static/app/views/issueDetails/utils.tsx | 45 ++---------- 9 files changed, 95 insertions(+), 154 deletions(-) rename static/app/views/issueDetails/{groupTags.spec.tsx => groupTags/groupTagsTab.spec.tsx} (93%) rename static/app/views/issueDetails/{groupTags.tsx => groupTags/groupTagsTab.tsx} (94%) create mode 100644 static/app/views/issueDetails/groupTags/useGroupTags.tsx diff --git a/static/app/actionCreators/group.tsx b/static/app/actionCreators/group.tsx index 2a2cd21a154b73..23ba413dd7aa18 100644 --- a/static/app/actionCreators/group.tsx +++ b/static/app/actionCreators/group.tsx @@ -1,6 +1,5 @@ import * as Sentry from '@sentry/react'; -import type {Tag} from 'sentry/actionCreators/events'; import type {RequestCallbacks, RequestOptions} from 'sentry/api'; import {Client} from 'sentry/api'; import GroupStore from 'sentry/stores/groupStore'; @@ -8,7 +7,7 @@ import type {Actor} from 'sentry/types/core'; import type {Group, Note, Tag as GroupTag, TagValue} from 'sentry/types/group'; import type {Member} from 'sentry/types/organization'; import type {User} from 'sentry/types/user'; -import {buildTeamId, buildUserId, defined} from 'sentry/utils'; +import {buildTeamId, buildUserId} from 'sentry/utils'; import {uniqueId} from 'sentry/utils/guid'; import type {ApiQueryKey, UseApiQueryOptions} from 'sentry/utils/queryClient'; import {useApiQuery} from 'sentry/utils/queryClient'; @@ -414,46 +413,6 @@ export type GroupTagResponseItem = { export type GroupTagsResponse = GroupTagResponseItem[]; -type FetchIssueTagsParameters = { - environment: string[]; - orgSlug: string; - groupId?: string; - isStatisticalDetector?: boolean; - limit?: number; - readable?: boolean; - statisticalDetectorParameters?: { - durationBaseline: number; - end: string; - start: string; - transaction: string; - }; -}; - -export const makeFetchIssueTagsQueryKey = ({ - groupId, - orgSlug, - environment, - readable, - limit, -}: FetchIssueTagsParameters): ApiQueryKey => [ - `/organizations/${orgSlug}/issues/${groupId}/tags/`, - {query: {environment, readable, limit}}, -]; - -export const useFetchIssueTags = ( - parameters: FetchIssueTagsParameters, - { - enabled = true, - ...options - }: Partial> = {} -) => { - return useApiQuery(makeFetchIssueTagsQueryKey(parameters), { - staleTime: 30000, - enabled: defined(parameters.groupId) && enabled, - ...options, - }); -}; - type FetchIssueTagValuesParameters = { groupId: string; orgSlug: string; diff --git a/static/app/components/group/tagFacets/index.tsx b/static/app/components/group/tagFacets/index.tsx index 81c31bbae1a567..e3d30c2e2c3360 100644 --- a/static/app/components/group/tagFacets/index.tsx +++ b/static/app/components/group/tagFacets/index.tsx @@ -13,15 +13,13 @@ import QuestionTooltip from 'sentry/components/questionTooltip'; import * as SidebarSection from 'sentry/components/sidebarSection'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; -import type {Event} from 'sentry/types/event'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; -import {defined} from 'sentry/utils'; import {appendTagCondition} from 'sentry/utils/queryString'; import {useLocation} from 'sentry/utils/useLocation'; import useOrganization from 'sentry/utils/useOrganization'; import {formatVersion} from 'sentry/utils/versions/formatVersion'; -import {useFetchIssueTagsForDetailsPage} from 'sentry/views/issueDetails/utils'; +import {useGroupTagsReadable} from 'sentry/views/issueDetails/groupTags/useGroupTags'; import TagFacetsDistributionMeter from './tagFacetsDistributionMeter'; @@ -96,32 +94,11 @@ export function sumTagFacetsForTopValues(tag: Tag) { }; } -// Statistical detector issues need to use a Discover query -// which means we need to massage the values to fit the component API -function transformTagFacetDataToGroupTagResponseItems( - tagFacetData: Record -): Record { - const keyedResponse = {}; - - // Statistical detectors are scoped to a single transaction so - // the filter out transaction since the tag is not helpful in the UI - Object.keys(tagFacetData) - .filter(tagKey => tagKey !== 'transaction') - .forEach(tagKey => { - const tagData = tagFacetData[tagKey]; - keyedResponse[tagKey] = sumTagFacetsForTopValues(tagData); - }); - - return keyedResponse; -} - type Props = { environments: string[]; groupId: string; project: Project; tagKeys: string[]; - event?: Event; - isStatisticalDetector?: boolean; tagFormatter?: ( tagsData: Record ) => Record; @@ -133,28 +110,12 @@ export default function TagFacets({ groupId, tagFormatter, project, - isStatisticalDetector, - event, }: Props) { const organization = useOrganization(); - const now = useMemo(() => Date.now(), []); - const {transaction, aggregateRange2, breakpoint} = - event?.occurrence?.evidenceData ?? {}; - const {isPending, isError, data, refetch} = useFetchIssueTagsForDetailsPage({ + const {isPending, isError, data, refetch} = useGroupTagsReadable({ groupId, - orgSlug: organization.slug, environment: environments, - isStatisticalDetector, - statisticalDetectorParameters: - isStatisticalDetector && defined(breakpoint) - ? { - transaction, - durationBaseline: aggregateRange2, - start: new Date(breakpoint * 1000).toISOString(), - end: new Date(now).toISOString(), - } - : undefined, }); const tagsData = useMemo(() => { @@ -162,16 +123,12 @@ export default function TagFacets({ return {}; } - let keyed = keyBy(data, 'key'); - if (isStatisticalDetector) { - keyed = transformTagFacetDataToGroupTagResponseItems(keyed as Record); - } - + const keyed = keyBy(data, 'key'); const formatted = tagFormatter?.(keyed as Record) ?? keyed; return formatted as Record; - }, [data, tagFormatter, isStatisticalDetector]); + }, [data, tagFormatter]); // filter out replayId since we no longer want to // display this on issue details diff --git a/static/app/routes.tsx b/static/app/routes.tsx index 73f600c2b164a2..04b18d40ff0838 100644 --- a/static/app/routes.tsx +++ b/static/app/routes.tsx @@ -1808,7 +1808,9 @@ function buildRoutes() { /> import('sentry/views/issueDetails/groupTags')))} + component={hoc( + make(() => import('sentry/views/issueDetails/groupTags/groupTagsTab')) + )} /> )} {issueTypeConfig.regression.enabled && event && ( diff --git a/static/app/views/issueDetails/groupTags.spec.tsx b/static/app/views/issueDetails/groupTags/groupTagsTab.spec.tsx similarity index 93% rename from static/app/views/issueDetails/groupTags.spec.tsx rename to static/app/views/issueDetails/groupTags/groupTagsTab.spec.tsx index 3ec8ddea59e4fc..176c07f112dfe9 100644 --- a/static/app/views/issueDetails/groupTags.spec.tsx +++ b/static/app/views/issueDetails/groupTags/groupTagsTab.spec.tsx @@ -4,9 +4,9 @@ import {TagsFixture} from 'sentry-fixture/tags'; import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary'; -import GroupTags from 'sentry/views/issueDetails/groupTags'; +import GroupTagsTab from './groupTagsTab'; -describe('GroupTags', function () { +describe('GroupTagsTab', function () { const {routerProps, router, organization} = initializeOrg(); const group = GroupFixture(); let tagsMock; @@ -19,7 +19,7 @@ describe('GroupTags', function () { it('navigates to issue details events tab with correct query params', async function () { render( - >; + +const makeGroupTagsQueryKey = ({ + groupId, + orgSlug, + environment, + readable, + limit, +}: FetchIssueTagsParameters): ApiQueryKey => [ + `/organizations/${orgSlug}/issues/${groupId}/tags/`, + {query: {environment, readable, limit}}, +]; + +export function useGroupTags( + parameters: Omit, + {enabled = true, ...options}: GroupTagUseQueryOptions = {} +) { + const organization = useOrganization(); + return useApiQuery( + makeGroupTagsQueryKey({ + orgSlug: organization.slug, + ...parameters, + }), + { + staleTime: 30000, + enabled: defined(parameters.groupId) && enabled, + ...options, + } + ); +} + +/** + * Primarily used for tag facets + */ +export function useGroupTagsReadable( + parameters: Omit, + options: GroupTagUseQueryOptions = {} +) { + return useGroupTags( + { + readable: true, + limit: 4, + ...parameters, + }, + options + ); +} diff --git a/static/app/views/issueDetails/streamline/eventSearch.tsx b/static/app/views/issueDetails/streamline/eventSearch.tsx index 6155c4306dadbb..6546dea0c96776 100644 --- a/static/app/views/issueDetails/streamline/eventSearch.tsx +++ b/static/app/views/issueDetails/streamline/eventSearch.tsx @@ -1,7 +1,6 @@ import {useCallback, useMemo} from 'react'; import orderBy from 'lodash/orderBy'; -import {useFetchIssueTags} from 'sentry/actionCreators/group'; import {fetchTagValues} from 'sentry/actionCreators/tags'; import { SearchQueryBuilder, @@ -23,6 +22,7 @@ import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; import {Dataset} from 'sentry/views/alerts/rules/metric/types'; import {ALL_EVENTS_EXCLUDED_TAGS} from 'sentry/views/issueDetails/groupEvents'; +import {useGroupTags} from 'sentry/views/issueDetails/groupTags/useGroupTags'; import {mergeAndSortTagValues} from 'sentry/views/issueDetails/utils'; import {makeGetIssueTagValues} from 'sentry/views/issueList/utils/getIssueTagValues'; @@ -36,7 +36,6 @@ interface EventSearchProps { } export function useEventQuery({group}: {group: Group}): string { - const organization = useOrganization(); const {selection} = usePageFilters(); const location = useLocation(); const environments = selection.environments; @@ -49,8 +48,7 @@ export function useEventQuery({group}: {group: Group}): string { eventQuery = locationQuery; } - const {data = []} = useFetchIssueTags({ - orgSlug: organization.slug, + const {data = []} = useGroupTags({ groupId: group.id, environment: environments, }); @@ -138,8 +136,7 @@ export function EventSearch({ const api = useApi(); const organization = useOrganization(); - const {data = []} = useFetchIssueTags({ - orgSlug: organization.slug, + const {data = []} = useGroupTags({ groupId: group.id, environment: environments, }); diff --git a/static/app/views/issueDetails/utils.tsx b/static/app/views/issueDetails/utils.tsx index 4084e84bec627e..4ec03bb51a7375 100644 --- a/static/app/views/issueDetails/utils.tsx +++ b/static/app/views/issueDetails/utils.tsx @@ -1,7 +1,7 @@ import {useMemo} from 'react'; import orderBy from 'lodash/orderBy'; -import {bulkUpdate, useFetchIssueTags} from 'sentry/actionCreators/group'; +import {bulkUpdate} from 'sentry/actionCreators/group'; import {Client} from 'sentry/api'; import {t} from 'sentry/locale'; import ConfigStore from 'sentry/stores/configStore'; @@ -11,9 +11,9 @@ import type {Event} from 'sentry/types/event'; import type {Group, GroupActivity, TagValue} from 'sentry/types/group'; import {defined} from 'sentry/utils'; import {useLocation} from 'sentry/utils/useLocation'; -import useOrganization from 'sentry/utils/useOrganization'; import {useParams} from 'sentry/utils/useParams'; import {useUser} from 'sentry/utils/useUser'; +import {useGroupTagsReadable} from 'sentry/views/issueDetails/groupTags/useGroupTags'; export function markEventSeen( api: Client, @@ -190,41 +190,6 @@ export function getGroupReprocessingStatus( } } -export const useFetchIssueTagsForDetailsPage = ( - { - groupId, - orgSlug, - environment = [], - isStatisticalDetector = false, - statisticalDetectorParameters, - }: { - environment: string[]; - orgSlug: string; - groupId?: string; - isStatisticalDetector?: boolean; - statisticalDetectorParameters?: { - durationBaseline: number; - end: string; - start: string; - transaction: string; - }; - }, - {enabled = true}: {enabled?: boolean} = {} -) => { - return useFetchIssueTags( - { - groupId, - orgSlug, - environment, - readable: true, - limit: 4, - isStatisticalDetector, - statisticalDetectorParameters, - }, - {enabled} - ); -}; - export function useEnvironmentsFromUrl(): string[] { const location = useLocation(); const envs = location.query.environment; @@ -284,18 +249,16 @@ export function useHasStreamlinedUI() { } export function useIsSampleEvent(): boolean { - const params = useParams(); - const organization = useOrganization(); + const params = useParams<{groupId: string}>(); const environments = useEnvironmentsFromUrl(); const groupId = params.groupId; const group = GroupStore.get(groupId); - const {data} = useFetchIssueTagsForDetailsPage( + const {data} = useGroupTagsReadable( { groupId: groupId, - orgSlug: organization.slug, environment: environments, }, // Don't want this query to take precedence over the main requests From a7e811b3f22e23001bc245fecd9dbdcca51a3bcd Mon Sep 17 00:00:00 2001 From: Vu Luong Date: Mon, 23 Sep 2024 11:49:31 -0700 Subject: [PATCH 21/31] ref(chevron): Lighten stroke color (#77965) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Chevrons, when acting as a secondary visual indicator, e.g. to indicate that a button opens a dropdown menu, can be lightened to redirect focus to more important elements, e.g. button labels. Before — Screenshot 2024-09-23 at 11 22 50 AM After — Screenshot 2024-09-23 at 11 23 02 AM --- static/app/components/actions/archive.tsx | 9 ++++++++- static/app/components/actions/resolve.tsx | 9 ++++++++- static/app/components/assigneeBadge.tsx | 6 +++--- static/app/components/badge/groupPriority.tsx | 2 +- static/app/components/chevron.tsx | 7 +++++++ static/app/components/dropdownButton.tsx | 6 ++---- static/app/components/forms/controls/selectControl.tsx | 5 +++-- 7 files changed, 32 insertions(+), 12 deletions(-) diff --git a/static/app/components/actions/archive.tsx b/static/app/components/actions/archive.tsx index 29abc3b3e28c0e..510a99b48bf022 100644 --- a/static/app/components/actions/archive.tsx +++ b/static/app/components/actions/archive.tsx @@ -159,7 +159,14 @@ function ArchiveActions({ {...triggerProps} aria-label={t('Archive options')} size={size} - icon={} + icon={ + + } disabled={disabled} /> )} diff --git a/static/app/components/actions/resolve.tsx b/static/app/components/actions/resolve.tsx index 156a9442f578bd..3c74e066baeb2c 100644 --- a/static/app/components/actions/resolve.tsx +++ b/static/app/components/actions/resolve.tsx @@ -272,7 +272,14 @@ function ResolveActions({ size={size} priority={priority} aria-label={t('More resolve options')} - icon={} + icon={ + + } disabled={isDisabled} /> )} diff --git a/static/app/components/assigneeBadge.tsx b/static/app/components/assigneeBadge.tsx index 76f2547ab2574a..6a308895bfcd2c 100644 --- a/static/app/components/assigneeBadge.tsx +++ b/static/app/components/assigneeBadge.tsx @@ -64,7 +64,7 @@ export function AssigneeBadge({ style={{color: theme.textColor}} >{`${actor.type === 'team' ? '#' : ''}${actor.name}`} )} - + ); }; @@ -73,7 +73,7 @@ export function AssigneeBadge({ {showLabel && 'Loading...'} - + ); @@ -86,7 +86,7 @@ export function AssigneeBadge({ height={`${AVATAR_SIZE}px`} /> {showLabel && Unassigned} - + ); diff --git a/static/app/components/badge/groupPriority.tsx b/static/app/components/badge/groupPriority.tsx index d2649dafa0434b..a4eada534f3f88 100644 --- a/static/app/components/badge/groupPriority.tsx +++ b/static/app/components/badge/groupPriority.tsx @@ -208,7 +208,7 @@ export function GroupPriorityDropdown({ size="zero" > - + )} diff --git a/static/app/components/chevron.tsx b/static/app/components/chevron.tsx index 77de3a115e0eb7..d80b62050d3e22 100644 --- a/static/app/components/chevron.tsx +++ b/static/app/components/chevron.tsx @@ -5,6 +5,11 @@ import theme from 'sentry/utils/theme'; interface ChevronProps extends React.SVGAttributes { direction?: 'up' | 'right' | 'down' | 'left'; + /** + * Whether to lighten (by lowering the opacity) the chevron. Useful if the chevron is + * inside a dropdown trigger button. + */ + light?: boolean; /** * The size of the checkbox. Defaults to 'sm'. */ @@ -51,6 +56,7 @@ function Chevron({ size = 'medium', weight = 'regular', direction = 'down', + light = false, ...props }: ChevronProps) { return ( @@ -58,6 +64,7 @@ function Chevron({ viewBox="0 0 14 14" size={chevronSizeMap[size]} weightFactor={rubikWeightFactor[weight]} + strokeOpacity={light ? 0.6 : 1} {...props} > - + ); } From ec2d760915d3b8a32cb0efa925d8d43bc273cb5b Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Mon, 23 Sep 2024 11:51:41 -0700 Subject: [PATCH 22/31] test(ui): Remove duplicate global test mocks (#77967) --- static/app/actionCreators/account.spec.tsx | 2 -- static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx | 2 -- static/app/utils/useCleanQueryParamsOnRouteLeave.spec.tsx | 1 - static/app/utils/useUrlParams.spec.tsx | 2 -- static/app/views/alerts/create.spec.tsx | 1 - static/app/views/projectsDashboard/index.spec.tsx | 2 -- .../app/views/replays/detail/console/useConsoleFilters.spec.tsx | 1 - .../app/views/replays/detail/errorList/useErrorFilters.spec.tsx | 1 - .../app/views/replays/detail/errorList/useSortErrors.spec.tsx | 1 - .../app/views/replays/detail/network/useNetworkFilters.spec.tsx | 1 - static/app/views/replays/detail/network/useSortNetwork.spec.tsx | 1 - static/app/views/replays/detail/tagPanel/useTagFilters.spec.tsx | 1 - .../organizationMembers/organizationMembersList.spec.tsx | 2 -- 13 files changed, 18 deletions(-) diff --git a/static/app/actionCreators/account.spec.tsx b/static/app/actionCreators/account.spec.tsx index 6f73b479ffd516..3d5d42d4d2ea44 100644 --- a/static/app/actionCreators/account.spec.tsx +++ b/static/app/actionCreators/account.spec.tsx @@ -2,8 +2,6 @@ import {waitFor} from 'sentry-test/reactTestingLibrary'; import {logout} from './account'; -jest.mock('react-router-dom'); - describe('logout', () => { it('has can logout', async function () { jest.spyOn(window.location, 'assign').mockImplementation(() => {}); diff --git a/static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx b/static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx index ac8a804cef9c36..8ec6b8f95f6744 100644 --- a/static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx +++ b/static/app/utils/replays/hooks/useActiveReplayTab.spec.tsx @@ -5,8 +5,6 @@ import {renderHook} from 'sentry-test/reactTestingLibrary'; import {browserHistory} from 'sentry/utils/browserHistory'; import useActiveReplayTab, {TabKey} from 'sentry/utils/replays/hooks/useActiveReplayTab'; -jest.mock('react-router'); - const mockPush = jest.mocked(browserHistory.push); function mockLocation(query: string = '') { diff --git a/static/app/utils/useCleanQueryParamsOnRouteLeave.spec.tsx b/static/app/utils/useCleanQueryParamsOnRouteLeave.spec.tsx index a919d2e6d42962..3fdf334f464b03 100644 --- a/static/app/utils/useCleanQueryParamsOnRouteLeave.spec.tsx +++ b/static/app/utils/useCleanQueryParamsOnRouteLeave.spec.tsx @@ -10,7 +10,6 @@ import useCleanQueryParamsOnRouteLeave, { } from './useCleanQueryParamsOnRouteLeave'; import {useLocation} from './useLocation'; -jest.mock('react-router'); jest.mock('./useLocation'); const MockBrowserHistoryListen = jest.mocked(browserHistory.listen); diff --git a/static/app/utils/useUrlParams.spec.tsx b/static/app/utils/useUrlParams.spec.tsx index a233b2b8a7aaec..ee2f45fa93a785 100644 --- a/static/app/utils/useUrlParams.spec.tsx +++ b/static/app/utils/useUrlParams.spec.tsx @@ -6,8 +6,6 @@ import {browserHistory} from 'sentry/utils/browserHistory'; import useUrlParams from './useUrlParams'; -jest.mock('react-router'); - describe('useUrlParams', () => { beforeEach(() => { window.location.search = qs.stringify({ diff --git a/static/app/views/alerts/create.spec.tsx b/static/app/views/alerts/create.spec.tsx index cfffabd87c81d9..719e7e6f89c032 100644 --- a/static/app/views/alerts/create.spec.tsx +++ b/static/app/views/alerts/create.spec.tsx @@ -26,7 +26,6 @@ jest.mock('sentry/actionCreators/members', () => ({ return {}; }), })); -jest.mock('react-router'); jest.mock('sentry/utils/analytics', () => ({ metric: { startSpan: jest.fn(() => ({ diff --git a/static/app/views/projectsDashboard/index.spec.tsx b/static/app/views/projectsDashboard/index.spec.tsx index a529c4ae539183..832120ba6ace83 100644 --- a/static/app/views/projectsDashboard/index.spec.tsx +++ b/static/app/views/projectsDashboard/index.spec.tsx @@ -17,8 +17,6 @@ import ProjectsStatsStore from 'sentry/stores/projectsStatsStore'; import ProjectsStore from 'sentry/stores/projectsStore'; import {Dashboard} from 'sentry/views/projectsDashboard'; -jest.mock('sentry/api'); - jest.unmock('lodash/debounce'); jest.mock('lodash/debounce', () => { const debounceMap = new Map(); diff --git a/static/app/views/replays/detail/console/useConsoleFilters.spec.tsx b/static/app/views/replays/detail/console/useConsoleFilters.spec.tsx index 252da1c9915761..eefa3bae3ca7cf 100644 --- a/static/app/views/replays/detail/console/useConsoleFilters.spec.tsx +++ b/static/app/views/replays/detail/console/useConsoleFilters.spec.tsx @@ -11,7 +11,6 @@ import {useLocation} from 'sentry/utils/useLocation'; import type {FilterFields} from 'sentry/views/replays/detail/console/useConsoleFilters'; import useConsoleFilters from 'sentry/views/replays/detail/console/useConsoleFilters'; -jest.mock('react-router'); jest.mock('sentry/utils/useLocation'); const mockUseLocation = jest.mocked(useLocation); diff --git a/static/app/views/replays/detail/errorList/useErrorFilters.spec.tsx b/static/app/views/replays/detail/errorList/useErrorFilters.spec.tsx index 9e8dd3049101e6..fe726b6098e2f3 100644 --- a/static/app/views/replays/detail/errorList/useErrorFilters.spec.tsx +++ b/static/app/views/replays/detail/errorList/useErrorFilters.spec.tsx @@ -13,7 +13,6 @@ import type { } from 'sentry/views/replays/detail/errorList/useErrorFilters'; import useErrorFilters from 'sentry/views/replays/detail/errorList/useErrorFilters'; -jest.mock('react-router'); jest.mock('sentry/utils/useLocation'); const mockUseLocation = jest.mocked(useLocation); diff --git a/static/app/views/replays/detail/errorList/useSortErrors.spec.tsx b/static/app/views/replays/detail/errorList/useSortErrors.spec.tsx index 7039ff2edcaed5..bde4d73fcffdbe 100644 --- a/static/app/views/replays/detail/errorList/useSortErrors.spec.tsx +++ b/static/app/views/replays/detail/errorList/useSortErrors.spec.tsx @@ -6,7 +6,6 @@ import {act, renderHook} from 'sentry-test/reactTestingLibrary'; import hydrateErrors from 'sentry/utils/replays/hydrateErrors'; import useSortErrors from 'sentry/views/replays/detail/errorList/useSortErrors'; -jest.mock('react-router'); jest.mock('sentry/utils/useUrlParams', () => { const map = new Map(); return (name, dflt) => { diff --git a/static/app/views/replays/detail/network/useNetworkFilters.spec.tsx b/static/app/views/replays/detail/network/useNetworkFilters.spec.tsx index 4a707fb679158a..c38915855a0d7d 100644 --- a/static/app/views/replays/detail/network/useNetworkFilters.spec.tsx +++ b/static/app/views/replays/detail/network/useNetworkFilters.spec.tsx @@ -16,7 +16,6 @@ import {useLocation} from 'sentry/utils/useLocation'; import type {FilterFields, NetworkSelectOption} from './useNetworkFilters'; import useNetworkFilters from './useNetworkFilters'; -jest.mock('react-router'); jest.mock('sentry/utils/useLocation'); const mockUseLocation = jest.mocked(useLocation); diff --git a/static/app/views/replays/detail/network/useSortNetwork.spec.tsx b/static/app/views/replays/detail/network/useSortNetwork.spec.tsx index 063ed1875d56c1..017b2d4804edfe 100644 --- a/static/app/views/replays/detail/network/useSortNetwork.spec.tsx +++ b/static/app/views/replays/detail/network/useSortNetwork.spec.tsx @@ -12,7 +12,6 @@ import hydrateSpans from 'sentry/utils/replays/hydrateSpans'; import useSortNetwork from './useSortNetwork'; -jest.mock('react-router'); jest.mock('sentry/utils/useUrlParams', () => { const map = new Map(); return (name, dflt) => { diff --git a/static/app/views/replays/detail/tagPanel/useTagFilters.spec.tsx b/static/app/views/replays/detail/tagPanel/useTagFilters.spec.tsx index 1e278bcc7afa9b..f1dd253a6f6bae 100644 --- a/static/app/views/replays/detail/tagPanel/useTagFilters.spec.tsx +++ b/static/app/views/replays/detail/tagPanel/useTagFilters.spec.tsx @@ -8,7 +8,6 @@ import {useLocation} from 'sentry/utils/useLocation'; import type {FilterFields} from 'sentry/views/replays/detail/tagPanel/useTagFilters'; import useTagFilters from 'sentry/views/replays/detail/tagPanel/useTagFilters'; -jest.mock('react-router'); jest.mock('sentry/utils/useLocation'); const mockUseLocation = jest.mocked(useLocation); diff --git a/static/app/views/settings/organizationMembers/organizationMembersList.spec.tsx b/static/app/views/settings/organizationMembers/organizationMembersList.spec.tsx index 82ad33b5395f00..652eddf89ed6c1 100644 --- a/static/app/views/settings/organizationMembers/organizationMembersList.spec.tsx +++ b/static/app/views/settings/organizationMembers/organizationMembersList.spec.tsx @@ -24,8 +24,6 @@ import {browserHistory} from 'sentry/utils/browserHistory'; import OrganizationMembersList from 'sentry/views/settings/organizationMembers/organizationMembersList'; jest.mock('sentry/utils/analytics'); - -jest.mock('sentry/api'); jest.mock('sentry/actionCreators/indicator'); const roles = [ From ef8fbd8c31dc95a6480f26b30fe4e5d40bab5541 Mon Sep 17 00:00:00 2001 From: Andrew Liu <159852527+aliu39@users.noreply.github.com> Date: Mon, 23 Sep 2024 11:52:32 -0700 Subject: [PATCH 23/31] chore(replay): remove FE refs to HE flag (#77961) BE followup: https://github.com/getsentry/sentry/pull/77881 --- .../projectFilters/projectFiltersSettings.tsx | 24 +++++++------------ .../views/settings/project/projectReplays.tsx | 3 --- 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx b/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx index 77f0e4f52ad2ff..836e96ef02d720 100644 --- a/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx +++ b/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx @@ -538,22 +538,16 @@ export function ProjectFiltersSettings({project, params, features}: Props) { type: 'boolean', name: 'filters:react-hydration-errors', label: t('Filter out hydration errors'), - help: organization.features.includes( - 'session-replay-hydration-error-issue-creation' - ) - ? tct( - 'React falls back to do a full re-render on a page. [replaySettings: Hydration Errors created from captured replays] are excluded from this setting.', - { - replaySettings: ( - - ), - } - ) - : t( - 'React falls back to do a full re-render on a page and these errors are often not actionable.' + help: tct( + 'React falls back to do a full re-render on a page. [replaySettings: Hydration Errors created from captured replays] are excluded from this setting.', + { + replaySettings: ( + ), + } + ), disabled: !hasAccess, }} /> diff --git a/static/app/views/settings/project/projectReplays.tsx b/static/app/views/settings/project/projectReplays.tsx index 3b12afb0518522..1b7105f26ddba0 100644 --- a/static/app/views/settings/project/projectReplays.tsx +++ b/static/app/views/settings/project/projectReplays.tsx @@ -54,9 +54,6 @@ function ProjectReplaySettings({organization, project, params: {projectId}}: Pro ); }, getData: data => ({options: data}), - visible({features}) { - return features.has('session-replay-hydration-error-issue-creation'); - }, }, ], }, From 1942a9b8a615bf81f10a61f904cf3fb76de95ee5 Mon Sep 17 00:00:00 2001 From: Snigdha Sharma Date: Mon, 23 Sep 2024 12:00:44 -0700 Subject: [PATCH 24/31] cleanup(priority-alerts): Remove priority-ga flag from the frontend (#77829) The feature has been GA for a few weeks and alert migrations are complete. We can remove the flag now. --- static/app/views/alerts/create.spec.tsx | 62 +++++++++++++++---- static/app/views/alerts/rules/issue/index.tsx | 12 ++-- .../projectInstall/issueAlertOptions.tsx | 8 +-- 3 files changed, 56 insertions(+), 26 deletions(-) diff --git a/static/app/views/alerts/create.spec.tsx b/static/app/views/alerts/create.spec.tsx index 719e7e6f89c032..4027f24bec3723 100644 --- a/static/app/views/alerts/create.spec.tsx +++ b/static/app/views/alerts/create.spec.tsx @@ -161,7 +161,7 @@ describe('ProjectAlertsCreate', function () { 'The issue is older or newer than...', ]); - await userEvent.click(screen.getAllByLabelText('Delete Node')[1]); + await userEvent.click(screen.getAllByLabelText('Delete Node')[2]); await userEvent.click(screen.getByText('Save Rule')); @@ -174,7 +174,10 @@ describe('ProjectAlertsCreate', function () { actions: [], conditions: [ expect.objectContaining({ - id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition', + id: 'sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition', + }), + expect.objectContaining({ + id: 'sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition', }), ], filterMatch: 'all', @@ -196,7 +199,8 @@ describe('ProjectAlertsCreate', function () { body: ProjectAlertRuleFixture(), }); // delete node - await userEvent.click(screen.getByLabelText('Delete Node')); + await userEvent.click(screen.getAllByLabelText('Delete Node')[0]); + await userEvent.click(screen.getAllByLabelText('Delete Node')[0]); // Change name of alert rule await userEvent.type(screen.getByPlaceholderText('Enter Alert Name'), 'myname'); @@ -263,7 +267,7 @@ describe('ProjectAlertsCreate', function () { 'Send a notification to all legacy integrations', ]); - await userEvent.click(screen.getAllByLabelText('Delete Node')[1]); + await userEvent.click(screen.getAllByLabelText('Delete Node')[2]); await userEvent.click(screen.getByText('Save Rule')); @@ -276,7 +280,10 @@ describe('ProjectAlertsCreate', function () { actions: [], conditions: [ expect.objectContaining({ - id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition', + id: 'sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition', + }), + expect.objectContaining({ + id: 'sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition', }), ], filterMatch: 'all', @@ -333,7 +340,10 @@ describe('ProjectAlertsCreate', function () { filterMatch: 'any', conditions: [ expect.objectContaining({ - id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition', + id: 'sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition', + }), + expect.objectContaining({ + id: 'sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition', }), ], actions: [], @@ -382,7 +392,10 @@ describe('ProjectAlertsCreate', function () { actions: [], conditions: [ expect.objectContaining({ - id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition', + id: 'sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition', + }), + expect.objectContaining({ + id: 'sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition', }), ], filterMatch: 'all', @@ -442,7 +455,11 @@ describe('ProjectAlertsCreate', function () { }, ], actions: [], - conditions: [], + conditions: [ + expect.objectContaining({ + id: 'sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition', + }), + ], frequency: 60 * 24, name: 'myname', owner: null, @@ -484,7 +501,10 @@ describe('ProjectAlertsCreate', function () { ], conditions: [ expect.objectContaining({ - id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition', + id: 'sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition', + }), + expect.objectContaining({ + id: 'sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition', }), ], filterMatch: 'all', @@ -531,7 +551,10 @@ describe('ProjectAlertsCreate', function () { actionMatch: 'any', conditions: [ expect.objectContaining({ - id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition', + id: 'sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition', + }), + expect.objectContaining({ + id: 'sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition', }), ], filterMatch: 'all', @@ -560,7 +583,8 @@ describe('ProjectAlertsCreate', function () { statusCode: 400, }); createWrapper(); - // delete existion condition + // delete existion conditions + await userEvent.click(screen.getAllByLabelText('Delete Node')[0]); await userEvent.click(screen.getAllByLabelText('Delete Node')[0]); await waitFor(() => { @@ -604,6 +628,13 @@ describe('ProjectAlertsCreate', function () { it('shows error for incompatible conditions', async () => { createWrapper(); + await userEvent.click(screen.getAllByLabelText('Delete Node')[0]); + await userEvent.click(screen.getAllByLabelText('Delete Node')[0]); + + await selectEvent.select(screen.getByText('Add optional trigger...'), [ + 'A new issue is created', + ]); + const anyDropdown = screen.getByText('any'); expect(anyDropdown).toBeInTheDocument(); await selectEvent.select(anyDropdown, ['all']); @@ -624,6 +655,13 @@ describe('ProjectAlertsCreate', function () { it('test any filterMatch', async () => { createWrapper(); + await userEvent.click(screen.getAllByLabelText('Delete Node')[0]); + await userEvent.click(screen.getAllByLabelText('Delete Node')[0]); + + await selectEvent.select(screen.getByText('Add optional trigger...'), [ + 'A new issue is created', + ]); + const allDropdown = screen.getByText('all'); await selectEvent.select(allDropdown, ['any']); await selectEvent.select(screen.getByText('Add optional filter...'), [ @@ -669,6 +707,7 @@ describe('ProjectAlertsCreate', function () { createWrapper({organization: {features: ['noisy-alert-warning']}}); await userEvent.click((await screen.findAllByLabelText('Delete Node'))[0]); + await userEvent.click((await screen.findAllByLabelText('Delete Node'))[0]); await selectEvent.select(screen.getByText('Add action...'), [ 'Suggested Assignees, Team, or Member', @@ -701,6 +740,7 @@ describe('ProjectAlertsCreate', function () { it('does not display noisy alert banner for legacy integrations', async function () { createWrapper({organization: {features: ['noisy-alert-warning']}}); await userEvent.click((await screen.findAllByLabelText('Delete Node'))[0]); + await userEvent.click((await screen.findAllByLabelText('Delete Node'))[0]); await selectEvent.select(screen.getByText('Add action...'), [ 'Send a notification to all legacy integrations', diff --git a/static/app/views/alerts/rules/issue/index.tsx b/static/app/views/alerts/rules/issue/index.tsx index 03d782a606c217..9349df36139c14 100644 --- a/static/app/views/alerts/rules/issue/index.tsx +++ b/static/app/views/alerts/rules/issue/index.tsx @@ -326,14 +326,10 @@ class IssueRuleEditor extends DeprecatedAsyncView { if (!ruleId && !this.isDuplicateRule) { // now that we've loaded all the possible conditions, we can populate the // value of conditions for a new alert - if (this.props.organization.features.includes('priority-ga-features')) { - this.handleChange('conditions', [ - {id: IssueAlertConditionType.NEW_HIGH_PRIORITY_ISSUE}, - {id: IssueAlertConditionType.EXISTING_HIGH_PRIORITY_ISSUE}, - ]); - } else { - this.handleChange('conditions', [{id: IssueAlertConditionType.FIRST_SEEN_EVENT}]); - } + this.handleChange('conditions', [ + {id: IssueAlertConditionType.NEW_HIGH_PRIORITY_ISSUE}, + {id: IssueAlertConditionType.EXISTING_HIGH_PRIORITY_ISSUE}, + ]); } } diff --git a/static/app/views/projectInstall/issueAlertOptions.tsx b/static/app/views/projectInstall/issueAlertOptions.tsx index d42ab23acf5b4e..f016865bc00e82 100644 --- a/static/app/views/projectInstall/issueAlertOptions.tsx +++ b/static/app/views/projectInstall/issueAlertOptions.tsx @@ -177,9 +177,7 @@ class IssueAlertOptions extends DeprecatedAsyncComponent { , ]; - const default_label = this.shouldUseNewDefaultSetting() - ? t('Alert me on high priority issues') - : t('Alert me on every new issue'); + const default_label = t('Alert me on high priority issues'); const options: [string, React.ReactNode][] = [ [RuleAction.DEFAULT_ALERT.toString(), default_label], @@ -192,10 +190,6 @@ class IssueAlertOptions extends DeprecatedAsyncComponent { ]); } - shouldUseNewDefaultSetting(): boolean { - return this.props.organization.features.includes('priority-ga-features'); - } - getUpdatedData(): RequestDataFragment { let defaultRules: boolean; let shouldCreateCustomRule: boolean; From edc6ae4a26c3167ba46436a2586cda08bc14a19d Mon Sep 17 00:00:00 2001 From: Snigdha Sharma Date: Mon, 23 Sep 2024 12:01:03 -0700 Subject: [PATCH 25/31] cleanup(priority-alerts): Remove priority-ga flag from the backend (#77830) The feature has been GA for a few weeks and alert migrations are complete. We can remove the flag now. --- .../endpoints/project_rules_configuration.py | 6 ---- src/sentry/features/temporary.py | 2 +- src/sentry/receivers/rules.py | 34 +++---------------- .../existing_high_priority_issue.py | 4 --- .../conditions/new_high_priority_issue.py | 10 ------ .../api/endpoints/test_project_details.py | 2 +- .../test_project_rules_configuration.py | 19 +++-------- .../api/serializers/test_organization.py | 1 + .../integrations/slack/tasks/test_tasks.py | 6 ++-- .../test_existing_high_priority_issue.py | 2 -- .../test_new_high_priority_issue.py | 3 -- 11 files changed, 13 insertions(+), 76 deletions(-) diff --git a/src/sentry/api/endpoints/project_rules_configuration.py b/src/sentry/api/endpoints/project_rules_configuration.py index 75adf346545751..ce4e6c568bfb1c 100644 --- a/src/sentry/api/endpoints/project_rules_configuration.py +++ b/src/sentry/api/endpoints/project_rules_configuration.py @@ -7,7 +7,6 @@ from sentry.api.base import region_silo_endpoint from sentry.api.bases.project import ProjectEndpoint from sentry.constants import MIGRATED_CONDITIONS, SENTRY_APP_ACTIONS, TICKET_ACTIONS -from sentry.receivers.rules import has_high_priority_issue_alerts from sentry.rules import rules @@ -75,11 +74,6 @@ def get(self, request: Request, project) -> Response: continue if rule_type.startswith("condition/"): - if not has_high_priority_issue_alerts(project=project) and context["id"] in ( - "sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition", - "sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition", - ): - continue condition_list.append(context) elif rule_type.startswith("filter/"): if ( diff --git a/src/sentry/features/temporary.py b/src/sentry/features/temporary.py index aca40bc21abbea..c91ea699ade8a3 100644 --- a/src/sentry/features/temporary.py +++ b/src/sentry/features/temporary.py @@ -316,7 +316,7 @@ def register_temporary_features(manager: FeatureManager): # Enable showing INP web vital in default views manager.add("organizations:performance-vitals-inp", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=False) # Enable the GA features for priority alerts - manager.add("organizations:priority-ga-features", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, api_expose=True) + manager.add("organizations:priority-ga-features", OrganizationFeature, FeatureHandlerStrategy.FLAGPOLE, default=True, api_expose=True) # Enable profiling manager.add("organizations:profiling", OrganizationFeature, FeatureHandlerStrategy.INTERNAL, api_expose=True) # Enabled for those orgs who participated in the profiling Beta program diff --git a/src/sentry/receivers/rules.py b/src/sentry/receivers/rules.py index 42ec2dce8c9bc3..e024ff4fab2410 100644 --- a/src/sentry/receivers/rules.py +++ b/src/sentry/receivers/rules.py @@ -1,10 +1,9 @@ -from sentry import features from sentry.models.project import Project from sentry.models.rule import Rule from sentry.notifications.types import FallthroughChoiceType from sentry.signals import project_created -DEFAULT_RULE_LABEL = "Send a notification for new issues" +DEFAULT_RULE_LABEL = "Send a notification for high priority issues" DEFAULT_RULE_ACTIONS = [ { "id": "sentry.mail.actions.NotifyEmailAction", @@ -14,27 +13,12 @@ } ] DEFAULT_RULE_DATA = { - "action_match": "all", - "conditions": [{"id": "sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}], - "actions": DEFAULT_RULE_ACTIONS, -} - -DEFAULT_RULE_LABEL_NEW = "Send a notification for high priority issues" -DEFAULT_RULE_ACTIONS_NEW = [ - { - "id": "sentry.mail.actions.NotifyEmailAction", - "targetType": "IssueOwners", - "targetIdentifier": None, - "fallthroughType": FallthroughChoiceType.ACTIVE_MEMBERS.value, - } -] -DEFAULT_RULE_DATA_NEW = { "action_match": "any", "conditions": [ {"id": "sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition"}, {"id": "sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition"}, ], - "actions": DEFAULT_RULE_ACTIONS_NEW, + "actions": DEFAULT_RULE_ACTIONS, } @@ -42,22 +26,12 @@ PLATFORMS_WITH_PRIORITY_ALERTS = ["python", "javascript"] -def has_high_priority_issue_alerts(project: Project) -> bool: - # Seer-based priority is enabled if the organization has the feature flag - return features.has("organizations:priority-ga-features", project.organization) - - def create_default_rules(project: Project, default_rules=True, RuleModel=Rule, **kwargs): if not default_rules: return - if has_high_priority_issue_alerts(project): - rule_data = DEFAULT_RULE_DATA_NEW - RuleModel.objects.create(project=project, label=DEFAULT_RULE_LABEL_NEW, data=rule_data) - - else: - rule_data = DEFAULT_RULE_DATA - RuleModel.objects.create(project=project, label=DEFAULT_RULE_LABEL, data=rule_data) + rule_data = DEFAULT_RULE_DATA + RuleModel.objects.create(project=project, label=DEFAULT_RULE_LABEL, data=rule_data) project_created.connect(create_default_rules, dispatch_uid="create_default_rules", weak=False) diff --git a/src/sentry/rules/conditions/existing_high_priority_issue.py b/src/sentry/rules/conditions/existing_high_priority_issue.py index f5f41a7fc282fa..b845b8cf2e698e 100644 --- a/src/sentry/rules/conditions/existing_high_priority_issue.py +++ b/src/sentry/rules/conditions/existing_high_priority_issue.py @@ -5,7 +5,6 @@ from sentry.models.activity import Activity from sentry.rules import EventState from sentry.rules.conditions.base import EventCondition -from sentry.rules.conditions.new_high_priority_issue import has_high_priority_issue_alerts from sentry.types.activity import ActivityType from sentry.types.condition_activity import ConditionActivity, ConditionActivityType from sentry.types.group import PriorityLevel @@ -16,9 +15,6 @@ class ExistingHighPriorityIssueCondition(EventCondition): label = "Sentry marks an existing issue as high priority" def passes(self, event: GroupEvent, state: EventState) -> bool: - if not has_high_priority_issue_alerts(self.project): - return False - if state.is_new: return False diff --git a/src/sentry/rules/conditions/new_high_priority_issue.py b/src/sentry/rules/conditions/new_high_priority_issue.py index 8d335924f2fdb4..423b1825ba3a55 100644 --- a/src/sentry/rules/conditions/new_high_priority_issue.py +++ b/src/sentry/rules/conditions/new_high_priority_issue.py @@ -1,21 +1,14 @@ from collections.abc import Sequence from datetime import datetime -from sentry import features from sentry.eventstore.models import GroupEvent from sentry.models.group import Group -from sentry.models.project import Project from sentry.rules import EventState from sentry.rules.conditions.base import EventCondition from sentry.types.condition_activity import ConditionActivity, ConditionActivityType from sentry.types.group import PriorityLevel -def has_high_priority_issue_alerts(project: Project) -> bool: - # Seer-based priority is enabled if the organization has the feature flag - return features.has("organizations:priority-ga-features", project.organization) - - class NewHighPriorityIssueCondition(EventCondition): id = "sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition" label = "Sentry marks a new issue as high priority" @@ -27,9 +20,6 @@ def is_new(self, state: EventState) -> bool: return state.is_new_group_environment def passes(self, event: GroupEvent, state: EventState) -> bool: - if not has_high_priority_issue_alerts(self.project): - return False - is_new = self.is_new(state) if not event.project.flags.has_high_priority_alerts: return is_new diff --git a/tests/sentry/api/endpoints/test_project_details.py b/tests/sentry/api/endpoints/test_project_details.py index 269d637ab80203..af5549e99f8586 100644 --- a/tests/sentry/api/endpoints/test_project_details.py +++ b/tests/sentry/api/endpoints/test_project_details.py @@ -1419,7 +1419,7 @@ def assert_settings_not_copied(self, project, teams=()): # default rule rules = Rule.objects.filter(project_id=project.id) assert len(rules) == 1 - assert rules[0].label == "Send a notification for new issues" + assert rules[0].label == "Send a notification for high priority issues" def test_simple(self): project = self.create_project() diff --git a/tests/sentry/api/endpoints/test_project_rules_configuration.py b/tests/sentry/api/endpoints/test_project_rules_configuration.py index 7b31b18dc4169f..6d577919744d0a 100644 --- a/tests/sentry/api/endpoints/test_project_rules_configuration.py +++ b/tests/sentry/api/endpoints/test_project_rules_configuration.py @@ -33,7 +33,7 @@ def test_simple(self): response = self.get_success_response(self.organization.slug, project1.slug) assert len(response.data["actions"]) == 12 - assert len(response.data["conditions"]) == 7 + assert len(response.data["conditions"]) == 9 assert len(response.data["filters"]) == 8 @property @@ -148,7 +148,7 @@ def test_sentry_app_alertable_webhook(self): "service": {"type": "choice", "choices": [[sentry_app.slug, sentry_app.name]]} }, } in response.data["actions"] - assert len(response.data["conditions"]) == 7 + assert len(response.data["conditions"]) == 9 assert len(response.data["filters"]) == 8 @patch("sentry.sentry_apps.components.SentryAppComponentPreparer.run") @@ -179,29 +179,18 @@ def test_sentry_app_alert_rules(self, mock_sentry_app_components_preparer): "formFields": settings_schema["settings"], "sentryAppInstallationUuid": str(install.uuid), } in response.data["actions"] - assert len(response.data["conditions"]) == 7 + assert len(response.data["conditions"]) == 9 assert len(response.data["filters"]) == 8 def test_issue_type_and_category_filter_feature(self): response = self.get_success_response(self.organization.slug, self.project.slug) assert len(response.data["actions"]) == 12 - assert len(response.data["conditions"]) == 7 + assert len(response.data["conditions"]) == 9 assert len(response.data["filters"]) == 8 filter_ids = {f["id"] for f in response.data["filters"]} assert IssueCategoryFilter.id in filter_ids - def test_high_priority_issue_condition(self): - with self.feature({"organizations:priority-ga-features": True}): - response = self.get_success_response(self.organization.slug, self.project.slug) - assert "sentry.rules.conditions.high_priority_issue.NewHighPriorityIssueCondition" in [ - filter["id"] for filter in response.data["conditions"] - ] - assert ( - "sentry.rules.conditions.high_priority_issue.ExistingHighPriorityIssueCondition" - in [filter["id"] for filter in response.data["conditions"]] - ) - def test_is_in_feature(self): response = self.get_success_response(self.organization.slug, self.project.slug) tagged_event_filter = next( diff --git a/tests/sentry/api/serializers/test_organization.py b/tests/sentry/api/serializers/test_organization.py index d7f5f3a2275034..0bef3c10161ef6 100644 --- a/tests/sentry/api/serializers/test_organization.py +++ b/tests/sentry/api/serializers/test_organization.py @@ -85,6 +85,7 @@ def test_simple(self): "integrations-stacktrace-link", "integrations-ticket-rules", "performance-tracing-without-performance", + "priority-ga-features", "invite-members", "minute-resolution-sessions", "new-page-filter", diff --git a/tests/sentry/integrations/slack/tasks/test_tasks.py b/tests/sentry/integrations/slack/tasks/test_tasks.py index e970eb2cfc91a8..c59ac49a58a41b 100644 --- a/tests/sentry/integrations/slack/tasks/test_tasks.py +++ b/tests/sentry/integrations/slack/tasks/test_tasks.py @@ -16,7 +16,7 @@ from sentry.integrations.slack.utils.channel import SlackChannelIdData from sentry.integrations.slack.utils.rule_status import RedisRuleStatus from sentry.models.rule import Rule -from sentry.receivers.rules import DEFAULT_RULE_LABEL, DEFAULT_RULE_LABEL_NEW +from sentry.receivers.rules import DEFAULT_RULE_LABEL from sentry.testutils.cases import TestCase from sentry.testutils.helpers import install_slack from sentry.testutils.skips import requires_snuba @@ -100,9 +100,7 @@ def test_task_new_rule(self, mock_set_value): with self.tasks(): find_channel_id_for_rule(**data) - rule = Rule.objects.exclude(label__in=[DEFAULT_RULE_LABEL, DEFAULT_RULE_LABEL_NEW]).get( - project_id=self.project.id - ) + rule = Rule.objects.exclude(label__in=[DEFAULT_RULE_LABEL]).get(project_id=self.project.id) mock_set_value.assert_called_with("success", rule.id) assert rule.label == "New Rule" # check that the channel_id got added diff --git a/tests/sentry/rules/conditions/test_existing_high_priority_issue.py b/tests/sentry/rules/conditions/test_existing_high_priority_issue.py index a505b8577cd13a..7cbd0d1cf7cc30 100644 --- a/tests/sentry/rules/conditions/test_existing_high_priority_issue.py +++ b/tests/sentry/rules/conditions/test_existing_high_priority_issue.py @@ -1,7 +1,6 @@ from sentry.models.rule import Rule from sentry.rules.conditions.existing_high_priority_issue import ExistingHighPriorityIssueCondition from sentry.testutils.cases import RuleTestCase -from sentry.testutils.helpers.features import with_feature from sentry.testutils.skips import requires_snuba from sentry.types.group import PriorityLevel @@ -14,7 +13,6 @@ class ExistingHighPriorityIssueConditionTest(RuleTestCase): def setUp(self): self.rule = Rule(environment_id=1, project=self.project, label="label") - @with_feature("organizations:priority-ga-features") def test_applies_correctly(self): rule = self.get_rule(rule=self.rule) diff --git a/tests/sentry/rules/conditions/test_new_high_priority_issue.py b/tests/sentry/rules/conditions/test_new_high_priority_issue.py index 3d3d8f374d3337..ce2240c913b1f7 100644 --- a/tests/sentry/rules/conditions/test_new_high_priority_issue.py +++ b/tests/sentry/rules/conditions/test_new_high_priority_issue.py @@ -1,7 +1,6 @@ from sentry.models.rule import Rule from sentry.rules.conditions.new_high_priority_issue import NewHighPriorityIssueCondition from sentry.testutils.cases import RuleTestCase -from sentry.testutils.helpers.features import with_feature from sentry.testutils.skips import requires_snuba from sentry.types.group import PriorityLevel @@ -14,7 +13,6 @@ class NewHighPriorityIssueConditionTest(RuleTestCase): def setUp(self): self.rule = Rule(environment_id=1, project=self.project, label="label") - @with_feature("organizations:priority-ga-features") def test_applies_correctly_with_high_priority_alerts(self): self.project.flags.has_high_priority_alerts = True self.project.save() @@ -34,7 +32,6 @@ def test_applies_correctly_with_high_priority_alerts(self): self.event.group.update(priority=PriorityLevel.LOW) self.assertDoesNotPass(rule, is_new_group_environment=True) - @with_feature("organizations:priority-ga-features") def test_applies_correctly_without_high_priority_alerts(self): self.project.flags.has_high_priority_alerts = False self.project.save() From 377927ee3d951b53a65209b349a97c5b118c630a Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 23 Sep 2024 15:08:50 -0400 Subject: [PATCH 26/31] chore(deletions) Refactor deletions init to enable model move (#77854) I'd like to move the deletions models into the deletions namespace, but doing so will create cirucular imports. In order to resolve those import cycles I need to remove `default_manager`. I've retained the top-level module API and deferred manager creation until first use. I've also improved typing in a the model class so that the next set of changes is smaller. --- src/sentry/conf/server.py | 1 - src/sentry/deletions/__init__.py | 227 +++++++++++------- src/sentry/discover/tasks.py | 7 - src/sentry/models/scheduledeletion.py | 27 ++- src/sentry/tasks/repository.py | 5 +- .../sentry/deletions/tasks/test_scheduled.py | 31 ++- 6 files changed, 177 insertions(+), 121 deletions(-) delete mode 100644 src/sentry/discover/tasks.py diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py index d64382f29eca46..fd98edfc9b8a46 100644 --- a/src/sentry/conf/server.py +++ b/src/sentry/conf/server.py @@ -739,7 +739,6 @@ def SOCIAL_AUTH_DEFAULT_USERNAME() -> str: CELERY_ACCEPT_CONTENT = {"pickle"} CELERY_IMPORTS = ( "sentry.data_export.tasks", - "sentry.discover.tasks", "sentry.deletions.tasks.groups", "sentry.deletions.tasks.scheduled", "sentry.deletions.tasks.hybrid_cloud", diff --git a/src/sentry/deletions/__init__.py b/src/sentry/deletions/__init__.py index c4f83f46e9bd1d..e5e7e0a7ec59f5 100644 --- a/src/sentry/deletions/__init__.py +++ b/src/sentry/deletions/__init__.py @@ -77,16 +77,21 @@ registered Group task. It will instead take a more efficient approach of batch deleting its indirect descendants, such as Event, so it can more efficiently bulk delete rows. """ +from __future__ import annotations -from .base import BulkModelDeletionTask, ModelDeletionTask, ModelRelation # NOQA -from .manager import DeletionTaskManager +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + from sentry.db.models.base import Model + from sentry.deletions.base import BaseDeletionTask -default_manager = DeletionTaskManager(default_task=ModelDeletionTask) +from .manager import DeletionTaskManager -def load_defaults() -> None: +def load_defaults(manager: DeletionTaskManager) -> None: from sentry import models - from sentry.discover.models import DiscoverSavedQuery + from sentry.deletions.base import BulkModelDeletionTask + from sentry.discover.models import DiscoverSavedQuery, DiscoverSavedQueryProject from sentry.incidents.models.alert_rule import ( AlertRule, AlertRuleTrigger, @@ -107,83 +112,135 @@ def load_defaults() -> None: from . import defaults - default_manager.register(models.Activity, BulkModelDeletionTask) - default_manager.register(AlertRule, defaults.AlertRuleDeletionTask) - default_manager.register(AlertRuleTrigger, defaults.AlertRuleTriggerDeletionTask) - default_manager.register(AlertRuleTriggerAction, defaults.AlertRuleTriggerActionDeletionTask) - default_manager.register(models.ApiApplication, defaults.ApiApplicationDeletionTask) - default_manager.register(models.ApiGrant, BulkModelDeletionTask) - default_manager.register(models.ApiKey, BulkModelDeletionTask) - default_manager.register(models.ApiToken, BulkModelDeletionTask) - default_manager.register(models.Commit, defaults.CommitDeletionTask) - default_manager.register(models.CommitAuthor, defaults.CommitAuthorDeletionTask) - default_manager.register(CommitFileChange, BulkModelDeletionTask) - default_manager.register(models.Deploy, BulkModelDeletionTask) - default_manager.register(DiscoverSavedQuery, defaults.DiscoverSavedQueryDeletionTask) - default_manager.register(models.Distribution, BulkModelDeletionTask) - default_manager.register(models.EnvironmentProject, BulkModelDeletionTask) - default_manager.register(models.Group, defaults.GroupDeletionTask) - default_manager.register(models.GroupAssignee, BulkModelDeletionTask) - default_manager.register(models.GroupBookmark, BulkModelDeletionTask) - default_manager.register(models.GroupCommitResolution, BulkModelDeletionTask) - default_manager.register(models.GroupEmailThread, BulkModelDeletionTask) - default_manager.register(models.GroupEnvironment, BulkModelDeletionTask) - default_manager.register(models.GroupHash, defaults.GroupHashDeletionTask) - default_manager.register(models.GroupHashMetadata, BulkModelDeletionTask) - default_manager.register(models.GroupHistory, defaults.GroupHistoryDeletionTask) - default_manager.register(models.GroupLink, BulkModelDeletionTask) - default_manager.register(models.GroupMeta, BulkModelDeletionTask) - default_manager.register(models.GroupRedirect, BulkModelDeletionTask) - default_manager.register(models.GroupRelease, BulkModelDeletionTask) - default_manager.register(models.GroupResolution, BulkModelDeletionTask) - default_manager.register(models.GroupRuleStatus, BulkModelDeletionTask) - default_manager.register(models.GroupSeen, BulkModelDeletionTask) - default_manager.register(models.GroupShare, BulkModelDeletionTask) - default_manager.register(models.GroupSnooze, BulkModelDeletionTask) - default_manager.register(models.GroupSubscription, BulkModelDeletionTask) - default_manager.register(monitor_models.Monitor, defaults.MonitorDeletionTask) - default_manager.register( - monitor_models.MonitorEnvironment, defaults.MonitorEnvironmentDeletionTask - ) - default_manager.register(models.Organization, defaults.OrganizationDeletionTask) - default_manager.register(OrganizationIntegration, defaults.OrganizationIntegrationDeletionTask) - default_manager.register(models.OrganizationMember, defaults.OrganizationMemberDeletionTask) - default_manager.register(models.OrganizationMemberTeam, BulkModelDeletionTask) - default_manager.register( - models.PlatformExternalIssue, defaults.PlatformExternalIssueDeletionTask - ) - default_manager.register(models.Project, defaults.ProjectDeletionTask) - default_manager.register(models.ProjectBookmark, BulkModelDeletionTask) - default_manager.register(models.ProjectKey, BulkModelDeletionTask) - default_manager.register(models.PullRequest, defaults.PullRequestDeletionTask) - default_manager.register(snuba_models.QuerySubscription, defaults.QuerySubscriptionDeletionTask) - default_manager.register(models.Release, defaults.ReleaseDeletionTask) - default_manager.register(models.ReleaseCommit, BulkModelDeletionTask) - default_manager.register(models.ReleaseEnvironment, BulkModelDeletionTask) - default_manager.register(models.ReleaseHeadCommit, BulkModelDeletionTask) - default_manager.register(models.ReleaseProject, BulkModelDeletionTask) - default_manager.register(models.ReleaseProjectEnvironment, BulkModelDeletionTask) - default_manager.register(models.Repository, defaults.RepositoryDeletionTask) - default_manager.register( - RepositoryProjectPathConfig, defaults.RepositoryProjectPathConfigDeletionTask - ) - default_manager.register(SentryApp, defaults.SentryAppDeletionTask) - default_manager.register(SentryAppInstallation, defaults.SentryAppInstallationDeletionTask) - default_manager.register( - SentryAppInstallationToken, defaults.SentryAppInstallationTokenDeletionTask - ) - default_manager.register(ServiceHook, defaults.ServiceHookDeletionTask) - default_manager.register(models.SavedSearch, BulkModelDeletionTask) - default_manager.register(models.Team, defaults.TeamDeletionTask) - default_manager.register(models.UserReport, BulkModelDeletionTask) - default_manager.register(models.ArtifactBundle, defaults.ArtifactBundleDeletionTask) - default_manager.register(models.Rule, defaults.RuleDeletionTask) - default_manager.register(RuleFireHistory, defaults.RuleFireHistoryDeletionTask) - - -load_defaults() - -get = default_manager.get -register = default_manager.register -exec_sync = default_manager.exec_sync -exec_sync_many = default_manager.exec_sync_many + manager.register(models.Activity, BulkModelDeletionTask) + manager.register(AlertRule, defaults.AlertRuleDeletionTask) + manager.register(AlertRuleTrigger, defaults.AlertRuleTriggerDeletionTask) + manager.register(AlertRuleTriggerAction, defaults.AlertRuleTriggerActionDeletionTask) + manager.register(models.ApiApplication, defaults.ApiApplicationDeletionTask) + manager.register(models.ApiGrant, BulkModelDeletionTask) + manager.register(models.ApiKey, BulkModelDeletionTask) + manager.register(models.ApiToken, BulkModelDeletionTask) + manager.register(models.Commit, defaults.CommitDeletionTask) + manager.register(models.CommitAuthor, defaults.CommitAuthorDeletionTask) + manager.register(CommitFileChange, BulkModelDeletionTask) + manager.register(models.Deploy, BulkModelDeletionTask) + manager.register(DiscoverSavedQuery, defaults.DiscoverSavedQueryDeletionTask) + manager.register(DiscoverSavedQueryProject, BulkModelDeletionTask) + manager.register(models.Distribution, BulkModelDeletionTask) + manager.register(models.EnvironmentProject, BulkModelDeletionTask) + manager.register(models.Group, defaults.GroupDeletionTask) + manager.register(models.GroupAssignee, BulkModelDeletionTask) + manager.register(models.GroupBookmark, BulkModelDeletionTask) + manager.register(models.GroupCommitResolution, BulkModelDeletionTask) + manager.register(models.GroupEmailThread, BulkModelDeletionTask) + manager.register(models.GroupEnvironment, BulkModelDeletionTask) + manager.register(models.GroupHash, defaults.GroupHashDeletionTask) + manager.register(models.GroupHashMetadata, BulkModelDeletionTask) + manager.register(models.GroupHistory, defaults.GroupHistoryDeletionTask) + manager.register(models.GroupLink, BulkModelDeletionTask) + manager.register(models.GroupMeta, BulkModelDeletionTask) + manager.register(models.GroupRedirect, BulkModelDeletionTask) + manager.register(models.GroupRelease, BulkModelDeletionTask) + manager.register(models.GroupResolution, BulkModelDeletionTask) + manager.register(models.GroupRuleStatus, BulkModelDeletionTask) + manager.register(models.GroupSeen, BulkModelDeletionTask) + manager.register(models.GroupShare, BulkModelDeletionTask) + manager.register(models.GroupSnooze, BulkModelDeletionTask) + manager.register(models.GroupSubscription, BulkModelDeletionTask) + manager.register(monitor_models.Monitor, defaults.MonitorDeletionTask) + manager.register(monitor_models.MonitorEnvironment, defaults.MonitorEnvironmentDeletionTask) + manager.register(models.Organization, defaults.OrganizationDeletionTask) + manager.register(OrganizationIntegration, defaults.OrganizationIntegrationDeletionTask) + manager.register(models.OrganizationMember, defaults.OrganizationMemberDeletionTask) + manager.register(models.OrganizationMemberTeam, BulkModelDeletionTask) + manager.register(models.PlatformExternalIssue, defaults.PlatformExternalIssueDeletionTask) + manager.register(models.Project, defaults.ProjectDeletionTask) + manager.register(models.ProjectBookmark, BulkModelDeletionTask) + manager.register(models.ProjectKey, BulkModelDeletionTask) + manager.register(models.PullRequest, defaults.PullRequestDeletionTask) + manager.register(snuba_models.QuerySubscription, defaults.QuerySubscriptionDeletionTask) + manager.register(models.Release, defaults.ReleaseDeletionTask) + manager.register(models.ReleaseCommit, BulkModelDeletionTask) + manager.register(models.ReleaseEnvironment, BulkModelDeletionTask) + manager.register(models.ReleaseHeadCommit, BulkModelDeletionTask) + manager.register(models.ReleaseProject, BulkModelDeletionTask) + manager.register(models.ReleaseProjectEnvironment, BulkModelDeletionTask) + manager.register(models.Repository, defaults.RepositoryDeletionTask) + manager.register(RepositoryProjectPathConfig, defaults.RepositoryProjectPathConfigDeletionTask) + manager.register(SentryApp, defaults.SentryAppDeletionTask) + manager.register(SentryAppInstallation, defaults.SentryAppInstallationDeletionTask) + manager.register(SentryAppInstallationToken, defaults.SentryAppInstallationTokenDeletionTask) + manager.register(ServiceHook, defaults.ServiceHookDeletionTask) + manager.register(models.SavedSearch, BulkModelDeletionTask) + manager.register(models.Team, defaults.TeamDeletionTask) + manager.register(models.UserReport, BulkModelDeletionTask) + manager.register(models.ArtifactBundle, defaults.ArtifactBundleDeletionTask) + manager.register(models.Rule, defaults.RuleDeletionTask) + manager.register(RuleFireHistory, defaults.RuleFireHistoryDeletionTask) + + +_default_manager = None + + +def get_manager() -> DeletionTaskManager: + """ + Get the deletions default_manager + + The first call to this method will create the manager and register all + default deletion tasks + """ + from sentry.deletions.base import ModelDeletionTask + + global _default_manager + + if _default_manager is None: + _default_manager = DeletionTaskManager(default_task=ModelDeletionTask) + load_defaults(_default_manager) + + return _default_manager + + +def __getattr__(name: str) -> Any: + # Shim for getsentry + if name == "default_manager": + return get_manager() + raise AttributeError(f"module {__name__} has no attribute {name}") + + +def get( + task: type[BaseDeletionTask[Any]] | None = None, + **kwargs: Any, +) -> BaseDeletionTask[Any]: + """ + Get a deletion task for a given Model class. + + Uses the default_manager from get_manager() + """ + return get_manager().get(task, **kwargs) + + +def register(model: type[Model], task: type[BaseDeletionTask[Any]]) -> None: + """ + Register a deletion task for a given model. + + Uses the default_manager from get_manager() + """ + return get_manager().register(model, task) + + +def exec_sync(instance: Model) -> None: + """ + Execute a deletion task synchronously + + Uses the default_manager from get_manager() + """ + return get_manager().exec_sync(instance) + + +def exec_sync_many(instances: list[Model]) -> None: + """ + Execute a deletion task for multiple records synchronously + + Uses the default_manager from get_manager() + """ + return get_manager().exec_sync_many(instances) diff --git a/src/sentry/discover/tasks.py b/src/sentry/discover/tasks.py deleted file mode 100644 index 54e25dc9725d34..00000000000000 --- a/src/sentry/discover/tasks.py +++ /dev/null @@ -1,7 +0,0 @@ -from sentry import deletions -from sentry.deletions.base import BulkModelDeletionTask - -from . import models - -deletions.default_manager.register(models.DiscoverSavedQuery, BulkModelDeletionTask) -deletions.default_manager.register(models.DiscoverSavedQueryProject, BulkModelDeletionTask) diff --git a/src/sentry/models/scheduledeletion.py b/src/sentry/models/scheduledeletion.py index 60411b4a4e1dc5..318ba96a29491d 100644 --- a/src/sentry/models/scheduledeletion.py +++ b/src/sentry/models/scheduledeletion.py @@ -1,7 +1,8 @@ from __future__ import annotations import logging -from datetime import timedelta +from datetime import datetime, timedelta +from typing import Any, Self from uuid import uuid4 from django.apps import apps @@ -16,18 +17,18 @@ control_silo_model, region_silo_model, ) -from sentry.silo.base import SiloMode +from sentry.silo.base import SiloLimit, SiloMode from sentry.users.services.user import RpcUser from sentry.users.services.user.service import user_service delete_logger = logging.getLogger("sentry.deletions.api") -def default_guid(): +def default_guid() -> str: return uuid4().hex -def default_date_schedule(): +def default_date_schedule() -> datetime: return timezone.now() + timedelta(days=30) @@ -59,12 +60,18 @@ class Meta: in_progress = models.BooleanField(default=False) @classmethod - def schedule(cls, instance, days=30, hours=0, data=None, actor=None): + def schedule( + cls, instance: Model, days: int = 30, hours: int = 0, data: Any = None, actor: Any = None + ) -> Self: model = type(instance) silo_mode = SiloMode.get_current_mode() - if silo_mode not in model._meta.silo_limit.modes and silo_mode != SiloMode.MONOLITH: + model_silo = getattr(model._meta, "silo_limit", None) + assert ( + model_silo + ), "model._meta.silo_limit undefined. This model cannot be used with deletions" + if silo_mode not in model_silo.modes and silo_mode != SiloMode.MONOLITH: # Pre-empt the fact that our silo protections wouldn't fire for mismatched model <-> silo deletion objects. - raise model._meta.silo_limit.AvailabilityError( + raise SiloLimit.AvailabilityError( f"{model!r} was scheduled for deletion by {cls!r}, but is unavailable in {silo_mode!r}" ) @@ -97,7 +104,7 @@ def schedule(cls, instance, days=30, hours=0, data=None, actor=None): return record @classmethod - def cancel(cls, instance): + def cancel(cls, instance: Model): model_name = type(instance).__name__ try: deletion = cls.objects.get( @@ -116,10 +123,10 @@ def cancel(cls, instance): extra={"object_id": instance.pk, "model": model_name}, ) - def get_model(self): + def get_model(self) -> type[Any]: return apps.get_model(self.app_label, self.model_name) - def get_instance(self): + def get_instance(self) -> Model: from sentry import deletions from sentry.deletions.base import ModelDeletionTask diff --git a/src/sentry/tasks/repository.py b/src/sentry/tasks/repository.py index 054756150b0de5..77ad3a0a76e362 100644 --- a/src/sentry/tasks/repository.py +++ b/src/sentry/tasks/repository.py @@ -1,4 +1,4 @@ -from sentry.deletions import default_manager +from sentry.deletions import get_manager from sentry.deletions.base import _delete_children from sentry.deletions.defaults.repository import _get_repository_child_relations from sentry.models.repository import Repository @@ -22,6 +22,7 @@ def repository_cascade_delete_on_hide(repo_id: int) -> None: except Repository.DoesNotExist: return + deletions_manager = get_manager() has_more = True while has_more: @@ -29,4 +30,4 @@ def repository_cascade_delete_on_hide(repo_id: int) -> None: child_relations = _get_repository_child_relations(repo) # no need to filter relations; delete them if child_relations: - has_more = _delete_children(manager=default_manager, relations=child_relations) + has_more = _delete_children(manager=deletions_manager, relations=child_relations) diff --git a/tests/sentry/deletions/tasks/test_scheduled.py b/tests/sentry/deletions/tasks/test_scheduled.py index 1f906fd8e0d880..3e32884fad1fd7 100644 --- a/tests/sentry/deletions/tasks/test_scheduled.py +++ b/tests/sentry/deletions/tasks/test_scheduled.py @@ -53,7 +53,7 @@ def reattempt_deletions(self) -> None: def test_schedule_and_cancel(self): qs = self.create_simple_deletion() - inst = qs.first() + inst = qs.get() schedule = self.ScheduledDeletion.schedule(inst, days=0) self.ScheduledDeletion.cancel(inst) @@ -64,7 +64,7 @@ def test_schedule_and_cancel(self): def test_duplicate_schedule(self): qs = self.create_simple_deletion() - inst = qs.first() + inst = qs.get() first = self.ScheduledDeletion.schedule(inst, days=0) second = self.ScheduledDeletion.schedule(inst, days=1) @@ -76,7 +76,7 @@ def test_duplicate_schedule(self): def test_simple(self): qs = self.create_simple_deletion() - inst = qs.first() + inst = qs.get() schedule = self.ScheduledDeletion.schedule(instance=inst, days=0) with self.tasks(): @@ -87,7 +87,7 @@ def test_simple(self): def test_should_proceed_check(self): qs = self.create_does_not_proceed_deletion() - inst = qs.first() + inst = qs.get() schedule = self.ScheduledDeletion.schedule(instance=inst, days=0) @@ -99,7 +99,7 @@ def test_should_proceed_check(self): def test_ignore_in_progress(self): qs = self.create_simple_deletion() - inst = qs.first() + inst = qs.get() schedule = self.ScheduledDeletion.schedule(instance=inst, days=0) schedule.update(in_progress=True) @@ -111,7 +111,7 @@ def test_ignore_in_progress(self): def test_future_schedule(self): qs = self.create_simple_deletion() - inst = qs.first() + inst = qs.get() schedule = self.ScheduledDeletion.schedule(instance=inst, days=1) with self.tasks(): @@ -125,7 +125,7 @@ def test_triggers_pending_delete_signal(self): pending_delete.connect(signal_handler) qs = self.create_simple_deletion() - inst = qs.first() + inst = qs.get() self.ScheduledDeletion.schedule(instance=inst, actor=self.user, days=0) with self.tasks(): @@ -139,7 +139,7 @@ def test_triggers_pending_delete_signal(self): def test_no_pending_delete_trigger_on_skipped_delete(self): qs = self.create_does_not_proceed_deletion() - inst = qs.first() + inst = qs.get() signal_handler = Mock() pending_delete.connect(signal_handler) @@ -154,8 +154,7 @@ def test_no_pending_delete_trigger_on_skipped_delete(self): def test_handle_missing_record(self): qs = self.create_simple_deletion() - inst = qs.first() - assert inst is not None + inst = qs.get() schedule = self.ScheduledDeletion.schedule(instance=inst, days=0) # Delete the inst, the deletion should remove itself, as its work is done. inst.delete() @@ -167,7 +166,7 @@ def test_handle_missing_record(self): def test_reattempt_simple(self): qs = self.create_simple_deletion() - inst = qs.first() + inst = qs.get() schedule = self.ScheduledDeletion.schedule(instance=inst, days=-3) schedule.update(in_progress=True) with self.tasks(): @@ -178,7 +177,7 @@ def test_reattempt_simple(self): def test_reattempt_ignore_recent_jobs(self): qs = self.create_simple_deletion() - inst = qs.first() + inst = qs.get() schedule = self.ScheduledDeletion.schedule(instance=inst, days=0) schedule.update(in_progress=True) with self.tasks(): @@ -199,13 +198,13 @@ def run_scheduled_deletions(self) -> None: def reattempt_deletions(self) -> None: return reattempt_deletions() - def create_simple_deletion(self) -> QuerySet: + def create_simple_deletion(self) -> QuerySet[Team]: org = self.create_organization(name="test") team = self.create_team(organization=org, name="delete") return Team.objects.filter(id=team.id) - def create_does_not_proceed_deletion(self) -> QuerySet: + def create_does_not_proceed_deletion(self) -> QuerySet[Repository]: org = self.create_organization(name="test") project = self.create_project(organization=org) repo = self.create_repo(project=project, name="example/example") @@ -226,13 +225,13 @@ def run_scheduled_deletions(self) -> None: def reattempt_deletions(self) -> None: return reattempt_deletions_control() - def create_simple_deletion(self) -> QuerySet: + def create_simple_deletion(self) -> QuerySet[ApiApplication]: app = ApiApplication.objects.create(owner_id=self.user.id, allowed_origins="example.com") app.status = ApiApplicationStatus.pending_deletion app.save() return ApiApplication.objects.filter(id=app.id) - def create_does_not_proceed_deletion(self) -> QuerySet: + def create_does_not_proceed_deletion(self) -> QuerySet[ApiApplication]: app = ApiApplication.objects.create(owner_id=self.user.id, allowed_origins="example.com") app.status = ApiApplicationStatus.active app.save() From 64876170dc83f1b520dbb4d245635a20e0cf5d27 Mon Sep 17 00:00:00 2001 From: Michael Sun <55160142+MichaelSun48@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:14:42 -0700 Subject: [PATCH 27/31] feat(issue-views): Tab title char limits (dupe) (#77966) This PR is nearly identical to [this previous PR](https://github.com/getsentry/sentry/pull/77883) that I reverted because of an unexpected visual bug that occurred because of a conflict with another PR. (specifically [this one](https://github.com/getsentry/sentry/pull/77890) that added animation wrappers over the tab title) The behavior is identical to the previous PR, see that one for a visual demo. This PR also fixes an unrelated bug with the duplicate functionality. Previously, clicking on the "Duplicate" action for a tab would flash the Add View page before going back to the issue stream. This happened because the add view page is automatically triggered when the search's viewId begins with a "_", indicating a tab that has not been persisted on our backend yet. --- .../app/views/issueList/customViewsHeader.tsx | 3 + .../groupSearchViewTabs/draggableTabBar.tsx | 16 +++-- .../groupSearchViewTabs/editableTabTitle.tsx | 61 +++++++++++++------ 3 files changed, 51 insertions(+), 29 deletions(-) diff --git a/static/app/views/issueList/customViewsHeader.tsx b/static/app/views/issueList/customViewsHeader.tsx index 632060f3f497cd..ecb154cad5f32a 100644 --- a/static/app/views/issueList/customViewsHeader.tsx +++ b/static/app/views/issueList/customViewsHeader.tsx @@ -326,6 +326,9 @@ function CustomViewsIssueListHeaderTabsContent({ useEffect(() => { if (viewId?.startsWith('_')) { + if (draggableTabs.find(tab => tab.id === viewId)?.label.endsWith('(Copy)')) { + return; + } // If the user types in query manually while the new view flow is showing, // then replace the add view flow with the issue stream with the query loaded, // and persist the query diff --git a/static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx b/static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx index 6337d25ab819ce..9e71e09b087c5f 100644 --- a/static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx +++ b/static/app/views/issueList/groupSearchViewTabs/draggableTabBar.tsx @@ -388,15 +388,13 @@ export function DraggableTabBar({ disabled={tab.key === editingTabKey} > - - setEditingTabKey(isEditing ? tab.key : null)} - onChange={newLabel => handleOnTabRenamed(newLabel.trim(), tab.key)} - tabKey={tab.key} - /> - + setEditingTabKey(isEditing ? tab.key : null)} + onChange={newLabel => handleOnTabRenamed(newLabel.trim(), tab.key)} + tabKey={tab.key} + /> {/* If tablistState isn't initialized, we want to load the elipsis menu for the initial tab, that way it won't load in a second later and cause the tabs to shift and animate on load. diff --git a/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx b/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx index 4913509c2bbc31..d791e1b6b06962 100644 --- a/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx +++ b/static/app/views/issueList/groupSearchViewTabs/editableTabTitle.tsx @@ -1,9 +1,11 @@ import {useContext, useEffect, useMemo, useRef, useState} from 'react'; import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; +import {motion} from 'framer-motion'; import {GrowingInput} from 'sentry/components/growingInput'; import {TabsContext} from 'sentry/components/tabs'; +import {Tooltip} from 'sentry/components/tooltip'; interface EditableTabTitleProps { isEditing: boolean; @@ -84,31 +86,47 @@ function EditableTabTitle({ setInputValue(e.target.value); }; - return isSelected ? ( - isSelected && setIsEditing(true)} - onBlur={handleOnBlur} - ref={inputRef} - style={memoizedStyles} - isEditing={isEditing} - onFocus={e => e.target.select()} - onPointerDown={e => { - e.stopPropagation(); - }} - onMouseDown={e => { - e.stopPropagation(); - }} - /> - ) : ( -
{label}
+ return ( + + + {isSelected ? ( + isSelected && setIsEditing(true)} + onBlur={handleOnBlur} + ref={inputRef} + style={memoizedStyles} + isEditing={isEditing} + onFocus={e => e.target.select()} + onPointerDown={e => { + e.stopPropagation(); + }} + onMouseDown={e => { + e.stopPropagation(); + }} + maxLength={128} + /> + ) : ( + {label} + )} + + ); } export default EditableTabTitle; +const UnselectedTabTitle = styled('div')` + height: 20px; + /* The max width is slightly smaller than the GrowingInput since the text in the growing input is bolded */ + max-width: 310px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +`; + const StyledGrowingInput = styled(GrowingInput)<{ isEditing: boolean; }>` @@ -119,8 +137,11 @@ const StyledGrowingInput = styled(GrowingInput)<{ min-height: 0px; height: 20px; border-radius: 0px; + text-overflow: ellipsis; cursor: ${p => (p.isEditing ? 'text' : 'pointer')}; + ${p => !p.isEditing && `max-width: 325px;`} + &, &:focus, &:active, From 57891d8b84bca3a475615000818409d54b596ed9 Mon Sep 17 00:00:00 2001 From: mia hsu <55610339+ameliahsu@users.noreply.github.com> Date: Mon, 23 Sep 2024 12:30:39 -0700 Subject: [PATCH 28/31] fix(invite-members): enable member role in dropdown (#77871) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix for the "Member" role being disabled for members sending invites Screenshot 2024-09-20 at 1 43 16 PM chose to fix this on the frontend rather than modifying the GET request for this specific page so that the backend continues to returns the same result for all pages --- static/app/components/roleSelectControl.tsx | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/static/app/components/roleSelectControl.tsx b/static/app/components/roleSelectControl.tsx index a30c57dbeabbb9..6f6222545701d8 100644 --- a/static/app/components/roleSelectControl.tsx +++ b/static/app/components/roleSelectControl.tsx @@ -3,6 +3,7 @@ import styled from '@emotion/styled'; import type {ControlProps} from 'sentry/components/forms/controls/selectControl'; import SelectControl from 'sentry/components/forms/controls/selectControl'; import type {BaseRole} from 'sentry/types/organization'; +import useOrganization from 'sentry/utils/useOrganization'; type OptionType = { details: React.ReactNode; @@ -22,6 +23,12 @@ type Props = Omit, 'onChange' | 'value'> & { }; function RoleSelectControl({roles, disableUnallowed, ...props}: Props) { + const organization = useOrganization(); + const isMemberInvite = + organization.features.includes('members-invite-teammates') && + organization.allowMemberInvite && + organization.access?.includes('member:invite'); + return ( {r.desc}, }) as OptionType )} From e7cc9c4a53a6a872b7a10a029917fb5b9908c9ec Mon Sep 17 00:00:00 2001 From: William Mak Date: Mon, 23 Sep 2024 15:38:07 -0400 Subject: [PATCH 29/31] temp(spans): Remove numeric attr tests for now (#77970) - Snuba changed the keys, will need to rewrite this functionality & tests --- .../snuba/api/endpoints/test_organization_events_span_indexed.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/snuba/api/endpoints/test_organization_events_span_indexed.py b/tests/snuba/api/endpoints/test_organization_events_span_indexed.py index 5aad5dafb58f94..8f6af4cfed770e 100644 --- a/tests/snuba/api/endpoints/test_organization_events_span_indexed.py +++ b/tests/snuba/api/endpoints/test_organization_events_span_indexed.py @@ -525,6 +525,7 @@ def test_query_for_missing_tag(self): assert response.data["data"] == [{"foo": "", "count()": 1}] +@pytest.mark.xfail(reason="Snuba is prefixing keys, and Sentry wasn't updated first") class OrganizationEventsEAPSpanEndpointTest(OrganizationEventsSpanIndexedEndpointTest): is_eap = True From 3d302dd0f30a47d67def8cf4bbe55525597cb34c Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Mon, 23 Sep 2024 13:03:26 -0700 Subject: [PATCH 30/31] ref(ui): Remove `qs` imports, not a dependency (#77972) --- .../app/views/insights/queues/components/tables/queuesTable.tsx | 2 +- .../insights/queues/components/tables/transactionsTable.tsx | 2 +- .../newTraceDetails/traceDrawer/details/span/sections/http.tsx | 2 +- .../views/performance/newTraceDetails/traceModels/traceTree.tsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/static/app/views/insights/queues/components/tables/queuesTable.tsx b/static/app/views/insights/queues/components/tables/queuesTable.tsx index cdf110ed865925..58cadff85dcdc9 100644 --- a/static/app/views/insights/queues/components/tables/queuesTable.tsx +++ b/static/app/views/insights/queues/components/tables/queuesTable.tsx @@ -1,7 +1,7 @@ import {Fragment} from 'react'; import styled from '@emotion/styled'; import type {Location} from 'history'; -import qs from 'qs'; +import * as qs from 'query-string'; import GridEditable, { COL_WIDTH_UNDEFINED, diff --git a/static/app/views/insights/queues/components/tables/transactionsTable.tsx b/static/app/views/insights/queues/components/tables/transactionsTable.tsx index 5ebf1daf4fbb8b..df3bb8fd600ee8 100644 --- a/static/app/views/insights/queues/components/tables/transactionsTable.tsx +++ b/static/app/views/insights/queues/components/tables/transactionsTable.tsx @@ -1,7 +1,7 @@ import {Fragment} from 'react'; import styled from '@emotion/styled'; import type {Location} from 'history'; -import qs from 'qs'; +import * as qs from 'query-string'; import GridEditable, { COL_WIDTH_UNDEFINED, diff --git a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/http.tsx b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/http.tsx index 6a2e484dbd75e0..0be2c90f3a5e3f 100644 --- a/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/http.tsx +++ b/static/app/views/performance/newTraceDetails/traceDrawer/details/span/sections/http.tsx @@ -1,4 +1,4 @@ -import qs from 'qs'; +import * as qs from 'query-string'; import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types'; import {t} from 'sentry/locale'; diff --git a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx index 50cd137d2eeb67..85dc6ae35ef881 100644 --- a/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx +++ b/static/app/views/performance/newTraceDetails/traceModels/traceTree.tsx @@ -1,7 +1,7 @@ import type {Theme} from '@emotion/react'; import * as Sentry from '@sentry/react'; import type {Location} from 'history'; -import qs from 'qs'; +import * as qs from 'query-string'; import type {Client} from 'sentry/api'; import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types'; From 6ca40f9c06610d0a7a7f61ad2d898b74d838d863 Mon Sep 17 00:00:00 2001 From: Scott Cooper Date: Mon, 23 Sep 2024 13:03:46 -0700 Subject: [PATCH 31/31] deps(ui): Bump webpack dependencies, clear security warning (#77969) --- package.json | 10 +- yarn.lock | 693 ++++++++++++++++++++++++++++----------------------- 2 files changed, 381 insertions(+), 322 deletions(-) diff --git a/package.json b/package.json index a097bf14338e0f..e8cb6ce69cfed4 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@react-stately/tabs": "^3.6.9", "@react-stately/tree": "^3.8.4", "@react-types/shared": "^3.24.1", - "@rsdoctor/webpack-plugin": "0.3.11", + "@rsdoctor/webpack-plugin": "0.4.4", "@sentry-internal/global-search": "^1.0.0", "@sentry-internal/react-inspector": "6.0.1-4", "@sentry-internal/rrweb": "2.26.0", @@ -65,7 +65,7 @@ "@sentry/status-page-list": "^0.3.0", "@sentry/types": "^8.28.0", "@sentry/utils": "^8.28.0", - "@sentry/webpack-plugin": "^2.22.2", + "@sentry/webpack-plugin": "^2.22.4", "@spotlightjs/spotlight": "^2.0.0-alpha.1", "@tanstack/react-query": "^5.56.2", "@tanstack/react-query-devtools": "^5.56.2", @@ -173,14 +173,14 @@ "typescript": "^5.5.2", "u2f-api": "1.0.10", "url-loader": "^4.1.1", - "webpack": "5.93.0", + "webpack": "5.94.0", "webpack-cli": "5.1.4", "webpack-remove-empty-scripts": "^1.0.4", "zxcvbn": "^4.4.2" }, "devDependencies": { "@biomejs/biome": "^1.9.1", - "@codecov/webpack-plugin": "^1.0.0", + "@codecov/webpack-plugin": "^1.2.0", "@pmmmwh/react-refresh-webpack-plugin": "0.5.15", "@sentry/jest-environment": "6.0.0", "@sentry/profiling-node": "^8.28.0", @@ -207,7 +207,7 @@ "stylelint-config-recommended": "^14.0.0", "terser": "5.31.6", "tsconfig-paths": "^4.2.0", - "webpack-dev-server": "5.0.4" + "webpack-dev-server": "5.1.0" }, "resolutions": { "react-mentions/@babel/runtime": "*" diff --git a/yarn.lock b/yarn.lock index 0492a0651246fa..13a36b7f979ce0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1283,10 +1283,10 @@ resolved "https://registry.yarnpkg.com/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.1.tgz#f754bb8a94f2dab6311eff39dc30c4a6aa0f5628" integrity sha512-liSRWjWzFhyG7s1jg/Bbv9FL+ha/CEd5tFO3+dFIJNplL4TnvAivtyfRVi/tu/pNjISbV1k9JwdBewtAKAgA0w== -"@codecov/bundler-plugin-core@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.0.0.tgz#134a796460e85e71e6be121eaec4bbd08d430630" - integrity sha512-RIhOBa2cQ8rn2az3BaOVIIJHNbOkqOfIxW+NPgjFB3fuNQiYjLDC9X6nExlzo03ntu01Ph/5FF01NPrCvtd5lw== +"@codecov/bundler-plugin-core@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@codecov/bundler-plugin-core/-/bundler-plugin-core-1.2.0.tgz#4a896dd3bd9f9f98a60519aadcf8e3daff37ae5d" + integrity sha512-ublUP5V0tW6oDnaJ1UBWvEmVAkvMmPNEwWkpF+WwJSCBWNLvWrkSwG84S3Gt5Xbnh17xEyAxXBmNzF+mXVXBgw== dependencies: "@actions/core" "^1.10.1" "@actions/github" "^6.0.0" @@ -1295,12 +1295,12 @@ unplugin "^1.10.1" zod "^3.22.4" -"@codecov/webpack-plugin@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@codecov/webpack-plugin/-/webpack-plugin-1.0.0.tgz#d9ca06e6a996156e2cfc4537fc1d5dd91cb62674" - integrity sha512-DpnxHqLp1agFjO5Yj3Lz464C0yQK7+dJICgqq6xmOIAG+OnFOF0A6k3D8OtF384t/t9X3+SgEVgTZjThMORcgw== +"@codecov/webpack-plugin@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@codecov/webpack-plugin/-/webpack-plugin-1.2.0.tgz#db0466f0494f9a141697af4665830c650d030991" + integrity sha512-yawRyKgC8tXj/C/UoTJ+kRMePfhkYJtJey+xmywRqmVmvbxQGnYFkg+Dzix/HxKt4iGpSwKT4p8Glz2MNQ7gTQ== dependencies: - "@codecov/bundler-plugin-core" "^1.0.0" + "@codecov/bundler-plugin-core" "^1.2.0" unplugin "^1.10.1" "@cspotcode/source-map-support@^0.8.0": @@ -2020,6 +2020,26 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsonjoy.com/base64@^1.1.1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" + integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== + +"@jsonjoy.com/json-pack@^1.0.3": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.1.0.tgz#33ca57ee29d12feef540f2139225597469dec894" + integrity sha512-zlQONA+msXPPwHWZMKFVS78ewFczIll5lXiVPwFPCZUsrOKdxc2AvxU1HoNBmMRhqDZUR9HkC3UOm+6pME6Xsg== + dependencies: + "@jsonjoy.com/base64" "^1.1.1" + "@jsonjoy.com/util" "^1.1.2" + hyperdyperid "^1.2.0" + thingies "^1.20.0" + +"@jsonjoy.com/util@^1.1.2", "@jsonjoy.com/util@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.3.0.tgz#e5623885bb5e0c48c1151e4dae422fb03a5887a1" + integrity sha512-Cebt4Vk7k1xHy87kHY7KSPLT77A7Ev7IfOblyLZhtYEhrdQ6fX4EoLq3xOQ3O/DRMEh2ok5nyC180E+ABS8Wmw== + "@leichtgewicht/ip-codec@^2.0.1": version "2.0.5" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz#4fc56c15c580b9adb7dc3c333a134e540b44bfb1" @@ -2431,9 +2451,9 @@ source-map "^0.7.3" "@polka/url@^1.0.0-next.24": - version "1.0.0-next.25" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.25.tgz#f077fdc0b5d0078d30893396ff4827a13f99e817" - integrity sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ== + version "1.0.0-next.28" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73" + integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw== "@popperjs/core@^2.11.5": version "2.11.5" @@ -3061,107 +3081,106 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.19.1.tgz#984771bfd1de2715f42394c87fb716c1349e014f" integrity sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg== -"@rsdoctor/client@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@rsdoctor/client/-/client-0.3.11.tgz#010fe58c0872f05e2d327f8c83053ab43fc40c23" - integrity sha512-mqbatlzgBMlz2C40fXCH7+oHFzDLzOD7yCvvLpolQ6AGwDADJK16fXVtvpBPLP3kzRgw+G4mDwy+UiAmAaCGaQ== +"@rsdoctor/client@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@rsdoctor/client/-/client-0.4.4.tgz#b566a6577db0ef360ff7959a0cda8c3ebab63006" + integrity sha512-xMtOWtLR9qidnXhQTRaaMVHhNqPYApk3uN5cGQJwWJDzmNNmmVeB663sIpHPKXD8cmC/w0z4SDjWA4jue8LM2w== -"@rsdoctor/core@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@rsdoctor/core/-/core-0.3.11.tgz#85730315425b9ececea6f64145ae04140c5bfdcd" - integrity sha512-+l9Kpjge+xpBKovc6aP7gtBPM6vGJRi475DOC6We5/AbnpI60Jt3wiJ0arEfgCZ7p8qNvTLsBYSY6CYJm4dDLw== +"@rsdoctor/core@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@rsdoctor/core/-/core-0.4.4.tgz#d2c9693b4ad20c396e3d571ec359cd58ed16bf96" + integrity sha512-cV1f9Fu/S9cjZ9F/oWhgIZHaX0qZJVNh2/DS9vh0gWBZJhBVPz5NBCKjA322W7hGT343sadVdys2PqIxjCHcOw== dependencies: - "@rsdoctor/graph" "0.3.11" - "@rsdoctor/sdk" "0.3.11" - "@rsdoctor/types" "0.3.11" - "@rsdoctor/utils" "0.3.11" + "@rsdoctor/graph" "0.4.4" + "@rsdoctor/sdk" "0.4.4" + "@rsdoctor/types" "0.4.4" + "@rsdoctor/utils" "0.4.4" axios "^1.7.2" enhanced-resolve "5.12.0" - filesize "^10.1.4" + filesize "^10.1.6" fs-extra "^11.1.1" - loader-utils "^2.0.4" lodash "^4.17.21" path-browserify "1.0.1" semver "^7.6.3" source-map "^0.7.4" webpack-bundle-analyzer "^4.10.2" - webpack-sources "^3.2.3" -"@rsdoctor/graph@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@rsdoctor/graph/-/graph-0.3.11.tgz#b8d1a6452545e36b508285fc98b5776d074479f4" - integrity sha512-1WDLgwA0TWUS8NIPsV1eTbXH0UMNbLoYDLGSiaUVrwnl90cQsajaEUkwMcKu9VU3YVC4ayjwG6/RCrPlRqE4bw== +"@rsdoctor/graph@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@rsdoctor/graph/-/graph-0.4.4.tgz#d00f93bb1da93bb3379c5f3237d8629cfad1092b" + integrity sha512-ZcCRo9ydqyNI5otai+qUGxw4HZQAE0Rolb9tV4aadNudl7A2Mcm6FT6yECB4jlbNECqc8B5kHKZuz971WgFkLA== dependencies: - "@rsdoctor/types" "0.3.11" - "@rsdoctor/utils" "0.3.11" + "@rsdoctor/types" "0.4.4" + "@rsdoctor/utils" "0.4.4" lodash "^4.17.21" socket.io "4.7.2" source-map "^0.7.4" -"@rsdoctor/sdk@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@rsdoctor/sdk/-/sdk-0.3.11.tgz#58bb83b077ed167b5f29b6835ead9e78fbd718c3" - integrity sha512-ys9lTasYm9ecvj0oUbQuSpnULqImYq9QET1LUtoVBa5QIpQBbxOOegWnBmSsDlH7LlFQYhqLl5DR9FdJ7yTmcw== - dependencies: - "@rsdoctor/client" "0.3.11" - "@rsdoctor/graph" "0.3.11" - "@rsdoctor/types" "0.3.11" - "@rsdoctor/utils" "0.3.11" - body-parser "1.20.2" +"@rsdoctor/sdk@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@rsdoctor/sdk/-/sdk-0.4.4.tgz#1a8d48b69b25e4cc24dfc2f8ba0a63d6da458094" + integrity sha512-Y+ySVfrFAT0GVwI0xTU/BdEsufnC/+1eLYISWTEa19MdWU/RpcLqsIdglKNbDUbS6k0AELOvl7CUuOiI0go8gw== + dependencies: + "@rsdoctor/client" "0.4.4" + "@rsdoctor/graph" "0.4.4" + "@rsdoctor/types" "0.4.4" + "@rsdoctor/utils" "0.4.4" + "@types/fs-extra" "^11.0.4" + body-parser "1.20.3" cors "2.8.5" - dayjs "1.11.12" + dayjs "1.11.13" + fs-extra "^11.1.1" lodash "^4.17.21" open "^8.4.2" - serve-static "1.15.0" + serve-static "1.16.0" socket.io "4.7.2" source-map "^0.7.4" tapable "2.2.1" -"@rsdoctor/types@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@rsdoctor/types/-/types-0.3.11.tgz#d2a37650476bbf7a9d63d951bc0499c8a9b72ac4" - integrity sha512-BH+P5RekIQy3VqFswsTkSB5yeZBYTbMNIlVq1chHhIQcF0Hpd9fuUjFG59NJgBILZoL2vknUhBfhmiv2WEOYig== +"@rsdoctor/types@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@rsdoctor/types/-/types-0.4.4.tgz#ec5d25a264c8ad6ec55402696c6193349548d6ef" + integrity sha512-Ltf03hd/gAazRTmrwz7SqNDqB+BHC0BBVwQi7wUVsF6MphvTaqSdAocNefTgWDeXj0WEP0Vy5wrj+aUPTbPWtg== dependencies: "@types/connect" "3.4.38" "@types/estree" "1.0.5" "@types/tapable" "2.2.7" - "@types/webpack" "5.28.5" source-map "^0.7.4" -"@rsdoctor/utils@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@rsdoctor/utils/-/utils-0.3.11.tgz#650b2b8cf2c4d38a0f35d05f13625965c467d185" - integrity sha512-S1h/rG3wPFjHE9AffGjBGxPhP1j8X23KZKiyYnYHiOTCHxnYTyBqRJXUA0OvGewFY9zi7ucz800y+s0G1ujGyg== +"@rsdoctor/utils@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@rsdoctor/utils/-/utils-0.4.4.tgz#a80d7e2f3d4d4de3c4866f675cede3cc63103988" + integrity sha512-J61vLwKdNnuToeU7ZfCIy5rYH4biMH7LfinQG9m4ySveyT9hD8z/CyxbpOlr7vdmnzBVzZCy+0aHm9UbhUpjxQ== dependencies: "@babel/code-frame" "7.24.7" - "@rsdoctor/types" "0.3.11" + "@rsdoctor/types" "0.4.4" "@types/estree" "1.0.5" acorn "^8.10.0" acorn-import-assertions "1.9.0" - acorn-walk "8.3.3" + acorn-walk "8.3.4" chalk "^4.1.2" connect "3.7.0" deep-eql "4.1.4" - envinfo "7.13.0" - filesize "^10.1.4" + envinfo "7.14.0" + filesize "^10.1.6" fs-extra "^11.1.1" get-port "5.1.1" json-stream-stringify "3.0.1" lines-and-columns "2.0.4" lodash "^4.17.21" - rslog "^1.2.2" + rslog "^1.2.3" strip-ansi "^6.0.1" -"@rsdoctor/webpack-plugin@0.3.11": - version "0.3.11" - resolved "https://registry.yarnpkg.com/@rsdoctor/webpack-plugin/-/webpack-plugin-0.3.11.tgz#b5f43b7c3c8c365e379dbd5a51bfae284aee61b4" - integrity sha512-Gqr5GMOhHkJl9rtsbnCZV5mXuvsfpRtg735cRie9eT1E/dMpHerIGZFRGRIMPNkLAd/lfe0KwrGRZknWX9M/zQ== +"@rsdoctor/webpack-plugin@0.4.4": + version "0.4.4" + resolved "https://registry.yarnpkg.com/@rsdoctor/webpack-plugin/-/webpack-plugin-0.4.4.tgz#7e64e9e5693c21035bbc7f8d4e2bc2eb9873ce76" + integrity sha512-lXUNQaX3jGx+V6TCzOnWV5jMJ6vsziPGBPrz9m4PEsW+4nDrzEJLieE6e7AwrxGEUi02GbxmCq6FXBJVBIWdqA== dependencies: - "@rsdoctor/core" "0.3.11" - "@rsdoctor/graph" "0.3.11" - "@rsdoctor/sdk" "0.3.11" - "@rsdoctor/types" "0.3.11" - "@rsdoctor/utils" "0.3.11" + "@rsdoctor/core" "0.4.4" + "@rsdoctor/graph" "0.4.4" + "@rsdoctor/sdk" "0.4.4" + "@rsdoctor/types" "0.4.4" + "@rsdoctor/utils" "0.4.4" fs-extra "^11.1.1" lodash "^4.17.21" @@ -3264,10 +3283,10 @@ fflate "^0.4.4" mitt "^3.0.0" -"@sentry/babel-plugin-component-annotate@2.22.2": - version "2.22.2" - resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.2.tgz#f4a1ddea4bcac06584a6cec9a43ec088cbb6caaf" - integrity sha512-6kFAHGcs0npIC4HTt4ULs8uOfEucvMI7VW4hoyk17jhRaW8CbxzxfWCfIeRbDkE8pYwnARaq83tu025Hrk2zgA== +"@sentry/babel-plugin-component-annotate@2.22.4": + version "2.22.4" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.4.tgz#c5adef7201a799c971cdccc5ba11c97d4609b1a2" + integrity sha512-hbSq067KwmeKIEkmyzkTNJbmbtx2KRqvpiy9Q/DynI5Z46Nko/ppvgIfyFXK9DelwvEPOqZic4WXTIhO4iv3DA== "@sentry/browser@8.28.0": version "8.28.0" @@ -3282,13 +3301,13 @@ "@sentry/types" "8.28.0" "@sentry/utils" "8.28.0" -"@sentry/bundler-plugin-core@2.22.2": - version "2.22.2" - resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.2.tgz#bd418541245c5167a439d4e28a84096deb20c512" - integrity sha512-TwEEW4FeEJ5Mamp4fGnktfVjzN77KAW0xFQsEPuxZtOAPG17zX/PGvdyRX/TE1jkZWhTzqUDIdgzqlNLjyEnUw== +"@sentry/bundler-plugin-core@2.22.4": + version "2.22.4" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.4.tgz#e2c4efc713436b5a38dbbc4be510769c1c548cca" + integrity sha512-25NiyV3v6mdqOXlpzbbJnq0FHdAu1uTEDr+DU8CzNLjIXlq2Sr2CFZ/mhRcR6daM8OAretJdQ34lu0yHUVeE4Q== dependencies: "@babel/core" "^7.18.5" - "@sentry/babel-plugin-component-annotate" "2.22.2" + "@sentry/babel-plugin-component-annotate" "2.22.4" "@sentry/cli" "^2.33.1" dotenv "^16.3.1" find-up "^5.0.0" @@ -3296,45 +3315,45 @@ magic-string "0.30.8" unplugin "1.0.1" -"@sentry/cli-darwin@2.33.1": - version "2.33.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.33.1.tgz#e4eb1dd01ee3ce2788025426b860ccc63759589c" - integrity sha512-+4/VIx/E1L2hChj5nGf5MHyEPHUNHJ/HoG5RY+B+vyEutGily1c1+DM2bum7RbD0xs6wKLIyup5F02guzSzG8A== - -"@sentry/cli-linux-arm64@2.33.1": - version "2.33.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.33.1.tgz#9ea1718c21ef32ca83b0852ca29fb461fd26d25a" - integrity sha512-DbGV56PRKOLsAZJX27Jt2uZ11QfQEMmWB4cIvxkKcFVE+LJP4MVA+MGGRUL6p+Bs1R9ZUuGbpKGtj0JiG6CoXw== - -"@sentry/cli-linux-arm@2.33.1": - version "2.33.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.33.1.tgz#e8a1dca4d008dd6a72ab5935304c104e98e2901c" - integrity sha512-zbxEvQju+tgNvzTOt635le4kS/Fbm2XC2RtYbCTs034Vb8xjrAxLnK0z1bQnStUV8BkeBHtsNVrG+NSQDym2wg== - -"@sentry/cli-linux-i686@2.33.1": - version "2.33.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.33.1.tgz#f1fe8dd4d6dde0812a94fba31de8054ddfb7284a" - integrity sha512-g2LS4oPXkPWOfKWukKzYp4FnXVRRSwBxhuQ9eSw2peeb58ZIObr4YKGOA/8HJRGkooBJIKGaAR2mH2Pk1TKaiA== - -"@sentry/cli-linux-x64@2.33.1": - version "2.33.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.33.1.tgz#6e086675356a9eb79731bf9e447d078bae1b5adf" - integrity sha512-IV3dcYV/ZcvO+VGu9U6kuxSdbsV2kzxaBwWUQxtzxJ+cOa7J8Hn1t0koKGtU53JVZNBa06qJWIcqgl4/pCuKIg== - -"@sentry/cli-win32-i686@2.33.1": - version "2.33.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.33.1.tgz#0e6b36c4a2f5f6e85a59247a123d276b3ef10f1a" - integrity sha512-F7cJySvkpzIu7fnLKNHYwBzZYYwlhoDbAUnaFX0UZCN+5DNp/5LwTp37a5TWOsmCaHMZT4i9IO4SIsnNw16/zQ== - -"@sentry/cli-win32-x64@2.33.1": - version "2.33.1" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.33.1.tgz#2d00b38a2dd9f3355df91825582ada3ea0034e86" - integrity sha512-8VyRoJqtb2uQ8/bFRKNuACYZt7r+Xx0k2wXRGTyH05lCjAiVIXn7DiS2BxHFty7M1QEWUCMNsb/UC/x/Cu2wuA== +"@sentry/cli-darwin@2.36.2": + version "2.36.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.36.2.tgz#554f67971d80bc1956670ed6365955a7980ae49e" + integrity sha512-To64Pq+pcmecEr+gFXiqaZy8oKhyLQLXO/SVDdf16CUL2qpuahE3bO5h9kFacMxPPxOWcgc2btF+4gYa1+bQTA== + +"@sentry/cli-linux-arm64@2.36.2": + version "2.36.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.36.2.tgz#ae5b61cfdb12d5423e08ee1f5c6adf1e5c34ae9a" + integrity sha512-g+FFmj1oJ2iRMsfs1ORz6THOO6MiAR55K9YxdZUBvqfoHLjSMt7Jst43sbZ3O0u55hnfixSKLNzDaTGaM/jxIQ== + +"@sentry/cli-linux-arm@2.36.2": + version "2.36.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.36.2.tgz#677cce94f20a161c226db67d6693d05c0c5d3741" + integrity sha512-cRSvOQK97WM0m03k/c+LVAWT042Qz887WP/2Gy64eUi/PfArwb+QZZnsu4FCygxK9jnzgLTo4+ewoJVi17xaLQ== + +"@sentry/cli-linux-i686@2.36.2": + version "2.36.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.36.2.tgz#37eb2e7cf3d798d65a7e44c456210ebab8b55d5b" + integrity sha512-rjxTw/CMd0Q7qlOb7gWFiwn3hJIxNkhbn1bOU54xj9CZvQSCvh10l7l4Y9o8znJLl41c5kMXVq8yuYws9A7AGQ== + +"@sentry/cli-linux-x64@2.36.2": + version "2.36.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.36.2.tgz#5887c01eb1ff87c21b93cc5243fa74ff3c29af7d" + integrity sha512-cF8IPFTlwiC7JgVvSW4rS99sxb1W1N//iANxuzqaDswUnmJLi0AJy/jES87qE5GRB6ljaPVMvH7Kq0OCp3bvPA== + +"@sentry/cli-win32-i686@2.36.2": + version "2.36.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.36.2.tgz#daf562c33e2b7b41002c77aaffef6828dedb3ece" + integrity sha512-YDH/Kcd8JAo1Bg4jtSwF8dr7FZZ8QbYLMx8q/5eenHpq6VdOgPENsTvayLW3cAjWLcm44u8Ed/gcEK0z1IxQmQ== + +"@sentry/cli-win32-x64@2.36.2": + version "2.36.2" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.36.2.tgz#81678829dbe3a409e5e1c934a022268035ee24d0" + integrity sha512-Kac8WPbkFSVAJqPAVRBiW0uij9PVoXo0owf+EDeIIDLs9yxZat0d1xgyQPlUWrCGdxowMSbDvaSUz1YnE7MUmg== "@sentry/cli@^2.33.1": - version "2.33.1" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.33.1.tgz#cfbdffdd896b05b92a659baf435b5607037af928" - integrity sha512-dUlZ4EFh98VFRPJ+f6OW3JEYQ7VvqGNMa0AMcmvk07ePNeK/GicAWmSQE4ZfJTTl80ul6HZw1kY01fGQOQlVRA== + version "2.36.2" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.36.2.tgz#37ea8307529d829e382323193e858851365b8d73" + integrity sha512-QoijP9TnO1UVNnRKtH718jlu/F9bBki6ffrOfmcjxkvLT6Q3nBMmqhYNH/AJV/RcgqLd6noWss4fbDMXZLzgIQ== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -3342,13 +3361,13 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.33.1" - "@sentry/cli-linux-arm" "2.33.1" - "@sentry/cli-linux-arm64" "2.33.1" - "@sentry/cli-linux-i686" "2.33.1" - "@sentry/cli-linux-x64" "2.33.1" - "@sentry/cli-win32-i686" "2.33.1" - "@sentry/cli-win32-x64" "2.33.1" + "@sentry/cli-darwin" "2.36.2" + "@sentry/cli-linux-arm" "2.36.2" + "@sentry/cli-linux-arm64" "2.36.2" + "@sentry/cli-linux-i686" "2.36.2" + "@sentry/cli-linux-x64" "2.36.2" + "@sentry/cli-win32-i686" "2.36.2" + "@sentry/cli-win32-x64" "2.36.2" "@sentry/core@8.28.0", "@sentry/core@^8.28.0": version "8.28.0" @@ -3454,12 +3473,12 @@ dependencies: "@sentry/types" "8.28.0" -"@sentry/webpack-plugin@^2.22.2": - version "2.22.2" - resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-2.22.2.tgz#576de848985d29c524370f8da623bcaf19bf16df" - integrity sha512-zVPs3BLClHM8jIjr2FChux16GMLel8OjXBjd4V8/r1Kf2fGiQDXo72GxsrW8AdVlIHgQApLzubuQ2kpcFVK4Sw== +"@sentry/webpack-plugin@^2.22.4": + version "2.22.4" + resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-2.22.4.tgz#0102e83c77a6a4415344e7141f4aebc586d625da" + integrity sha512-Y7+RBrXBZlEuvoC0SbuClHZ8VC0nM0wZXroFuY/157EfUFtWr0J8f3b8+mzNshDGaCWV/UzFn6092M/BlAXCQA== dependencies: - "@sentry/bundler-plugin-core" "2.22.2" + "@sentry/bundler-plugin-core" "2.22.4" unplugin "1.0.1" uuid "^9.0.0" @@ -3762,31 +3781,20 @@ dependencies: "@types/trusted-types" "*" -"@types/eslint-scope@^3.7.3": - version "3.7.3" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224" - integrity sha512-PB3ldyrcnAicT35TWPs5IcwKD8S333HMaa2VVv4+wdvebJkjWuW/xESoB8IwRcog8HYVYamb1g/R31Qv5Bx03g== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "7.2.13" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.13.tgz#e0ca7219ba5ded402062ad6f926d491ebb29dd53" - integrity sha512-LKmQCWAlnVHvvXq4oasNUMTJJb2GwSyTY8+1C7OH5ILR8mPLaljv1jxL1bXW3xB3jFbQxTKxJAvI8PyjB09aBg== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@1.0.5", "@types/estree@^1.0.5": +"@types/estree@1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== +"@types/estree@^1.0.5": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50" + integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw== + "@types/express-serve-static-core@*", "@types/express-serve-static-core@^4.17.33": - version "4.19.0" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz#3ae8ab3767d98d0b682cda063c3339e1e86ccfaa" - integrity sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ== + version "4.19.5" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" + integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== dependencies: "@types/node" "*" "@types/qs" "*" @@ -3803,6 +3811,14 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/fs-extra@^11.0.4": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45" + integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ== + dependencies: + "@types/jsonfile" "*" + "@types/node" "*" + "@types/graceful-fs@^4.1.3": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -3826,9 +3842,9 @@ integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== "@types/http-proxy@^1.17.8": - version "1.17.14" - resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.14.tgz#57f8ccaa1c1c3780644f8a94f9c6b5000b5e2eec" - integrity sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w== + version "1.17.15" + resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.15.tgz#12118141ce9775a6499ecb4c01d02f90fc839d36" + integrity sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ== dependencies: "@types/node" "*" @@ -3883,7 +3899,7 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" -"@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -3893,6 +3909,13 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/jsonfile@*": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.4.tgz#614afec1a1164e7d670b4a7ad64df3e7beb7b702" + integrity sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ== + dependencies: + "@types/node" "*" + "@types/lodash@^4.14.182": version "4.14.182" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.182.tgz#05301a4d5e62963227eaafe0ce04dd77c54ea5c2" @@ -3925,9 +3948,9 @@ "@types/node" "*" "@types/node@*", "@types/node@>=10.0.0": - version "22.4.0" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.4.0.tgz#c295fe1d6f5f58916cc61dbef8cf65b5b9b71de9" - integrity sha512-49AbMDwYUz7EXxKU/r7mXOsxwFr4BYbvB7tWYxVuLdb2ibd30ijjXINSMAHiEEZk5PCRBmW1gUeisn2VMKt3cQ== + version "22.6.0" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.6.0.tgz#b604c9a628760221905c1b272fd6aee661f45042" + integrity sha512-QyR8d5bmq+eR72TwQDfujwShHMcIrWIYsaQFtXRE58MHPTEKUNxjxvl0yS0qPMds5xbSDWtp7ZpvGFtd7dfMdQ== dependencies: undici-types "~6.19.2" @@ -3991,9 +4014,9 @@ integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== "@types/qs@*": - version "6.9.15" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" - integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + version "6.9.16" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.16.tgz#52bba125a07c0482d26747d5d4947a64daf8f794" + integrity sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A== "@types/range-parser@*": version "1.2.7" @@ -4173,19 +4196,10 @@ resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.18.5.tgz#eccda0b04fe024bed505881e2e532f9c119169bf" integrity sha512-wz7kjjRRj8/Lty4B+Kr0LN6Ypc/3SymeCCGSbaXp2leH0ZVg/PriNiOwNj4bD4uphI7A8NXS4b6Gl373sfO5mA== -"@types/webpack@5.28.5": - version "5.28.5" - resolved "https://registry.yarnpkg.com/@types/webpack/-/webpack-5.28.5.tgz#0e9d9a15efa09bbda2cef41356ca4ac2031ea9a2" - integrity sha512-wR87cgvxj3p6D0Crt1r5avwqffqPXUkNlnQ1mjU93G7gCuFjufZR4I6j8cz5g1F1tTYpfOOFvly+cmIQwL9wvw== - dependencies: - "@types/node" "*" - tapable "^2.2.0" - webpack "^5" - "@types/ws@^8.5.10": - version "8.5.10" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.10.tgz#4acfb517970853fa6574a3a6886791d04a396787" - integrity sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A== + version "8.5.12" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" + integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== dependencies: "@types/node" "*" @@ -4550,14 +4564,14 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn-walk@8.3.3, acorn-walk@^8.0.0, acorn-walk@^8.0.2, acorn-walk@^8.1.1: - version "8.3.3" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" - integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== +acorn-walk@8.3.4, acorn-walk@^8.0.0, acorn-walk@^8.0.2, acorn-walk@^8.1.1: + version "8.3.4" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.4.tgz#794dd169c3977edf4ba4ea47583587c5866236b7" + integrity sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g== dependencies: acorn "^8.11.0" -acorn@^8.0.4, acorn@^8.1.0, acorn@^8.10.0, acorn@^8.11.0, acorn@^8.11.3, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.0.4, acorn@^8.1.0, acorn@^8.10.0, acorn@^8.11.0, acorn@^8.12.1, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.1, acorn@^8.8.2, acorn@^8.9.0: version "8.12.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== @@ -4861,9 +4875,9 @@ available-typed-arrays@^1.0.7: possible-typed-array-names "^1.0.0" axios@^1.7.2: - version "1.7.4" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.4.tgz#4c8ded1b43683c8dd362973c393f3ede24052aa2" - integrity sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw== + version "1.7.7" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.7.tgz#2f554296f9892a72ac8d8e4c5b79c14a91d0a47f" + integrity sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q== dependencies: follow-redirects "^1.15.6" form-data "^4.0.0" @@ -5036,10 +5050,10 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.1.0.tgz#30fa40c9e7fe07dbc895678cd287024dea241dd9" integrity sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ== -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -5049,7 +5063,7 @@ body-parser@1.20.2: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" + qs "6.13.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -5236,9 +5250,9 @@ chokidar@^3.5.3, chokidar@^3.6.0: fsevents "~2.3.2" chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== ci-info@^3.2.0: version "3.3.0" @@ -5827,10 +5841,10 @@ date-fns@^2.16.1: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.17.0.tgz#afa55daea539239db0a64e236ce716ef3d681ba1" integrity sha512-ZEhqxUtEZeGgg9eHNSOAJ8O9xqSgiJdrL0lzSSfMF54x6KXWJiOH/xntSJ9YomJPrYH/p08t6gWjGWq1SDJlSA== -dayjs@1.11.12: - version "1.11.12" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.12.tgz#5245226cc7f40a15bf52e0b99fd2a04669ccac1d" - integrity sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg== +dayjs@1.11.13: + version "1.11.13" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c" + integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg== debounce@^1.2.1: version "1.2.1" @@ -5845,11 +5859,11 @@ debug@2.6.9: ms "2.0.0" debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: - version "4.3.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" - integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== + version "4.3.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: - ms "2.1.2" + ms "^2.1.3" debug@^3.2.6, debug@^3.2.7: version "3.2.7" @@ -5903,13 +5917,6 @@ default-browser@^5.2.1: bundle-name "^4.1.0" default-browser-id "^5.0.0" -default-gateway@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" - integrity sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg== - dependencies: - execa "^5.0.0" - define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" @@ -6187,7 +6194,7 @@ editorconfig@^1.0.4: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-to-chromium@^1.5.4: version "1.5.6" @@ -6225,7 +6232,12 @@ emojis-list@^3.0.0: encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== + +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== encoding@^0.1.12: version "0.1.13" @@ -6263,7 +6275,7 @@ enhanced-resolve@5.12.0: graceful-fs "^4.2.4" tapable "^2.2.0" -enhanced-resolve@^5.12.0, enhanced-resolve@^5.17.0: +enhanced-resolve@^5.12.0, enhanced-resolve@^5.17.1: version "5.17.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== @@ -6286,10 +6298,10 @@ env-paths@^2.2.1: resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A== -envinfo@7.13.0, envinfo@^7.7.3: - version "7.13.0" - resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.13.0.tgz#81fbb81e5da35d74e814941aeab7c325a606fb31" - integrity sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q== +envinfo@7.14.0, envinfo@^7.7.3: + version "7.14.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.14.0.tgz#26dac5db54418f2a4c1159153a0b2ae980838aae" + integrity sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg== errno@^0.1.1: version "0.1.7" @@ -6397,9 +6409,9 @@ es-iterator-helpers@^1.0.19: safe-array-concat "^1.1.2" es-module-lexer@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.2.1.tgz#ba303831f63e6a394983fde2f97ad77b22324527" - integrity sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg== + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== es-object-atoms@^1.0.0: version "1.0.0" @@ -6476,7 +6488,7 @@ escape-carriage@^1.3.0: escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@^1.0.5: version "1.0.5" @@ -6782,7 +6794,7 @@ esutils@^2.0.2: etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== eventemitter3@^1.1.1: version "1.2.0" @@ -6830,37 +6842,37 @@ expect@^29.0.0, expect@^29.7.0: jest-message-util "^29.7.0" jest-util "^29.7.0" -express@^4.17.3: - version "4.19.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== +express@^4.19.2: + version "4.21.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" + integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.2" + body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.2.0" + finalhandler "1.3.1" fresh "0.5.2" http-errors "2.0.0" - merge-descriptors "1.0.1" + merge-descriptors "1.0.3" methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.7" + path-to-regexp "0.1.10" proxy-addr "~2.0.7" - qs "6.11.0" + qs "6.13.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" + send "0.19.0" + serve-static "1.16.2" setprototypeof "1.2.0" statuses "2.0.1" type-is "~1.6.18" @@ -6938,10 +6950,10 @@ file-entry-cache@^8.0.0: dependencies: flat-cache "^4.0.0" -filesize@^10.1.4: - version "10.1.4" - resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.4.tgz#184f256063a201f08b6e6b3cc47d21b60f5b8d89" - integrity sha512-ryBwPIIeErmxgPnm6cbESAzXjuEFubs+yKYLBZvg3CaiNcmkJChoOGcBSrZ6IwkMwPABwPpVXE6IlNdGJJrvEg== +filesize@^10.1.6: + version "10.1.6" + resolved "https://registry.yarnpkg.com/filesize/-/filesize-10.1.6.tgz#31194da825ac58689c0bce3948f33ce83aabd361" + integrity sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w== fill-range@^7.1.1: version "7.1.1" @@ -6968,13 +6980,13 @@ finalhandler@1.1.2: statuses "~1.5.0" unpipe "~1.0.0" -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== dependencies: debug "2.6.9" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" on-finished "2.4.1" parseurl "~1.3.3" @@ -7054,9 +7066,9 @@ focus-trap@^7.3.1: tabbable "^6.1.1" follow-redirects@^1.0.0, follow-redirects@^1.15.6: - version "1.15.6" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" - integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + version "1.15.9" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.9.tgz#a604fa10e443bf98ca94228d9eebcc2e8a2c8ee1" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== for-each@^0.3.3: version "0.3.3" @@ -7115,7 +7127,7 @@ framer-motion@^11.3.21: fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== fs-extra@^10.0.0: version "10.0.1" @@ -7611,6 +7623,11 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +hyperdyperid@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" + integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== + iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -7765,9 +7782,9 @@ ipaddr.js@1.9.1: integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== ipaddr.js@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" - integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ== + version "2.2.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" + integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== is-array-buffer@^3.0.4: version "3.0.4" @@ -8726,9 +8743,9 @@ known-css-properties@^0.29.0: integrity sha512-Ne7wqW7/9Cz54PDt4I3tcV+hAyat8ypyOGzYRJQfdxnnjeWsTxt1cy8pjvvKeI5kfXuyvULyeeAvwvvtAX3ayQ== launch-editor@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.6.1.tgz#f259c9ef95cbc9425620bbbd14b468fcdb4ffe3c" - integrity sha512-eB/uXmFVpY4zezmGp5XtU21kwo7GBbKB+EQ+UZeWtGb9yAM5xt/Evk+lYH3eRNAtId+ej4u7TYPFZ07w4s7rRw== + version "2.9.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.9.1.tgz#253f173bd441e342d4344b4dae58291abb425047" + integrity sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w== dependencies: picocolors "^1.0.0" shell-quote "^1.8.1" @@ -8867,9 +8884,9 @@ lines-and-columns@^1.1.6: integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= loader-runner@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" - integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== loader-utils@^1.4.2: version "1.4.2" @@ -9043,7 +9060,7 @@ mdn-data@2.0.30: media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== memfs@^3.4.1: version "3.4.9" @@ -9053,10 +9070,13 @@ memfs@^3.4.1: fs-monkey "^1.0.3" memfs@^4.6.0: - version "4.8.2" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.8.2.tgz#9bb7c3e43647348451082557f05fb170b7442949" - integrity sha512-j4WKth315edViMBGkHW6NTF0QBjsTrcRDmYNcGsPq+ozMEyCCCIlX2d2mJ5wuh6iHvJ3FevUrr48v58YRqVdYg== + version "4.12.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.12.0.tgz#76570478aee461695fb3336ca3356a7a8cfc26cc" + integrity sha512-74wDsex5tQDSClVkeK1vtxqYCAgCoXxx+K4NSHzgU/muYVYByFqa+0RnrPO9NM6naWm1+G9JmZ0p6QHhXmeYfA== dependencies: + "@jsonjoy.com/json-pack" "^1.0.3" + "@jsonjoy.com/util" "^1.3.0" + tree-dump "^1.0.1" tslib "^2.0.0" memoize-one@^5.0.0: @@ -9069,10 +9089,10 @@ meow@^13.1.0: resolved "https://registry.yarnpkg.com/meow/-/meow-13.2.0.tgz#6b7d63f913f984063b3cc261b6e8800c4cd3474f" integrity sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA== -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== merge-stream@^2.0.0: version "2.0.0" @@ -9097,11 +9117,16 @@ micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: braces "^3.0.3" picomatch "^2.3.1" -mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": +mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== +"mime-db@>= 1.43.0 < 2": + version "1.53.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.53.0.tgz#3cb63cd820fc29896d9d4e8c32ab4fcd74ccb447" + integrity sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg== + mime-types@^2.1.12, mime-types@^2.1.27, mime-types@^2.1.31, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" @@ -9244,14 +9269,9 @@ mrmime@^2.0.0: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -ms@2.1.3, ms@^2.1.1: +ms@2.1.3, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -9685,10 +9705,10 @@ path-scurry@^1.10.1, path-scurry@^1.6.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== path-type@^4.0.0: version "4.0.0" @@ -10256,12 +10276,12 @@ qrcode.react@^3.1.0: resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-3.1.0.tgz#5c91ddc0340f768316fbdb8fff2765134c2aecd8" integrity sha512-oyF+Urr3oAMUG/OiOuONL3HXM+53wvuH3mtIWQrYmsXoAq0DkvZp2RYUWFSMFtbdOpuS++9v+WAkzNVkMlNW6Q== -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== dependencies: - side-channel "^1.0.4" + side-channel "^1.0.6" query-string@7.0.1: version "7.0.1" @@ -10753,10 +10773,10 @@ rimraf@^5.0.5: dependencies: glob "^10.3.7" -rslog@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/rslog/-/rslog-1.2.2.tgz#057791013c1de27d1d8b0728a766688d11c13830" - integrity sha512-tZP8KjrI1nz6qOYCrFxAV7cfmfS2GV94jotU2zOmF/6ByO1zNvGR6/+0inylpjqyBjAdnnutTUW0m4th06bSTw== +rslog@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/rslog/-/rslog-1.2.3.tgz#9114d93056312fbe35c11b3fea3f2774a7debe56" + integrity sha512-antALPJaKBRPBU1X2q9t085K4htWDOOv/K1qhTUk7h0l1ePU/KbDqKJn19eKP0dk7PqMioeA0+fu3gyPXCsXxQ== run-applescript@^7.0.0: version "7.0.0" @@ -10899,6 +10919,25 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + serialize-javascript@^6.0.1, serialize-javascript@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" @@ -10919,16 +10958,26 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== +serve-static@1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92" + integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" parseurl "~1.3.3" send "0.18.0" +serve-static@1.16.2: + version "1.16.2" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.2.tgz#b6a5343da47f6bdd2673848bf45754941e803296" + integrity sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw== + dependencies: + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.19.0" + set-function-length@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" @@ -11535,6 +11584,11 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thingies@^1.20.0: + version "1.21.0" + resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" + integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== + thunky@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" @@ -11596,6 +11650,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +tree-dump@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.2.tgz#c460d5921caeb197bde71d0e9a7b479848c5b8ac" + integrity sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ== + ts-api-utils@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" @@ -11840,7 +11899,7 @@ universalify@^2.0.0: unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== unplugin@1.0.1: version "1.0.1" @@ -11853,14 +11912,12 @@ unplugin@1.0.1: webpack-virtual-modules "^0.5.0" unplugin@^1.10.1, unplugin@^1.4.0: - version "1.10.1" - resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.10.1.tgz#8ceda065dc71bc67d923dea0920f05c67f2cd68c" - integrity sha512-d6Mhq8RJeGA8UfKCu54Um4lFA0eSaRa3XxdAJg8tIdxbu1ubW0hBCZUL7yI2uGyYCRndvbK8FLHzqy2XKfeMsg== + version "1.14.1" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.14.1.tgz#c76d6155a661e43e6a897bce6b767a1ecc344c1a" + integrity sha512-lBlHbfSFPToDYp9pjXlUEFVxYLaue9f9T1HC+4OHlmj+HnMDdz9oZY+erXfoCe/5V/7gKUSY2jpXPb9S7f0f/w== dependencies: - acorn "^8.11.3" - chokidar "^3.6.0" - webpack-sources "^3.2.3" - webpack-virtual-modules "^0.6.1" + acorn "^8.12.1" + webpack-virtual-modules "^0.6.2" update-browserslist-db@^1.1.0: version "1.1.0" @@ -11907,7 +11964,7 @@ utila@~0.4: utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== uuid@^8.3.2: version "8.3.2" @@ -11997,9 +12054,9 @@ warning@^4.0.2: loose-envify "^1.0.0" watchpack@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" - integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -12058,10 +12115,10 @@ webpack-cli@5.1.4: rechoir "^0.8.0" webpack-merge "^5.7.3" -webpack-dev-middleware@^7.1.0: - version "7.2.1" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.2.1.tgz#2af00538b6e4eda05f5afdd5d711dbebc05958f7" - integrity sha512-hRLz+jPQXo999Nx9fXVdKlg/aehsw1ajA9skAneGmT03xwmyuhvF93p6HUKKbWhXdcERtGTzUCtIQr+2IQegrA== +webpack-dev-middleware@^7.4.2: + version "7.4.2" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.4.2.tgz#40e265a3d3d26795585cff8207630d3a8ff05877" + integrity sha512-xOO8n6eggxnwYpy1NlzUKpvrjfJTvae5/D6WOK0S2LSo7vjmo5gCM1DbLUmFqrMTJP+W/0YZNctm7jasWvLuBA== dependencies: colorette "^2.0.10" memfs "^4.6.0" @@ -12070,10 +12127,10 @@ webpack-dev-middleware@^7.1.0: range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz#cb6ea47ff796b9251ec49a94f24a425e12e3c9b8" - integrity sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA== +webpack-dev-server@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.1.0.tgz#8f44147402b4d8ab99bfeb9b6880daa1411064e5" + integrity sha512-aQpaN81X6tXie1FoOB7xlMfCsN19pSvRAeYUHOdFWOlhpQ/LlbfTqYwwmEDFV0h8GGuqmCmKmT+pxcUV/Nt2gQ== dependencies: "@types/bonjour" "^3.5.13" "@types/connect-history-api-fallback" "^1.5.4" @@ -12088,8 +12145,7 @@ webpack-dev-server@5.0.4: colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^2.0.0" - default-gateway "^6.0.3" - express "^4.17.3" + express "^4.19.2" graceful-fs "^4.2.6" html-entities "^2.4.0" http-proxy-middleware "^2.0.3" @@ -12097,14 +12153,13 @@ webpack-dev-server@5.0.4: launch-editor "^2.6.1" open "^10.0.3" p-retry "^6.2.0" - rimraf "^5.0.5" schema-utils "^4.2.0" selfsigned "^2.4.1" serve-index "^1.9.1" sockjs "^0.3.24" spdy "^4.0.2" - webpack-dev-middleware "^7.1.0" - ws "^8.16.0" + webpack-dev-middleware "^7.4.2" + ws "^8.18.0" webpack-merge@^5.7.3: version "5.10.0" @@ -12132,17 +12187,16 @@ webpack-virtual-modules@^0.5.0: resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c" integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== -webpack-virtual-modules@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz#ac6fdb9c5adb8caecd82ec241c9631b7a3681b6f" - integrity sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg== +webpack-virtual-modules@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8" + integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ== -webpack@5.93.0, webpack@^5: - version "5.93.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.93.0.tgz#2e89ec7035579bdfba9760d26c63ac5c3462a5e5" - integrity sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA== +webpack@5.94.0: + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== dependencies: - "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" "@webassemblyjs/ast" "^1.12.1" "@webassemblyjs/wasm-edit" "^1.12.1" @@ -12151,7 +12205,7 @@ webpack@5.93.0, webpack@^5: acorn-import-attributes "^1.9.5" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.17.0" + enhanced-resolve "^5.17.1" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" @@ -12336,7 +12390,12 @@ ws@^7.3.1: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.16.0, ws@^8.9.0, ws@~8.17.1: +ws@^8.18.0, ws@^8.9.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + +ws@~8.17.1: version "8.17.1" resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== @@ -12410,9 +12469,9 @@ yocto-queue@^1.0.0: integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g== zod@^3.22.4: - version "3.22.4" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" - integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== + version "3.23.8" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" + integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== zrender@5.4.0: version "5.4.0"