Merge branch 'main' into oscark/show-transformations-when-editing-scene-dashboard

This commit is contained in:
Oscar Kilhed
2024-01-16 09:58:17 +01:00
15 changed files with 435 additions and 409 deletions
-10
View File
@@ -2437,17 +2437,7 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
],
"public/app/features/dashboard-scene/utils/DashboardModelCompatibilityWrapper.ts:5381": [
<<<<<<< HEAD
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"]
||||||| eb8dfe7933d
[0, 0, 0, "Do not use any type assertions.", "0"],
[0, 0, 0, "Do not use any type assertions.", "1"],
[0, 0, 0, "Do not use any type assertions.", "2"],
[0, 0, 0, "Do not use any type assertions.", "3"]
=======
[0, 0, 0, "Do not use any type assertions.", "0"]
>>>>>>> 6073626683dd3e9f437a533ef55fd79baaceb982
],
"public/app/features/dashboard-scene/utils/test-utils.ts:5381": [
[0, 0, 0, "Do not use any type assertions.", "0"],
+11 -11
View File
@@ -132,7 +132,7 @@
"@types/react": "18.2.15",
"@types/react-beautiful-dnd": "13.1.8",
"@types/react-dom": "18.2.7",
"@types/react-grid-layout": "1.3.2",
"@types/react-grid-layout": "1.3.5",
"@types/react-highlight-words": "0.16.7",
"@types/react-router-dom": "5.3.3",
"@types/react-table": "7.7.19",
@@ -198,15 +198,15 @@
"jest-junit": "16.0.0",
"jest-matcher-utils": "29.7.0",
"lerna": "7.4.1",
"mini-css-extract-plugin": "2.7.6",
"mini-css-extract-plugin": "2.7.7",
"msw": "1.3.2",
"mutationobserver-shim": "0.3.7",
"ngtemplate-loader": "2.1.0",
"node-notifier": "10.0.1",
"postcss": "8.4.31",
"postcss-loader": "7.3.3",
"postcss": "8.4.33",
"postcss-loader": "7.3.4",
"postcss-reporter": "7.0.5",
"postcss-scss": "4.0.6",
"postcss-scss": "4.0.9",
"prettier": "3.0.0",
"react-refresh": "0.14.0",
"react-select-event": "5.5.1",
@@ -350,7 +350,7 @@
"ml-regression-polynomial": "^3.0.0",
"ml-regression-simple-linear": "^3.0.0",
"moment": "2.29.4",
"moment-timezone": "0.5.43",
"moment-timezone": "0.5.44",
"monaco-editor": "0.34.0",
"monaco-promql": "1.7.4",
"mousetrap": "1.6.5",
@@ -359,7 +359,7 @@
"nanoid": "^5.0.4",
"node-forge": "^1.3.1",
"ol": "7.4.0",
"ol-ext": "4.0.10",
"ol-ext": "4.0.13",
"papaparse": "5.4.1",
"pluralize": "^8.0.0",
"prismjs": "1.29.0",
@@ -369,15 +369,15 @@
"rc-drawer": "6.5.2",
"rc-slider": "10.3.1",
"rc-time-picker": "3.7.3",
"rc-tree": "5.8.0",
"re-resizable": "6.9.9",
"rc-tree": "5.8.2",
"re-resizable": "6.9.11",
"react": "18.2.0",
"react-beautiful-dnd": "13.1.1",
"react-diff-viewer": "^3.1.1",
"react-dom": "18.2.0",
"react-draggable": "4.4.5",
"react-draggable": "4.4.6",
"react-dropzone": "^14.2.3",
"react-grid-layout": "1.4.2",
"react-grid-layout": "1.4.4",
"react-highlight-words": "0.20.0",
"react-hook-form": "^7.49.2",
"react-i18next": "^12.0.0",
+1 -1
View File
@@ -49,7 +49,7 @@
"marked": "5.1.1",
"marked-mangle": "1.1.6",
"moment": "2.29.4",
"moment-timezone": "0.5.43",
"moment-timezone": "0.5.44",
"ol": "7.4.0",
"papaparse": "5.4.1",
"react-use": "17.4.0",
+10
View File
@@ -128,6 +128,9 @@ var (
// MAccessPermissionsSummary is a metric summary for loading permissions request duration when evaluating access
MAccessPermissionsSummary prometheus.Histogram
// MSearchPermissionsSummary is a metric summary for searching permissions request duration
MAccessSearchPermissionsSummary prometheus.Histogram
// MAccessEvaluationsSummary is a metric summary for loading permissions request duration when evaluating access
MAccessEvaluationsSummary prometheus.Histogram
)
@@ -581,6 +584,12 @@ func init() {
Namespace: ExporterName,
})
MAccessSearchPermissionsSummary = prometheus.NewHistogram(prometheus.HistogramOpts{
Name: "access_search_permissions_duration",
Help: "Histogram for the runtime of permissions search function",
Buckets: prometheus.ExponentialBuckets(0.001, 4, 5),
})
StatsTotalLibraryPanels = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "stat_totals_library_panels",
Help: "total amount of library panels in the database",
@@ -698,6 +707,7 @@ func initMetricVars(reg prometheus.Registerer) {
MRenderingQueue,
MAccessPermissionsSummary,
MAccessEvaluationsSummary,
MAccessSearchPermissionsSummary,
MAlertingActiveAlerts,
MStatTotalDashboards,
MStatTotalFolders,
@@ -242,6 +242,9 @@ func (s *Service) DeclarePluginRoles(ctx context.Context, ID, name string, regs
// SearchUsersPermissions returns all users' permissions filtered by action prefixes
func (s *Service) SearchUsersPermissions(ctx context.Context, user identity.Requester,
options accesscontrol.SearchOptions) (map[int64][]accesscontrol.Permission, error) {
timer := prometheus.NewTimer(metrics.MAccessSearchPermissionsSummary)
defer timer.ObserveDuration()
// Filter ram permissions
basicPermissions := map[string][]accesscontrol.Permission{}
for role, basicRole := range s.roles {
+8 -13
View File
@@ -112,7 +112,7 @@ func (c *OAuth) Authenticate(ctx context.Context, r *authn.Request) (*authn.Iden
if err != nil {
return nil, errOAuthMissingPKCE.Errorf("no pkce cookie found: %w", err)
}
opts = append(opts, oauth2.SetAuthURLParam(codeVerifierParamName, pkceCookie.Value))
opts = append(opts, oauth2.VerifierOption(pkceCookie.Value))
}
clientCtx := context.WithValue(ctx, oauth2.HTTPClient, c.httpClient)
@@ -184,16 +184,13 @@ func (c *OAuth) RedirectURL(ctx context.Context, r *authn.Request) (*authn.Redir
var plainPKCE string
if c.oauthCfg.UsePKCE {
pkce, hashedPKCE, err := genPKCECode()
verifier, err := genPKCECodeVerifier()
if err != nil {
return nil, errOAuthGenPKCE.Errorf("failed to generate pkce: %w", err)
}
plainPKCE = pkce
opts = append(opts,
oauth2.SetAuthURLParam(codeChallengeParamName, hashedPKCE),
oauth2.SetAuthURLParam(codeChallengeMethodParamName, codeChallengeMethod),
)
plainPKCE = verifier
opts = append(opts, oauth2.S256ChallengeOption(plainPKCE))
}
state, hashedSate, err := genOAuthState(c.cfg.SecretKey, c.oauthCfg.ClientSecret)
@@ -233,8 +230,8 @@ func (c *OAuth) Logout(ctx context.Context, user identity.Requester, info *login
return &authn.Redirect{URL: redirctURL}, true
}
// genPKCECode returns a random URL-friendly string and it's base64 URL encoded SHA256 digest.
func genPKCECode() (string, string, error) {
// genPKCECodeVerifier returns code verifier that 128 characters random URL-friendly string.
func genPKCECodeVerifier() (string, error) {
// IETF RFC 7636 specifies that the code verifier should be 43-128
// characters from a set of unreserved URI characters which is
// almost the same as the set of characters in base64url.
@@ -249,14 +246,12 @@ func genPKCECode() (string, string, error) {
raw := make([]byte, 96)
_, err := rand.Read(raw)
if err != nil {
return "", "", err
return "", err
}
ascii := make([]byte, 128)
base64.RawURLEncoding.Encode(ascii, raw)
shasum := sha256.Sum256(ascii)
pkce := base64.RawURLEncoding.EncodeToString(shasum[:])
return string(ascii), pkce, nil
return string(ascii), nil
}
func genOAuthState(secret, seed string) (string, string, error) {
+7 -1
View File
@@ -268,7 +268,7 @@ func TestOAuth_RedirectURL(t *testing.T) {
{
desc: "should generate redirect url with pkce if configured",
oauthCfg: &social.OAuthInfo{UsePKCE: true},
numCallOptions: 2,
numCallOptions: 1,
authCodeUrlCalled: true,
},
}
@@ -404,6 +404,12 @@ func TestOAuth_Logout(t *testing.T) {
}
}
func TestGenPKCECodeVerifier(t *testing.T) {
verifier, err := genPKCECodeVerifier()
assert.NoError(t, err)
assert.Len(t, verifier, 128)
}
type mockConnector struct {
AuthCodeURLFunc func(state string, opts ...oauth2.AuthCodeOption) string
social.SocialConnector
+16
View File
@@ -3,6 +3,7 @@ package cloudwatch
import (
"context"
"fmt"
"regexp"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"golang.org/x/sync/errgroup"
@@ -121,6 +122,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, logger
Error: fmt.Errorf("metric request error: %q", err),
}
resultChan <- &responseWrapper{
RefId: getQueryRefIdFromErrorString(err.Error(), requestQueries),
DataResponse: &dataResponse,
}
}
@@ -132,3 +134,17 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, logger
return resp, nil
}
func getQueryRefIdFromErrorString(err string, queries []*models.CloudWatchQuery) string {
// error can be in format "Error in expression 'test': Invalid syntax"
// so we can find the query id or ref id between the quotations
erroredRefId := ""
for _, query := range queries {
if regexp.MustCompile(`'`+query.RefId+`':`).MatchString(err) || regexp.MustCompile(`'`+query.Id+`':`).MatchString(err) {
erroredRefId = query.RefId
}
}
// if errorRefId is empty, it means the error concerns all queries (error metric limit exceeded, for example)
return erroredRefId
}
@@ -16,12 +16,7 @@ export interface ContactPointSelectorProps {
}
export function ContactPointSelector({ alertManager, contactPoints, onSelectContactPoint }: ContactPointSelectorProps) {
const styles = useStyles2(getStyles);
const {
register,
control,
formState: { errors },
watch,
} = useFormContext<RuleFormValues>();
const { control, watch } = useFormContext<RuleFormValues>();
const options = contactPoints.map((receiver) => {
const integrations = receiver?.grafana_managed_receiver_configs;
@@ -39,11 +34,7 @@ export function ContactPointSelector({ alertManager, contactPoints, onSelectCont
return (
<Stack direction="column">
<Field
label="Contact point"
{...register(`contactPoints.${alertManager}.selectedContactPoint`, { required: true })}
invalid={!!errors.contactPoints?.[alertManager]?.selectedContactPoint}
>
<Field label="Contact point">
<InputControl
render={({ field: { onChange, ref, ...field }, fieldState: { error } }) => (
<>
@@ -65,9 +56,10 @@ export function ContactPointSelector({ alertManager, contactPoints, onSelectCont
width={50}
/>
</div>
{error && <FieldValidationMessage>{'Contact point is required.'}</FieldValidationMessage>}
{error && <FieldValidationMessage>{error.message}</FieldValidationMessage>}
</>
)}
rules={{ required: { value: true, message: 'Contact point is required.' } }}
control={control}
name={`contactPoints.${alertManager}.selectedContactPoint`}
/>
@@ -1,7 +1,6 @@
import { of, throwError } from 'rxjs';
import { of } from 'rxjs';
import { CustomVariableModel, DataQueryError, DataQueryRequest, DataSourceInstanceSettings } from '@grafana/data';
import { BackendDataSourceResponse, toDataQueryResponse } from '@grafana/runtime';
import { CustomVariableModel, DataQueryRequest, DataQueryResponse, DataSourceInstanceSettings } from '@grafana/data';
import { CloudWatchMetricsQueryRunner } from '../query-runner/CloudWatchMetricsQueryRunner';
import { CloudWatchJsonData, CloudWatchQuery } from '../types';
@@ -10,23 +9,17 @@ import { CloudWatchSettings, setupMockedTemplateService } from './CloudWatchData
import { TimeRangeMock } from './timeRange';
export function setupMockedMetricsQueryRunner({
data = {
results: {},
},
response = { data: [] },
variables,
errorResponse,
instanceSettings = CloudWatchSettings,
}: {
data?: BackendDataSourceResponse;
response?: DataQueryResponse;
variables?: CustomVariableModel[];
errorResponse?: DataQueryError;
instanceSettings?: DataSourceInstanceSettings<CloudWatchJsonData>;
} = {}) {
const templateService = setupMockedTemplateService(variables);
const queryMock = errorResponse
? jest.fn().mockImplementation(() => throwError(errorResponse))
: jest.fn().mockReturnValue(of(toDataQueryResponse({ data })));
const queryMock = jest.fn().mockImplementation(() => of(response));
const runner = new CloudWatchMetricsQueryRunner(instanceSettings, templateService);
const request: DataQueryRequest<CloudWatchQuery> = {
@@ -3,8 +3,8 @@ import React from 'react';
import { GrafanaTheme2, DataSourceInstanceSettings } from '@grafana/data';
import { ConfigSection } from '@grafana/experimental';
import { DataSourcePicker } from '@grafana/runtime';
import { Alert, Field, InlineField, useStyles2 } from '@grafana/ui';
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
const getStyles = (theme: GrafanaTheme2) => ({
@@ -2,7 +2,7 @@ import { of } from 'rxjs';
import { CustomVariableModel, getFrameDisplayName, VariableHide } from '@grafana/data';
import { dateTime } from '@grafana/data/src/datetime/moment_wrapper';
import { BackendDataSourceResponse } from '@grafana/runtime';
import { toDataQueryResponse } from '@grafana/runtime';
import * as redux from 'app/store/store';
import {
@@ -17,18 +17,28 @@ import {
import { initialVariableModelState } from '../__mocks__/CloudWatchVariables';
import { setupMockedMetricsQueryRunner } from '../__mocks__/MetricsQueryRunner';
import { validMetricSearchBuilderQuery, validMetricSearchCodeQuery } from '../__mocks__/queries';
import { MetricQueryType, MetricEditorMode, CloudWatchMetricsQuery, DataQueryError } from '../types';
import { MetricQueryType, MetricEditorMode, CloudWatchMetricsQuery } from '../types';
describe('CloudWatchMetricsQueryRunner', () => {
describe('performTimeSeriesQuery', () => {
it('should return the same length of data as result', async () => {
const { runner, timeRange, request, queryMock } = setupMockedMetricsQueryRunner({
const resultsFromBEQuery = {
data: {
results: {
a: { refId: 'a', series: [{ target: 'cpu', datapoints: [[1, 1]] }] },
b: { refId: 'b', series: [{ target: 'memory', datapoints: [[2, 2]] }] },
a: {
refId: 'a',
series: [{ target: 'cpu', datapoints: [[1, 2]], meta: { custom: { period: 60 } } }],
},
b: {
refId: 'b',
series: [{ target: 'cpu', datapoints: [[1, 2]], meta: { custom: { period: 120 } } }],
},
},
},
};
const { runner, timeRange, request, queryMock } = setupMockedMetricsQueryRunner({
// DataSourceWithBackend runs toDataQueryResponse({response from CW backend})
response: toDataQueryResponse(resultsFromBEQuery),
});
const observable = runner.performTimeSeriesQuery(
@@ -47,7 +57,7 @@ describe('CloudWatchMetricsQueryRunner', () => {
});
it('sets fields.config.interval based on period', async () => {
const { runner, timeRange, request, queryMock } = setupMockedMetricsQueryRunner({
const resultsFromBEQuery = {
data: {
results: {
a: {
@@ -60,6 +70,10 @@ describe('CloudWatchMetricsQueryRunner', () => {
},
},
},
};
const { runner, timeRange, request, queryMock } = setupMockedMetricsQueryRunner({
// DataSourceWithBackend runs toDataQueryResponse({response from CW backend})
response: toDataQueryResponse(resultsFromBEQuery),
});
const observable = runner.performTimeSeriesQuery(
@@ -98,31 +112,36 @@ describe('CloudWatchMetricsQueryRunner', () => {
},
];
const data: BackendDataSourceResponse = {
results: {
A: {
tables: [],
error: '',
refId: 'A',
series: [
{
target: 'CPUUtilization_Average',
datapoints: [
[1, 1483228800000],
[2, 1483229100000],
[5, 1483229700000],
],
tags: {
InstanceId: 'i-12345678',
const resultsFromBEQuery = {
data: {
results: {
A: {
tables: [],
error: '',
refId: 'A',
series: [
{
target: 'CPUUtilization_Average',
datapoints: [
[1, 1483228800000],
[2, 1483229100000],
[5, 1483229700000],
],
tags: {
InstanceId: 'i-12345678',
},
},
},
],
],
},
},
},
};
it('should generate the correct query', async () => {
const { runner, queryMock, request } = setupMockedMetricsQueryRunner({ data });
const { runner, queryMock, request } = setupMockedMetricsQueryRunner({
// DataSourceWithBackend runs toDataQueryResponse({response from CW backend})
response: toDataQueryResponse(resultsFromBEQuery),
});
await expect(runner.handleMetricQueries(queries, request, queryMock)).toEmitValuesWith(() => {
expect(queryMock.mock.calls[0][0].targets).toMatchObject(
@@ -159,7 +178,8 @@ describe('CloudWatchMetricsQueryRunner', () => {
];
const { runner, queryMock, request } = setupMockedMetricsQueryRunner({
data,
// DataSourceWithBackend runs toDataQueryResponse({response from CW backend})
response: toDataQueryResponse(resultsFromBEQuery),
variables: [periodIntervalVariable],
});
@@ -169,110 +189,111 @@ describe('CloudWatchMetricsQueryRunner', () => {
});
it('should return series list', async () => {
const { runner, request, queryMock } = setupMockedMetricsQueryRunner({ data });
const { runner, request, queryMock } = setupMockedMetricsQueryRunner({
// DataSourceWithBackend runs toDataQueryResponse({response from CW backend})
response: toDataQueryResponse(resultsFromBEQuery),
});
await expect(runner.handleMetricQueries(queries, request, queryMock)).toEmitValuesWith((received) => {
const result = received[0];
expect(getFrameDisplayName(result.data[0])).toBe(
data.results.A.series?.length && data.results.A.series[0].target
);
expect(result.data[0].fields[1].values[0]).toBe(
data.results.A.series?.length && data.results.A.series[0].datapoints[0][0]
);
expect(getFrameDisplayName(result.data[0])).toBe('CPUUtilization_Average');
expect(result.data[0].fields[1].values[0]).toBe(1);
});
});
});
describe('and throttling exception is thrown', () => {
const partialQuery: CloudWatchMetricsQuery = {
metricQueryType: MetricQueryType.Search,
metricEditorMode: MetricEditorMode.Builder,
queryMode: 'Metrics',
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
dimensions: {
InstanceId: 'i-12345678',
},
statistic: 'Average',
period: '300',
expression: '',
id: '',
region: '',
refId: '',
};
const queries: CloudWatchMetricsQuery[] = [
{ ...partialQuery, refId: 'A', region: 'us-east-1' },
{ ...partialQuery, refId: 'B', region: 'us-east-2' },
{ ...partialQuery, refId: 'C', region: 'us-east-1' },
{ ...partialQuery, refId: 'D', region: 'us-east-2' },
{ ...partialQuery, refId: 'E', region: 'eu-north-1' },
];
const dataWithThrottlingError = {
data: {
message: 'Throttling: exception',
results: {
A: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'A',
meta: {},
},
B: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'B',
meta: {},
},
C: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'C',
meta: {},
},
D: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'D',
meta: {},
},
E: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'E',
meta: {},
},
},
},
};
beforeEach(() => {
redux.setStore({
...redux.store,
dispatch: jest.fn(),
});
});
describe('and throttling exception is thrown', () => {
const partialQuery: CloudWatchMetricsQuery = {
metricQueryType: MetricQueryType.Search,
metricEditorMode: MetricEditorMode.Builder,
queryMode: 'Metrics',
namespace: 'AWS/EC2',
metricName: 'CPUUtilization',
dimensions: {
InstanceId: 'i-12345678',
},
statistic: 'Average',
period: '300',
expression: '',
id: '',
region: '',
refId: '',
};
const queries: CloudWatchMetricsQuery[] = [
{ ...partialQuery, refId: 'A', region: 'us-east-1' },
{ ...partialQuery, refId: 'B', region: 'us-east-2' },
{ ...partialQuery, refId: 'C', region: 'us-east-1' },
{ ...partialQuery, refId: 'D', region: 'us-east-2' },
{ ...partialQuery, refId: 'E', region: 'eu-north-1' },
];
const backendErrorResponse: DataQueryError<CloudWatchMetricsQuery> = {
data: {
message: 'Throttling: exception',
results: {
A: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'A',
meta: {},
},
B: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'B',
meta: {},
},
C: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'C',
meta: {},
},
D: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'D',
meta: {},
},
E: {
frames: [],
series: [],
tables: [],
error: 'Throttling: exception',
refId: 'E',
meta: {},
},
},
},
};
beforeEach(() => {
redux.setStore({
...redux.store,
dispatch: jest.fn(),
});
it('should display one alert error message per region+datasource combination', async () => {
const { runner, request, queryMock } = setupMockedMetricsQueryRunner({
response: toDataQueryResponse(dataWithThrottlingError),
});
const memoizedDebounceSpy = jest.spyOn(runner, 'debouncedThrottlingAlert');
it('should display one alert error message per region+datasource combination', async () => {
const { runner, request, queryMock } = setupMockedMetricsQueryRunner({ errorResponse: backendErrorResponse });
const memoizedDebounceSpy = jest.spyOn(runner, 'debouncedAlert');
await expect(runner.handleMetricQueries(queries, request, queryMock)).toEmitValuesWith(() => {
expect(memoizedDebounceSpy).toHaveBeenCalledWith('CloudWatch Test Datasource', 'us-east-1');
expect(memoizedDebounceSpy).toHaveBeenCalledWith('CloudWatch Test Datasource', 'us-east-2');
expect(memoizedDebounceSpy).toHaveBeenCalledWith('CloudWatch Test Datasource', 'eu-north-1');
expect(memoizedDebounceSpy).toBeCalledTimes(3);
});
await expect(runner.handleMetricQueries(queries, request, queryMock)).toEmitValuesWith((received) => {
expect(received[0].errors).toHaveLength(5);
expect(memoizedDebounceSpy).toHaveBeenCalledWith('CloudWatch Test Datasource', 'us-east-1');
expect(memoizedDebounceSpy).toHaveBeenCalledWith('CloudWatch Test Datasource', 'us-east-2');
expect(memoizedDebounceSpy).toHaveBeenCalledWith('CloudWatch Test Datasource', 'eu-north-1');
expect(memoizedDebounceSpy).toBeCalledTimes(3);
});
});
});
@@ -298,43 +319,80 @@ describe('CloudWatchMetricsQueryRunner', () => {
},
];
const data: BackendDataSourceResponse = {
results: {
A: {
tables: [],
error: '',
refId: 'A',
series: [
{
target: 'TargetResponseTime_p90.00',
datapoints: [
[1, 1483228800000],
[2, 1483229100000],
[5, 1483229700000],
],
tags: {
LoadBalancer: 'lb',
TargetGroup: 'tg',
const responseFromBEQuery = {
data: {
results: {
A: {
tables: [],
error: '',
refId: 'A',
series: [
{
target: 'TargetResponseTime_p90.00',
datapoints: [
[1, 1483228800000],
[2, 1483229100000],
[5, 1483229700000],
],
tags: {
LoadBalancer: 'lb',
TargetGroup: 'tg',
},
},
},
],
],
},
},
},
};
it('should return series list', async () => {
const { runner, request, queryMock } = setupMockedMetricsQueryRunner({ data });
const { runner, request, queryMock } = setupMockedMetricsQueryRunner({
// DataSourceWithBackend runs toDataQueryResponse({response from CW backend})
response: toDataQueryResponse(responseFromBEQuery),
});
await expect(runner.handleMetricQueries(queries, request, queryMock)).toEmitValuesWith((received) => {
const result = received[0];
expect(getFrameDisplayName(result.data[0])).toBe(
data.results.A.series?.length && data.results.A.series[0].target
responseFromBEQuery.data.results.A.series?.length && responseFromBEQuery.data.results.A.series[0].target
);
expect(result.data[0].fields[1].values[0]).toBe(
data.results.A.series?.length && data.results.A.series[0].datapoints[0][0]
responseFromBEQuery.data.results.A.series?.length &&
responseFromBEQuery.data.results.A.series[0].datapoints[0][0]
);
});
});
it('should pass the error list from DatasourceWithBackend srv', async () => {
const dataWithError = {
data: {
results: {
A: {
error:
"metric request error: \"ValidationError: Error in expression 'query': Invalid syntax\\n\\tstatus code: 400",
status: 500,
},
},
},
status: 500,
statusText: 'Internal Server Error',
};
const { runner, request, queryMock } = setupMockedMetricsQueryRunner({
// DataSourceWithBackend runs toDataQueryResponse({response from CW backend})
response: toDataQueryResponse(dataWithError),
});
await expect(runner.handleMetricQueries(queries, request, queryMock)).toEmitValuesWith((received) => {
const result = received[0];
expect(result.data).toEqual([]);
expect(result.errors).toEqual([
{
message:
"metric request error: \"ValidationError: Error in expression 'query': Invalid syntax\\n\\tstatus code: 400",
status: 500,
refId: 'A',
},
]);
});
});
});
describe('template variable interpolation', () => {
@@ -1,9 +1,10 @@
import { findLast, isEmpty } from 'lodash';
import { isEmpty } from 'lodash';
import React from 'react';
import { catchError, map, Observable, of, throwError } from 'rxjs';
import { catchError, map, Observable, of } from 'rxjs';
import {
DataFrame,
DataQueryError,
DataQueryRequest,
DataQueryResponse,
DataSourceInstanceSettings,
@@ -21,7 +22,7 @@ import { AppNotificationTimeout } from 'app/types';
import { ThrottlingErrorMessage } from '../components/Errors/ThrottlingErrorMessage';
import memoizedDebounce from '../memoizedDebounce';
import { migrateMetricQuery } from '../migrations/metricQueryMigrations';
import { CloudWatchJsonData, CloudWatchMetricsQuery, CloudWatchQuery, DataQueryError } from '../types';
import { CloudWatchJsonData, CloudWatchMetricsQuery, CloudWatchQuery } from '../types';
import { filterMetricsQuery } from '../utils/utils';
import { CloudWatchRequest } from './CloudWatchRequest';
@@ -37,9 +38,10 @@ const displayAlert = (datasourceName: string, region: string) =>
)
)
);
// This class handles execution of CloudWatch metrics query data queries
export class CloudWatchMetricsQueryRunner extends CloudWatchRequest {
debouncedAlert: (datasourceName: string, region: string) => void = memoizedDebounce(
debouncedThrottlingAlert: (datasourceName: string, region: string) => void = memoizedDebounce(
displayAlert,
AppNotificationTimeout.Error
);
@@ -109,12 +111,7 @@ export class CloudWatchMetricsQueryRunner extends CloudWatchRequest {
): Observable<DataQueryResponse> {
return queryFn(request).pipe(
map((res) => {
const dataframes: DataFrame[] = res.data;
if (!dataframes || dataframes.length <= 0) {
return { data: [] };
}
const lastError = findLast(res.data, (v) => !!v.error);
const dataframes: DataFrame[] = res.data || [];
dataframes.forEach((frame) => {
frame.fields.forEach((field) => {
@@ -125,47 +122,48 @@ export class CloudWatchMetricsQueryRunner extends CloudWatchRequest {
});
});
if (res.errors?.length) {
this.alertOnErrors(res.errors, request);
}
return {
data: dataframes,
error: lastError ? { message: lastError.error } : undefined,
// DataSourceWithBackend will not throw an error, instead it will return "errors" field along with the response
errors: res.errors,
};
}),
catchError((err: DataQueryError<CloudWatchMetricsQuery>) => {
const isFrameError = err.data?.results;
// Error is not frame specific
if (!isFrameError && err.data && err.data.message === 'Metric request error' && err.data.error) {
err.message = err.data.error;
return throwError(() => err);
catchError((err: unknown) => {
if (Array.isArray(err)) {
return of({ data: [], errors: err });
} else {
return of({ data: [], errors: [{ message: err }] });
}
// The error is either for a specific frame or for all the frames
const results: Array<{ error?: string }> = Object.values(err.data?.results ?? {});
const firstErrorResult = results.find((r) => r.error);
if (firstErrorResult) {
err.message = firstErrorResult.error;
}
if (results.some((r) => r.error && /^Throttling:.*/.test(r.error))) {
const failedRedIds = Object.keys(err.data?.results ?? {});
const regionsAffected = Object.values(request.targets).reduce(
(res: string[], { refId, region }) =>
(refId && !failedRedIds.includes(refId)) || res.includes(region) ? res : [...res, region],
[]
);
regionsAffected.forEach((region) => {
const actualRegion = this.getActualRegion(region);
if (actualRegion) {
this.debouncedAlert(this.instanceSettings.name, actualRegion);
}
});
}
return throwError(() => err);
})
);
}
alertOnErrors(errors: DataQueryError[], request: DataQueryRequest<CloudWatchQuery>) {
const hasThrottlingError = errors.some(
(err) => err.message && (/^Throttling:.*/.test(err.message) || /^Rate exceeded.*/.test(err.message))
);
if (hasThrottlingError) {
const failedRefIds = errors.map((error) => error.refId).filter((refId) => refId);
if (failedRefIds.length > 0) {
const regionsAffected = Object.values(request.targets).reduce(
(res: string[], { refId, region }) =>
(refId && !failedRefIds.includes(refId)) || res.includes(region) ? res : [...res, region],
[]
);
regionsAffected.forEach((region) => {
const actualRegion = this.getActualRegion(region);
if (actualRegion) {
this.debouncedThrottlingAlert(this.instanceSettings.name, actualRegion);
}
});
}
}
}
filterMetricQuery(query: CloudWatchMetricsQuery): boolean {
return filterMetricsQuery(query);
}
@@ -5,23 +5,15 @@ import React, { useCallback, useMemo } from 'react';
import { useAsync } from 'react-use';
import { DataQuery, GrafanaTheme2, SelectableValue, DataTopic, QueryEditorProps } from '@grafana/data';
import {
Card,
Field,
Select,
useStyles2,
VerticalGroup,
HorizontalGroup,
Spinner,
Switch,
RadioButtonGroup,
} from '@grafana/ui';
import { Field, Select, useStyles2, Spinner, RadioButtonGroup, Stack, InlineSwitch } from '@grafana/ui';
import config from 'app/core/config';
import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv';
import { PanelModel } from 'app/features/dashboard/state';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { filterPanelDataToQuery } from 'app/features/query/components/QueryEditorRow';
import { OperationsEditorRow } from '../prometheus/querybuilder/shared/OperationsEditorRow';
import { DashboardDatasource } from './datasource';
import { DashboardQuery, ResultInfo, SHARED_DASHBOARD_QUERY } from './types';
@@ -150,64 +142,61 @@ export function DashboardQueryEditor({ data, query, onChange, onRunQuery }: Prop
const selected = panels.find((panel) => panel.value === query.panelId);
return (
<>
<Field label="Source" description="Use the same results as panel">
<Select
inputId={selectId}
placeholder="Choose panel"
isSearchable={true}
options={panels}
value={selected}
onChange={(item) => onPanelChanged(item.value!)}
/>
</Field>
<HorizontalGroup height="auto" wrap={true} align="flex-start">
<Field
label="Data Source"
description="Use data or annotations from the panel"
className={styles.horizontalField}
>
<RadioButtonGroup options={topics} value={query.topic === DataTopic.Annotations} onChange={onTopicChanged} />
</Field>
{showTransforms && (
<Field label="Transform" description="Apply panel transformations from the source panel">
<Switch value={Boolean(query.withTransforms)} onChange={onTransformToggle} />
<OperationsEditorRow>
<Stack direction="column">
<Stack gap={3}>
<Field label="Source panel" description="Use query results from another panel">
<Select
inputId={selectId}
placeholder="Choose panel"
isSearchable={true}
options={panels}
value={selected}
onChange={(item) => onPanelChanged(item.value!)}
/>
</Field>
)}
</HorizontalGroup>
{loadingResults ? (
<Spinner />
) : (
<>
{results && Boolean(results.length) && (
<Field label="Queries from panel">
<VerticalGroup spacing="sm">
{results.map((target, i) => (
<Card key={`DashboardQueryRow-${i}`}>
<Card.Heading>{target.refId}</Card.Heading>
<Card.Figure>
<img src={target.img} alt={target.name} title={target.name} width={40} />
</Card.Figure>
<Card.Meta>{target.query}</Card.Meta>
</Card>
))}
</VerticalGroup>
<Field label="Data" description="Use data or annotations from the panel">
<RadioButtonGroup
options={topics}
value={query.topic === DataTopic.Annotations}
onChange={onTopicChanged}
/>
</Field>
{showTransforms && (
<Field label="Transform" description="Apply transformations from the source panel">
<InlineSwitch value={Boolean(query.withTransforms)} onChange={onTransformToggle} />
</Field>
)}
</>
)}
</>
</Stack>
{loadingResults ? (
<Spinner />
) : (
<>
{results && Boolean(results.length) && (
<Field label="Queries from panel">
<Stack direction="column">
{results.map((target, i) => (
<Stack key={i} alignItems="center" gap={1}>
<div>{target.refId}</div>
<img src={target.img} alt={target.name} title={target.name} width={16} />
<div>{target.query}</div>
</Stack>
))}
</Stack>
</Field>
)}
</>
)}
</Stack>
</OperationsEditorRow>
);
}
function getStyles(theme: GrafanaTheme2) {
return {
horizontalField: css({
marginRight: theme.spacing(2),
}),
noQueriesText: css({
padding: theme.spacing(1.25),
}),
+78 -102
View File
@@ -3110,7 +3110,7 @@ __metadata:
marked: "npm:5.1.1"
marked-mangle: "npm:1.1.6"
moment: "npm:2.29.4"
moment-timezone: "npm:0.5.43"
moment-timezone: "npm:0.5.44"
ol: "npm:7.4.0"
papaparse: "npm:5.4.1"
react: "npm:18.2.0"
@@ -9024,12 +9024,12 @@ __metadata:
languageName: node
linkType: hard
"@types/react-grid-layout@npm:1.3.2":
version: 1.3.2
resolution: "@types/react-grid-layout@npm:1.3.2"
"@types/react-grid-layout@npm:1.3.5":
version: 1.3.5
resolution: "@types/react-grid-layout@npm:1.3.5"
dependencies:
"@types/react": "npm:*"
checksum: a21fe7f790d8bd2ae385829e5d5c369788c2facfc0a9f888dc1f07b4982398e02d1ffc8fca902cd77495094a598e0951e5c907edeb773abd1bf8a6db293d54a1
checksum: 21599054dfa977ed8445b1ab3198a842531cb36bd620564c3f8a469688cbea051149eb644a99b2f8f6d02f8d9909a6145668f385781b5647601e9663de216946
languageName: node
linkType: hard
@@ -12832,15 +12832,20 @@ __metadata:
languageName: node
linkType: hard
"cosmiconfig@npm:^8.2.0":
version: 8.2.0
resolution: "cosmiconfig@npm:8.2.0"
"cosmiconfig@npm:^8.2.0, cosmiconfig@npm:^8.3.5":
version: 8.3.6
resolution: "cosmiconfig@npm:8.3.6"
dependencies:
import-fresh: "npm:^3.2.1"
import-fresh: "npm:^3.3.0"
js-yaml: "npm:^4.1.0"
parse-json: "npm:^5.0.0"
parse-json: "npm:^5.2.0"
path-type: "npm:^4.0.0"
checksum: e0b188f9a672ee7135851bf9d9fc8f0ba00f9769c95fda5af0ebc274804f6aeb713b753e04e706f595e1fbd0fa67c5073840666019068c0296a06057560ab39d
peerDependencies:
typescript: ">=4.9.5"
peerDependenciesMeta:
typescript:
optional: true
checksum: 91d082baca0f33b1c085bf010f9ded4af43cbedacba8821da0fb5667184d0a848addc52c31fadd080007f904a555319c238cf5f4c03e6d58ece2e4876b2e73d6
languageName: node
linkType: hard
@@ -17108,7 +17113,7 @@ __metadata:
"@types/react": "npm:18.2.15"
"@types/react-beautiful-dnd": "npm:13.1.8"
"@types/react-dom": "npm:18.2.7"
"@types/react-grid-layout": "npm:1.3.2"
"@types/react-grid-layout": "npm:1.3.5"
"@types/react-highlight-words": "npm:0.16.7"
"@types/react-resizable": "npm:3.0.7"
"@types/react-router-dom": "npm:5.3.3"
@@ -17232,11 +17237,11 @@ __metadata:
marked: "npm:5.1.1"
marked-mangle: "npm:1.1.6"
memoize-one: "npm:6.0.0"
mini-css-extract-plugin: "npm:2.7.6"
mini-css-extract-plugin: "npm:2.7.7"
ml-regression-polynomial: "npm:^3.0.0"
ml-regression-simple-linear: "npm:^3.0.0"
moment: "npm:2.29.4"
moment-timezone: "npm:0.5.43"
moment-timezone: "npm:0.5.44"
monaco-editor: "npm:0.34.0"
monaco-promql: "npm:1.7.4"
mousetrap: "npm:1.6.5"
@@ -17249,13 +17254,13 @@ __metadata:
node-forge: "npm:^1.3.1"
node-notifier: "npm:10.0.1"
ol: "npm:7.4.0"
ol-ext: "npm:4.0.10"
ol-ext: "npm:4.0.13"
papaparse: "npm:5.4.1"
pluralize: "npm:^8.0.0"
postcss: "npm:8.4.31"
postcss-loader: "npm:7.3.3"
postcss: "npm:8.4.33"
postcss-loader: "npm:7.3.4"
postcss-reporter: "npm:7.0.5"
postcss-scss: "npm:4.0.6"
postcss-scss: "npm:4.0.9"
prettier: "npm:3.0.0"
prismjs: "npm:1.29.0"
prop-types: "npm:15.8.1"
@@ -17264,15 +17269,15 @@ __metadata:
rc-drawer: "npm:6.5.2"
rc-slider: "npm:10.3.1"
rc-time-picker: "npm:3.7.3"
rc-tree: "npm:5.8.0"
re-resizable: "npm:6.9.9"
rc-tree: "npm:5.8.2"
re-resizable: "npm:6.9.11"
react: "npm:18.2.0"
react-beautiful-dnd: "npm:13.1.1"
react-diff-viewer: "npm:^3.1.1"
react-dom: "npm:18.2.0"
react-draggable: "npm:4.4.5"
react-draggable: "npm:4.4.6"
react-dropzone: "npm:^14.2.3"
react-grid-layout: "npm:1.4.2"
react-grid-layout: "npm:1.4.4"
react-highlight-words: "npm:0.20.0"
react-hook-form: "npm:^7.49.2"
react-i18next: "npm:^12.0.0"
@@ -18177,7 +18182,7 @@ __metadata:
languageName: node
linkType: hard
"import-fresh@npm:^3.1.0, import-fresh@npm:^3.2.1":
"import-fresh@npm:^3.1.0, import-fresh@npm:^3.2.1, import-fresh@npm:^3.3.0":
version: 3.3.0
resolution: "import-fresh@npm:3.3.0"
dependencies:
@@ -19786,12 +19791,12 @@ __metadata:
languageName: node
linkType: hard
"jiti@npm:^1.18.2":
version: 1.19.1
resolution: "jiti@npm:1.19.1"
"jiti@npm:^1.20.0":
version: 1.21.0
resolution: "jiti@npm:1.21.0"
bin:
jiti: bin/jiti.js
checksum: 88f11b406a877db348b956eb9cecf42d6cb6edcd6317aa75c032130fc88c1150baf8def5511e808af10b11336db2639a295ffe1ed455d65216dcef45a0a424b5
checksum: 005a0239e50381b5c9919f59dbab86128367bd64872f3376dbbde54b6523f41bd134bf22909e2a509e38fd87e1c22125ca255b9b6b53e7df0fedd23f737334cc
languageName: node
linkType: hard
@@ -21269,14 +21274,14 @@ __metadata:
languageName: node
linkType: hard
"mini-css-extract-plugin@npm:2.7.6":
version: 2.7.6
resolution: "mini-css-extract-plugin@npm:2.7.6"
"mini-css-extract-plugin@npm:2.7.7":
version: 2.7.7
resolution: "mini-css-extract-plugin@npm:2.7.7"
dependencies:
schema-utils: "npm:^4.0.0"
peerDependencies:
webpack: ^5.0.0
checksum: 1f718bfdcb7c2bf5e4336f694e5576432149d63f9dacaf94eae38ad046534050471a712a2d1bedf95e1722a2d3b56c3361d7352849e802e4875e716885e952c3
checksum: 55e9cb89a648e9d493dd66eb54cbf02783c6623d0e332ddb4c2374891fcd8de051d5f97473e40478eb2e091ea32fc9ac84c3cd583b445df9f444cc6b7483e1b1
languageName: node
linkType: hard
@@ -21633,12 +21638,12 @@ __metadata:
languageName: node
linkType: hard
"moment-timezone@npm:0.5.43":
version: 0.5.43
resolution: "moment-timezone@npm:0.5.43"
"moment-timezone@npm:0.5.44":
version: 0.5.44
resolution: "moment-timezone@npm:0.5.44"
dependencies:
moment: "npm:^2.29.4"
checksum: f8b66f8562960d6c2ec90ea7e2ca8c10bd5f5cf5ced2eaaac83deb1011b145d0154e8d77018cf5e913d489898a343122a3d815768809653ab039306dce1db1eb
checksum: 72f0a425c52a858cca76d5ab35dfb02d68ac7292b60e4b34fb432c1fae07e32ad7f67de7ff5d2d2c868eee8052c9981fc3244f5fc9a2f454fc4fd15db2ec45a7
languageName: node
linkType: hard
@@ -21850,12 +21855,12 @@ __metadata:
languageName: node
linkType: hard
"nanoid@npm:^3.3.6":
version: 3.3.6
resolution: "nanoid@npm:3.3.6"
"nanoid@npm:^3.3.7":
version: 3.3.7
resolution: "nanoid@npm:3.3.7"
bin:
nanoid: bin/nanoid.cjs
checksum: 67235c39d1bc05851383dadde5cf77ae1c90c2a1d189e845c7f20f646f0488d875ad5f5226bbba072a88cebbb085a3f784a6673117daf785bdf614a852550362
checksum: ac1eb60f615b272bccb0e2b9cd933720dad30bf9708424f691b8113826bb91aca7e9d14ef5d9415a6ba15c266b37817256f58d8ce980c82b0ba3185352565679
languageName: node
linkType: hard
@@ -22510,12 +22515,12 @@ __metadata:
languageName: node
linkType: hard
"ol-ext@npm:4.0.10":
version: 4.0.10
resolution: "ol-ext@npm:4.0.10"
"ol-ext@npm:4.0.13":
version: 4.0.13
resolution: "ol-ext@npm:4.0.13"
peerDependencies:
ol: ">= 5.3.0"
checksum: 11a799c71ef53a75a7aea6bfbe43384ae4ffe1b9d5f4dac950213da165f72e33555a10f2929a6197bf49eeb25d4721a67e4798fdcbf5da519d7ad9bedae770ee
checksum: 656eea9d354296406a48590a69246433a6ae0233f2b28d77f0a6f954a53bf4b54d42a54ff50a0395b382044e7ef912f58687e0538e79a9ff0cfe2f22adf0f718
languageName: node
linkType: hard
@@ -23365,17 +23370,17 @@ __metadata:
languageName: node
linkType: hard
"postcss-loader@npm:7.3.3":
version: 7.3.3
resolution: "postcss-loader@npm:7.3.3"
"postcss-loader@npm:7.3.4":
version: 7.3.4
resolution: "postcss-loader@npm:7.3.4"
dependencies:
cosmiconfig: "npm:^8.2.0"
jiti: "npm:^1.18.2"
semver: "npm:^7.3.8"
cosmiconfig: "npm:^8.3.5"
jiti: "npm:^1.20.0"
semver: "npm:^7.5.4"
peerDependencies:
postcss: ^7.0.0 || ^8.0.1
webpack: ^5.0.0
checksum: 743a4286db68169d271bef31e6e9351874bcf2dfa408b82c648c2d5bfba9c862cbfe3004494d927469654d6ac8b82fe647f2b80a186c1dbd44d81632eec1e838
checksum: 234b01149a966a6190290c6d265b8e3df10f43262dd679451c1e7370bae74e27b746b02e660d204b901e3cf1ad28759c2679a93c64a3eb499169d8dec39df1c1
languageName: node
linkType: hard
@@ -23665,12 +23670,12 @@ __metadata:
languageName: node
linkType: hard
"postcss-scss@npm:4.0.6, postcss-scss@npm:^4.0.6":
version: 4.0.6
resolution: "postcss-scss@npm:4.0.6"
"postcss-scss@npm:4.0.9, postcss-scss@npm:^4.0.6":
version: 4.0.9
resolution: "postcss-scss@npm:4.0.9"
peerDependencies:
postcss: ^8.4.19
checksum: fa436a2c92555135f07135586798089b4d8b31ad9d7f3352563e5c9b722e8ff64c32f95071541f87bc78b63dc6dd1475a68c4683b05e5618aaf55adfc3b007d7
postcss: ^8.4.29
checksum: d191c771344357a28995a2f53041ec699070331b8238e076001cedde97215bd3ebf596d0a9882b22c566977c4b72816dafc00028dc09153c6f97f71cd28a70f7
languageName: node
linkType: hard
@@ -23714,14 +23719,14 @@ __metadata:
languageName: node
linkType: hard
"postcss@npm:8.4.31, postcss@npm:^8.4.21, postcss@npm:^8.4.24, postcss@npm:^8.4.28":
version: 8.4.31
resolution: "postcss@npm:8.4.31"
"postcss@npm:8.4.33, postcss@npm:^8.4.21, postcss@npm:^8.4.24, postcss@npm:^8.4.28":
version: 8.4.33
resolution: "postcss@npm:8.4.33"
dependencies:
nanoid: "npm:^3.3.6"
nanoid: "npm:^3.3.7"
picocolors: "npm:^1.0.0"
source-map-js: "npm:^1.0.2"
checksum: 1a6653e72105907377f9d4f2cd341d8d90e3fde823a5ddea1e2237aaa56933ea07853f0f2758c28892a1d70c53bbaca200eb8b80f8ed55f13093003dbec5afa0
checksum: e22a4594c255f26117f38419fb494d7ecab0f596cd409f7aadc8a6173abf180ed7ea970cd13fd366ab12b5840be901d2a09b25197700c2ebcb5a8077326bf519
languageName: node
linkType: hard
@@ -24413,23 +24418,7 @@ __metadata:
languageName: node
linkType: hard
"rc-tree@npm:5.8.0":
version: 5.8.0
resolution: "rc-tree@npm:5.8.0"
dependencies:
"@babel/runtime": "npm:^7.10.1"
classnames: "npm:2.x"
rc-motion: "npm:^2.0.1"
rc-util: "npm:^5.16.1"
rc-virtual-list: "npm:^3.5.1"
peerDependencies:
react: "*"
react-dom: "*"
checksum: 130cc89f582aedbe252590f8078bd39535ed2de43fa7bf3be88695065d15405768340fb26dda12d90601296afd439d876ce859d4604659a6adde4f783ff7f26a
languageName: node
linkType: hard
"rc-tree@npm:~5.8.1":
"rc-tree@npm:5.8.2, rc-tree@npm:~5.8.1":
version: 5.8.2
resolution: "rc-tree@npm:5.8.2"
dependencies:
@@ -24501,13 +24490,13 @@ __metadata:
languageName: node
linkType: hard
"re-resizable@npm:6.9.9":
version: 6.9.9
resolution: "re-resizable@npm:6.9.9"
"re-resizable@npm:6.9.11":
version: 6.9.11
resolution: "re-resizable@npm:6.9.11"
peerDependencies:
react: ^16.13.1 || ^17.0.0 || ^18.0.0
react-dom: ^16.13.1 || ^17.0.0 || ^18.0.0
checksum: c2e22bfeca9765b75d33b0e9135b0034684c6fa6b11927d81e464f70b7c5cf20176fd52d128ab3ee342ca0271357efdca2c4856a80e174b58101349bba35c11c
checksum: 3f2590374d03a6b5f7ebc26f273bdb461ceecb1322adf3b7925c695b1c6fa0841225feb79cf63d19c3c2451a9a8ed4f4a82fa119f3a22565ce78e0619d2b624c
languageName: node
linkType: hard
@@ -24675,20 +24664,7 @@ __metadata:
languageName: node
linkType: hard
"react-draggable@npm:4.4.5":
version: 4.4.5
resolution: "react-draggable@npm:4.4.5"
dependencies:
clsx: "npm:^1.1.1"
prop-types: "npm:^15.8.1"
peerDependencies:
react: ">= 16.3.0"
react-dom: ">= 16.3.0"
checksum: a6af765a71a62636a9c12868e9015e86e0160fbd9218a895200813f502e9ff848b8aab16ff7177faa808dd5a498b8a03c355edd2be0b823ec1035e91edf2103d
languageName: node
linkType: hard
"react-draggable@npm:^4.0.0, react-draggable@npm:^4.0.3, react-draggable@npm:^4.4.5":
"react-draggable@npm:4.4.6, react-draggable@npm:^4.0.0, react-draggable@npm:^4.0.3, react-draggable@npm:^4.4.5":
version: 4.4.6
resolution: "react-draggable@npm:4.4.6"
dependencies:
@@ -24780,9 +24756,9 @@ __metadata:
languageName: node
linkType: hard
"react-grid-layout@npm:1.4.2":
version: 1.4.2
resolution: "react-grid-layout@npm:1.4.2"
"react-grid-layout@npm:1.4.4":
version: 1.4.4
resolution: "react-grid-layout@npm:1.4.4"
dependencies:
clsx: "npm:^2.0.0"
fast-equals: "npm:^4.0.3"
@@ -24793,7 +24769,7 @@ __metadata:
peerDependencies:
react: ">= 16.3.0"
react-dom: ">= 16.3.0"
checksum: 651819b1b5af8d57cd7ee702fa367f43e00c77be6403b5ab423179207be340a01d8bf9cb61e76fefafec40a8aacbcb2f47efb902eef77eedb6ce56f3716a3408
checksum: 43c9bc7e8ce5a902720ebe559b1551724615c617391b040a0a275597748e1927b293c5ddcb73053b1925909e1c293948b4969a35799d541bf9d8886e07ac0085
languageName: node
linkType: hard
@@ -24811,11 +24787,11 @@ __metadata:
linkType: hard
"react-hook-form@npm:^7.49.2":
version: 7.49.2
resolution: "react-hook-form@npm:7.49.2"
version: 7.49.3
resolution: "react-hook-form@npm:7.49.3"
peerDependencies:
react: ^16.8.0 || ^17 || ^18
checksum: 7895d65b8458c42d46eb338803bb0fd1aab42fc69ecf80b47846eace9493a10cac5b05c9b744a5f9f1f7969a3e2703fc2118cdab97e49a7798a72d09f106383f
checksum: ccd35d73f26b56c2933312c5d445552aab8c20a02ab07360808f039c9daf37a046a0afa2c2c56fac0a16b1963ae3e4ebce37136d14388002d00d6c5483e3fdb7
languageName: node
linkType: hard