From 5f6126693117ef3abbb1bc5b5d50e5739aa95280 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Wed, 9 Oct 2024 14:38:56 +0200 Subject: [PATCH 001/110] AutoSizeInput: Forward onChange event (#94459) Forward onChange event --- .../src/components/Input/AutoSizeInput.test.tsx | 10 ++++++++++ .../grafana-ui/src/components/Input/AutoSizeInput.tsx | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/packages/grafana-ui/src/components/Input/AutoSizeInput.test.tsx b/packages/grafana-ui/src/components/Input/AutoSizeInput.test.tsx index b47fc7da3d9..b2756e7b2d9 100644 --- a/packages/grafana-ui/src/components/Input/AutoSizeInput.test.tsx +++ b/packages/grafana-ui/src/components/Input/AutoSizeInput.test.tsx @@ -15,6 +15,16 @@ jest.mock('../../utils/measureText', () => { }); describe('AutoSizeInput', () => { + it('should support default Input API', () => { + const onChange = jest.fn(); + render(); + + const input: HTMLInputElement = screen.getByTestId('autosize-input'); + fireEvent.change(input, { target: { value: 'foo' } }); + + expect(onChange).toHaveBeenCalled(); + }); + it('should have default minWidth when empty', () => { render(); diff --git a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx index de855ff5302..d1e61001de3 100644 --- a/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx +++ b/packages/grafana-ui/src/components/Input/AutoSizeInput.tsx @@ -20,6 +20,7 @@ export const AutoSizeInput = React.forwardRef((props, r minWidth = 10, maxWidth, onCommitChange, + onChange, onKeyDown, onBlur, value: controlledValue, @@ -48,6 +49,9 @@ export const AutoSizeInput = React.forwardRef((props, r ref={ref} value={value.toString()} onChange={(event) => { + if (onChange) { + onChange(event); + } setValue(event.currentTarget.value); }} width={inputWidth} From 5f26fd87c754d70c788d406b667c2bc1f639d1d2 Mon Sep 17 00:00:00 2001 From: Haris Rozajac <58232930+harisrozajac@users.noreply.github.com> Date: Wed, 9 Oct 2024 06:54:11 -0600 Subject: [PATCH 002/110] Query Library: Notifications and query counter (#94444) * Notifications about the feature * i18n * Fix test --- public/app/features/explore/ExplorePage.tsx | 43 +++++++++++++------ .../explore/QueryLibrary/QueryLibrary.tsx | 25 ++++++++++- .../QueryLibrary/QueryLibraryExpmInfo.tsx | 23 ++++++++++ .../QueryLibrary/QueryTemplateForm.tsx | 6 +-- .../QueryLibrary/QueryTemplatesList.tsx | 14 +++++- .../explore/RichHistory/RichHistory.tsx | 5 ++- .../RichHistory/RichHistoryAddToLibrary.tsx | 4 +- .../explore/spec/queryLibrary.test.tsx | 2 +- .../app/features/query-library/api/factory.ts | 4 +- public/locales/en-US/grafana.json | 4 +- public/locales/pseudo-LOCALE/grafana.json | 4 +- 11 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx diff --git a/public/app/features/explore/ExplorePage.tsx b/public/app/features/explore/ExplorePage.tsx index 1ab96c05414..f5f4c9b6ccc 100644 --- a/public/app/features/explore/ExplorePage.tsx +++ b/public/app/features/explore/ExplorePage.tsx @@ -1,10 +1,11 @@ import { css, cx } from '@emotion/css'; import { useEffect, useState } from 'react'; +import { useLocalStorage } from 'react-use'; import { CoreApp, GrafanaTheme2 } from '@grafana/data'; import { config } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema/dist/esm/index'; -import { ErrorBoundaryAlert, Modal, useStyles2, useTheme2 } from '@grafana/ui'; +import { Badge, ErrorBoundaryAlert, Modal, useStyles2, useTheme2 } from '@grafana/ui'; import { QueryOperationAction } from 'app/core/components/QueryOperationRow/QueryOperationAction'; import { SplitPaneWrapper } from 'app/core/components/SplitPaneWrapper/SplitPaneWrapper'; import { useGrafana } from 'app/core/context/GrafanaContext'; @@ -21,6 +22,7 @@ import { ExploreActions } from './ExploreActions'; import { ExploreDrawer } from './ExploreDrawer'; import { ExplorePaneContainer } from './ExplorePaneContainer'; import { QueriesDrawerContextProvider, useQueriesDrawerContext } from './QueriesDrawer/QueriesDrawerContext'; +import { QUERY_LIBRARY_LOCAL_STORAGE_KEYS } from './QueryLibrary/QueryLibrary'; import { queryLibraryTrackAddFromQueryRow } from './QueryLibrary/QueryLibraryAnalyticsEvents'; import { QueryTemplateForm } from './QueryLibrary/QueryTemplateForm'; import RichHistoryContainer from './RichHistory/RichHistoryContainer'; @@ -63,6 +65,10 @@ function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryPa const { drawerOpened, setDrawerOpened, queryLibraryAvailable } = useQueriesDrawerContext(); const showCorrelationEditorBar = config.featureToggles.correlations && (correlationDetails?.editorMode || false); const [queryToAdd, setQueryToAdd] = useState(); + const [showQueryLibraryBadgeButton, setShowQueryLibraryBadgeButton] = useLocalStorage( + QUERY_LIBRARY_LOCAL_STORAGE_KEYS.explore.newButton, + true + ); useEffect(() => { //This is needed for breadcrumbs and topnav. @@ -77,19 +83,32 @@ function ExplorePageContent(props: GrafanaRouteComponentProps<{}, ExploreQueryPa if (hasQueryLibrary) { RowActionComponents.addKeyedExtraRenderAction(QUERY_LIBRARY_ACTION_KEY, { scope: CoreApp.Explore, - queryActionComponent: (props) => ( - { - setQueryToAdd(props.query); - }} - /> - ), + queryActionComponent: (props) => + showQueryLibraryBadgeButton ? ( + { + setQueryToAdd(props.query); + setShowQueryLibraryBadgeButton(false); + }} + style={{ cursor: 'pointer' }} + /> + ) : ( + { + setQueryToAdd(props.query); + }} + /> + ), }); } - }, []); + }, [showQueryLibraryBadgeButton, setShowQueryLibraryBadgeButton]); useKeyboardShortcuts(); diff --git a/public/app/features/explore/QueryLibrary/QueryLibrary.tsx b/public/app/features/explore/QueryLibrary/QueryLibrary.tsx index 9a296b41fbb..fffc454ee3a 100644 --- a/public/app/features/explore/QueryLibrary/QueryLibrary.tsx +++ b/public/app/features/explore/QueryLibrary/QueryLibrary.tsx @@ -1,3 +1,6 @@ +import { useLocalStorage } from 'react-use'; + +import { QueryLibraryExpmInfo } from './QueryLibraryExpmInfo'; import { QueryTemplatesList } from './QueryTemplatesList'; export interface QueryLibraryProps { @@ -6,6 +9,26 @@ export interface QueryLibraryProps { activeDatasources?: string[]; } +export const QUERY_LIBRARY_LOCAL_STORAGE_KEYS = { + explore: { + notifyUserAboutQueryLibrary: 'grafana.explore.query-library.notifyUserAboutQueryLibrary', + newButton: 'grafana.explore.query-library.newButton', + }, +}; + export function QueryLibrary({ activeDatasources }: QueryLibraryProps) { - return ; + const [notifyUserAboutQueryLibrary, setNotifyUserAboutQueryLibrary] = useLocalStorage( + QUERY_LIBRARY_LOCAL_STORAGE_KEYS.explore.notifyUserAboutQueryLibrary, + true + ); + + return ( + <> + setNotifyUserAboutQueryLibrary(false)} + /> + + + ); } diff --git a/public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx b/public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx new file mode 100644 index 00000000000..7c88af4a555 --- /dev/null +++ b/public/app/features/explore/QueryLibrary/QueryLibraryExpmInfo.tsx @@ -0,0 +1,23 @@ +import { Alert, Modal } from '@grafana/ui'; + +interface Props { + isOpen: boolean; + onDismiss: () => void; +} + +export function QueryLibraryExpmInfo({ isOpen, onDismiss }: Props) { + return ( + + + + + + ); +} diff --git a/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx index ed654fa378d..eefab87c7d1 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplateForm.tsx @@ -66,9 +66,7 @@ export const QueryTemplateForm = ({ onCancel, onSave, queryToAdd, templateData } .then(() => { getAppEvents().publish({ type: AppEvents.alertSuccess.name, - payload: [ - t('explore.query-library.query-template-added', 'Query template successfully added to the library'), - ], + payload: [t('explore.query-library.query-template-added', 'Query successfully saved to the library')], }); return true; }) @@ -76,7 +74,7 @@ export const QueryTemplateForm = ({ onCancel, onSave, queryToAdd, templateData } getAppEvents().publish({ type: AppEvents.alertError.name, payload: [ - t('explore.query-library.query-template-add-error', 'Error attempting to add this query to the library'), + t('explore.query-library.query-template-add-error', 'Error attempting to save this query to the library'), ], }); return false; diff --git a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx index ead35b647c6..48fd253e0f1 100644 --- a/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx +++ b/public/app/features/explore/QueryLibrary/QueryTemplatesList.tsx @@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from 'react'; import { AppEvents, GrafanaTheme2, SelectableValue } from '@grafana/data'; import { getAppEvents, getDataSourceSrv } from '@grafana/runtime'; -import { EmptyState, FilterInput, InlineLabel, MultiSelect, Spinner, useStyles2, Stack } from '@grafana/ui'; +import { EmptyState, FilterInput, InlineLabel, MultiSelect, Spinner, useStyles2, Stack, Badge } from '@grafana/ui'; import { t, Trans } from 'app/core/internationalization'; import { createQueryText } from 'app/core/utils/richHistory'; import { useAllQueryTemplatesQuery } from 'app/features/query-library'; @@ -15,6 +15,7 @@ import { getDatasourceSrv } from '../../plugins/datasource_srv'; import { QueryLibraryProps } from './QueryLibrary'; import { queryLibraryTrackFilterDatasource } from './QueryLibraryAnalyticsEvents'; +import { QueryLibraryExpmInfo } from './QueryLibraryExpmInfo'; import QueryTemplatesTable from './QueryTemplatesTable'; import { QueryTemplateRow } from './QueryTemplatesTable/types'; import { searchQueryLibrary } from './utils/search'; @@ -23,6 +24,7 @@ interface QueryTemplatesListProps extends QueryLibraryProps {} export function QueryTemplatesList(props: QueryTemplatesListProps) { const { data, isLoading, error } = useAllQueryTemplatesQuery(); + const [isModalOpen, setIsModalOpen] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [datasourceFilters, setDatasourceFilters] = useState>>( props.activeDatasources?.map((ds) => ({ value: ds, label: ds })) || [] @@ -163,6 +165,7 @@ export function QueryTemplatesList(props: QueryTemplatesListProps) { return ( <> + setIsModalOpen(false)} /> + setIsModalOpen(true)} + /> diff --git a/public/app/features/explore/RichHistory/RichHistory.tsx b/public/app/features/explore/RichHistory/RichHistory.tsx index cb00212d199..7ddb1d05c90 100644 --- a/public/app/features/explore/RichHistory/RichHistory.tsx +++ b/public/app/features/explore/RichHistory/RichHistory.tsx @@ -11,6 +11,7 @@ import { RichHistorySettings, createDatasourcesList, } from 'app/core/utils/richHistory'; +import { QUERY_LIBRARY_GET_LIMIT, queryLibraryApi } from 'app/features/query-library/api/factory'; import { useSelector } from 'app/types'; import { RichHistoryQuery } from 'app/types/explore'; @@ -96,8 +97,10 @@ export function RichHistory(props: RichHistoryProps) { .map((eDs) => listOfDatasources.find((ds) => ds.uid === eDs.datasource?.uid)?.name) .filter((name): name is string => !!name); + const queryTemplatesCount = useSelector(queryLibraryApi.endpoints.allQueryTemplates.select()).data?.length || 0; + const QueryLibraryTab: TabConfig = { - label: i18n.queryLibrary, + label: `${i18n.queryLibrary} (${queryTemplatesCount}/${QUERY_LIBRARY_GET_LIMIT})`, value: Tabs.QueryLibrary, content: , icon: 'book', diff --git a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx index 3c30c6eae7c..cbbcac5f6fe 100644 --- a/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx +++ b/public/app/features/explore/RichHistory/RichHistoryAddToLibrary.tsx @@ -3,7 +3,7 @@ import { useState } from 'react'; import { DataQuery } from '@grafana/schema'; import { Button, Modal } from '@grafana/ui'; -import { isQueryLibraryEnabled } from 'app/features/query-library'; +import { isQueryLibraryEnabled, useAllQueryTemplatesQuery } from 'app/features/query-library'; import { queryLibraryTrackAddFromQueryHistory, @@ -16,6 +16,7 @@ type Props = { }; export const RichHistoryAddToLibrary = ({ query }: Props) => { + const { refetch } = useAllQueryTemplatesQuery(); const [isOpen, setIsOpen] = useState(false); const [hasBeenSaved, setHasBeenSaved] = useState(false); @@ -45,6 +46,7 @@ export const RichHistoryAddToLibrary = ({ query }: Props) => { if (isSuccess) { setIsOpen(false); setHasBeenSaved(true); + refetch(); queryLibraryTrackAddFromQueryHistory(query.datasource?.type || ''); } }} diff --git a/public/app/features/explore/spec/queryLibrary.test.tsx b/public/app/features/explore/spec/queryLibrary.test.tsx index d69cc001f16..f2422f450d7 100644 --- a/public/app/features/explore/spec/queryLibrary.test.tsx +++ b/public/app/features/explore/spec/queryLibrary.test.tsx @@ -137,7 +137,7 @@ describe('QueryLibrary', () => { expect(testEventBus.publish).toHaveBeenCalledWith( expect.objectContaining({ type: 'alert-success', - payload: ['Query template successfully added to the library'], + payload: ['Query successfully saved to the library'], }) ); await assertAddToQueryLibraryButtonExists(false); diff --git a/public/app/features/query-library/api/factory.ts b/public/app/features/query-library/api/factory.ts index 5ee9b021c59..d456fa4e453 100644 --- a/public/app/features/query-library/api/factory.ts +++ b/public/app/features/query-library/api/factory.ts @@ -7,7 +7,7 @@ import { baseQuery } from './query'; // Currently, we are loading all query templates // Organizations can have maximum of 1000 query templates -const GET_LIMIT = 1000; +export const QUERY_LIBRARY_GET_LIMIT = 1000; export const queryLibraryApi = createApi({ baseQuery, @@ -15,7 +15,7 @@ export const queryLibraryApi = createApi({ endpoints: (builder) => ({ allQueryTemplates: builder.query({ query: () => ({ - url: `?limit=${GET_LIMIT}`, + url: `?limit=${QUERY_LIBRARY_GET_LIMIT}`, }), transformResponse: convertDataQueryResponseToQueryTemplates, providesTags: ['QueryTemplatesList'], diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 0823f93c9b3..2b6cff36111 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -831,8 +831,8 @@ "private": "Private", "public": "Public", "query-deleted": "Query deleted", - "query-template-add-error": "Error attempting to add this query to the library", - "query-template-added": "Query template successfully added to the library", + "query-template-add-error": "Error attempting to save this query to the library", + "query-template-added": "Query successfully saved to the library", "query-template-edit-error": "Error attempting to edit this query", "query-template-edited": "Query template successfully edited", "save": "Save" diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index a2a87a13fd4..c5c78519a42 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -831,8 +831,8 @@ "private": "Přįväŧę", "public": "Pūþľįč", "query-deleted": "Qūęřy đęľęŧęđ", - "query-template-add-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő äđđ ŧĥįş qūęřy ŧő ŧĥę ľįþřäřy", - "query-template-added": "Qūęřy ŧęmpľäŧę şūččęşşƒūľľy äđđęđ ŧő ŧĥę ľįþřäřy", + "query-template-add-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő şävę ŧĥįş qūęřy ŧő ŧĥę ľįþřäřy", + "query-template-added": "Qūęřy şūččęşşƒūľľy şävęđ ŧő ŧĥę ľįþřäřy", "query-template-edit-error": "Ēřřőř äŧŧęmpŧįʼnģ ŧő ęđįŧ ŧĥįş qūęřy", "query-template-edited": "Qūęřy ŧęmpľäŧę şūččęşşƒūľľy ęđįŧęđ", "save": "Ŝävę" From 97a90591ca10f18af0194a5c762834e7f52c318b Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Wed, 9 Oct 2024 14:21:10 +0100 Subject: [PATCH 003/110] AzureMonitor: Fix App Insights portal URL for multi-resource trace queries (#94119) * Retrieve the resource in the query * Appropriately construct url * Update tests --- .../azure-log-analytics-datasource.go | 16 +- pkg/tsdb/azuremonitor/loganalytics/traces.go | 4 +- .../azuremonitor/loganalytics/traces_test.go | 156 +++++++++--------- 3 files changed, 84 insertions(+), 92 deletions(-) diff --git a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go index f06ef71f098..6785a0addd8 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go +++ b/pkg/tsdb/azuremonitor/loganalytics/azure-log-analytics-datasource.go @@ -393,7 +393,7 @@ func addDataLinksToFields(query *AzureLogAnalyticsQuery, azurePortalBaseUrl stri } func addTraceDataLinksToFields(query *AzureLogAnalyticsQuery, azurePortalBaseUrl string, frame *data.Frame, dsInfo types.DatasourceInfo) error { - tracesUrl, err := getTracesQueryUrl(query.Resources, azurePortalBaseUrl) + tracesUrl, err := getTracesQueryUrl(azurePortalBaseUrl) if err != nil { return err } @@ -552,20 +552,12 @@ func getQueryUrl(query string, resources []string, azurePortalUrl string, timeRa return portalUrl, nil } -func getTracesQueryUrl(resources []string, azurePortalUrl string) (string, error) { +func getTracesQueryUrl(azurePortalUrl string) (string, error) { portalUrl := azurePortalUrl portalUrl += "/#view/AppInsightsExtension/DetailsV2Blade/ComponentId~/" - resource := struct { - ResourceId string `json:"ResourceId"` - }{ - resources[0], - } - resourceMarshalled, err := json.Marshal(resource) - if err != nil { - return "", fmt.Errorf("failed to marshal application insights resource: %s", err) - } - portalUrl += url.PathEscape(string(resourceMarshalled)) + resource := "%7B%22ResourceId%22:%22${__data.fields.resource:percentencode}%22%7D" + portalUrl += resource portalUrl += "/DataModel~/" // We're making use of data link variables to select the necessary fields in the frontend diff --git a/pkg/tsdb/azuremonitor/loganalytics/traces.go b/pkg/tsdb/azuremonitor/loganalytics/traces.go index e5c2824ca07..e9b7ddf5078 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/traces.go +++ b/pkg/tsdb/azuremonitor/loganalytics/traces.go @@ -114,8 +114,8 @@ func buildTracesQuery(operationId string, parentSpanID *string, traceTypes []str `| extend serviceName = cloud_RoleName` + `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` propertiesQuery := fmt.Sprintf(`| extend tags = %s`, propertiesFunc) - projectClause := `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + projectClause := `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc` return baseQuery + whereClause + parentWhereClause + propertiesStaticQuery + errorProperty + propertiesQuery + filtersClause + projectClause } diff --git a/pkg/tsdb/azuremonitor/loganalytics/traces_test.go b/pkg/tsdb/azuremonitor/loganalytics/traces_test.go index da069c41db4..3e365061c6d 100644 --- a/pkg/tsdb/azuremonitor/loganalytics/traces_test.go +++ b/pkg/tsdb/azuremonitor/loganalytics/traces_test.go @@ -135,8 +135,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -150,8 +150,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -163,8 +163,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -210,8 +210,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -225,8 +225,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true trace` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -238,8 +238,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -282,8 +282,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -297,8 +297,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == '${__data.fields.traceID}') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == '${__data.fields.traceID}')` + @@ -310,8 +310,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"${__data.fields.traceID}\"", @@ -357,8 +357,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -372,8 +372,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -385,8 +385,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -435,8 +435,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -451,8 +451,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -465,8 +465,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -515,8 +515,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -531,8 +531,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -545,8 +545,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -595,8 +595,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")| where clientId in ("test-client-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -611,8 +611,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")| where clientId in ("test-client-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,traces` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -625,8 +625,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + `| where appId !in ("test-app-id")| where clientId in ("test-client-id")` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -669,8 +669,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -684,8 +684,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests` + `| where (operation_Id != '' and operation_Id == '${__data.fields.traceID}') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == '${__data.fields.traceID}')` + @@ -697,8 +697,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"${__data.fields.traceID}\"", @@ -744,8 +744,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -759,8 +759,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests` + `| where (operation_Id != '' and operation_Id == 'test-op-id') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'test-op-id')` + @@ -772,8 +772,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union availabilityResults,\n" + "customEvents,\n" + "dependencies,\n" + "exceptions,\n" + "pageViews,\n" + "requests,\n" + "traces\n" + "| where operation_Id == \"test-op-id\"", @@ -861,8 +861,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1"}, TimeRange: timeRange, @@ -875,8 +875,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').requests` + `| where (operation_Id != '' and operation_Id == 'op-id-multi') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-multi')` + @@ -887,8 +887,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union *,\n" + "app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,\n" + @@ -937,8 +937,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1", "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r2"}, TimeRange: timeRange, @@ -951,8 +951,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').requests` + `| where (operation_Id != '' and operation_Id == '${__data.fields.traceID}') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == '${__data.fields.traceID}')` + @@ -963,8 +963,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union *,\n" + "app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,\n" + @@ -1016,8 +1016,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1", "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r2"}, TimeRange: timeRange, @@ -1030,8 +1030,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').requests` + `| where (operation_Id != '' and operation_Id == 'op-id-multi') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-multi')` + @@ -1042,8 +1042,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union *,\n" + "app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,\n" + @@ -1095,8 +1095,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, Resources: []string{"/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r1", "/subscriptions/test-sub/resourceGroups/test-rg/providers/Microsoft.Insights/components/r2"}, TimeRange: timeRange, @@ -1109,8 +1109,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceParentExploreQuery: `set truncationmaxrecords=10000; set truncationmaxsize=67108864; union isfuzzy=true availabilityResults,customEvents,dependencies,exceptions,pageViews,requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').requests,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').availabilityResults,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').customEvents,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').dependencies,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').exceptions,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').pageViews,app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r3').requests` + `| where (operation_Id != '' and operation_Id == 'op-id-non-overlapping') or (customDimensions.ai_legacyRootId != '' and customDimensions.ai_legacyRootId == 'op-id-non-overlapping')` + @@ -1121,8 +1121,8 @@ func TestBuildAppInsightsQuery(t *testing.T) { `| extend serviceName = cloud_RoleName| extend serviceTags = bag_pack_columns(cloud_RoleInstance, cloud_RoleName)` + `| extend error = todynamic(iff(itemType == "exception", "true", "false"))` + `| extend tags = bag_merge(bag_pack_columns(appId,appName,application_Version,assembly,client_Browser,client_City,client_CountryOrRegion,client_IP,client_Model,client_OS,client_StateOrProvince,client_Type,data,details,duration,error,handledAt,iKey,id,innermostAssembly,innermostMessage,innermostMethod,innermostType,itemCount,itemId,itemType,location,message,method,name,operation_Id,operation_Name,operation_ParentId,operation_SyntheticSource,outerAssembly,outerMessage,outerMethod,outerType,performanceBucket,problemId,resultCode,sdkVersion,session_Id,severityLevel,size,source,success,target,timestamp,type,url,user_AccountId,user_AuthenticatedId,user_Id), customDimensions, customMeasurements)` + - `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp` + - `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId` + + `| project-rename traceID = operation_Id, parentSpanID = operation_ParentId, startTime = timestamp, resource = _ResourceId` + + `| project startTime, itemType, serviceName, duration, traceID, spanID, parentSpanID, operationName, serviceTags, tags, itemId, resource` + `| order by startTime asc`, TraceLogsExploreQuery: "union *,\n" + "app('/subscriptions/test-sub/resourcegroups/test-rg/providers/microsoft.insights/components/r2').availabilityResults,\n" + From 38827e5a16d97dd0cde1b10233dbe9c962354301 Mon Sep 17 00:00:00 2001 From: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:05:41 -0400 Subject: [PATCH 004/110] ExploreMetrics: Keep gaps consistent between sticky elements (#94441) style: maintain gap between sticky elements while scrolling --- public/app/features/trails/MetricGraphScene.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/app/features/trails/MetricGraphScene.tsx b/public/app/features/trails/MetricGraphScene.tsx index 5e439480f71..69003e9edc5 100644 --- a/public/app/features/trails/MetricGraphScene.tsx +++ b/public/app/features/trails/MetricGraphScene.tsx @@ -62,6 +62,8 @@ function getStyles(theme: GrafanaTheme2, chromeHeaderHeight: number) { flexDirection: 'row', background: theme.isLight ? theme.colors.background.primary : theme.colors.background.canvas, position: 'sticky', + paddingTop: theme.spacing(1), + marginTop: `-${theme.spacing(1)}`, top: `${chromeHeaderHeight + 70}px`, zIndex: 10, }), From 55d970ef9a9fc6be52e3bdcfbfb52ed7d2aeee4b Mon Sep 17 00:00:00 2001 From: Vishal N Date: Wed, 9 Oct 2024 20:12:32 +0530 Subject: [PATCH 005/110] fix missing hyperlink to permissions page in docs (#94077) Co-authored-by: Isabel Matwawana <76437239+imatwawana@users.noreply.github.com> --- docs/sources/dashboards/create-reports/index.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/sources/dashboards/create-reports/index.md b/docs/sources/dashboards/create-reports/index.md index 29beabb4794..272b948d796 100644 --- a/docs/sources/dashboards/create-reports/index.md +++ b/docs/sources/dashboards/create-reports/index.md @@ -39,6 +39,11 @@ refs: destination: /docs/grafana//administration/roles-and-permissions/access-control/ - pattern: /docs/grafana-cloud/ destination: /docs/grafana//administration/roles-and-permissions/access-control/ + permission: + - pattern: /docs/grafana/ + destination: /docs/grafana//administration/roles-and-permissions/ + - pattern: /docs/grafana-cloud/ + destination: /docs/grafana//administration/roles-and-permissions/ role-based-access-control: - pattern: /docs/grafana/ destination: /docs/grafana//administration/roles-and-permissions/access-control/ @@ -109,7 +114,7 @@ For information about recent improvements to the reporting UI, refer to [Grafana ## Access control -When [RBAC](ref:rbac) is enabled, you need to have the relevant [Permissions][] to create and manage reports. +When [RBAC](ref:rbac) is enabled, you need to have the relevant [Permissions](ref:permission) to create and manage reports. ## Create or update a report From 322dccdb4d903264467c50c51e826441ad799055 Mon Sep 17 00:00:00 2001 From: Joao Silva <100691367+JoaoSilvaGrafana@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:53:05 +0100 Subject: [PATCH 006/110] Navigation: Fix wrong active item shown when parent is bookmarked (#94478) --- .../AppChrome/MegaMenu/utils.test.ts | 20 +++++++++++++++++++ .../components/AppChrome/MegaMenu/utils.ts | 4 +++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts index 0a99bffd6b3..ea5a289bd95 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.test.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.test.ts @@ -5,6 +5,22 @@ import { enrichHelpItem, getActiveItem, findByUrl } from './utils'; const starredDashboardUid = 'foo'; const mockNavTree: NavModelItem[] = [ + { + text: 'Bookmarks', + url: '/bookmarks', + id: 'bookmarks', + children: [ + { + text: 'Item with children', + url: '/itemWithChildren', + id: 'item-with-children', + parentItem: { + text: 'Bookmarks', + id: 'bookmarks', + }, + }, + ], + }, { text: 'Item', url: '/item', @@ -112,6 +128,10 @@ describe('getActiveItem', () => { const mockPage: NavModelItem = { text: 'Some child page', id: 'child', + parentItem: { + text: 'Item with children', + id: 'item-with-children', + }, }; expect(getActiveItem(mockNavTree, mockPage)?.id).toEqual('child'); }); diff --git a/public/app/core/components/AppChrome/MegaMenu/utils.ts b/public/app/core/components/AppChrome/MegaMenu/utils.ts index ab41e68dee6..e8864399a0c 100644 --- a/public/app/core/components/AppChrome/MegaMenu/utils.ts +++ b/public/app/core/components/AppChrome/MegaMenu/utils.ts @@ -98,7 +98,9 @@ export const getActiveItem = ( } } - if (parentItem) { + // Do not search for the parent in the bookmarks section + const isInBookmarksSection = navTree[0]?.parentItem?.id === 'bookmarks'; + if (parentItem && !isInBookmarksSection) { return getActiveItem(navTree, parentItem); } From ace177f20a5ff71591879771859888f7ad205625 Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Wed, 9 Oct 2024 17:08:11 +0200 Subject: [PATCH 007/110] AuthN: Set access token name (#94471) * Set access token name --- pkg/services/authn/clients/ext_jwt.go | 2 +- pkg/services/authn/clients/ext_jwt_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/services/authn/clients/ext_jwt.go b/pkg/services/authn/clients/ext_jwt.go index 26f98338ec2..638101615a2 100644 --- a/pkg/services/authn/clients/ext_jwt.go +++ b/pkg/services/authn/clients/ext_jwt.go @@ -185,10 +185,10 @@ func (s *ExtendedJWT) authenticateAsService(accessTokenClaims authlib.Claims[aut return &authn.Identity{ ID: id, UID: id, + Name: id, Type: t, OrgID: s.cfg.DefaultOrgID(), AccessTokenClaims: &accessTokenClaims, - IDTokenClaims: nil, AuthenticatedBy: login.ExtendedJWTModule, AuthID: accessTokenClaims.Subject, AllowedKubernetesNamespace: accessTokenClaims.Rest.Namespace, diff --git a/pkg/services/authn/clients/ext_jwt_test.go b/pkg/services/authn/clients/ext_jwt_test.go index 6b61748dd03..3f9e9ee6197 100644 --- a/pkg/services/authn/clients/ext_jwt_test.go +++ b/pkg/services/authn/clients/ext_jwt_test.go @@ -228,6 +228,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { want: &authn.Identity{ ID: "this-uid", UID: "this-uid", + Name: "this-uid", Type: claims.TypeAccessPolicy, OrgID: 1, AccessTokenClaims: &validAccessTokenClaims, @@ -246,6 +247,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { want: &authn.Identity{ ID: "this-uid", UID: "this-uid", + Name: "this-uid", Type: claims.TypeAccessPolicy, OrgID: 1, AccessTokenClaims: &validAccessTokenClaimsWildcard, @@ -343,6 +345,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { want: &authn.Identity{ ID: "this-uid", UID: "this-uid", + Name: "this-uid", Type: claims.TypeAccessPolicy, OrgID: 1, AccessTokenClaims: &validAccessTokenClaimsWithStackSet, @@ -369,6 +372,7 @@ func TestExtendedJWT_Authenticate(t *testing.T) { want: &authn.Identity{ ID: "this-uid", UID: "this-uid", + Name: "this-uid", Type: claims.TypeAccessPolicy, OrgID: 1, AccessTokenClaims: &validAccessTokenClaimsWithDeprecatedStackClaimSet, From 748bfff60158c19bbd71c3edf51da47cda78066d Mon Sep 17 00:00:00 2001 From: Brendan O'Handley Date: Wed, 9 Oct 2024 10:25:18 -0500 Subject: [PATCH 008/110] Prometheus: Add series endpoint configuration (#94443) * add series endpoint configuration * gf-form is unnecessary and deprecated --- .../src/configuration/PromSettings.test.tsx | 7 +++++++ .../src/configuration/PromSettings.tsx | 20 +++++++++++++++++++ packages/grafana-prometheus/src/datasource.ts | 8 ++++++++ packages/grafana-prometheus/src/types.ts | 1 + 4 files changed, 36 insertions(+) diff --git a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx index 15af11210cc..45ac59a46d7 100644 --- a/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx +++ b/packages/grafana-prometheus/src/configuration/PromSettings.test.tsx @@ -112,5 +112,12 @@ describe('PromSettings', () => { fireEvent.blur(input); expect(queryByText(countError)).toBeInTheDocument(); }); + + it('should have a series endpoint configuration element', () => { + const options = defaultProps; + + render( {}} options={options} />); + expect(screen.getByText('Use series endpoint')).toBeInTheDocument(); + }); }); }); diff --git a/packages/grafana-prometheus/src/configuration/PromSettings.tsx b/packages/grafana-prometheus/src/configuration/PromSettings.tsx index c08273b4ff7..9291ec95040 100644 --- a/packages/grafana-prometheus/src/configuration/PromSettings.tsx +++ b/packages/grafana-prometheus/src/configuration/PromSettings.tsx @@ -492,6 +492,26 @@ export const PromSettings = (props: Props) => { + + Checking this option will favor the series endpoint with match[] parameter over the label values + endpoint with match[] parameter. While the label values endpoint is considered more performant, some + users may prefer the series because it has a POST method while the label values endpoint only has a GET + method. {docsTip()} + + } + interactive={true} + disabled={options.readOnly} + className={styles.switchField} + > + + diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index 29b53f51902..00edfe60784 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -111,6 +111,7 @@ export class PrometheusDatasource cacheLevel: PrometheusCacheLevel; cache: QueryCache; metricNamesAutocompleteSuggestionLimit: number; + seriesEndpoint: boolean; constructor( instanceSettings: DataSourceInstanceSettings, @@ -136,6 +137,7 @@ export class PrometheusDatasource this.customQueryParameters = new URLSearchParams(instanceSettings.jsonData.customQueryParameters); this.datasourceConfigurationPrometheusFlavor = instanceSettings.jsonData.prometheusType; this.datasourceConfigurationPrometheusVersion = instanceSettings.jsonData.prometheusVersion; + this.seriesEndpoint = instanceSettings.jsonData.seriesEndpoint ?? false; this.defaultEditor = instanceSettings.jsonData.defaultEditor; this.disableRecordingRules = instanceSettings.jsonData.disableRecordingRules ?? false; this.variables = new PrometheusVariableSupport(this, this.templateSrv); @@ -183,6 +185,12 @@ export class PrometheusDatasource } hasLabelsMatchAPISupport(): boolean { + // users may choose the series endpoint as it has a POST method + // while the label values is only GET + if (this.seriesEndpoint) { + return false; + } + return ( // https://github.com/prometheus/prometheus/releases/tag/v2.24.0 this._isDatasourceVersionGreaterOrEqualTo('2.24.0', PromApplication.Prometheus) || diff --git a/packages/grafana-prometheus/src/types.ts b/packages/grafana-prometheus/src/types.ts index a8b20b64e87..57903231250 100644 --- a/packages/grafana-prometheus/src/types.ts +++ b/packages/grafana-prometheus/src/types.ts @@ -53,6 +53,7 @@ export interface PromOptions extends DataSourceJsonData { sigV4Auth?: boolean; oauthPassThru?: boolean; codeModeMetricNamesSuggestionLimit?: number; + seriesEndpoint?: boolean; } export type ExemplarTraceIdDestination = { From 2a7319809ab003bd624286f6831b670d73f3e225 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 9 Oct 2024 17:43:30 +0200 Subject: [PATCH 009/110] Fix levitate detect breaking changes pipeline not sending slack messages (#94482) * Fix levitate detect breaking changes pipeline not sending slack messages * Use ref --- .github/CODEOWNERS | 1 - .../detect-breaking-changes-levitate.yml | 68 +++++++++++-------- .github/workflows/scripts/pr-get-job-link.js | 9 --- scripts/check-breaking-changes.sh | 2 +- 4 files changed, 40 insertions(+), 40 deletions(-) delete mode 100644 .github/workflows/scripts/pr-get-job-link.js diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index aea94e92eeb..d9c88232b09 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -731,7 +731,6 @@ embed.go @grafana/grafana-as-code /.github/workflows/remove-milestone.yml @grafana/grafana-release-guild /.github/workflows/sbom-report.yml @grafana/security-team /.github/workflows/scripts/json-file-to-job-output.js @grafana/plugins-platform-frontend -/.github/workflows/scripts/pr-get-job-link.js @grafana/plugins-platform-frontend /.github/workflows/stale.yml @grafana/grafana-release-guild /.github/workflows/update-changelog.yml @grafana/grafana-release-guild /.github/workflows/update-make-docs.yml @grafana/docs-tooling diff --git a/.github/workflows/detect-breaking-changes-levitate.yml b/.github/workflows/detect-breaking-changes-levitate.yml index 8e802abd639..bb71dc4cfe8 100644 --- a/.github/workflows/detect-breaking-changes-levitate.yml +++ b/.github/workflows/detect-breaking-changes-levitate.yml @@ -6,6 +6,10 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +permissions: + contents: read + id-token: write + on: pull_request: paths: @@ -154,26 +158,16 @@ jobs: project_id: 'grafanalabs-global' install_components: 'bq' - - name: Get link for the Github Action job - id: job - uses: actions/github-script@v6 - with: - script: | - const name = 'Detect breaking changes'; - const script = require('./.github/workflows/scripts/pr-get-job-link.js') - await script({name, github, context, core}) - - name: Detect breaking changes id: breaking-changes run: ./scripts/check-breaking-changes.sh env: FORCE_COLOR: 3 - GITHUB_JOB_LINK: ${{ steps.job.outputs.link }} - name: Persisting the check output run: | mkdir -p ./levitate - echo "{ \"exit_code\": ${{ steps.breaking-changes.outputs.is_breaking }}, \"message\": \"${{ steps.breaking-changes.outputs.message }}\", \"job_link\": \"${{ steps.job.outputs.link }}#step:${GITHUB_STEP_NUMBER}:1\", \"pr_number\": \"${{ github.event.pull_request.number }}\" }" > ./levitate/result.json + echo "{ \"exit_code\": ${{ steps.breaking-changes.outputs.is_breaking }}, \"message\": \"${{ steps.breaking-changes.outputs.message }}\", \"pr_number\": \"${{ github.event.pull_request.number }}\" }" > ./levitate/result.json - name: Upload check output as artifact uses: actions/upload-artifact@v4 @@ -219,15 +213,12 @@ jobs: PR_NUMBER: ${{ github.event.pull_request.number }} with: script: | - const { data } = await github.rest.issues.listLabelsOnIssue({ - issue_number: process.env.PR_NUMBER, + const { data: labels } = await github.rest.issues.listLabelsOnIssue({ + issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, }); - const labels = data.map(({ name }) => name); - const doesExist = labels.includes('levitate breaking change'); - - return doesExist ? 1 : 0; + return labels.some(label => label.name === 'levitate breaking change') ? 1 : 0 # put the markdown into a variable - name: Levitate Markdown @@ -271,22 +262,41 @@ jobs: delete: true GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} - # Posts a notification to Slack if a PR has a breaking change and it did not have a breaking change before - - name: Post to Slack + - name: Send Slack Message via Payload id: slack - if: steps.levitate-run.outputs.exit_code == 1 && steps.does-label-exist.outputs.result == 0 && env.HAS_SECRETS - uses: slackapi/slack-github-action@v1.26.0 + if: steps.levitate-run.outputs.exit_code == 1 && steps.does-label-exist.outputs.result == 0 && github.repository == 'grafana/grafana' + uses: grafana/shared-workflows/actions/send-slack-message@main with: - payload: | + channel-id: "C031SLFH6G0" + payload: | { - "pr_link": "https://github.com/grafana/grafana/pull/${{ steps.levitate-run.outputs.pr_number }}", - "pr_number": "${{ steps.levitate-run.outputs.pr_number }}", - "job_link": "${{ steps.levitate-run.outputs.job_link }}", - "message": "${{ steps.levitate-run.outputs.message }}" + "channel": "C031SLFH6G0", + "text": ":warning: Possible breaking changes detected in *PR:* <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }} :warning:", + "icon_emoji": ":grot:", + "username": "Levitate Bot", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*grafana/grafana* repository has possible breaking changes" + } + }, + { + "type": "section", + "fields": [ + { + "type": "mrkdwn", + "text": "*PR:* <${{ github.event.pull_request.html_url }}|#${{ github.event.pull_request.number }}>" + }, + { + "type": "mrkdwn", + "text": "*Job:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Job>" + } + ] + } + ] } - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_LEVITATE_WEBHOOK_URL }} - HAS_SECRETS: ${{ (github.repository == 'grafana/grafana' || secrets.SLACK_LEVITATE_WEBHOOK_URL != '') || '' }} # Add the label - name: Add "levitate breaking change" label diff --git a/.github/workflows/scripts/pr-get-job-link.js b/.github/workflows/scripts/pr-get-job-link.js deleted file mode 100644 index b1b49c89941..00000000000 --- a/.github/workflows/scripts/pr-get-job-link.js +++ /dev/null @@ -1,9 +0,0 @@ - -module.exports = async ({ name, github, context, core }) => { - const { owner, repo } = context.repo; - const url = `https://api.github.com/repos/${owner}/${repo}/actions/runs/${context.runId}/jobs` - const result = await github.request(url); - const job = result.data.jobs.find(j => j.name === name); - - core.setOutput('link', `${job.html_url}?check_suite_focus=true`); -} diff --git a/scripts/check-breaking-changes.sh b/scripts/check-breaking-changes.sh index 07ff141e6ba..d37a6c23669 100755 --- a/scripts/check-breaking-changes.sh +++ b/scripts/check-breaking-changes.sh @@ -55,7 +55,7 @@ while IFS=" " read -r -a package; do # (non-zero if any of the packages failed the checks) if [ "$STATUS" -gt 0 ]; then EXIT_CODE=1 - GITHUB_MESSAGE="${GITHUB_MESSAGE}**\\\`${PACKAGE_PATH}\\\`** has possible breaking changes ([more info](${GITHUB_JOB_LINK}#step:${GITHUB_STEP_NUMBER}:1))
" + GITHUB_MESSAGE="${GITHUB_MESSAGE}**\\\`${PACKAGE_PATH}\\\`** has possible breaking changes
" GITHUB_LEVITATE_MARKDOWN+="

${PACKAGE_PATH}

${CURRENT_REPORT}
" fi From bf75e1fbf4f374372511ea51200fa06f4c0db632 Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Wed, 9 Oct 2024 17:54:09 +0200 Subject: [PATCH 010/110] Bump scenes to 5.19.1 (#94491) --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index e261fc3abcf..f1fb2d03c25 100644 --- a/package.json +++ b/package.json @@ -268,7 +268,7 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "5.18.3", + "@grafana/scenes": "5.19.1", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 915142467a8..7e7b59eb3e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4117,9 +4117,9 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes@npm:5.18.3": - version: 5.18.3 - resolution: "@grafana/scenes@npm:5.18.3" +"@grafana/scenes@npm:5.19.1": + version: 5.19.1 + resolution: "@grafana/scenes@npm:5.19.1" dependencies: "@floating-ui/react": "npm:0.26.16" "@grafana/e2e-selectors": "npm:^11.0.0" @@ -4136,7 +4136,7 @@ __metadata: "@grafana/ui": ">=10.4" react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/fb1179db5b53d709b859facc7742473dbd13947c2ec9593035e617f32166716de20b712002c14e1eec7cf90a80e58c60cdc039a5252a54c7ab1f8659bba64476 + checksum: 10/b27dcae3ae03f4ad49815bcf445ef27f5ccf66099b837001b4e255cb37a637cd7f563dd99e4f00290668e60b8cc57a5f75c97d5577690034543fe4cfb0830c4a languageName: node linkType: hard @@ -18922,7 +18922,7 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:5.18.3" + "@grafana/scenes": "npm:5.19.1" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" From c86c4ca65a3100b557a19e88e5d2b51642d606ad Mon Sep 17 00:00:00 2001 From: Scott Lepper Date: Wed, 9 Oct 2024 12:02:52 -0400 Subject: [PATCH 011/110] [unified search] fix: remove unified searcher (#94492) --- pkg/services/unifiedSearch/service.go | 1 + .../app/features/search/service/searcher.ts | 6 - public/app/features/search/service/unified.ts | 265 ------------------ 3 files changed, 1 insertion(+), 271 deletions(-) delete mode 100644 public/app/features/search/service/unified.ts diff --git a/pkg/services/unifiedSearch/service.go b/pkg/services/unifiedSearch/service.go index eb83ac8bbaa..42792251e0f 100644 --- a/pkg/services/unifiedSearch/service.go +++ b/pkg/services/unifiedSearch/service.go @@ -161,6 +161,7 @@ func (s *StandardSearchService) doSearchQuery(ctx context.Context, qry Query, _ req := &resource.SearchRequest{Tenant: s.cfg.StackID, Query: qry.Query} res, err := s.resourceClient.Search(ctx, req) if err != nil { + s.logger.Error("Failed to search resources", "error", err) response.Error = err return response } diff --git a/public/app/features/search/service/searcher.ts b/public/app/features/search/service/searcher.ts index b7e8b885424..354411534c9 100644 --- a/public/app/features/search/service/searcher.ts +++ b/public/app/features/search/service/searcher.ts @@ -4,7 +4,6 @@ import { BlugeSearcher } from './bluge'; import { FrontendSearcher } from './frontend'; import { SQLSearcher } from './sql'; import { GrafanaSearcher } from './types'; -import { UnifiedSearcher } from './unified'; let searcher: GrafanaSearcher | undefined = undefined; @@ -14,11 +13,6 @@ export function getGrafanaSearcher(): GrafanaSearcher { const useBluge = config.featureToggles.panelTitleSearch; searcher = useBluge ? new BlugeSearcher(sqlSearcher) : sqlSearcher; - const useUnified = config.featureToggles.unifiedStorageSearch; - if (useUnified) { - searcher = new UnifiedSearcher(sqlSearcher); - } - if (useBluge && location.search.includes('do-frontend-query')) { searcher = new FrontendSearcher(searcher); } diff --git a/public/app/features/search/service/unified.ts b/public/app/features/search/service/unified.ts deleted file mode 100644 index 882b3a0a5bc..00000000000 --- a/public/app/features/search/service/unified.ts +++ /dev/null @@ -1,265 +0,0 @@ -// TODO: fix - copied from bluge.ts -import { - DataFrame, - DataFrameJSON, - DataFrameView, - getDisplayProcessor, - SelectableValue, - toDataFrame, -} from '@grafana/data'; -import { config, getBackendSrv } from '@grafana/runtime'; -import { TermCount } from 'app/core/components/TagFilter/TagFilter'; - -import { replaceCurrentFolderQuery } from './utils'; - -import { DashboardQueryResult, GrafanaSearcher, QueryResponse, SearchQuery } from '.'; - -// The backend returns an empty frame with a special name to indicate that the indexing engine is being rebuilt, -// and that it can not serve any search requests. We are temporarily using the old SQL Search API as a fallback when that happens. -const loadingFrameName = 'Loading'; - -const searchURI = 'api/unified-search'; - -type SearchAPIResponse = { - frames: DataFrameJSON[]; -}; - -const folderViewSort = 'name_sort'; - -export class UnifiedSearcher implements GrafanaSearcher { - constructor(private fallbackSearcher: GrafanaSearcher) {} - - async search(query: SearchQuery): Promise { - if (query.facet?.length) { - throw new Error('facets not supported!'); - } - return this.doSearchQuery(query); - } - - // TODO: fix - copied from bluge.ts - async starred(query: SearchQuery): Promise { - if (query.facet?.length) { - throw new Error('facets not supported!'); - } - // get the starred dashboards - const starsUIDS = await getBackendSrv().get('api/user/stars'); - if (starsUIDS?.length) { - return this.doSearchQuery({ - uid: starsUIDS, - query: query.query ?? '*', - }); - } - // Nothing is starred - return { - view: new DataFrameView({ length: 0, fields: [] }), - totalRows: 0, - loadMoreItems: async (startIndex: number, stopIndex: number): Promise => { - return; - }, - isItemLoaded: (index: number): boolean => { - return true; - }, - }; - } - - // TODO: fix - copied from bluge.ts - async tags(query: SearchQuery): Promise { - const req = { - ...query, - query: query.query ?? '*', - sort: undefined, // no need to sort the initial query results (not used) - facet: [{ field: 'tag' }], - limit: 1, // 0 would be better, but is ignored by the backend - }; - - const resp = await getBackendSrv().post(searchURI, req); - const frames = resp.frames.map((f) => toDataFrame(f)); - - if (frames[0]?.name === loadingFrameName) { - return this.fallbackSearcher.tags(query); - } - - for (const frame of frames) { - if (frame.fields[0].name === 'tag') { - return getTermCountsFrom(frame); - } - } - - return []; - } - - // TODO: fix - copied from bluge.ts - getSortOptions(): Promise { - const opts: SelectableValue[] = [ - { value: folderViewSort, label: 'Alphabetically (A-Z)' }, - { value: '-name_sort', label: 'Alphabetically (Z-A)' }, - ]; - - if (config.licenseInfo.enabledFeatures.analytics) { - for (const sf of sortFields) { - opts.push({ value: `-${sf.name}`, label: `${sf.display} (most)` }); - opts.push({ value: `${sf.name}`, label: `${sf.display} (least)` }); - } - for (const sf of sortTimeFields) { - opts.push({ value: `-${sf.name}`, label: `${sf.display} (recent)` }); - opts.push({ value: `${sf.name}`, label: `${sf.display} (oldest)` }); - } - } - - return Promise.resolve(opts); - } - - // TODO: update - copied from bluge.ts - async doSearchQuery(query: SearchQuery): Promise { - query = await replaceCurrentFolderQuery(query); - const req = { - ...query, - query: query.query ?? '*', - limit: query.limit ?? firstPageSize, - }; - - const rsp = await getBackendSrv().post(searchURI, req); - const frames = rsp.frames.map((f) => toDataFrame(f)); - - const first = frames.length ? toDataFrame(frames[0]) : { fields: [], length: 0 }; - - if (first.name === loadingFrameName) { - return this.fallbackSearcher.search(query); - } - - for (const field of first.fields) { - field.display = getDisplayProcessor({ field, theme: config.theme2 }); - } - - // Make sure the object exists - if (!first.meta?.custom) { - first.meta = { - ...first.meta, - custom: { - count: first.length, - max_score: 1, - }, - }; - } - - const meta = first.meta.custom || {}; - if (!meta.locationInfo) { - meta.locationInfo = {}; // always set it so we can append - } - - // Set the field name to a better display name - if (meta.sortBy?.length) { - const field = first.fields.find((f) => f.name === meta.sortBy); - if (field) { - const name = getSortFieldDisplayName(field.name); - meta.sortBy = name; - field.name = name; // make it look nicer - } - } - - let loadMax = 0; - let pending: Promise | undefined = undefined; - const getNextPage = async () => { - while (loadMax > view.dataFrame.length) { - const from = view.dataFrame.length; - if (from >= meta.count) { - return; - } - const resp = await getBackendSrv().post(searchURI, { - ...(req ?? {}), - from, - limit: nextPageSizes, - }); - const frame = toDataFrame(resp.frames[0]); - - if (!frame) { - console.log('no results', frame); - return; - } - if (frame.fields.length !== view.dataFrame.fields.length) { - console.log('invalid shape', frame, view.dataFrame); - return; - } - - // Append the raw values to the same array buffer - const length = frame.length + view.dataFrame.length; - for (let i = 0; i < frame.fields.length; i++) { - const values = view.dataFrame.fields[i].values; - values.push(...frame.fields[i].values); - } - view.dataFrame.length = length; - - // Add all the location lookup info - const submeta = frame.meta?.custom; - if (submeta?.locationInfo && meta) { - for (const [key, value] of Object.entries(submeta.locationInfo)) { - meta.locationInfo[key] = value; - } - } - } - pending = undefined; - }; - - const view = new DataFrameView(first); - return { - totalRows: meta.count ?? first.length, - view, - loadMoreItems: async (startIndex: number, stopIndex: number): Promise => { - loadMax = Math.max(loadMax, stopIndex); - if (!pending) { - pending = getNextPage(); - } - return pending; - }, - isItemLoaded: (index: number): boolean => { - return index < view.dataFrame.length; - }, - }; - } - - getFolderViewSort(): string { - return 'name_sort'; - } -} - -const firstPageSize = 50; -const nextPageSizes = 100; - -function getTermCountsFrom(frame: DataFrame): TermCount[] { - const keys = frame.fields[0].values; - const vals = frame.fields[1].values; - const counts: TermCount[] = []; - for (let i = 0; i < frame.length; i++) { - counts.push({ term: keys[i], count: vals[i] }); - } - return counts; -} - -// Enterprise only sort field values for dashboards -const sortFields = [ - { name: 'views_total', display: 'Views total' }, - { name: 'views_last_30_days', display: 'Views 30 days' }, - { name: 'errors_total', display: 'Errors total' }, - { name: 'errors_last_30_days', display: 'Errors 30 days' }, -]; - -// Enterprise only time sort field values for dashboards -const sortTimeFields = [ - { name: 'created_at', display: 'Created time' }, - { name: 'updated_at', display: 'Updated time' }, -]; - -/** Given the internal field name, this gives a reasonable display name for the table colum header */ -function getSortFieldDisplayName(name: string) { - for (const sf of sortFields) { - if (sf.name === name) { - return sf.display; - } - } - for (const sf of sortTimeFields) { - if (sf.name === name) { - return sf.display; - } - } - return name; -} From d5ff74ebacc35e70a150511935c3556ea6320379 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:55:02 +0000 Subject: [PATCH 012/110] Update dependency @grafana/experimental to v2.1.2 --- package.json | 2 +- .../grafana-o11y-ds-frontend/package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-sql/package.json | 2 +- .../datasource/azuremonitor/package.json | 2 +- .../datasource/cloud-monitoring/package.json | 2 +- .../package.json | 2 +- .../grafana-testdata-datasource/package.json | 2 +- .../plugins/datasource/jaeger/package.json | 2 +- .../app/plugins/datasource/mssql/package.json | 2 +- .../app/plugins/datasource/mysql/package.json | 2 +- .../app/plugins/datasource/tempo/package.json | 2 +- .../plugins/datasource/zipkin/package.json | 2 +- yarn.lock | 52 ++++++++++++++----- 14 files changed, 52 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index f1fb2d03c25..3029cab50fd 100644 --- a/package.json +++ b/package.json @@ -256,7 +256,7 @@ "@grafana/azure-sdk": "0.0.3", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/faro-core": "^1.3.6", "@grafana/faro-web-sdk": "^1.3.6", "@grafana/faro-web-tracing": "^1.8.2", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index a1e256f8dd1..90f13ecb692 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -20,7 +20,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 5c8924babaf..b0e0b11379c 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -39,7 +39,7 @@ "@emotion/css": "11.13.4", "@floating-ui/react": "0.26.24", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/faro-web-sdk": "1.10.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index b291478a8f3..806a2c99540 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -17,7 +17,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", "@react-awesome-query-builder/ui": "6.6.3", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index 5b8cc33a8fc..b7e2c1fc43a 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index daba101b281..22c4d50f4da 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/google-sdk": "0.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index a002e77c490..8c7a04589cc 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/sql": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index cb4a0b23e73..2d3a0165f48 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index 66cd27b8eac..9811041480a 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index 51cbcb5dff3..1f0a2e0b5e6 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/sql": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index cbb68d22ebe..1531f4f935d 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -6,7 +6,7 @@ "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "11.3.0-pre", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/runtime": "11.3.0-pre", "@grafana/sql": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 53e42363454..63d2b5c785f 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/lezer-logql": "0.2.6", "@grafana/lezer-traceql": "0.0.19", "@grafana/monaco-logql": "^0.0.7", diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index 5279c2da89b..0d91729726b 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -7,7 +7,7 @@ "@emotion/css": "11.13.4", "@grafana/data": "workspace:*", "@grafana/e2e-selectors": "workspace:*", - "@grafana/experimental": "2.1.1", + "@grafana/experimental": "2.1.2", "@grafana/o11y-ds-frontend": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 7e7b59eb3e0..80d791c1aac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3135,7 +3135,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/schema": "npm:11.3.0-pre" @@ -3179,7 +3179,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/sql": "npm:11.3.0-pre" @@ -3251,7 +3251,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/schema": "npm:11.3.0-pre" @@ -3292,7 +3292,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/plugin-configs": "workspace:*" "@grafana/runtime": "workspace:*" @@ -3334,7 +3334,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/sql": "npm:11.3.0-pre" @@ -3365,7 +3365,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/sql": "npm:11.3.0-pre" @@ -3428,7 +3428,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/google-sdk": "npm:0.1.2" "@grafana/plugin-configs": "npm:11.3.0-pre" "@grafana/runtime": "npm:11.3.0-pre" @@ -3476,7 +3476,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/lezer-logql": "npm:0.2.6" "@grafana/lezer-traceql": "npm:0.0.19" "@grafana/monaco-logql": "npm:^0.0.7" @@ -3536,7 +3536,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "workspace:*" "@grafana/e2e-selectors": "workspace:*" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/o11y-ds-frontend": "workspace:*" "@grafana/plugin-configs": "workspace:*" "@grafana/runtime": "workspace:*" @@ -3732,6 +3732,32 @@ __metadata: languageName: node linkType: hard +"@grafana/experimental@npm:2.1.2": + version: 2.1.2 + resolution: "@grafana/experimental@npm:2.1.2" + dependencies: + "@hello-pangea/dnd": "npm:^16.6.0" + "@types/uuid": "npm:^8.3.3" + lodash: "npm:^4.17.21" + prismjs: "npm:^1.29.0" + react-popper-tooltip: "npm:^4.4.2" + react-use: "npm:^17.4.2" + semver: "npm:^7.5.4" + uuid: "npm:^8.3.2" + peerDependencies: + "@emotion/css": ^11.11.2 + "@grafana/data": ^10.4.0 || ^11.0.0 + "@grafana/e2e-selectors": ^10.0.0 || ^11.0.0 + "@grafana/runtime": ^10.4.0 || ^11.0.0 + "@grafana/ui": ^10.4.0 || ^11.0.0 + react: ^18.2.0 + react-dom: ^18.2.0 + react-select: ^5.8.0 + rxjs: ^7.8.1 + checksum: 10/6a00737f870a842d1178ab8515de06fb5eb845a5d6f2adcb32e3dcb5bc76af7d2f72c8c5ea5cedbb22526277bdd32916089bad47b9b04a65b95780244ac1558f + languageName: node + linkType: hard + "@grafana/faro-core@npm:^1.10.2, @grafana/faro-core@npm:^1.3.6": version: 1.10.2 resolution: "@grafana/faro-core@npm:1.10.2" @@ -3867,7 +3893,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/schema": "npm:11.3.0-pre" "@grafana/tsconfig": "npm:^2.0.0" @@ -3938,7 +3964,7 @@ __metadata: "@floating-ui/react": "npm:0.26.24" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/faro-web-sdk": "npm:1.10.2" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/schema": "npm:11.3.0-pre" @@ -4165,7 +4191,7 @@ __metadata: "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" "@grafana/e2e-selectors": "npm:11.3.0-pre" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/runtime": "npm:11.3.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@grafana/ui": "npm:11.3.0-pre" @@ -18909,7 +18935,7 @@ __metadata: "@grafana/e2e-selectors": "workspace:*" "@grafana/eslint-config": "npm:7.0.0" "@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules" - "@grafana/experimental": "npm:2.1.1" + "@grafana/experimental": "npm:2.1.2" "@grafana/faro-core": "npm:^1.3.6" "@grafana/faro-web-sdk": "npm:^1.3.6" "@grafana/faro-web-tracing": "npm:^1.8.2" From e8bcc5e8317da595306511c2968fa085a1ae41fe Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 15:55:38 +0000 Subject: [PATCH 013/110] Update dependency sass to v1.79.4 --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- yarn.lock | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 3029cab50fd..bd16396820c 100644 --- a/package.json +++ b/package.json @@ -222,7 +222,7 @@ "redux-mock-store": "1.5.4", "rimraf": "6.0.1", "rudder-sdk-js": "2.48.19", - "sass": "1.79.3", + "sass": "1.79.4", "sass-loader": "16.0.2", "smtp-tester": "^2.1.0", "style-loader": "4.0.0", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index b0e0b11379c..942c0f721a8 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -131,7 +131,7 @@ "rollup-plugin-dts": "^6.1.1", "rollup-plugin-esbuild": "6.1.1", "rollup-plugin-node-externals": "^7.1.3", - "sass": "1.79.3", + "sass": "1.79.4", "sass-loader": "16.0.2", "style-loader": "4.0.0", "testing-library-selector": "0.3.1", diff --git a/yarn.lock b/yarn.lock index 80d791c1aac..5d25ed259d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4048,7 +4048,7 @@ __metadata: rollup-plugin-esbuild: "npm:6.1.1" rollup-plugin-node-externals: "npm:^7.1.3" rxjs: "npm:7.8.1" - sass: "npm:1.79.3" + sass: "npm:1.79.4" sass-loader: "npm:16.0.2" semver: "npm:7.6.3" style-loader: "npm:4.0.0" @@ -19214,7 +19214,7 @@ __metadata: rimraf: "npm:6.0.1" rudder-sdk-js: "npm:2.48.19" rxjs: "npm:7.8.1" - sass: "npm:1.79.3" + sass: "npm:1.79.4" sass-loader: "npm:16.0.2" selecto: "npm:1.26.3" semver: "npm:7.6.3" @@ -29181,16 +29181,16 @@ __metadata: languageName: node linkType: hard -"sass@npm:1.79.3": - version: 1.79.3 - resolution: "sass@npm:1.79.3" +"sass@npm:1.79.4": + version: 1.79.4 + resolution: "sass@npm:1.79.4" dependencies: chokidar: "npm:^4.0.0" immutable: "npm:^4.0.0" source-map-js: "npm:>=0.6.2 <2.0.0" bin: sass: sass.js - checksum: 10/9b83e91c44a4c5d738ded27fcd2c88260f5f407e49c6aab92f75a1f768831182a8f4f89f30e2599b74447451d6536ea6f3a55cac34c5ec00b45983d493732e5d + checksum: 10/82e2ee5c2e46c96818454c7d97bcfb5b36c1c27de3b1e705adad7a49a8b32226c5254cc4c8804f45db2b6aa018848973177274c2b1137d4caf7abb5cb7bbf8b9 languageName: node linkType: hard From 1f9562ea72cb4e46c202ee911ffaa41d269425f2 Mon Sep 17 00:00:00 2001 From: Kristina Date: Wed, 9 Oct 2024 11:24:06 -0500 Subject: [PATCH 014/110] State Timeline: Align left text to 0 when rectangle is left-truncated (#94422) Handle negative x placement with displaying text --- public/app/core/components/TimelineChart/timeline.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/core/components/TimelineChart/timeline.ts b/public/app/core/components/TimelineChart/timeline.ts index f0b7eec771c..68acea164f6 100644 --- a/public/app/core/components/TimelineChart/timeline.ts +++ b/public/app/core/components/TimelineChart/timeline.ts @@ -321,7 +321,11 @@ export function getConfig(opts: TimelineCoreOptions) { continue; } - let maxChars = Math.floor(boxRect?.w / pxPerChar); + // if x placement is negative, rect is left truncated, remove it from width for calculating how many chars will display + // right truncation happens automatically + const displayedBoxWidth = boxRect.x < 0 ? boxRect?.w + boxRect.x : boxRect?.w; + + let maxChars = Math.floor(displayedBoxWidth / pxPerChar); if (showValue === VisibilityMode.Auto && maxChars < 2) { continue; @@ -333,7 +337,7 @@ export function getConfig(opts: TimelineCoreOptions) { let x = round(boxRect.x + xOff + boxRect.w / 2); if (mode === TimelineMode.Changes) { if (alignValue === 'left') { - x = round(boxRect.x + xOff + strokeWidth + textPadding); + x = round(Math.max(boxRect.x, 0) + xOff + strokeWidth + textPadding); } else if (alignValue === 'right') { x = round(boxRect.x + xOff + boxRect.w - strokeWidth - textPadding); } From 612b86477265976eecdaaf1517d7748982a639fb Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Wed, 9 Oct 2024 11:20:05 -0600 Subject: [PATCH 015/110] Indexing PoC: Add search/browse (#94126) * adds Filter gRPC and make protobuf * adds route for querying the filter gRPC * wires up Filter gRPC call * [WIP] index from start * renames gRPC endpoint to "Search" * adds /apis/search route into k8s routes. Hacky for now. * updates readme - wrong casing * adds feature toggle for unified storage search * hides US search behind feature flag. Clean up print statements. * removes indexer - will be added in another PR * Search: Add API Builder * adds required method * implementing UpdateAPIGroupInfo (WIP) * adds groupversion * commenting out for now * remove unneeded code from experimenting and update register.go to match interface required * list resources and load into index * pass context * namespaces search route * lint * watch * add todo * add todo * merge * cleanup * add todo * gen protobuf * lint; fix migration issue * Updates index mapping function to map unified storage object Value * Changes Index() to pointer receiver - fixes panic * add delete * cleanup * gets search/browse functioning. Results show up as base64 encoded. Still a WIP. * Doesnt json re-encode gRPC response in search handler * add kind to SearchRequest proto * Updates query interface to be more generic. Make proto. Parses query params in api server. * make protobuf * removes unused method and imports * Returns all indexed fields in search results. Adds pagination support (limit + offset). * remove comment * remove unused struct * gets tenant in search k8s api handler * adds hardcoded spec field mappings - starting with playlists * adds all spec fields to search results * moved helper function for field mappings into index * only includes allowed spec fields in search results * cleans up error handling * removes debug log --------- Co-authored-by: leonorfmartins Co-authored-by: Todd Treece Co-authored-by: Scott Lepper --- go.work.sum | 2 - pkg/registry/apis/search/register.go | 67 +++- pkg/services/featuremgmt/toggles_gen.json | 16 +- pkg/services/unifiedSearch/service.go | 2 +- pkg/storage/unified/resource/go.mod | 26 ++ pkg/storage/unified/resource/index.go | 80 ++++- pkg/storage/unified/resource/index_server.go | 15 +- pkg/storage/unified/resource/resource.pb.go | 304 +++++++++++-------- pkg/storage/unified/resource/resource.proto | 10 +- 9 files changed, 355 insertions(+), 167 deletions(-) diff --git a/go.work.sum b/go.work.sum index 8fbaee04958..59348c11716 100644 --- a/go.work.sum +++ b/go.work.sum @@ -585,8 +585,6 @@ github.com/grafana/alerting v0.0.0-20240830172655-aa466962ea18/go.mod h1:GMLi6d0 github.com/grafana/alerting v0.0.0-20240917171353-6c25eb6eff10 h1:oDbLKM34O+JUF9EQFS+9aYhdYoeNfUpXqNjFCLIxwF4= github.com/grafana/alerting v0.0.0-20240917171353-6c25eb6eff10/go.mod h1:GMLi6d09Xqo96fCVUjNk//rcjP5NKEdjOzfWIffD5r4= github.com/grafana/gomemcache v0.0.0-20240229205252-cd6a66d6fb56/go.mod h1:PGk3RjYHpxMM8HFPhKKo+vve3DdlPUELZLSDEFehPuU= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240625192351-66ec17e3aa45 h1:AJKOtDKAOg8XNFnIZSmqqqutoTSxVlRs6vekL2p2KEY= -github.com/grafana/prometheus-alertmanager v0.25.1-0.20240625192351-66ec17e3aa45/go.mod h1:01sXtHoRwI8W324IPAzuxDFOmALqYLCOhvSC2fUHWXc= github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/grafana/thema v0.0.0-20230511182720-3146087fcc26 h1:HX927q4X1n451pnGb8U0wq74i8PCzuxVjzv7TyD10kc= github.com/grafana/thema v0.0.0-20230511182720-3146087fcc26/go.mod h1:Pn9nfzCk7nV0mvNgwusgCjCROZP6nm4GpwTnmEhLT24= diff --git a/pkg/registry/apis/search/register.go b/pkg/registry/apis/search/register.go index 78b2afbfd07..683f9dfbcf5 100644 --- a/pkg/registry/apis/search/register.go +++ b/pkg/registry/apis/search/register.go @@ -3,7 +3,12 @@ package search import ( "encoding/json" "net/http" + "net/url" + "strconv" + "github.com/grafana/grafana/pkg/api/response" + request2 "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/setting" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/authorization/authorizer" @@ -21,14 +26,17 @@ import ( var _ builder.APIGroupBuilder = (*SearchAPIBuilder)(nil) type SearchAPIBuilder struct { - unified resource.ResourceClient + unified resource.ResourceClient + namespacer request2.NamespaceMapper } func NewSearchAPIBuilder( unified resource.ResourceClient, + cfg *setting.Cfg, ) (*SearchAPIBuilder, error) { return &SearchAPIBuilder{ - unified: unified, + unified: unified, + namespacer: request2.GetNamespaceMapper(cfg), }, nil } @@ -36,11 +44,12 @@ func RegisterAPIService( features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar, unified resource.ResourceClient, + cfg *setting.Cfg, ) (*SearchAPIBuilder, error) { if !(features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorageSearch) || features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs)) { return nil, nil } - builder, err := NewSearchAPIBuilder(unified) + builder, err := NewSearchAPIBuilder(unified, cfg) apiregistration.RegisterAPI(builder) return builder, err } @@ -73,17 +82,55 @@ func (b *SearchAPIBuilder) GetAPIRoutes() *builder.APIRoutes { }, }, }, - Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - urlQuery := r.URL.Query().Get("query") - searchRequest := &resource.SearchRequest{Query: urlQuery} + Handler: func(w http.ResponseWriter, r *http.Request) { + // get tenant + orgId, err := request2.OrgIDForList(r.Context()) + if err != nil { + response.Error(500, "failed to get orgId", err) + } + tenant := b.namespacer(orgId) + + queryParams, err := url.ParseQuery(r.URL.RawQuery) + if err != nil { + response.Error(500, "failed to parse query params", err) + } + + // get limit and offset from query params + limit := 0 + offset := 0 + if queryParams.Has("limit") { + limit, _ = strconv.Atoi(queryParams.Get("limit")) + } + if queryParams.Has("offset") { + offset, _ = strconv.Atoi(queryParams.Get("offset")) + } + + searchRequest := &resource.SearchRequest{ + Tenant: tenant, + Kind: queryParams.Get("kind"), + QueryType: queryParams.Get("queryType"), + Query: queryParams.Get("query"), + Limit: int64(limit), + Offset: int64(offset), + } + res, err := b.unified.Search(r.Context(), searchRequest) if err != nil { - panic(err) + response.Error(500, "search request failed", err) } - if err := json.NewEncoder(w).Encode(res); err != nil { - panic(err) + + // TODO need a nicer way of handling this + // the [][]byte response already contains the marshalled JSON, so we don't need to re-encode it + rawMessages := make([]json.RawMessage, len(res.GetItems())) + for i, item := range res.GetItems() { + rawMessages[i] = item.Value } - }), + + w.Header().Set("Content-Type", "application/json") + if err := json.NewEncoder(w).Encode(rawMessages); err != nil { + response.Error(500, "failed to json encode raw response", err) + } + }, }, }, } diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index fbd7b6e335a..797f974e49d 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -3087,6 +3087,20 @@ "codeowner": "@grafana/identity-access-team" } }, + { + "metadata": { + "name": "unifiedStorageSearch", + "resourceVersion": "1726771421439", + "creationTimestamp": "2024-09-19T18:43:41Z" + }, + "spec": { + "description": "Enable unified storage search", + "stage": "experimental", + "codeowner": "@grafana/search-and-storage", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "vizActions", @@ -3141,4 +3155,4 @@ } } ] -} \ No newline at end of file +} diff --git a/pkg/services/unifiedSearch/service.go b/pkg/services/unifiedSearch/service.go index 42792251e0f..4874a62858a 100644 --- a/pkg/services/unifiedSearch/service.go +++ b/pkg/services/unifiedSearch/service.go @@ -158,7 +158,7 @@ func (s *StandardSearchService) doQuery(ctx context.Context, signedInUser *user. func (s *StandardSearchService) doSearchQuery(ctx context.Context, qry Query, _ string) *backend.DataResponse { response := &backend.DataResponse{} - req := &resource.SearchRequest{Tenant: s.cfg.StackID, Query: qry.Query} + req := &resource.SearchRequest{Tenant: s.cfg.StackID, Query: qry.Query, Limit: int64(qry.Limit), Offset: int64(qry.From)} res, err := s.resourceClient.Search(ctx, req) if err != nil { s.logger.Error("Failed to search resources", "error", err) diff --git a/pkg/storage/unified/resource/go.mod b/pkg/storage/unified/resource/go.mod index 3be85811afb..7f5f354e248 100644 --- a/pkg/storage/unified/resource/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -43,6 +43,32 @@ require ( go.etcd.io/bbolt v1.3.9 // indirect ) +require ( + github.com/RoaringBitmap/roaring v1.9.3 // indirect + github.com/bits-and-blooms/bitset v1.12.0 // indirect + github.com/blevesearch/bleve_index_api v1.1.10 // indirect + github.com/blevesearch/geo v0.1.20 // indirect + github.com/blevesearch/go-faiss v1.0.20 // indirect + github.com/blevesearch/go-porterstemmer v1.0.3 // indirect + github.com/blevesearch/gtreap v0.1.1 // indirect + github.com/blevesearch/mmap-go v1.0.4 // indirect + github.com/blevesearch/scorch_segment_api/v2 v2.2.15 // indirect + github.com/blevesearch/segment v0.9.1 // indirect + github.com/blevesearch/snowballstem v0.9.0 // indirect + github.com/blevesearch/upsidedown_store_api v1.0.2 // indirect + github.com/blevesearch/vellum v1.0.10 // indirect + github.com/blevesearch/zapx/v11 v11.3.10 // indirect + github.com/blevesearch/zapx/v12 v12.3.10 // indirect + github.com/blevesearch/zapx/v13 v13.3.10 // indirect + github.com/blevesearch/zapx/v14 v14.3.10 // indirect + github.com/blevesearch/zapx/v15 v15.3.13 // indirect + github.com/blevesearch/zapx/v16 v16.1.5 // indirect + github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/mschoch/smat v0.2.0 // indirect + go.etcd.io/bbolt v1.3.9 // indirect +) + require ( github.com/beorn7/perks v1.0.1 // indirect github.com/blevesearch/bleve/v2 v2.4.2 diff --git a/pkg/storage/unified/resource/index.go b/pkg/storage/unified/resource/index.go index cdd8b6f68c5..08988e4c128 100644 --- a/pkg/storage/unified/resource/index.go +++ b/pkg/storage/unified/resource/index.go @@ -6,11 +6,13 @@ import ( "fmt" "log" "os" + "strings" "github.com/blevesearch/bleve/v2" "github.com/blevesearch/bleve/v2/analysis/lang/en" "github.com/blevesearch/bleve/v2/mapping" "github.com/google/uuid" + "golang.org/x/exp/slices" ) type Shard struct { @@ -111,7 +113,7 @@ func (i *Index) Delete(ctx context.Context, uid string, key *ResourceKey) error return nil } -func (i *Index) Search(ctx context.Context, tenant string, query string) ([]string, error) { +func (i *Index) Search(ctx context.Context, tenant string, query string, limit int, offset int) ([]SearchSummary, error) { if tenant == "" { tenant = "default" } @@ -119,20 +121,50 @@ func (i *Index) Search(ctx context.Context, tenant string, query string) ([]stri if err != nil { return nil, err } + + // use 10 as a default limit for now + if limit <= 0 { + limit = 10 + } + req := bleve.NewSearchRequest(bleve.NewQueryStringQuery(query)) - req.Fields = []string{"kind", "spec.title"} + req.From = offset + req.Size = limit + + req.Fields = []string{"*"} // return all indexed fields in search results res, err := shard.index.Search(req) if err != nil { return nil, err } - hits := res.Hits - results := []string{} - for _, hit := range hits { - val := fmt.Sprintf("%s:%s", hit.Fields["kind"], hit.Fields["spec.title"]) - results = append(results, val) + + results := make([]SearchSummary, len(hits)) + for resKey, hit := range hits { + searchSummary := SearchSummary{} + + // add common fields to search results + searchSummary.Kind = hit.Fields["kind"].(string) + searchSummary.Metadata.CreationTimestamp = hit.Fields["metadata.creationTimestamp"].(string) + searchSummary.Metadata.Uid = hit.Fields["metadata.uid"].(string) + + // add allowed indexed spec fields to search results + specResult := map[string]interface{}{} + for k, v := range hit.Fields { + if strings.HasPrefix(k, "spec.") { + mappedFields := specFieldMappings(searchSummary.Kind) + // should only include spec fields we care about in search results + if slices.Contains(mappedFields, k) { + specKey := strings.TrimPrefix(k, "spec.") + specResult[specKey] = v + } + } + searchSummary.Spec = specResult + } + + results[resKey] = searchSummary } + return results, nil } @@ -140,11 +172,17 @@ func tenant(res *Resource) string { return res.Metadata.Namespace } +type SearchSummary struct { + Kind string `json:"kind"` + Metadata `json:"metadata"` + Spec map[string]interface{} `json:"spec"` +} + type Metadata struct { Name string Namespace string - Uid string - CreationTimestamp string + Uid string `json:"uid"` + CreationTimestamp string `json:"creationTimestamp"` Labels map[string]string Annotations map[string]string } @@ -170,27 +208,26 @@ func createFileIndex() (bleve.Index, string, error) { return index, indexPath, err } -// TODO: clean this up. it was copied from owens performance test func createIndexMappings() *mapping.IndexMappingImpl { - //Create mapping for the name and creationTimestamp fields in the metadata - nameFieldMapping := bleve.NewTextFieldMapping() + //Create mapping for the creationTimestamp field in the metadata creationTimestampFieldMapping := bleve.NewDateTimeFieldMapping() + uidMapping := bleve.NewTextFieldMapping() metaMapping := bleve.NewDocumentMapping() - metaMapping.AddFieldMappingsAt("name", nameFieldMapping) metaMapping.AddFieldMappingsAt("creationTimestamp", creationTimestampFieldMapping) + metaMapping.AddFieldMappingsAt("uid", uidMapping) metaMapping.Dynamic = false metaMapping.Enabled = true + // Spec is different for all resources, so we create a dynamic mapping for it to index all fields (for now) specMapping := bleve.NewDocumentMapping() - specMapping.AddFieldMappingsAt("title", nameFieldMapping) - specMapping.Dynamic = false + specMapping.Dynamic = true specMapping.Enabled = true //Create a sub-document mapping for the metadata field objectMapping := bleve.NewDocumentMapping() objectMapping.AddSubDocumentMapping("metadata", metaMapping) objectMapping.AddSubDocumentMapping("spec", specMapping) - objectMapping.Dynamic = false + objectMapping.Dynamic = true objectMapping.Enabled = true // a generic reusable mapping for english text @@ -248,3 +285,14 @@ func fetchResourceTypes() []*ListOptions { }) return items } + +func specFieldMappings(kind string) []string { + mappedFields := map[string][]string{ + "Playlist": { + "spec.title", + "spec.interval", + }, + } + + return mappedFields[kind] +} diff --git a/pkg/storage/unified/resource/index_server.go b/pkg/storage/unified/resource/index_server.go index 47937e7f7f3..8feedacd60e 100644 --- a/pkg/storage/unified/resource/index_server.go +++ b/pkg/storage/unified/resource/index_server.go @@ -2,6 +2,7 @@ package resource import ( "context" + "encoding/json" "errors" "log" "strings" @@ -16,23 +17,27 @@ type IndexServer struct { ws *indexWatchServer } -func (is IndexServer) Search(ctx context.Context, req *SearchRequest) (*SearchResponse, error) { - results, err := is.index.Search(ctx, req.Tenant, req.Query) +func (is *IndexServer) Search(ctx context.Context, req *SearchRequest) (*SearchResponse, error) { + results, err := is.index.Search(ctx, req.Tenant, req.Query, int(req.Limit), int(req.Offset)) if err != nil { return nil, err } res := &SearchResponse{} for _, r := range results { - res.Items = append(res.Items, &ResourceWrapper{Value: []byte(r)}) + resJsonBytes, err := json.Marshal(r) + if err != nil { + return nil, err + } + res.Items = append(res.Items, &ResourceWrapper{Value: resJsonBytes}) } return res, nil } -func (is IndexServer) History(ctx context.Context, req *HistoryRequest) (*HistoryResponse, error) { +func (is *IndexServer) History(ctx context.Context, req *HistoryRequest) (*HistoryResponse, error) { return nil, nil } -func (is IndexServer) Origin(ctx context.Context, req *OriginRequest) (*OriginResponse, error) { +func (is *IndexServer) Origin(ctx context.Context, req *OriginRequest) (*OriginResponse, error) { return nil, nil } diff --git a/pkg/storage/unified/resource/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go index 7728dd880be..1a641f12284 100644 --- a/pkg/storage/unified/resource/resource.pb.go +++ b/pkg/storage/unified/resource/resource.pb.go @@ -1619,8 +1619,16 @@ type SearchRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` - Tenant string `protobuf:"bytes,2,opt,name=tenant,proto3" json:"tenant,omitempty"` + // query string for chosen implementation (currently just bleve) + Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"` + // default to bleve + QueryType string `protobuf:"bytes,2,opt,name=queryType,proto3" json:"queryType,omitempty"` + Tenant string `protobuf:"bytes,3,opt,name=tenant,proto3" json:"tenant,omitempty"` + // resource kind (playlists, dashboards, etc) + Kind string `protobuf:"bytes,4,opt,name=kind,proto3" json:"kind,omitempty"` + // pagination support + Limit int64 `protobuf:"varint,5,opt,name=limit,proto3" json:"limit,omitempty"` + Offset int64 `protobuf:"varint,6,opt,name=offset,proto3" json:"offset,omitempty"` } func (x *SearchRequest) Reset() { @@ -1662,6 +1670,13 @@ func (x *SearchRequest) GetQuery() string { return "" } +func (x *SearchRequest) GetQueryType() string { + if x != nil { + return x.QueryType + } + return "" +} + func (x *SearchRequest) GetTenant() string { if x != nil { return x.Tenant @@ -1669,6 +1684,27 @@ func (x *SearchRequest) GetTenant() string { return "" } +func (x *SearchRequest) GetKind() string { + if x != nil { + return x.Kind + } + return "" +} + +func (x *SearchRequest) GetLimit() int64 { + if x != nil { + return x.Limit + } + return 0 +} + +func (x *SearchRequest) GetOffset() int64 { + if x != nil { + return x.Offset + } + return 0 +} + type SearchResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -2449,136 +2485,142 @@ var file_resource_proto_rawDesc = []byte{ 0x09, 0x0a, 0x05, 0x41, 0x44, 0x44, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4d, 0x4f, 0x44, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x4f, 0x4f, 0x4b, 0x4d, 0x41, 0x52, - 0x4b, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x3d, - 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, - 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x22, 0x41, 0x0a, - 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, - 0x22, 0x9a, 0x01, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, - 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, - 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, - 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, - 0x6f, 0x77, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xbf, 0x01, - 0x0a, 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, - 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, - 0x8e, 0x01, 0x0a, 0x0d, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, - 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, - 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, - 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, - 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, - 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 0x22, 0xe5, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x72, 0x69, - 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x69, 0x7a, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, - 0x69, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, - 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x06, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, - 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, - 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xc4, 0x01, 0x0a, 0x0e, 0x4f, 0x72, 0x69, - 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x05, 0x69, - 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x72, - 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x12, - 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, - 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, - 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, 0x72, - 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x22, - 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, - 0xab, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, 0x0d, - 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, - 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, - 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, - 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x52, 0x56, - 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x2a, 0x33, 0x0a, - 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, 0x65, - 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, 0x74, - 0x10, 0x01, 0x32, 0xed, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, - 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x15, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, - 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, - 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x57, 0x61, 0x74, - 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x30, 0x01, 0x32, 0xc9, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x49, - 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, 0x17, - 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x2e, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x17, 0x2e, 0x72, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, - 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x57, - 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, 0x0a, - 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, - 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x4b, 0x10, 0x04, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x05, 0x22, 0x9d, + 0x01, 0x0a, 0x0d, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, 0x54, + 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x71, 0x75, 0x65, 0x72, 0x79, + 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x18, 0x03, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, 0x69, 0x6e, 0x64, + 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x6f, 0x66, 0x66, 0x73, 0x65, 0x74, 0x22, 0x41, + 0x0a, 0x0e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, + 0x73, 0x22, 0x9a, 0x01, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, + 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, + 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, + 0x69, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x68, 0x6f, 0x77, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0xbf, + 0x01, 0x0a, 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x22, 0x8e, 0x01, 0x0a, 0x0d, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, + 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, + 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, 0x69, + 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, + 0x6e, 0x22, 0xe5, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, + 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, + 0x67, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, 0x09, 0x74, + 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, + 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0xc4, 0x01, 0x0a, 0x0e, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x05, + 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, + 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x2b, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x45, 0x72, + 0x72, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, + 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x22, 0xab, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, - 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, - 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x4f, 0x0a, + 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, + 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x53, + 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x4e, 0x4f, 0x54, 0x5f, + 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, 0x0f, 0x53, 0x45, 0x52, + 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x03, 0x2a, 0x33, + 0x0a, 0x14, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x12, 0x10, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x4f, 0x6c, 0x64, + 0x65, 0x72, 0x54, 0x68, 0x61, 0x6e, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x78, 0x61, 0x63, + 0x74, 0x10, 0x01, 0x32, 0xed, 0x02, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, 0x15, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x61, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, + 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, + 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x37, 0x0a, 0x05, 0x57, 0x61, + 0x74, 0x63, 0x68, 0x12, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, + 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x14, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x45, 0x76, 0x65, 0x6e, + 0x74, 0x30, 0x01, 0x32, 0xc9, 0x01, 0x0a, 0x0d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x3b, 0x0a, 0x06, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x12, + 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, + 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x53, 0x65, 0x61, 0x72, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x17, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, + 0x57, 0x0a, 0x0b, 0x44, 0x69, 0x61, 0x67, 0x6e, 0x6f, 0x73, 0x74, 0x69, 0x63, 0x73, 0x12, 0x48, + 0x0a, 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, + 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, + 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x61, + 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/pkg/storage/unified/resource/resource.proto b/pkg/storage/unified/resource/resource.proto index dcaee25b909..bfbb654a3ae 100644 --- a/pkg/storage/unified/resource/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -325,8 +325,16 @@ message WatchEvent { } message SearchRequest { + // query string for chosen implementation (currently just bleve) string query = 1; - string tenant = 2; + // default to bleve + string queryType = 2; + string tenant = 3; + // resource kind (playlists, dashboards, etc) + string kind = 4; + // pagination support + int64 limit = 5; + int64 offset = 6; } message SearchResponse { From 8349db494701f3d50d14841fd9cbe0281bc50515 Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Wed, 9 Oct 2024 13:40:42 -0600 Subject: [PATCH 016/110] Table Component: Skip flaky test (#94506) * skip flaky test * use skip method on test --- packages/grafana-ui/src/components/Table/utils.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/grafana-ui/src/components/Table/utils.test.ts b/packages/grafana-ui/src/components/Table/utils.test.ts index 6f1d7f937f5..4883ffec578 100644 --- a/packages/grafana-ui/src/components/Table/utils.test.ts +++ b/packages/grafana-ui/src/components/Table/utils.test.ts @@ -545,7 +545,8 @@ describe('Table utils', () => { }); describe('guessLongestField', () => { - it('should guess the longest field correct if there are few records', () => { + // FLAKY TEST - https://drone.grafana.net/grafana/grafana/201232/1/5 + it.skip('should guess the longest field correct if there are few records', () => { const data = getWrappableData(10); const config = { defaults: { From 4a800eda9f4cf06b7abd2fd1ba3eb36e74285f58 Mon Sep 17 00:00:00 2001 From: Diego Augusto Molina Date: Wed, 9 Oct 2024 20:32:09 +0000 Subject: [PATCH 017/110] Unistore Chore: add inocuous preparative changes for otel-based db observability (#94473) add inocuous preparative changes for otel tracing --- pkg/storage/unified/sql/backend.go | 25 +- pkg/storage/unified/sql/db/dbimpl/dbEngine.go | 7 +- .../unified/sql/db/dbimpl/dbEngine_test.go | 14 +- pkg/storage/unified/sql/db/dbimpl/dbimpl.go | 11 +- .../unified/sql/db/dbimpl/driver_test.go | 95 ++++ .../dbimpl/regression_incident_2144_test.go | 90 ---- pkg/storage/unified/sql/db/mocks/DB.go | 482 ++++++++++++++++++ pkg/storage/unified/sql/db/mocks/Tx.go | 328 ++++++++++++ pkg/storage/unified/sql/db/service.go | 3 + 9 files changed, 936 insertions(+), 119 deletions(-) create mode 100644 pkg/storage/unified/sql/db/dbimpl/driver_test.go create mode 100644 pkg/storage/unified/sql/db/mocks/DB.go create mode 100644 pkg/storage/unified/sql/db/mocks/Tx.go diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index 1a6e79e3257..556c860bbb7 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -10,18 +10,19 @@ import ( "time" "github.com/google/uuid" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" + "google.golang.org/protobuf/proto" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/storage/unified/resource" "github.com/grafana/grafana/pkg/storage/unified/sql/db" "github.com/grafana/grafana/pkg/storage/unified/sql/dbutil" "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" - "go.opentelemetry.io/otel/trace" - "go.opentelemetry.io/otel/trace/noop" - "google.golang.org/protobuf/proto" - apierrors "k8s.io/apimachinery/pkg/api/errors" ) -const trace_prefix = "sql.resource." +const tracePrefix = "sql.resource." const defaultPollingInterval = 100 * time.Millisecond type Backend interface { @@ -119,7 +120,7 @@ func (b *backend) Stop(_ context.Context) error { } func (b *backend) WriteEvent(ctx context.Context, event resource.WriteEvent) (int64, error) { - _, span := b.tracer.Start(ctx, trace_prefix+"WriteEvent") + _, span := b.tracer.Start(ctx, tracePrefix+"WriteEvent") defer span.End() // TODO: validate key ? switch event.Type { @@ -135,7 +136,7 @@ func (b *backend) WriteEvent(ctx context.Context, event resource.WriteEvent) (in } func (b *backend) create(ctx context.Context, event resource.WriteEvent) (int64, error) { - ctx, span := b.tracer.Start(ctx, trace_prefix+"Create") + ctx, span := b.tracer.Start(ctx, tracePrefix+"Create") defer span.End() var newVersion int64 guid := uuid.New().String() @@ -192,7 +193,7 @@ func (b *backend) create(ctx context.Context, event resource.WriteEvent) (int64, } func (b *backend) update(ctx context.Context, event resource.WriteEvent) (int64, error) { - ctx, span := b.tracer.Start(ctx, trace_prefix+"Update") + ctx, span := b.tracer.Start(ctx, tracePrefix+"Update") defer span.End() var newVersion int64 guid := uuid.New().String() @@ -251,7 +252,7 @@ func (b *backend) update(ctx context.Context, event resource.WriteEvent) (int64, } func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64, error) { - ctx, span := b.tracer.Start(ctx, trace_prefix+"Delete") + ctx, span := b.tracer.Start(ctx, tracePrefix+"Delete") defer span.End() var newVersion int64 guid := uuid.New().String() @@ -303,7 +304,7 @@ func (b *backend) delete(ctx context.Context, event resource.WriteEvent) (int64, } func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) *resource.ReadResponse { - _, span := b.tracer.Start(ctx, trace_prefix+".Read") + _, span := b.tracer.Start(ctx, tracePrefix+".Read") defer span.End() // TODO: validate key ? @@ -338,7 +339,7 @@ func (b *backend) ReadResource(ctx context.Context, req *resource.ReadRequest) * } func (b *backend) ListIterator(ctx context.Context, req *resource.ListRequest, cb func(resource.ListIterator) error) (int64, error) { - _, span := b.tracer.Start(ctx, trace_prefix+"List") + _, span := b.tracer.Start(ctx, tracePrefix+"List") defer span.End() if req.Options == nil || req.Options.Key.Group == "" || req.Options.Key.Resource == "" { @@ -608,7 +609,7 @@ func fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialec } func (b *backend) poll(ctx context.Context, grp string, res string, since int64, stream chan<- *resource.WrittenEvent) (int64, error) { - ctx, span := b.tracer.Start(ctx, trace_prefix+"poll") + ctx, span := b.tracer.Start(ctx, tracePrefix+"poll") defer span.End() var records []*historyPollResponse diff --git a/pkg/storage/unified/sql/db/dbimpl/dbEngine.go b/pkg/storage/unified/sql/db/dbimpl/dbEngine.go index 3968cfc7839..46392d04cb6 100644 --- a/pkg/storage/unified/sql/db/dbimpl/dbEngine.go +++ b/pkg/storage/unified/sql/db/dbimpl/dbEngine.go @@ -9,11 +9,10 @@ import ( "github.com/go-sql-driver/mysql" "xorm.io/xorm" - "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/storage/unified/sql/db" ) -func getEngineMySQL(getter confGetter, tracer tracing.Tracer) (*xorm.Engine, error) { +func getEngineMySQL(getter confGetter) (*xorm.Engine, error) { config := mysql.NewConfig() config.User = getter.String("user") // accept the core Grafana jargon of `password` as well, originally Unified @@ -54,8 +53,6 @@ func getEngineMySQL(getter confGetter, tracer tracing.Tracer) (*xorm.Engine, err } // FIXME: get rid of xorm - // TODO figure out why wrapping the db driver with hooks causes mysql errors when writing - //driverName := sqlstore.WrapDatabaseDriverWithHooks(db.DriverMySQL, tracer) engine, err := xorm.NewEngine(db.DriverMySQL, config.FormatDSN()) if err != nil { return nil, fmt.Errorf("open database: %w", err) @@ -68,7 +65,7 @@ func getEngineMySQL(getter confGetter, tracer tracing.Tracer) (*xorm.Engine, err return engine, nil } -func getEnginePostgres(getter confGetter, tracer tracing.Tracer) (*xorm.Engine, error) { +func getEnginePostgres(getter confGetter) (*xorm.Engine, error) { dsnKV := map[string]string{ "user": getter.String("user"), // accept the core Grafana jargon of `password` as well, originally diff --git a/pkg/storage/unified/sql/db/dbimpl/dbEngine_test.go b/pkg/storage/unified/sql/db/dbimpl/dbEngine_test.go index 659d3bd1bb6..583e2bcdc99 100644 --- a/pkg/storage/unified/sql/db/dbimpl/dbEngine_test.go +++ b/pkg/storage/unified/sql/db/dbimpl/dbEngine_test.go @@ -25,14 +25,14 @@ func TestGetEngineMySQLFromConfig(t *testing.T) { t.Run("happy path - with key prefix", func(t *testing.T) { t.Parallel() - engine, err := getEngineMySQL(newValidMySQLGetter(true), nil) + engine, err := getEngineMySQL(newValidMySQLGetter(true)) assert.NotNil(t, engine) assert.NoError(t, err) }) t.Run("happy path - without key prefix", func(t *testing.T) { t.Parallel() - engine, err := getEngineMySQL(newValidMySQLGetter(false), nil) + engine, err := getEngineMySQL(newValidMySQLGetter(false)) assert.NotNil(t, engine) assert.NoError(t, err) }) @@ -47,7 +47,7 @@ func TestGetEngineMySQLFromConfig(t *testing.T) { "db_user": "user", "db_password": "password", }, "db_") - engine, err := getEngineMySQL(getter, nil) + engine, err := getEngineMySQL(getter) assert.Nil(t, engine) assert.Error(t, err) assert.ErrorIs(t, err, errInvalidUTF8Sequence) @@ -73,14 +73,14 @@ func TestGetEnginePostgresFromConfig(t *testing.T) { t.Run("happy path - with key prefix", func(t *testing.T) { t.Parallel() - engine, err := getEnginePostgres(newValidPostgresGetter(true), nil) + engine, err := getEnginePostgres(newValidPostgresGetter(true)) assert.NotNil(t, engine) assert.NoError(t, err) }) t.Run("happy path - without key prefix", func(t *testing.T) { t.Parallel() - engine, err := getEnginePostgres(newValidPostgresGetter(false), nil) + engine, err := getEnginePostgres(newValidPostgresGetter(false)) assert.NotNil(t, engine) assert.NoError(t, err) }) @@ -94,7 +94,7 @@ func TestGetEnginePostgresFromConfig(t *testing.T) { "db_user": "user", "db_password": "password", }, "db_") - engine, err := getEnginePostgres(getter, nil) + engine, err := getEnginePostgres(getter) assert.Nil(t, engine) assert.Error(t, err) @@ -110,7 +110,7 @@ func TestGetEnginePostgresFromConfig(t *testing.T) { "db_user": "user", "db_password": "password", }, "db_") - engine, err := getEnginePostgres(getter, nil) + engine, err := getEnginePostgres(getter) assert.Nil(t, engine) assert.Error(t, err) diff --git a/pkg/storage/unified/sql/db/dbimpl/dbimpl.go b/pkg/storage/unified/sql/db/dbimpl/dbimpl.go index 4756a1f4d56..2264ad3c8a3 100644 --- a/pkg/storage/unified/sql/db/dbimpl/dbimpl.go +++ b/pkg/storage/unified/sql/db/dbimpl/dbimpl.go @@ -9,6 +9,7 @@ import ( "github.com/dlmiddlecote/sqlstats" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/prometheus/client_golang/prometheus" + "go.opentelemetry.io/otel/trace" "xorm.io/xorm" infraDB "github.com/grafana/grafana/pkg/infra/db" @@ -63,7 +64,7 @@ type resourceDBProvider struct { logQueries bool } -func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer tracing.Tracer) (p *resourceDBProvider, err error) { +func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer trace.Tracer) (p *resourceDBProvider, err error) { // Resource API has other configs in its section besides database ones, so // we prefix them with "db_". We use the database config from core Grafana // as fallback, and as it uses a dedicated INI section, then keys are not @@ -85,12 +86,12 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer tracin // specific to Unified Storage case dbType == dbTypePostgres: p.registerMetrics = true - p.engine, err = getEnginePostgres(getter, tracer) + p.engine, err = getEnginePostgres(getter) return p, err case dbType == dbTypeMySQL: p.registerMetrics = true - p.engine, err = getEngineMySQL(getter, tracer) + p.engine, err = getEngineMySQL(getter) return p, err // TODO: add support for SQLite @@ -103,12 +104,12 @@ func newResourceDBProvider(grafanaDB infraDB.DB, cfg *setting.Cfg, tracer tracin case grafanaDBType == dbTypePostgres: p.registerMetrics = true - p.engine, err = getEnginePostgres(fallbackGetter, tracer) + p.engine, err = getEnginePostgres(fallbackGetter) return p, err case grafanaDBType == dbTypeMySQL: p.registerMetrics = true - p.engine, err = getEngineMySQL(fallbackGetter, tracer) + p.engine, err = getEngineMySQL(fallbackGetter) return p, err // TODO: add support for SQLite diff --git a/pkg/storage/unified/sql/db/dbimpl/driver_test.go b/pkg/storage/unified/sql/db/dbimpl/driver_test.go new file mode 100644 index 00000000000..b52f70e1197 --- /dev/null +++ b/pkg/storage/unified/sql/db/dbimpl/driver_test.go @@ -0,0 +1,95 @@ +package dbimpl + +import ( + "context" + "database/sql" + "database/sql/driver" + "sync" +) + +var _ driver.Driver = driverWithoutIsolationLevel{} +var _ driver.Driver = driverWithIsolationLevel{} + +const ( + driverWithoutIsolationLevelName = "test driver without isolation levels" + driverWithIsolationLevelName = "test driver with isolation levels" +) + +var registerTestDriversOnce sync.Once + +func registerTestSQLDrivers() { + registerTestDriversOnce.Do(func() { + sql.Register(driverWithoutIsolationLevelName, driverWithoutIsolationLevel{}) + sql.Register(driverWithIsolationLevelName, driverWithIsolationLevel{}) + }) +} + +type ( + // without isolation level + + driverWithoutIsolationLevel struct{} + connWithoutIsolationLevel struct{} + + // with isolation level + + driverWithIsolationLevel struct{} + connWithIsolationLevel struct { + connWithoutIsolationLevel + } + + // common + + testStmt struct{} + testTx struct{} + testResults struct{} + testRows struct{} +) + +// driver.Driver + +func (driverWithoutIsolationLevel) Open(name string) (driver.Conn, error) { + return connWithoutIsolationLevel{}, nil +} + +func (driverWithIsolationLevel) Open(name string) (driver.Conn, error) { + return connWithIsolationLevel{}, nil +} + +// driver.Conn + +func (connWithoutIsolationLevel) Prepare(query string) (driver.Stmt, error) { + return testStmt{}, nil +} +func (connWithoutIsolationLevel) Close() error { + return nil +} +func (connWithoutIsolationLevel) Begin() (driver.Tx, error) { + return testTx{}, nil +} + +func (connWithIsolationLevel) BeginTx(context.Context, driver.TxOptions) (driver.Tx, error) { + return testTx{}, nil +} + +// driver.Stmt + +func (testStmt) Close() error { return nil } +func (testStmt) NumInput() int { return 0 } +func (testStmt) Exec(args []driver.Value) (driver.Result, error) { return testResults{}, nil } +func (testStmt) Query(args []driver.Value) (driver.Rows, error) { return testRows{}, nil } + +// driver.Tx + +func (testTx) Commit() error { return nil } +func (testTx) Rollback() error { return nil } + +// driver.Results + +func (testResults) LastInsertId() (int64, error) { return 1, nil } +func (testResults) RowsAffected() (int64, error) { return 1, nil } + +// driver.Rows + +func (testRows) Columns() []string { return nil } +func (testRows) Close() error { return nil } +func (testRows) Next(dest []driver.Value) error { return nil } diff --git a/pkg/storage/unified/sql/db/dbimpl/regression_incident_2144_test.go b/pkg/storage/unified/sql/db/dbimpl/regression_incident_2144_test.go index 1d0b6ba9b02..ad29caf8cd2 100644 --- a/pkg/storage/unified/sql/db/dbimpl/regression_incident_2144_test.go +++ b/pkg/storage/unified/sql/db/dbimpl/regression_incident_2144_test.go @@ -1,10 +1,7 @@ package dbimpl import ( - "context" "database/sql" - "database/sql/driver" - "sync" "testing" "github.com/stretchr/testify/require" @@ -16,93 +13,6 @@ import ( const noIsolationLevelSupportErrStr = "sql: driver does not support non-" + "default isolation level" -var _ driver.Driver = driverWithoutIsolationLevel{} -var _ driver.Driver = driverWithIsolationLevel{} - -const ( - driverWithoutIsolationLevelName = "test driver without isolation levels" - driverWithIsolationLevelName = "test driver with isolation levels" -) - -var registerTestDriversOnce sync.Once - -func registerTestSQLDrivers() { - registerTestDriversOnce.Do(func() { - sql.Register(driverWithoutIsolationLevelName, driverWithoutIsolationLevel{}) - sql.Register(driverWithIsolationLevelName, driverWithIsolationLevel{}) - }) -} - -type ( - // without isolation level - - driverWithoutIsolationLevel struct{} - connWithoutIsolationLevel struct{} - - // with isolation level - - driverWithIsolationLevel struct{} - connWithIsolationLevel struct { - connWithoutIsolationLevel - } - - // common - - testStmt struct{} - testTx struct{} - testResults struct{} - testRows struct{} -) - -// driver.Driver - -func (driverWithoutIsolationLevel) Open(name string) (driver.Conn, error) { - return connWithoutIsolationLevel{}, nil -} - -func (driverWithIsolationLevel) Open(name string) (driver.Conn, error) { - return connWithIsolationLevel{}, nil -} - -// driver.Conn - -func (connWithoutIsolationLevel) Prepare(query string) (driver.Stmt, error) { - return testStmt{}, nil -} -func (connWithoutIsolationLevel) Close() error { - return nil -} -func (connWithoutIsolationLevel) Begin() (driver.Tx, error) { - return testTx{}, nil -} - -func (connWithIsolationLevel) BeginTx(context.Context, driver.TxOptions) (driver.Tx, error) { - return testTx{}, nil -} - -// driver.Stmt - -func (testStmt) Close() error { return nil } -func (testStmt) NumInput() int { return 0 } -func (testStmt) Exec(args []driver.Value) (driver.Result, error) { return testResults{}, nil } -func (testStmt) Query(args []driver.Value) (driver.Rows, error) { return testRows{}, nil } - -// driver.Tx - -func (testTx) Commit() error { return nil } -func (testTx) Rollback() error { return nil } - -// driver.Results - -func (testResults) LastInsertId() (int64, error) { return 1, nil } -func (testResults) RowsAffected() (int64, error) { return 1, nil } - -// driver.Rows - -func (testRows) Columns() []string { return nil } -func (testRows) Close() error { return nil } -func (testRows) Next(dest []driver.Value) error { return nil } - func TestReproIncident2144IndependentOfGrafanaDB(t *testing.T) { t.Parallel() registerTestSQLDrivers() diff --git a/pkg/storage/unified/sql/db/mocks/DB.go b/pkg/storage/unified/sql/db/mocks/DB.go new file mode 100644 index 00000000000..fa86325a6aa --- /dev/null +++ b/pkg/storage/unified/sql/db/mocks/DB.go @@ -0,0 +1,482 @@ +// Code generated by mockery v2.43.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + db "github.com/grafana/grafana/pkg/storage/unified/sql/db" + mock "github.com/stretchr/testify/mock" + + sql "database/sql" +) + +// DB is an autogenerated mock type for the DB type +type DB struct { + mock.Mock +} + +type DB_Expecter struct { + mock *mock.Mock +} + +func (_m *DB) EXPECT() *DB_Expecter { + return &DB_Expecter{mock: &_m.Mock} +} + +// BeginTx provides a mock function with given fields: _a0, _a1 +func (_m *DB) BeginTx(_a0 context.Context, _a1 *sql.TxOptions) (db.Tx, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for BeginTx") + } + + var r0 db.Tx + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sql.TxOptions) (db.Tx, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(context.Context, *sql.TxOptions) db.Tx); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(db.Tx) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sql.TxOptions) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DB_BeginTx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BeginTx' +type DB_BeginTx_Call struct { + *mock.Call +} + +// BeginTx is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *sql.TxOptions +func (_e *DB_Expecter) BeginTx(_a0 interface{}, _a1 interface{}) *DB_BeginTx_Call { + return &DB_BeginTx_Call{Call: _e.mock.On("BeginTx", _a0, _a1)} +} + +func (_c *DB_BeginTx_Call) Run(run func(_a0 context.Context, _a1 *sql.TxOptions)) *DB_BeginTx_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*sql.TxOptions)) + }) + return _c +} + +func (_c *DB_BeginTx_Call) Return(_a0 db.Tx, _a1 error) *DB_BeginTx_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DB_BeginTx_Call) RunAndReturn(run func(context.Context, *sql.TxOptions) (db.Tx, error)) *DB_BeginTx_Call { + _c.Call.Return(run) + return _c +} + +// DriverName provides a mock function with given fields: +func (_m *DB) DriverName() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for DriverName") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// DB_DriverName_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DriverName' +type DB_DriverName_Call struct { + *mock.Call +} + +// DriverName is a helper method to define mock.On call +func (_e *DB_Expecter) DriverName() *DB_DriverName_Call { + return &DB_DriverName_Call{Call: _e.mock.On("DriverName")} +} + +func (_c *DB_DriverName_Call) Run(run func()) *DB_DriverName_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *DB_DriverName_Call) Return(_a0 string) *DB_DriverName_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_DriverName_Call) RunAndReturn(run func() string) *DB_DriverName_Call { + _c.Call.Return(run) + return _c +} + +// ExecContext provides a mock function with given fields: ctx, query, args +func (_m *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for ExecContext") + } + + var r0 sql.Result + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (sql.Result, error)); ok { + return rf(ctx, query, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) sql.Result); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sql.Result) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { + r1 = rf(ctx, query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DB_ExecContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExecContext' +type DB_ExecContext_Call struct { + *mock.Call +} + +// ExecContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *DB_Expecter) ExecContext(ctx interface{}, query interface{}, args ...interface{}) *DB_ExecContext_Call { + return &DB_ExecContext_Call{Call: _e.mock.On("ExecContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *DB_ExecContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *DB_ExecContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *DB_ExecContext_Call) Return(_a0 sql.Result, _a1 error) *DB_ExecContext_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DB_ExecContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (sql.Result, error)) *DB_ExecContext_Call { + _c.Call.Return(run) + return _c +} + +// PingContext provides a mock function with given fields: _a0 +func (_m *DB) PingContext(_a0 context.Context) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for PingContext") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DB_PingContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PingContext' +type DB_PingContext_Call struct { + *mock.Call +} + +// PingContext is a helper method to define mock.On call +// - _a0 context.Context +func (_e *DB_Expecter) PingContext(_a0 interface{}) *DB_PingContext_Call { + return &DB_PingContext_Call{Call: _e.mock.On("PingContext", _a0)} +} + +func (_c *DB_PingContext_Call) Run(run func(_a0 context.Context)) *DB_PingContext_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *DB_PingContext_Call) Return(_a0 error) *DB_PingContext_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_PingContext_Call) RunAndReturn(run func(context.Context) error) *DB_PingContext_Call { + _c.Call.Return(run) + return _c +} + +// QueryContext provides a mock function with given fields: ctx, query, args +func (_m *DB) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for QueryContext") + } + + var r0 *sql.Rows + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (*sql.Rows, error)); ok { + return rf(ctx, query, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Rows); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Rows) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { + r1 = rf(ctx, query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DB_QueryContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryContext' +type DB_QueryContext_Call struct { + *mock.Call +} + +// QueryContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *DB_Expecter) QueryContext(ctx interface{}, query interface{}, args ...interface{}) *DB_QueryContext_Call { + return &DB_QueryContext_Call{Call: _e.mock.On("QueryContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *DB_QueryContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *DB_QueryContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *DB_QueryContext_Call) Return(_a0 *sql.Rows, _a1 error) *DB_QueryContext_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *DB_QueryContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (*sql.Rows, error)) *DB_QueryContext_Call { + _c.Call.Return(run) + return _c +} + +// QueryRowContext provides a mock function with given fields: ctx, query, args +func (_m *DB) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for QueryRowContext") + } + + var r0 *sql.Row + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Row); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Row) + } + } + + return r0 +} + +// DB_QueryRowContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryRowContext' +type DB_QueryRowContext_Call struct { + *mock.Call +} + +// QueryRowContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *DB_Expecter) QueryRowContext(ctx interface{}, query interface{}, args ...interface{}) *DB_QueryRowContext_Call { + return &DB_QueryRowContext_Call{Call: _e.mock.On("QueryRowContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *DB_QueryRowContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *DB_QueryRowContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *DB_QueryRowContext_Call) Return(_a0 *sql.Row) *DB_QueryRowContext_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_QueryRowContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) *sql.Row) *DB_QueryRowContext_Call { + _c.Call.Return(run) + return _c +} + +// Stats provides a mock function with given fields: +func (_m *DB) Stats() sql.DBStats { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Stats") + } + + var r0 sql.DBStats + if rf, ok := ret.Get(0).(func() sql.DBStats); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(sql.DBStats) + } + + return r0 +} + +// DB_Stats_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Stats' +type DB_Stats_Call struct { + *mock.Call +} + +// Stats is a helper method to define mock.On call +func (_e *DB_Expecter) Stats() *DB_Stats_Call { + return &DB_Stats_Call{Call: _e.mock.On("Stats")} +} + +func (_c *DB_Stats_Call) Run(run func()) *DB_Stats_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *DB_Stats_Call) Return(_a0 sql.DBStats) *DB_Stats_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_Stats_Call) RunAndReturn(run func() sql.DBStats) *DB_Stats_Call { + _c.Call.Return(run) + return _c +} + +// WithTx provides a mock function with given fields: _a0, _a1, _a2 +func (_m *DB) WithTx(_a0 context.Context, _a1 *sql.TxOptions, _a2 func(context.Context, db.Tx) error) error { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for WithTx") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *sql.TxOptions, func(context.Context, db.Tx) error) error); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DB_WithTx_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'WithTx' +type DB_WithTx_Call struct { + *mock.Call +} + +// WithTx is a helper method to define mock.On call +// - _a0 context.Context +// - _a1 *sql.TxOptions +// - _a2 func(context.Context , db.Tx) error +func (_e *DB_Expecter) WithTx(_a0 interface{}, _a1 interface{}, _a2 interface{}) *DB_WithTx_Call { + return &DB_WithTx_Call{Call: _e.mock.On("WithTx", _a0, _a1, _a2)} +} + +func (_c *DB_WithTx_Call) Run(run func(_a0 context.Context, _a1 *sql.TxOptions, _a2 func(context.Context, db.Tx) error)) *DB_WithTx_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*sql.TxOptions), args[2].(func(context.Context, db.Tx) error)) + }) + return _c +} + +func (_c *DB_WithTx_Call) Return(_a0 error) *DB_WithTx_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *DB_WithTx_Call) RunAndReturn(run func(context.Context, *sql.TxOptions, func(context.Context, db.Tx) error) error) *DB_WithTx_Call { + _c.Call.Return(run) + return _c +} + +// NewDB creates a new instance of DB. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewDB(t interface { + mock.TestingT + Cleanup(func()) +}) *DB { + mock := &DB{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/storage/unified/sql/db/mocks/Tx.go b/pkg/storage/unified/sql/db/mocks/Tx.go new file mode 100644 index 00000000000..2927a125fae --- /dev/null +++ b/pkg/storage/unified/sql/db/mocks/Tx.go @@ -0,0 +1,328 @@ +// Code generated by mockery v2.43.1. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + sql "database/sql" +) + +// Tx is an autogenerated mock type for the Tx type +type Tx struct { + mock.Mock +} + +type Tx_Expecter struct { + mock *mock.Mock +} + +func (_m *Tx) EXPECT() *Tx_Expecter { + return &Tx_Expecter{mock: &_m.Mock} +} + +// Commit provides a mock function with given fields: +func (_m *Tx) Commit() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Commit") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Tx_Commit_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Commit' +type Tx_Commit_Call struct { + *mock.Call +} + +// Commit is a helper method to define mock.On call +func (_e *Tx_Expecter) Commit() *Tx_Commit_Call { + return &Tx_Commit_Call{Call: _e.mock.On("Commit")} +} + +func (_c *Tx_Commit_Call) Run(run func()) *Tx_Commit_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Tx_Commit_Call) Return(_a0 error) *Tx_Commit_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Tx_Commit_Call) RunAndReturn(run func() error) *Tx_Commit_Call { + _c.Call.Return(run) + return _c +} + +// ExecContext provides a mock function with given fields: ctx, query, args +func (_m *Tx) ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for ExecContext") + } + + var r0 sql.Result + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (sql.Result, error)); ok { + return rf(ctx, query, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) sql.Result); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sql.Result) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { + r1 = rf(ctx, query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Tx_ExecContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExecContext' +type Tx_ExecContext_Call struct { + *mock.Call +} + +// ExecContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *Tx_Expecter) ExecContext(ctx interface{}, query interface{}, args ...interface{}) *Tx_ExecContext_Call { + return &Tx_ExecContext_Call{Call: _e.mock.On("ExecContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *Tx_ExecContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *Tx_ExecContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *Tx_ExecContext_Call) Return(_a0 sql.Result, _a1 error) *Tx_ExecContext_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Tx_ExecContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (sql.Result, error)) *Tx_ExecContext_Call { + _c.Call.Return(run) + return _c +} + +// QueryContext provides a mock function with given fields: ctx, query, args +func (_m *Tx) QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for QueryContext") + } + + var r0 *sql.Rows + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) (*sql.Rows, error)); ok { + return rf(ctx, query, args...) + } + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Rows); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Rows) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, string, ...interface{}) error); ok { + r1 = rf(ctx, query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Tx_QueryContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryContext' +type Tx_QueryContext_Call struct { + *mock.Call +} + +// QueryContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *Tx_Expecter) QueryContext(ctx interface{}, query interface{}, args ...interface{}) *Tx_QueryContext_Call { + return &Tx_QueryContext_Call{Call: _e.mock.On("QueryContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *Tx_QueryContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *Tx_QueryContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *Tx_QueryContext_Call) Return(_a0 *sql.Rows, _a1 error) *Tx_QueryContext_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Tx_QueryContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) (*sql.Rows, error)) *Tx_QueryContext_Call { + _c.Call.Return(run) + return _c +} + +// QueryRowContext provides a mock function with given fields: ctx, query, args +func (_m *Tx) QueryRowContext(ctx context.Context, query string, args ...interface{}) *sql.Row { + var _ca []interface{} + _ca = append(_ca, ctx, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for QueryRowContext") + } + + var r0 *sql.Row + if rf, ok := ret.Get(0).(func(context.Context, string, ...interface{}) *sql.Row); ok { + r0 = rf(ctx, query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sql.Row) + } + } + + return r0 +} + +// Tx_QueryRowContext_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'QueryRowContext' +type Tx_QueryRowContext_Call struct { + *mock.Call +} + +// QueryRowContext is a helper method to define mock.On call +// - ctx context.Context +// - query string +// - args ...interface{} +func (_e *Tx_Expecter) QueryRowContext(ctx interface{}, query interface{}, args ...interface{}) *Tx_QueryRowContext_Call { + return &Tx_QueryRowContext_Call{Call: _e.mock.On("QueryRowContext", + append([]interface{}{ctx, query}, args...)...)} +} + +func (_c *Tx_QueryRowContext_Call) Run(run func(ctx context.Context, query string, args ...interface{})) *Tx_QueryRowContext_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(context.Context), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *Tx_QueryRowContext_Call) Return(_a0 *sql.Row) *Tx_QueryRowContext_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Tx_QueryRowContext_Call) RunAndReturn(run func(context.Context, string, ...interface{}) *sql.Row) *Tx_QueryRowContext_Call { + _c.Call.Return(run) + return _c +} + +// Rollback provides a mock function with given fields: +func (_m *Tx) Rollback() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Rollback") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Tx_Rollback_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Rollback' +type Tx_Rollback_Call struct { + *mock.Call +} + +// Rollback is a helper method to define mock.On call +func (_e *Tx_Expecter) Rollback() *Tx_Rollback_Call { + return &Tx_Rollback_Call{Call: _e.mock.On("Rollback")} +} + +func (_c *Tx_Rollback_Call) Run(run func()) *Tx_Rollback_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Tx_Rollback_Call) Return(_a0 error) *Tx_Rollback_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Tx_Rollback_Call) RunAndReturn(run func() error) *Tx_Rollback_Call { + _c.Call.Return(run) + return _c +} + +// NewTx creates a new instance of Tx. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewTx(t interface { + mock.TestingT + Cleanup(func()) +}) *Tx { + mock := &Tx{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/storage/unified/sql/db/service.go b/pkg/storage/unified/sql/db/service.go index dfd9019789d..09f4c664b67 100755 --- a/pkg/storage/unified/sql/db/service.go +++ b/pkg/storage/unified/sql/db/service.go @@ -5,6 +5,9 @@ import ( "database/sql" ) +//go:generate mockery --with-expecter --name DB +//go:generate mockery --with-expecter --name Tx + const ( DriverPostgres = "postgres" DriverMySQL = "mysql" From c6c93a02aa3f97a3db4c322b523fafe263b5505d Mon Sep 17 00:00:00 2001 From: Diego Augusto Molina Date: Wed, 9 Oct 2024 20:32:23 +0000 Subject: [PATCH 018/110] Chore: add GIT_BASE variable to Makefile to allow configuring lint-go-diff target (#94480) add GIT_BASE variable to Makefile to allow configuring lint-go-diff target --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 9346e4b9b10..9c8f178a3d9 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ GO_RACE_FLAG := $(if $(GO_RACE),-race) GO_BUILD_FLAGS += $(if $(GO_BUILD_DEV),-dev) GO_BUILD_FLAGS += $(if $(GO_BUILD_TAGS),-build-tags=$(GO_BUILD_TAGS)) GO_BUILD_FLAGS += $(GO_RACE_FLAG) +GIT_BASE = remotes/origin/main # GNU xargs has flag -r, and BSD xargs (e.g. MacOS) has that behaviour by default XARGSR = $(shell xargs --version 2>&1 | grep -q GNU && echo xargs -r || echo xargs) @@ -308,7 +309,7 @@ lint-go: golangci-lint ## Run all code checks for backend. You can use GO_LINT_F .PHONY: lint-go-diff lint-go-diff: $(GOLANGCI_LINT) - git diff --name-only remotes/origin/main | \ + git diff --name-only $(GIT_BASE) | \ grep '\.go$$' | \ $(XARGSR) dirname | \ sort -u | \ From 0a7b73124270f8beaf768d2274f738bf65786bfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A1bor=20Farkas?= Date: Wed, 9 Oct 2024 23:28:27 +0200 Subject: [PATCH 019/110] datasources: querier: request parsing failures are not http 500 (#94488) * datasources: querier: request parsing failures are not http500 * fix test --------- Co-authored-by: Adam Simpson --- pkg/registry/apis/query/query.go | 19 +++++++++++++------ pkg/tests/apis/query/query_test.go | 5 ++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/pkg/registry/apis/query/query.go b/pkg/registry/apis/query/query.go index e5bd2a0d4ba..dcdcb00d136 100644 --- a/pkg/registry/apis/query/query.go +++ b/pkg/registry/apis/query/query.go @@ -122,15 +122,22 @@ func (r *queryREST) Connect(connectCtx context.Context, name string, _ runtime.O // Parses the request and splits it into multiple sub queries (if necessary) req, err := b.parser.parseRequest(ctx, raw) if err != nil { + reason := metav1.StatusReasonInvalid + message := err.Error() + if errors.Is(err, datasources.ErrDataSourceNotFound) { + reason = metav1.StatusReasonNotFound // TODO, can we wrap the error somehow? - err = &errorsK8s.StatusError{ErrStatus: metav1.Status{ - Status: metav1.StatusFailure, - Code: http.StatusBadRequest, // the URL is found, but includes bad requests - Reason: metav1.StatusReasonNotFound, - Message: "datasource not found", - }} + message = "datasource not found" } + + err = &errorsK8s.StatusError{ErrStatus: metav1.Status{ + Status: metav1.StatusFailure, + Code: http.StatusBadRequest, + Reason: reason, + Message: message, + }} + responder.Error(err) return } diff --git a/pkg/tests/apis/query/query_test.go b/pkg/tests/apis/query/query_test.go index 02e63b6ca02..515c2910f15 100644 --- a/pkg/tests/apis/query/query_test.go +++ b/pkg/tests/apis/query/query_test.go @@ -143,9 +143,8 @@ func TestIntegrationSimpleQuery(t *testing.T) { "apiVersion": "v1", "metadata": {}, "status": "Failure", - "message": "did not execute expression [Y] due to a failure to of the dependent expression or query [X]", - "reason": "BadRequest", - "details": { "uid": "sse.dependencyError" }, + "message": "[sse.dependencyError] did not execute expression [Y] due to a failure to of the dependent expression or query [X]", + "reason": "Invalid", "code": 400 }`, string(body)) // require.JSONEq(t, `{ From 97de44b0c228697b45f57b7f9aff51905d89da35 Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Wed, 9 Oct 2024 15:50:54 -0600 Subject: [PATCH 020/110] Dashboard Sharing: Skips flaky e2e test for dashboard sharing (#94507) skips flaky e2e test for dashboard sharing --- .../dashboard-share-externally-create.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/e2e/dashboards-suite/dashboard-share-externally-create.spec.ts b/e2e/dashboards-suite/dashboard-share-externally-create.spec.ts index 39e01c4e60f..6548198013d 100644 --- a/e2e/dashboards-suite/dashboard-share-externally-create.spec.ts +++ b/e2e/dashboards-suite/dashboard-share-externally-create.spec.ts @@ -22,7 +22,9 @@ describe('Shared dashboards', () => { e2e.pages.ShareDashboardDrawer.ShareExternally.container().should('not.exist'); }); - it('Create a shared dashboard and check API', () => { + // Skipping due to being a flaky test + // https://drone.grafana.net/grafana/grafana/201217/6/14 + it.skip('Create a shared dashboard and check API', () => { openDashboard(); // Open share externally drawer @@ -111,7 +113,7 @@ describe('Shared dashboards', () => { }); }); - it('Disable a shared dashboard', () => { + it.skip('Disable a shared dashboard', () => { openDashboard(); //TODO Failing in CI/CD. Fix it From 0201e8e8fb96e8fd0f183958cb30bdb1f1fec3a0 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Thu, 10 Oct 2024 01:57:14 +0100 Subject: [PATCH 021/110] Chore/TimezonesEditor: Add vertical gaps between inputs (#94487) --- .betterer.results | 4 -- .../panel/timeseries/TimezonesEditor.tsx | 40 +++++++++---------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/.betterer.results b/.betterer.results index f83e79923d8..5aa32246134 100644 --- a/.betterer.results +++ b/.betterer.results @@ -6712,10 +6712,6 @@ exports[`better eslint`] = { "public/app/plugins/panel/timeseries/SpanNullsEditor.tsx:5381": [ [0, 0, 0, "\'HorizontalGroup\' import from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] ], - "public/app/plugins/panel/timeseries/TimezonesEditor.tsx:5381": [ - [0, 0, 0, "Styles should be written using objects.", "0"], - [0, 0, 0, "Styles should be written using objects.", "1"] - ], "public/app/plugins/panel/timeseries/migrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"], diff --git a/public/app/plugins/panel/timeseries/TimezonesEditor.tsx b/public/app/plugins/panel/timeseries/TimezonesEditor.tsx index d62a538aebf..968c68727ac 100644 --- a/public/app/plugins/panel/timeseries/TimezonesEditor.tsx +++ b/public/app/plugins/panel/timeseries/TimezonesEditor.tsx @@ -34,36 +34,34 @@ export const TimezonesEditor = ({ value, onChange }: Props) => { }; return ( -
+
    {value.map((tz, idx) => ( -
    - - setTimezone(idx, v)} - includeInternal={true} - value={tz ?? InternalTimeZones.default} - /> - +
  • + setTimezone(idx, v)} + includeInternal={true} + value={tz ?? InternalTimeZones.default} + /> {idx === value.length - 1 ? ( ) : ( removeTimezone(idx)} tooltip="Remove timezone" /> )} -
  • + ))} -
+ ); }; const getStyles = (theme: GrafanaTheme2) => ({ - wrapper: css` - width: 100%; - display: flex; - flex-direction: rows; - align-items: center; - `, - first: css` - margin-right: 8px; - flex-grow: 2; - `, + list: css({ + listStyle: 'none', + display: 'flex', + flexDirection: 'column', + gap: theme.spacing(0.5), + }), + listItem: css({ + display: 'flex', + gap: theme.spacing(1), + }), }); From e0217c37a173ab864169499272312cf147389120 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 10 Oct 2024 07:33:04 +0300 Subject: [PATCH 022/110] AlertmanagerContext: Remove react router dependency (#94470) * AlertmanagerContext: Remove react router dependency * Cleanup --- .../state/AlertmanagerContext.test.tsx | 51 ++++--------------- .../unified/state/AlertmanagerContext.tsx | 5 +- 2 files changed, 14 insertions(+), 42 deletions(-) diff --git a/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx b/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx index 9a4ab2346f4..a6b007f507e 100644 --- a/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx +++ b/public/app/features/alerting/unified/state/AlertmanagerContext.test.tsx @@ -1,9 +1,7 @@ import { renderHook } from '@testing-library/react'; -import { createMemoryHistory } from 'history'; import * as React from 'react'; -import { MemoryRouter, Router } from 'react-router-dom'; -import { CompatRouter } from 'react-router-dom-v5-compat'; +import { locationService } from '@grafana/runtime'; import store from 'app/core/store'; import { AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; @@ -34,11 +32,7 @@ describe('useAlertmanager', () => { .spyOn(useAlertManagerSources, 'useAlertManagersByPermission') .mockReturnValueOnce({ availableExternalDataSources: [], availableInternalDataSources: [] }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); const { result } = renderHook(() => useAlertmanager(), { wrapper }); @@ -52,11 +46,7 @@ describe('useAlertmanager', () => { }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); const { result } = renderHook(() => useAlertmanager(), { wrapper }); @@ -68,15 +58,10 @@ describe('useAlertmanager', () => { .spyOn(useAlertManagerSources, 'useAlertManagersByPermission') .mockReturnValueOnce({ availableExternalDataSources: [externalAmProm], availableInternalDataSources: [] }); - const history = createMemoryHistory(); - history.push({ search: `alertmanager=${externalAmProm.name}` }); + locationService.push({ search: `alertmanager=${externalAmProm.name}` }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); const { result } = renderHook(() => useAlertmanager(), { wrapper }); @@ -88,15 +73,10 @@ describe('useAlertmanager', () => { .spyOn(useAlertManagerSources, 'useAlertManagersByPermission') .mockReturnValueOnce({ availableExternalDataSources: [], availableInternalDataSources: [] }); - const history = createMemoryHistory(); - history.push({ search: `alertmanager=Not available external AM` }); + locationService.push({ search: `alertmanager=Not available external AM` }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); const { result } = renderHook(() => useAlertmanager(), { wrapper }); @@ -109,15 +89,11 @@ describe('useAlertmanager', () => { .mockReturnValueOnce({ availableExternalDataSources: [externalAmProm], availableInternalDataSources: [] }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, externalAmProm.name); - + locationService.push({ search: '' }); const { result } = renderHook(() => useAlertmanager(), { wrapper }); expect(result.current.selectedAlertmanager).toBe(externalAmProm.name); }); @@ -128,15 +104,10 @@ describe('useAlertmanager', () => { availableInternalDataSources: [], }); - const history = createMemoryHistory(); - history.push({ search: `alertmanager=${externalAmProm.name}` }); + locationService.push({ search: `alertmanager=${externalAmProm.name}` }); const wrapper = ({ children }: React.PropsWithChildren) => ( - - - {children} - - + {children} ); store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, externalAmMimir.name); diff --git a/public/app/features/alerting/unified/state/AlertmanagerContext.tsx b/public/app/features/alerting/unified/state/AlertmanagerContext.tsx index f2d6e774ce8..d196dd7b565 100644 --- a/public/app/features/alerting/unified/state/AlertmanagerContext.tsx +++ b/public/app/features/alerting/unified/state/AlertmanagerContext.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; +import { locationService } from '@grafana/runtime'; import store from 'app/core/store'; import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; import { useAlertManagersByPermission } from '../hooks/useAlertManagerSources'; -import { useURLSearchParams } from '../hooks/useURLSearchParams'; import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../utils/constants'; import { AlertManagerDataSource, @@ -30,7 +30,8 @@ interface Props extends React.PropsWithChildren { } const AlertmanagerProvider = ({ children, accessType, alertmanagerSourceName }: Props) => { - const [queryParams, updateQueryParams] = useURLSearchParams(); + const queryParams = locationService.getSearch(); + const updateQueryParams = locationService.partial; const allAvailableAlertManagers = useAlertManagersByPermission(accessType); const availableAlertManagers = allAvailableAlertManagers.availableInternalDataSources.concat( allAvailableAlertManagers.availableExternalDataSources From 97249d15d107056cfcdfc5c0f1f99a7f7afb407c Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Thu, 10 Oct 2024 07:33:16 +0300 Subject: [PATCH 023/110] Routing: Update alerting routes to react router 6 (#94469) * Update alertingRuleEditor * Update Policy.test * Update AlertRuleForm * Update ReceiversSection * Update SimplifiedRuleEditor.test.tsx * Update CloneRule.tsx --- .../notification-policies/Policy.test.tsx | 22 +++++++++++-------- .../components/receivers/ReceiversSection.tsx | 2 +- .../alert-rule-form/AlertRuleForm.tsx | 4 ++-- .../SimplifiedRuleEditor.test.tsx | 7 ++++-- .../unified/components/rules/CloneRule.tsx | 3 +-- public/test/helpers/alertingRuleEditor.tsx | 22 ++++++++++++------- 6 files changed, 36 insertions(+), 24 deletions(-) diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx index dfff88a463a..1e3a0af1583 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Policy.test.tsx @@ -1,10 +1,10 @@ -import { render, renderHook, screen, within } from '@testing-library/react'; +import { renderHook, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { first, noop } from 'lodash'; -import { Router } from 'react-router-dom'; -import { CompatRouter } from 'react-router-dom-v5-compat'; +import { Routes, Route } from 'react-router-dom-v5-compat'; +import { render } from 'test/test-utils'; -import { config, locationService } from '@grafana/runtime'; +import { config } from '@grafana/runtime'; import { contextSrv } from 'app/core/core'; import { AlertmanagerGroup, @@ -345,13 +345,17 @@ describe('Policy', () => { }); }); +// Doesn't matter which path the routes use, it just needs to match the initialEntries history entry to render the element const renderPolicy = (element: JSX.Element) => render( - - - {element} - - + + {element}} /> + , + { + historyOptions: { + initialEntries: ['/'], + }, + } ); const eq = MatcherOperator.equal; diff --git a/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx b/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx index a8c9a2591e8..5b3fb521e8c 100644 --- a/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx +++ b/public/app/features/alerting/unified/components/receivers/ReceiversSection.tsx @@ -1,6 +1,6 @@ import { css, cx } from '@emotion/css'; import * as React from 'react'; -import { Link } from 'react-router-dom'; +import { Link } from 'react-router-dom-v5-compat'; import { useToggle } from 'react-use'; import { GrafanaTheme2 } from '@grafana/data'; diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 8805b40cf1e..57c385d856e 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -1,7 +1,7 @@ import { css } from '@emotion/css'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { FormProvider, SubmitErrorHandler, UseFormWatch, useForm } from 'react-hook-form'; -import { useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom-v5-compat'; import { GrafanaTheme2 } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; @@ -82,7 +82,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { const routeParams = useParams<{ type: string; id: string }>(); const ruleType = translateRouteParamToRuleType(routeParams.type); - const uidFromParams = routeParams.id; + const uidFromParams = routeParams.id || ''; const [showDeleteModal, setShowDeleteModal] = useState(false); diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx index 70dbaf94fdf..c78fb2df968 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/SimplifiedRuleEditor.test.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react'; -import { Route } from 'react-router-dom'; +import { Routes, Route } from 'react-router-dom-v5-compat'; import { ui } from 'test/helpers/alertingRuleEditor'; import { clickSelectOption } from 'test/helpers/selectOptionInTest'; import { render, screen, waitForElementToBeRemoved, userEvent } from 'test/test-utils'; @@ -157,7 +157,10 @@ describe('Can create a new grafana managed alert using simplified routing', () = function renderSimplifiedRuleEditor() { return render( - + + } /> + } /> + , { historyOptions: { initialEntries: ['/alerting/new/alerting'] } } ); diff --git a/public/app/features/alerting/unified/components/rules/CloneRule.tsx b/public/app/features/alerting/unified/components/rules/CloneRule.tsx index cb0d0cdbc80..4e12142a156 100644 --- a/public/app/features/alerting/unified/components/rules/CloneRule.tsx +++ b/public/app/features/alerting/unified/components/rules/CloneRule.tsx @@ -1,6 +1,5 @@ import { forwardRef, useState } from 'react'; -import { useLocation } from 'react-router-dom'; -import { Navigate } from 'react-router-dom-v5-compat'; +import { Navigate, useLocation } from 'react-router-dom-v5-compat'; import { Button, ConfirmModal } from '@grafana/ui'; import { RuleIdentifier } from 'app/types/unified-alerting'; diff --git a/public/test/helpers/alertingRuleEditor.tsx b/public/test/helpers/alertingRuleEditor.tsx index b40cdb66d64..e07a335fad9 100644 --- a/public/test/helpers/alertingRuleEditor.tsx +++ b/public/test/helpers/alertingRuleEditor.tsx @@ -1,4 +1,4 @@ -import { Route } from 'react-router-dom'; +import { Routes, Route } from 'react-router-dom-v5-compat'; import { render } from 'test/test-utils'; import { byRole, byTestId, byText } from 'testing-library-selector'; @@ -35,11 +35,17 @@ export const ui = { }; export function renderRuleEditor(identifier?: string, recording = false) { - return render(, { - historyOptions: { - initialEntries: [ - identifier ? `/alerting/${identifier}/edit` : `/alerting/new/${recording ? 'recording' : 'alerting'}`, - ], - }, - }); + return render( + + } /> + } /> + , + { + historyOptions: { + initialEntries: [ + identifier ? `/alerting/${identifier}/edit` : `/alerting/new/${recording ? 'recording' : 'alerting'}`, + ], + }, + } + ); } From bc7386e8155654907f5d7b06ab23795b0aa9542b Mon Sep 17 00:00:00 2001 From: Marcus Andersson Date: Thu, 10 Oct 2024 08:27:57 +0200 Subject: [PATCH 024/110] PluginExtension: Added debug log (#94146) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * add simple scenes object with logs panel * return hardcoded log message from runtime ds * simplify log entry * use log in links registry * wired the log together. * wip * Connected the extensions log to the runtime datasource to steam logs * wired the other registies. * implemented child function. * set right field type on labels * set meta type * using the logger in various places. * added type of onclick. * removed time picker. * removed imports. * passing log to functions where they are needed. * moved scene into admin page. * minor improvement to the message. * added possibility to update query with values based on the data. * added filter suppoert. * wip * wip * fixed so extension points are displayed. * use log level from grafana data * fixed bugs with the filtering. * Fixed some logs. * only register extensions page in development mode. * fixed filtering. * added on click debug log. * PluginExtensions: Add debug log to Grafana (Rewrite to scenes-react) (#93954) * refactoring. * simplify it even more. * Update public/app/features/plugins/extensions/logs/LogViewer.tsx Co-authored-by: Erik Sundell * used VizGridLayout instead of VizGrid component. * Fixed feedback and fixed bug in filtering logic. * fixed another nit. * empty string instead of title. * Added tests and fixed error. * added test file. * regenerated yarn.lock * Update public/app/features/plugins/extensions/logs/filterTransformation.test.ts Co-authored-by: Levente Balogh * fixed nit. * more nits. * added more test cases. * simplified filtering logic. * removed unused dep. * defined broadcast channel in jest setup. * added tests for datasource. * fixed failed tests. * fixed tests. * fixing go lint issue. * silent go lint. * fixed lint issue. --------- Co-authored-by: Erik Sundell Co-authored-by: Torkel Ödegaard Co-authored-by: Levente Balogh --- package.json | 1 + pkg/services/navtree/navtreeimpl/admin.go | 12 + .../extensions/getExploreExtensionConfigs.tsx | 5 +- .../extensions/getPluginExtensions.test.tsx | 44 ++- .../plugins/extensions/getPluginExtensions.ts | 28 +- .../extensions/logs/LogViewFilters.tsx | 199 ++++++++++ .../plugins/extensions/logs/LogViewer.tsx | 62 +++ .../extensions/logs/dataSource.test.ts | 127 ++++++ .../plugins/extensions/logs/dataSource.ts | 104 +++++ .../logs/filterTransformation.test.ts | 361 ++++++++++++++++++ .../extensions/logs/filterTransformation.ts | 86 +++++ .../features/plugins/extensions/logs/log.ts | 94 +++++ .../plugins/extensions/logs/testUtils.ts | 31 ++ .../registry/AddedComponentsRegistry.test.ts | 38 +- .../registry/AddedComponentsRegistry.ts | 32 +- .../registry/AddedLinksRegistry.test.ts | 30 +- .../extensions/registry/AddedLinksRegistry.ts | 34 +- .../ExportedComponentsRegistry.test.ts | 48 ++- .../registry/ExposedComponentsRegistry.ts | 32 +- .../plugins/extensions/registry/Registry.ts | 6 +- .../extensions/usePluginComponent.test.tsx | 23 +- .../plugins/extensions/usePluginComponent.tsx | 30 +- .../extensions/usePluginComponents.test.tsx | 27 +- .../extensions/usePluginComponents.tsx | 13 +- .../extensions/usePluginExtensions.tsx | 13 +- .../extensions/usePluginLinks.test.tsx | 27 +- .../plugins/extensions/usePluginLinks.tsx | 22 +- .../plugins/extensions/utils.test.tsx | 206 +++++----- .../app/features/plugins/extensions/utils.tsx | 105 +++-- public/app/features/sandbox/TestStuffPage.tsx | 1 - public/app/routes/routes.tsx | 10 + public/test/jest-setup.ts | 9 + yarn.lock | 46 ++- 33 files changed, 1643 insertions(+), 263 deletions(-) create mode 100644 public/app/features/plugins/extensions/logs/LogViewFilters.tsx create mode 100644 public/app/features/plugins/extensions/logs/LogViewer.tsx create mode 100644 public/app/features/plugins/extensions/logs/dataSource.test.ts create mode 100644 public/app/features/plugins/extensions/logs/dataSource.ts create mode 100644 public/app/features/plugins/extensions/logs/filterTransformation.test.ts create mode 100644 public/app/features/plugins/extensions/logs/filterTransformation.ts create mode 100644 public/app/features/plugins/extensions/logs/log.ts create mode 100644 public/app/features/plugins/extensions/logs/testUtils.ts diff --git a/package.json b/package.json index bd16396820c..589fa327342 100644 --- a/package.json +++ b/package.json @@ -269,6 +269,7 @@ "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", "@grafana/scenes": "5.19.1", + "@grafana/scenes-react": "5.19.1", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index 5a4d9cd2335..18a71f24dde 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -11,8 +11,10 @@ import ( "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol" "github.com/grafana/grafana/pkg/services/serviceaccounts" + "github.com/grafana/grafana/pkg/setting" ) +// nolint: gocyclo func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink, error) { var configNodes []*navtree.NavLink ctx := c.Req.Context() @@ -103,6 +105,16 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink }) } + if s.cfg.Env == setting.Dev { + pluginsNodeLinks = append(pluginsNodeLinks, &navtree.NavLink{ + Text: "Extensions", + Icon: "plug", + SubTitle: "Extend the UI of plugins and Grafana", + Id: "extensions", + Url: s.cfg.AppSubURL + "/admin/extensions", + }) + } + pluginsNode := &navtree.NavLink{ Text: "Plugins and data", SubTitle: "Install plugins and define the relationships between data", diff --git a/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx b/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx index d842e451aa0..887e446c36d 100644 --- a/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx +++ b/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx @@ -3,7 +3,8 @@ import { contextSrv } from 'app/core/core'; import { dispatch } from 'app/store/store'; import { AccessControlAction } from 'app/types'; -import { createAddedLinkConfig, logWarning } from '../../plugins/extensions/utils'; +import { log } from '../../plugins/extensions/logs/log'; +import { createAddedLinkConfig } from '../../plugins/extensions/utils'; import { changeCorrelationEditorDetails } from '../state/main'; import { runQueries } from '../state/query'; @@ -54,7 +55,7 @@ export function getExploreExtensionConfigs(): PluginExtensionAddedLinkConfig[] { }), ]; } catch (error) { - logWarning(`Could not configure extensions for Explore due to: "${error}"`); + log.warning(`Could not configure extensions for Explore due to: "${error}"`); return []; } } diff --git a/public/app/features/plugins/extensions/getPluginExtensions.test.tsx b/public/app/features/plugins/extensions/getPluginExtensions.test.tsx index 23d44c6f540..925b0cfbd7b 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.test.tsx +++ b/public/app/features/plugins/extensions/getPluginExtensions.test.tsx @@ -4,6 +4,8 @@ import { PluginExtensionAddedComponentConfig, PluginExtensionAddedLinkConfig } f import { reportInteraction } from '@grafana/runtime'; import { getPluginExtensions } from './getPluginExtensions'; +import { log } from './logs/log'; +import { resetLogMock } from './logs/testUtils'; import { AddedComponentsRegistry } from './registry/AddedComponentsRegistry'; import { AddedLinksRegistry } from './registry/AddedLinksRegistry'; import { isReadOnlyProxy } from './utils'; @@ -16,6 +18,16 @@ jest.mock('@grafana/runtime', () => { }; }); +jest.mock('./logs/log', () => { + const { createLogMock } = jest.requireActual('./logs/testUtils'); + const original = jest.requireActual('./logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + async function createRegistries( preloadResults: Array<{ pluginId: string; @@ -77,8 +89,8 @@ describe('getPluginExtensions()', () => { }, }; - global.console.warn = jest.fn(); jest.mocked(reportInteraction).mockReset(); + resetLogMock(log); }); test('should return the extensions for the given placement', async () => { @@ -279,7 +291,7 @@ describe('getPluginExtensions()', () => { expect(context.title).toBe('New title from the context!'); }); - test('should catch errors in the configure() function and log them as warnings', async () => { + test('should catch errors in the configure() function and log them as error', async () => { link2.configure = jest.fn().mockImplementation(() => { throw new Error('Something went wrong!'); }); @@ -291,8 +303,11 @@ describe('getPluginExtensions()', () => { }).not.toThrow(); expect(link2.configure).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledWith('[Plugin Extensions] Something went wrong!'); + expect(log.error).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledWith('Failed to configure link with title "Link 2"', { + message: 'Something went wrong!', + stack: expect.stringContaining('Error: Something went wrong!'), + }); }); test('should skip the link extension if the configure() function returns with an invalid path', async () => { @@ -320,7 +335,7 @@ describe('getPluginExtensions()', () => { expect(link1.configure).toHaveBeenCalledTimes(1); expect(link2.configure).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(2); + expect(log.error).toHaveBeenCalledTimes(2); }); test('should skip the extension if any of the updated props returned by the configure() function are invalid', async () => { @@ -336,7 +351,7 @@ describe('getPluginExtensions()', () => { expect(extensions).toHaveLength(0); expect(link2.configure).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledTimes(1); }); test('should skip the extension if the configure() function returns a promise', async () => { @@ -347,7 +362,7 @@ describe('getPluginExtensions()', () => { expect(extensions).toHaveLength(0); expect(link2.configure).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledTimes(1); }); test('should skip (hide) the extension if the configure() function returns undefined', async () => { @@ -357,7 +372,7 @@ describe('getPluginExtensions()', () => { const { extensions } = getPluginExtensions({ ...registries, extensionPointId: extensionPoint2 }); expect(extensions).toHaveLength(0); - expect(global.console.warn).toHaveBeenCalledTimes(0); // As this is intentional, no warning should be logged + expect(log.warning).toHaveBeenCalledTimes(0); // As this is intentional, no warning should be logged }); test('should pass event, context and helper to extension onClick()', async () => { @@ -386,7 +401,7 @@ describe('getPluginExtensions()', () => { ); }); - test('should catch errors in async/promise-based onClick function and log them as warnings', async () => { + test('should catch errors in async/promise-based onClick function and log them as errors', async () => { link2.path = undefined; link2.onClick = jest.fn().mockRejectedValue(new Error('testing')); @@ -400,10 +415,10 @@ describe('getPluginExtensions()', () => { expect(extensions).toHaveLength(1); expect(link2.onClick).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledTimes(1); }); - test('should catch errors in the onClick() function and log them as warnings', async () => { + test('should catch errors in the onClick() function and log them as errors', async () => { link2.path = undefined; link2.onClick = jest.fn().mockImplementation(() => { throw new Error('Something went wrong!'); @@ -417,8 +432,11 @@ describe('getPluginExtensions()', () => { extension.onClick?.({} as React.MouseEvent); expect(link2.onClick).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledTimes(1); - expect(global.console.warn).toHaveBeenCalledWith('[Plugin Extensions] Something went wrong!'); + expect(log.error).toHaveBeenCalledTimes(1); + expect(log.error).toHaveBeenCalledWith('Something went wrong!', { + message: 'Something went wrong!', + stack: expect.stringContaining('Error: Something went wrong!'), + }); }); test('should pass a read only context to the onClick() function', async () => { diff --git a/public/app/features/plugins/extensions/getPluginExtensions.ts b/public/app/features/plugins/extensions/getPluginExtensions.ts index bd7d55d6e0d..acaaf8d97ac 100644 --- a/public/app/features/plugins/extensions/getPluginExtensions.ts +++ b/public/app/features/plugins/extensions/getPluginExtensions.ts @@ -8,13 +8,13 @@ import { } from '@grafana/data'; import { GetPluginExtensions } from '@grafana/runtime'; +import { log } from './logs/log'; import { AddedComponentRegistryItem } from './registry/AddedComponentsRegistry'; import { AddedLinkRegistryItem } from './registry/AddedLinksRegistry'; import { RegistryType } from './registry/Registry'; import type { PluginExtensionRegistries } from './registry/types'; import { getReadOnlyProxy, - logWarning, generateExtensionId, wrapWithPluginContext, getLinkExtensionOnClick, @@ -78,8 +78,16 @@ export const getPluginExtensions: GetExtensions = ({ extensionsByPlugin[pluginId] = 0; } + const linkLog = log.child({ + pluginId, + extensionPointId, + path: addedLink.path ?? '', + title: addedLink.title, + description: addedLink.description, + onClick: typeof addedLink.onClick, + }); // Run the configure() function with the current context, and apply the ovverides - const overrides = getLinkExtensionOverrides(pluginId, addedLink, frozenContext); + const overrides = getLinkExtensionOverrides(pluginId, addedLink, linkLog, frozenContext); // configure() returned an `undefined` -> hide the extension if (addedLink.configure && overrides === undefined) { @@ -91,7 +99,7 @@ export const getPluginExtensions: GetExtensions = ({ id: generateExtensionId(pluginId, extensionPointId, addedLink.title), type: PluginExtensionTypes.link, pluginId: pluginId, - onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, frozenContext), + onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, linkLog, frozenContext), // Configurable properties icon: overrides?.icon || addedLink.icon, @@ -105,7 +113,10 @@ export const getPluginExtensions: GetExtensions = ({ extensionsByPlugin[pluginId] += 1; } catch (error) { if (error instanceof Error) { - logWarning(error.message); + log.error(error.message, { + stack: error.stack ?? '', + message: error.message, + }); } } } @@ -120,13 +131,20 @@ export const getPluginExtensions: GetExtensions = ({ if (extensionsByPlugin[addedComponent.pluginId] === undefined) { extensionsByPlugin[addedComponent.pluginId] = 0; } + + const componentLog = log.child({ + title: addedComponent.title, + description: addedComponent.description, + pluginId: addedComponent.pluginId, + }); + const extension: PluginExtensionComponent = { id: generateExtensionId(addedComponent.pluginId, extensionPointId, addedComponent.title), type: PluginExtensionTypes.component, pluginId: addedComponent.pluginId, title: addedComponent.title, description: addedComponent.description, - component: wrapWithPluginContext(addedComponent.pluginId, addedComponent.component), + component: wrapWithPluginContext(addedComponent.pluginId, addedComponent.component, componentLog), }; extensions.push(extension); diff --git a/public/app/features/plugins/extensions/logs/LogViewFilters.tsx b/public/app/features/plugins/extensions/logs/LogViewFilters.tsx new file mode 100644 index 00000000000..549af8fcbee --- /dev/null +++ b/public/app/features/plugins/extensions/logs/LogViewFilters.tsx @@ -0,0 +1,199 @@ +import { isEmpty } from 'lodash'; +import { ReactElement, useMemo } from 'react'; + +import { DataFrame, MatcherConfig, SelectableValue } from '@grafana/data'; +import { SceneDataProvider } from '@grafana/scenes'; +import { InlineField, InlineFieldRow, MultiSelect } from '@grafana/ui'; + +export type LogFilter = { + pluginIds?: Set; + extensionPointIds?: Set; + severity?: Set; + initial?: string; +}; + +type LogViewFiltersProps = { + provider: SceneDataProvider; + filteredProvider: SceneDataProvider; + filter: LogFilter; + onChange: (filter: LogFilter) => void; +}; + +export function LogViewFilters({ provider, filteredProvider, filter, onChange }: LogViewFiltersProps): ReactElement { + const { pluginIds, extensionPointIds, severity } = useLogFilters(provider, filteredProvider, filter); + + const onChangePluginIds = (values: Array>) => { + const update = { + ...filter, + pluginIds: mapToSet(values), + }; + + if (isEmpty(filter.extensionPointIds) && isEmpty(filter.severity)) { + update.initial = isEmpty(values) ? undefined : 'pluginId'; + } + + onChange(update); + }; + + const onChangeExtensionPointIds = (values: Array>) => { + const update = { + ...filter, + extensionPointIds: mapToSet(values), + }; + + if (isEmpty(filter.pluginIds) && isEmpty(filter.severity)) { + update.initial = isEmpty(values) ? undefined : 'extensionPointId'; + } + + onChange(update); + }; + + const onChangeSeverity = (values: Array>) => { + const update = { + ...filter, + severity: mapToSet(values), + }; + + if (isEmpty(filter.pluginIds) && isEmpty(filter.extensionPointIds)) { + update.initial = isEmpty(values) ? undefined : 'severity'; + } + + onChange(update); + }; + + return ( + + + + + + + + + + + + ); +} + +export type FilterConfig = { + fieldName: string; + config: MatcherConfig; +}; + +type LogFilterOptions = { + pluginIds: Array>; + extensionPointIds: Array>; + severity: Array>; +}; + +function useLogFilters( + provider: SceneDataProvider, + filteredProvider: SceneDataProvider, + filter: LogFilter +): LogFilterOptions { + const { data } = provider.useState(); + const { data: filteredData } = filteredProvider.useState(); + + return useMemo(() => { + if (data && data?.series.length > 1) { + console.warn('LogViewFilter does not support multiple series in query result.'); + } + + const frame = data?.series[0]; + const filteredFrame = filteredData?.series[0]; + + if (!frame) { + return { + pluginIds: [], + extensionPointIds: [], + severity: [], + }; + } + + if (!filteredFrame) { + return toFilterOptions({ + severity: frame, + pluginId: frame, + extensionPointId: frame, + }); + } + + switch (filter.initial) { + case 'extensionPointId': + return toFilterOptions({ + severity: filteredFrame, + pluginId: filteredFrame, + extensionPointId: frame, + }); + + case 'severity': + return toFilterOptions({ + severity: frame, + pluginId: filteredFrame, + extensionPointId: filteredFrame, + }); + + case 'pluginId': + return toFilterOptions({ + severity: filteredFrame, + pluginId: frame, + extensionPointId: filteredFrame, + }); + + default: + return toFilterOptions({ + severity: frame, + pluginId: frame, + extensionPointId: frame, + }); + } + }, [data, filteredData, filter]); +} + +function mapToSet(selected: Array>): Set | undefined { + if (selected.length <= 0) { + return undefined; + } + + return selected.reduce((set, selectable) => { + if (selectable.value) { + set.add(selectable.value); + } + return set; + }, new Set()); +} + +function toSelectableArray(source: Set): Array> { + return Array.from(source).reduce((all: Array>, current) => { + if (!current) { + return all; + } + all.push({ + value: current, + label: current, + }); + return all; + }, []); +} + +function toFilterOptions(sources: { + severity: DataFrame; + pluginId: DataFrame; + extensionPointId: DataFrame; +}): LogFilterOptions { + const { severity, pluginId, extensionPointId } = sources; + const severityIndex = severity.fields.findIndex((f) => f.name === 'severity'); + const pluginIdIndex = pluginId.fields.findIndex((f) => f.name === 'pluginId'); + const extensionPointIdIndex = extensionPointId.fields.findIndex((f) => f.name === 'extensionPointId'); + + const severities = new Set(severity.fields[severityIndex].values); + const pluginIds = new Set(pluginId.fields[pluginIdIndex].values); + const extensionPointIds = new Set(extensionPointId.fields[extensionPointIdIndex].values); + + return { + severity: toSelectableArray(severities), + pluginIds: toSelectableArray(pluginIds), + extensionPointIds: toSelectableArray(extensionPointIds), + }; +} diff --git a/public/app/features/plugins/extensions/logs/LogViewer.tsx b/public/app/features/plugins/extensions/logs/LogViewer.tsx new file mode 100644 index 00000000000..155a97611d5 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/LogViewer.tsx @@ -0,0 +1,62 @@ +import { nanoid } from 'nanoid'; +import { ReactElement, useState } from 'react'; + +import { sceneUtils, VizConfigBuilders } from '@grafana/scenes'; +import { + SceneContextProvider, + useDataTransformer, + useQueryRunner, + VizGridLayout, + VizPanel, +} from '@grafana/scenes-react'; +import { Page } from 'app/core/components/Page/Page'; + +import { LogFilter, LogViewFilters } from './LogViewFilters'; +import { ExtensionsLogDataSource } from './dataSource'; +import { createFilterTransformation } from './filterTransformation'; +import { log } from './log'; + +const DATASOURCE_REF = { + uid: nanoid(), + type: 'grafana-extensionslog-datasource', +}; + +const logsViz = VizConfigBuilders.logs().build(); + +sceneUtils.registerRuntimeDataSource({ + dataSource: new ExtensionsLogDataSource(DATASOURCE_REF.type, DATASOURCE_REF.uid, log), +}); + +export default function LogViewer(): ReactElement { + return ( + + + + ); +} + +function LogViewScene(): ReactElement | null { + const [filter, setFilter] = useState({}); + + const data = useQueryRunner({ + datasource: DATASOURCE_REF, + queries: [{ refId: 'A' }], + liveStreaming: true, + }); + + const filteredData = useDataTransformer({ + transformations: [createFilterTransformation(filter)], + data: data, + }); + + return ( + } + > + + + + + ); +} diff --git a/public/app/features/plugins/extensions/logs/dataSource.test.ts b/public/app/features/plugins/extensions/logs/dataSource.test.ts new file mode 100644 index 00000000000..e6b2db93a2c --- /dev/null +++ b/public/app/features/plugins/extensions/logs/dataSource.test.ts @@ -0,0 +1,127 @@ +import { nanoid } from 'nanoid'; +import { lastValueFrom, of } from 'rxjs'; + +import { DataQueryRequest, dateTime, LoadingState } from '@grafana/data'; + +import { ExtensionsLogDataSource } from './dataSource'; +import { log } from './log'; + +jest.mock('./log', () => { + const original = jest.requireActual('./log'); + return { + ...original, + log: { + asObservable: () => + of( + { + level: 'info', + labels: { + test: 'test', + }, + timestamp: Date.now(), + id: nanoid(), + message: 'a message', + pluginId: 'grafana-k8-app', + extensionPointId: 'grafana/dashboards/panel/menu', + }, + { + level: 'debug', + labels: { + title: 'a link', + onClick: 'function', + }, + timestamp: Date.now(), + id: nanoid(), + message: 'another message', + } + ), + }, + }; +}); + +describe('ExtensionsLogDataSource', () => { + const dataSource = new ExtensionsLogDataSource('pluginId', 'ds-uid', log); + + it('should return a stream when querying for data', async () => { + const response = await lastValueFrom(dataSource.query(createRequest())); + expect(response.state).toBe(LoadingState.Streaming); + }); + + it('should return logs as data frames when querying for data', async () => { + const { data } = await lastValueFrom(dataSource.query(createRequest())); + expect(data).toStrictEqual([ + { + refId: 'A', + meta: { + type: 'log-lines', + }, + length: 2, + fields: [ + { + config: expect.any(Object), + name: 'timestamp', + type: 'time', + values: [expect.any(Number), expect.any(Number)], + }, + { + config: expect.any(Object), + name: 'body', + type: 'string', + values: ['another message', 'a message'], + }, + { + config: expect.any(Object), + name: 'severity', + type: 'string', + values: ['debug', 'info'], + }, + { + config: expect.any(Object), + name: 'id', + type: 'string', + values: [expect.any(String), expect.any(String)], + }, + { + config: expect.any(Object), + name: 'labels', + type: 'other', + values: [{ onClick: 'function', title: 'a link' }, { test: 'test' }], + }, + { + config: expect.any(Object), + name: 'pluginId', + type: 'string', + values: [null, 'grafana-k8-app'], + }, + { + config: expect.any(Object), + name: 'extensionPointId', + type: 'string', + values: [null, 'grafana/dashboards/panel/menu'], + }, + ], + }, + ]); + }); +}); + +function createRequest(): DataQueryRequest { + return { + requestId: '', + interval: '', + intervalMs: 0, + range: { + from: dateTime(), + to: dateTime(), + raw: { + from: '', + to: '', + }, + }, + scopedVars: {}, + targets: [{ refId: 'A' }], + timezone: '', + app: '', + startTime: Date.now(), + }; +} diff --git a/public/app/features/plugins/extensions/logs/dataSource.ts b/public/app/features/plugins/extensions/logs/dataSource.ts new file mode 100644 index 00000000000..8b5ba2a4df1 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/dataSource.ts @@ -0,0 +1,104 @@ +import { Observable, scan } from 'rxjs'; + +import { + createDataFrame, + DataFrame, + DataFrameType, + DataQueryRequest, + DataQueryResponse, + FieldType, + LoadingState, + TestDataSourceResponse, +} from '@grafana/data'; +import { RuntimeDataSource, SceneDataQuery } from '@grafana/scenes'; + +import { ExtensionsLog, ExtensionsLogItem } from './log'; + +export class ExtensionsLogDataSource extends RuntimeDataSource { + constructor( + public readonly pluginId: string, + public readonly uid: string, + private readonly extensionsLog: ExtensionsLog + ) { + super(pluginId, uid); + } + + query(request: DataQueryRequest): Observable { + const [query] = request.targets; + + return this.extensionsLog.asObservable().pipe( + scan( + (response, item) => { + const [existing] = response.data; + + return { + data: [createFrame(query, item, existing)], + key: query.key ?? query.refId, + state: LoadingState.Streaming, + }; + }, + { + data: [], + key: query.key ?? query.refId, + state: LoadingState.Streaming, + } + ) + ); + } + + testDatasource(): Promise { + return Promise.resolve({ status: 'success', message: 'OK' }); + } +} + +function createFrame(query: SceneDataQuery, item: ExtensionsLogItem, existing?: DataFrame): DataFrame { + const timestamps = existing?.fields?.[0]?.values ?? []; + const messages = existing?.fields?.[1]?.values ?? []; + const levels = existing?.fields?.[2]?.values ?? []; + const ids = existing?.fields?.[3]?.values ?? []; + const labels = existing?.fields?.[4]?.values ?? []; + const pluginIds = existing?.fields?.[5]?.values ?? []; + const extensionPointIds = existing?.fields?.[6]?.values ?? []; + + return createDataFrame({ + refId: query.refId, + meta: { type: DataFrameType.LogLines }, + fields: [ + { + name: 'timestamp', + type: FieldType.time, + values: [item.timestamp, ...timestamps], + }, + { + name: 'body', + type: FieldType.string, + values: [item.message, ...messages], + }, + { + name: 'severity', + type: FieldType.string, + values: [item.level, ...levels], + }, + { + name: 'id', + type: FieldType.string, + values: [item.id, ...ids], + }, + { + name: 'labels', + type: FieldType.other, + values: [item.labels, ...labels], + }, + { + name: 'pluginId', + type: FieldType.string, + values: [item.pluginId ?? null, ...pluginIds], + }, + { + name: 'extensionPointId', + type: FieldType.string, + values: [item.extensionPointId ?? null, ...extensionPointIds], + }, + ], + }); +} diff --git a/public/app/features/plugins/extensions/logs/filterTransformation.test.ts b/public/app/features/plugins/extensions/logs/filterTransformation.test.ts new file mode 100644 index 00000000000..17ed1800535 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/filterTransformation.test.ts @@ -0,0 +1,361 @@ +import { lastValueFrom, Observable } from 'rxjs'; + +import { DataFrame, FieldType, toDataFrame } from '@grafana/data'; + +import { LogFilter } from './LogViewFilters'; +import { createFilterTransformation } from './filterTransformation'; + +const data = [ + toDataFrame({ + name: 'A', + length: 3, + fields: [ + { name: 'pluginId', type: FieldType.string, values: ['grafana-k8s-app', 'grafana', 'mckn-funnel-panel'] }, + { + name: 'extensionPointId', + type: FieldType.string, + values: [ + 'grafana/explore/toolbar/actions', + 'grafana-k8s-app/clusters/view/v1', + 'grafana/dashboards/panel/menu/v1', + ], + }, + { name: 'severity', type: FieldType.string, values: ['info', 'info', 'info'] }, + ], + }), + toDataFrame({ + name: 'B', + length: 3, + fields: [ + { name: 'pluginId', type: FieldType.string, values: ['grafana-k8s-app', 'grafana', 'mckn-funnel-panel'] }, + { + name: 'extensionPointId', + type: FieldType.string, + values: [ + 'grafana-k8s-app/clusters/view/v1', + 'grafana/dashboards/panel/menu/v1', + 'grafana/explore/toolbar/actions', + ], + }, + { name: 'severity', type: FieldType.string, values: ['debug', 'warning', 'error'] }, + ], + }), +]; + +describe('Transform data frames by filtering', () => { + it('should keep all rows when no filter is applied', async () => { + const [a, b] = await runTransformationWithFilter({}, data); + expect(a.length).toBe(data[0].length); + expect(b.length).toBe(data[1].length); + }); + + it('should exclude all rows not matching pluginId', async () => { + const filter = { + pluginIds: new Set(['grafana-k8s-app']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/explore/toolbar/actions'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['info'], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana-k8s-app/clusters/view/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['debug'], + }, + ]); + }); + + it('should exclude all rows not matching severity', async () => { + const filter = { + severity: new Set(['debug']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: [], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana-k8s-app/clusters/view/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['debug'], + }, + ]); + }); + + it('should exclude all rows not matching extensionPointId', async () => { + const filter = { + extensionPointIds: new Set(['grafana/dashboards/panel/menu/v1']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['mckn-funnel-panel'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/dashboards/panel/menu/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['info'], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/dashboards/panel/menu/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['warning'], + }, + ]); + }); + + it('should exclude all rows not matching pluginId and severity', async () => { + const filter = { + pluginIds: new Set(['grafana-k8s-app']), + severity: new Set(['debug']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: [], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana-k8s-app/clusters/view/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['debug'], + }, + ]); + }); + + it('should exclude all rows not matching pluginId and severity', async () => { + const filter = { + pluginIds: new Set(['grafana-k8s-app', 'grafana']), + severity: new Set(['info']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app', 'grafana'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/explore/toolbar/actions', 'grafana-k8s-app/clusters/view/v1'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['info', 'info'], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: [], + }, + ]); + }); + + it('should exclude all rows not matching one of pluginId with severity and extensionPointId', async () => { + const filter = { + pluginIds: new Set(['grafana-k8s-app', 'grafana']), + severity: new Set(['info']), + extensionPointIds: new Set(['grafana/explore/toolbar/actions']), + }; + const [a, b] = await runTransformationWithFilter(filter, data); + + expect(a.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: ['grafana-k8s-app'], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: ['grafana/explore/toolbar/actions'], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: ['info'], + }, + ]); + + expect(b.fields).toStrictEqual([ + { + config: {}, + name: 'pluginId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'extensionPointId', + type: FieldType.string, + values: [], + }, + { + config: {}, + name: 'severity', + type: FieldType.string, + values: [], + }, + ]); + }); +}); + +function runTransformationWithFilter(filter: LogFilter, frames: DataFrame[]): Promise { + const transformation = createFilterTransformation(filter); + const operator = transformation({ interpolate: () => '' }); + + return lastValueFrom( + new Observable((sub) => { + sub.next(frames); + sub.complete(); + }).pipe(operator) + ); +} diff --git a/public/app/features/plugins/extensions/logs/filterTransformation.ts b/public/app/features/plugins/extensions/logs/filterTransformation.ts new file mode 100644 index 00000000000..32852c38bdc --- /dev/null +++ b/public/app/features/plugins/extensions/logs/filterTransformation.ts @@ -0,0 +1,86 @@ +import { isEmpty } from 'lodash'; +import { Observable, scan } from 'rxjs'; + +import { createDataFrame, CustomTransformOperator, DataFrame, PartialDataFrame } from '@grafana/data'; + +import { LogFilter } from './LogViewFilters'; + +export function createFilterTransformation(filter: LogFilter): CustomTransformOperator { + return function cascadingFilterTransformation() { + return function (source: Observable) { + return source.pipe( + scan((filtered: DataFrame[], current) => { + if (isEmpty(filter.extensionPointIds) && isEmpty(filter.pluginIds) && isEmpty(filter.severity)) { + return current; + } + + for (const frame of current) { + const pluginIdIndex = frame.fields.findIndex((f) => f.name === 'pluginId'); + const extensionPointIdIndex = frame.fields.findIndex((f) => f.name === 'extensionPointId'); + const severityIndex = frame.fields.findIndex((f) => f.name === 'severity'); + + if (pluginIdIndex === -1 && !isEmpty(filter.pluginIds)) { + continue; + } + + if (extensionPointIdIndex === -1 && !isEmpty(filter.extensionPointIds)) { + continue; + } + + if (severityIndex === -1 && !isEmpty(filter.severity)) { + continue; + } + + const target: PartialDataFrame = { + ...frame, + fields: frame.fields.map((f) => ({ + ...f, + values: [], + })), + }; + + for (let index = 0; index < frame.length; index++) { + const pluginId = frame.fields[pluginIdIndex].values[index]; + const extensionPointId = frame.fields[extensionPointIdIndex].values[index]; + const severity = frame.fields[severityIndex].values[index]; + + if (!isEmpty(filter.pluginIds) && !filter.pluginIds?.has(pluginId)) { + continue; + } + + if (!isEmpty(filter.extensionPointIds) && !filter.extensionPointIds?.has(extensionPointId)) { + continue; + } + + if (!isEmpty(filter.severity) && !filter.severity?.has(severity)) { + continue; + } + + copyRow(frame, target, index); + } + + filtered.push(createDataFrame(target)); + } + + return filtered; + }, []) + ); + }; + }; +} + +function copyRow(source: DataFrame, target: PartialDataFrame, rowIndex: number) { + for (let index = 0; index < source.fields.length; index++) { + const field = source.fields[index]; + + if (!target.fields[index]) { + target.fields[index] = { + ...field, + values: [], + }; + } + + const value = source.fields[index].values[rowIndex]; + target.fields[index].values?.push(value); + } +} diff --git a/public/app/features/plugins/extensions/logs/log.ts b/public/app/features/plugins/extensions/logs/log.ts new file mode 100644 index 00000000000..a78d42ccea0 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/log.ts @@ -0,0 +1,94 @@ +import { isString } from 'lodash'; +import { nanoid } from 'nanoid'; +import { Observable, ReplaySubject } from 'rxjs'; + +import { Labels, LogLevel } from '@grafana/data'; + +export type ExtensionsLogItem = { + level: LogLevel; + timestamp: number; + labels: Labels; + message: string; + id: string; + pluginId?: string; + extensionPointId?: string; +}; + +const channelName = 'ui-extension-logs'; + +export class ExtensionsLog { + private baseLabels: Labels | undefined; + private subject: ReplaySubject | undefined; + private channel: BroadcastChannel; + + constructor(baseLabels?: Labels, subject?: ReplaySubject, channel?: BroadcastChannel) { + this.baseLabels = baseLabels; + this.channel = channel ?? new BroadcastChannel(channelName); + this.subject = subject; + } + + info(message: string, labels?: Labels): void { + this.log(LogLevel.info, message, labels); + } + + warning(message: string, labels?: Labels): void { + this.log(LogLevel.warning, message, labels); + } + + error(message: string, labels?: Labels): void { + this.log(LogLevel.error, message, labels); + } + + debug(message: string, labels?: Labels): void { + this.log(LogLevel.debug, message, labels); + } + + trace(message: string, labels?: Labels): void { + this.log(LogLevel.trace, message, labels); + } + + fatal(message: string, labels?: Labels): void { + this.log(LogLevel.fatal, message, labels); + } + + private log(level: LogLevel, message: string, labels?: Labels): void { + const combinedLabels = { ...labels, ...this.baseLabels }; + const { pluginId, extensionPointId } = combinedLabels; + + const item: ExtensionsLogItem = { + level: level, + labels: combinedLabels, + timestamp: Date.now(), + id: nanoid(), + message: message, + pluginId: isString(pluginId) ? pluginId : undefined, + extensionPointId: isString(extensionPointId) ? extensionPointId : undefined, + }; + + this.channel.postMessage(item); + } + + asObservable(): Observable { + if (!this.subject) { + // Lazily create the subject on first subscription to prevent + // to create buffers when no subscribers exists + this.subject = new ReplaySubject(1000, 1000 * 60 * 10); + this.channel.onmessage = (msg: MessageEvent) => this.subject?.next(msg.data); + } + + return this.subject.asObservable(); + } + + child(labels: Labels): ExtensionsLog { + return new ExtensionsLog( + { + ...labels, + ...this.baseLabels, + }, + this.subject, + this.channel + ); + } +} + +export const log = new ExtensionsLog(); diff --git a/public/app/features/plugins/extensions/logs/testUtils.ts b/public/app/features/plugins/extensions/logs/testUtils.ts new file mode 100644 index 00000000000..2877b9075c7 --- /dev/null +++ b/public/app/features/plugins/extensions/logs/testUtils.ts @@ -0,0 +1,31 @@ +import { ExtensionsLog } from './log'; + +export function createLogMock(): ExtensionsLog { + const { log: original } = jest.requireActual('./log'); + + const logMock = { + error: jest.fn(), + warning: jest.fn(), + info: jest.fn(), + debug: jest.fn(), + trace: jest.fn(), + fatal: jest.fn(), + child: jest.fn(), + }; + + logMock.child.mockReturnValue(logMock); + + return { + ...original, + ...logMock, + }; +} + +export function resetLogMock(log: ExtensionsLog): void { + jest.mocked(log.error).mockReset(); + jest.mocked(log.warning).mockReset(); + jest.mocked(log.info).mockReset(); + jest.mocked(log.debug).mockReset(); + jest.mocked(log.trace).mockReset(); + jest.mocked(log.fatal).mockReset(); +} diff --git a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts index 8e0041d185c..fcf3d4616bb 100644 --- a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts +++ b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.test.ts @@ -4,6 +4,8 @@ import { firstValueFrom } from 'rxjs'; import { PluginLoadingStrategy } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { log } from '../logs/log'; +import { resetLogMock } from '../logs/testUtils'; import { isGrafanaDevMode } from '../utils'; import { AddedComponentsRegistry } from './AddedComponentsRegistry'; @@ -17,8 +19,17 @@ jest.mock('../utils', () => ({ isGrafanaDevMode: jest.fn().mockReturnValue(false), })); +jest.mock('../logs/log', () => { + const { createLogMock } = jest.requireActual('../logs/testUtils'); + const original = jest.requireActual('../logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('AddedComponentsRegistry', () => { - const consoleWarn = jest.fn(); const originalApps = config.apps; const pluginId = 'grafana-basic-app'; const appPluginConfig = { @@ -47,8 +58,7 @@ describe('AddedComponentsRegistry', () => { }; beforeEach(() => { - global.console.warn = consoleWarn; - consoleWarn.mockReset(); + resetLogMock(log); jest.mocked(isGrafanaDevMode).mockReturnValue(false); config.apps = { [pluginId]: appPluginConfig, @@ -363,8 +373,8 @@ describe('AddedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - `[Plugin Extensions] Added component "Component 1 title": it's recommended to suffix the extension point id ("${extensionPointId}") with a version, e.g 'myorg-basic-app/extension-point/v1'.` + expect(log.warning).toHaveBeenCalledWith( + `Added component "Component 1 title": it's recommended to suffix the extension point id ("${extensionPointId}") with a version, e.g 'myorg-basic-app/extension-point/v1'.` ); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); @@ -386,8 +396,8 @@ describe('AddedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register added component with title 'Component 1 title'. Reason: Description is missing." + expect(log.error).toHaveBeenCalledWith( + "Could not register added component with title 'Component 1 title'. Reason: Description is missing." ); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); @@ -401,7 +411,7 @@ describe('AddedComponentsRegistry', () => { pluginId, configs: [ { - title: 'Component 1 title', + title: '', description: '', targets: [extensionPointId], component: () => React.createElement('div', null, 'Hello World1'), @@ -409,9 +419,7 @@ describe('AddedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register added component with title 'Component 1 title'. Reason: Description is missing." - ); + expect(log.error).toHaveBeenCalledWith('Could not register added component. Reason: Title is missing.'); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); @@ -497,7 +505,7 @@ describe('AddedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should register a component added by a core Grafana in dev-mode even if the meta-info is missing', async () => { @@ -520,7 +528,7 @@ describe('AddedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register a component added by a plugin in production mode even if the meta-info is missing', async () => { @@ -546,7 +554,7 @@ describe('AddedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register a component added by a plugin in dev-mode if the meta-info is present', async () => { @@ -572,6 +580,6 @@ describe('AddedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts index 1760f3d36cf..a57f56c6008 100644 --- a/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts +++ b/public/app/features/plugins/extensions/registry/AddedComponentsRegistry.ts @@ -2,7 +2,7 @@ import { ReplaySubject } from 'rxjs'; import { PluginExtensionAddedComponentConfig } from '@grafana/data'; -import { isAddedComponentMetaInfoMissing, isGrafanaDevMode, logWarning, wrapWithPluginContext } from '../utils'; +import { isAddedComponentMetaInfoMissing, isGrafanaDevMode, wrapWithPluginContext } from '../utils'; import { extensionPointEndsWithVersion, isGrafanaCoreExtensionPoint, isReactComponent } from '../validators'; import { PluginExtensionConfigs, Registry, RegistryType } from './Registry'; @@ -34,42 +34,58 @@ export class AddedComponentsRegistry extends Registry< const { pluginId, configs } = item; for (const config of configs) { + const configLog = this.logger.child({ + description: config.description, + title: config.title, + pluginId, + }); + if (!isReactComponent(config.component)) { - logWarning( - `Could not register added component with title '${config.title}'. Reason: The provided component is not a valid React component.` + configLog.error( + `Could not register added component. Reason: The provided component is not a valid React component.` ); continue; } if (!config.title) { - logWarning(`Could not register added component with title '${config.title}'. Reason: Title is missing.`); + configLog.error(`Could not register added component. Reason: Title is missing.`); continue; } if (!config.description) { - logWarning(`Could not register added component with title '${config.title}'. Reason: Description is missing.`); + configLog.error( + `Could not register added component with title '${config.title}'. Reason: Description is missing.` + ); continue; } - if (pluginId !== 'grafana' && isGrafanaDevMode() && isAddedComponentMetaInfoMissing(pluginId, config)) { + if ( + pluginId !== 'grafana' && + isGrafanaDevMode() && + isAddedComponentMetaInfoMissing(pluginId, config, configLog) + ) { continue; } const extensionPointIds = Array.isArray(config.targets) ? config.targets : [config.targets]; for (const extensionPointId of extensionPointIds) { + const pointIdLog = configLog.child({ extensionPointId }); + if (!isGrafanaCoreExtensionPoint(extensionPointId) && !extensionPointEndsWithVersion(extensionPointId)) { - logWarning( + pointIdLog.warning( `Added component "${config.title}": it's recommended to suffix the extension point id ("${extensionPointId}") with a version, e.g 'myorg-basic-app/extension-point/v1'.` ); } const result = { pluginId, - component: wrapWithPluginContext(pluginId, config.component), + component: wrapWithPluginContext(pluginId, config.component, pointIdLog), description: config.description, title: config.title, }; + pointIdLog.debug(`Added component from '${pluginId}' to '${extensionPointId}'`); + if (!(extensionPointId in registry)) { registry[extensionPointId] = [result]; } else { diff --git a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts index 45735806af9..3dd5b7c7f7d 100644 --- a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts +++ b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.test.ts @@ -3,6 +3,8 @@ import { firstValueFrom } from 'rxjs'; import { PluginLoadingStrategy } from '@grafana/data'; import { config } from '@grafana/runtime'; +import { log } from '../logs/log'; +import { resetLogMock } from '../logs/testUtils'; import { isGrafanaDevMode } from '../utils'; import { AddedLinksRegistry } from './AddedLinksRegistry'; @@ -16,9 +18,18 @@ jest.mock('../utils', () => ({ isGrafanaDevMode: jest.fn().mockReturnValue(false), })); +jest.mock('../logs/log', () => { + const { createLogMock } = jest.requireActual('../logs/testUtils'); + const original = jest.requireActual('../logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('AddedLinksRegistry', () => { const originalApps = config.apps; - const consoleWarn = jest.fn(); const pluginId = 'grafana-basic-app'; const appPluginConfig = { id: pluginId, @@ -46,8 +57,7 @@ describe('AddedLinksRegistry', () => { }; beforeEach(() => { - global.console.warn = consoleWarn; - consoleWarn.mockReset(); + resetLogMock(log); jest.mocked(isGrafanaDevMode).mockReturnValue(false); config.apps = { [pluginId]: appPluginConfig, @@ -503,7 +513,7 @@ describe('AddedLinksRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.error).toHaveBeenCalled(); observable.subscribe(subscribeCallback); expect(subscribeCallback).toHaveBeenCalledTimes(1); @@ -531,7 +541,7 @@ describe('AddedLinksRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.error).toHaveBeenCalled(); observable.subscribe(subscribeCallback); expect(subscribeCallback).toHaveBeenCalledTimes(1); @@ -559,7 +569,7 @@ describe('AddedLinksRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.error).toHaveBeenCalled(); observable.subscribe(subscribeCallback); expect(subscribeCallback).toHaveBeenCalledTimes(1); @@ -651,7 +661,7 @@ describe('AddedLinksRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should register a link added by core Grafana in dev-mode even if the meta-info is missing', async () => { @@ -675,7 +685,7 @@ describe('AddedLinksRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register a link added by a plugin in production mode even if the meta-info is missing', async () => { @@ -702,7 +712,7 @@ describe('AddedLinksRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register a link added by a plugin in dev-mode if the meta-info is present', async () => { @@ -729,6 +739,6 @@ describe('AddedLinksRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.ts b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.ts index d2f6207e496..0e978b993c6 100644 --- a/public/app/features/plugins/extensions/registry/AddedLinksRegistry.ts +++ b/public/app/features/plugins/extensions/registry/AddedLinksRegistry.ts @@ -3,7 +3,7 @@ import { ReplaySubject } from 'rxjs'; import { IconName, PluginExtensionAddedLinkConfig } from '@grafana/data'; import { PluginAddedLinksConfigureFunc, PluginExtensionEventHelpers } from '@grafana/data/src/types/pluginExtensions'; -import { isAddedLinkMetaInfoMissing, isGrafanaDevMode, logWarning } from '../utils'; +import { isAddedLinkMetaInfoMissing, isGrafanaDevMode } from '../utils'; import { extensionPointEndsWithVersion, isConfigureFnValid, @@ -43,43 +43,53 @@ export class AddedLinksRegistry extends Registry ({ isGrafanaDevMode: jest.fn().mockReturnValue(false), })); +jest.mock('../logs/log', () => { + const { createLogMock } = jest.requireActual('../logs/testUtils'); + const original = jest.requireActual('../logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('ExposedComponentsRegistry', () => { - const consoleWarn = jest.fn(); const originalApps = config.apps; const pluginId = 'grafana-basic-app'; const appPluginConfig = { @@ -47,8 +58,7 @@ describe('ExposedComponentsRegistry', () => { }; beforeEach(() => { - global.console.warn = consoleWarn; - consoleWarn.mockReset(); + resetLogMock(log); jest.mocked(isGrafanaDevMode).mockReturnValue(false); config.apps = { [pluginId]: appPluginConfig, @@ -282,7 +292,7 @@ describe('ExposedComponentsRegistry', () => { }); registry.register({ - pluginId: 'grafana-basic-app2', + pluginId: 'grafana-basic-app1', configs: [ { id: 'grafana-basic-app1/hello-world/v1', // incorrectly scoped @@ -293,8 +303,8 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register exposed component with id 'grafana-basic-app1/hello-world/v1'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'." + expect(log.error).toHaveBeenCalledWith( + "Could not register exposed component with 'grafana-basic-app1/hello-world/v1'. Reason: An exposed component with the same id already exists." ); const currentState2 = await registry.getState(); expect(Object.keys(currentState2)).toHaveLength(1); @@ -314,14 +324,14 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register exposed component with id 'hello-world/v1'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'." + expect(log.error).toHaveBeenCalledWith( + "Could not register exposed component with 'hello-world/v1'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'." ); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); }); - it('should log a warning when exposed component id is not suffixed with component version', async () => { + it('should log a error when exposed component id is not suffixed with component version', async () => { const registry = new ExposedComponentsRegistry(); registry.register({ pluginId: 'grafana-basic-app1', @@ -335,8 +345,8 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Exposed component with id 'grafana-basic-app1/hello-world' does not match the convention. It's recommended to suffix the id with the component version. e.g 'myorg-basic-app/my-component-id/v1'." + expect(log.error).toHaveBeenCalledWith( + "Exposed component does not match the convention. It's recommended to suffix the id with the component version. e.g 'myorg-basic-app/my-component-id/v1'." ); const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); @@ -357,8 +367,8 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register exposed component with id 'grafana-basic-app/hello-world/v1'. Reason: Description is missing." + expect(log.error).toHaveBeenCalledWith( + "Could not register exposed component with id 'grafana-basic-app/hello-world/v1'. Reason: Description is missing." ); const currentState = await registry.getState(); @@ -380,8 +390,8 @@ describe('ExposedComponentsRegistry', () => { ], }); - expect(consoleWarn).toHaveBeenCalledWith( - "[Plugin Extensions] Could not register exposed component with id 'grafana-basic-app/hello-world/v1'. Reason: Title is missing." + expect(log.error).toHaveBeenCalledWith( + "Could not register exposed component with id 'grafana-basic-app/hello-world/v1'. Reason: Title is missing." ); const currentState = await registry.getState(); @@ -468,7 +478,7 @@ describe('ExposedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(0); - expect(consoleWarn).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should register an exposed component added by a core Grafana in dev-mode even if the meta-info is missing', async () => { @@ -491,7 +501,7 @@ describe('ExposedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register an exposed component added by a plugin in production mode even if the meta-info is missing', async () => { @@ -517,7 +527,7 @@ describe('ExposedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should register an exposed component added by a plugin in dev-mode if the meta-info is present', async () => { @@ -543,6 +553,6 @@ describe('ExposedComponentsRegistry', () => { const currentState = await registry.getState(); expect(Object.keys(currentState)).toHaveLength(1); - expect(consoleWarn).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.ts b/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.ts index c84c89ab199..85f3776f5b9 100644 --- a/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.ts +++ b/public/app/features/plugins/extensions/registry/ExposedComponentsRegistry.ts @@ -2,7 +2,7 @@ import { ReplaySubject } from 'rxjs'; import { PluginExtensionExposedComponentConfig } from '@grafana/data'; -import { isExposedComponentMetaInfoMissing, isGrafanaDevMode, logWarning } from '../utils'; +import { isExposedComponentMetaInfoMissing, isGrafanaDevMode } from '../utils'; import { extensionPointEndsWithVersion } from '../validators'; import { Registry, RegistryType, PluginExtensionConfigs } from './Registry'; @@ -37,41 +37,53 @@ export class ExposedComponentsRegistry extends Registry< for (const config of configs) { const { id, description, title } = config; + const pointIdLog = this.logger.child({ + extensionPointId: id, + description, + title, + pluginId, + }); if (!id.startsWith(pluginId)) { - logWarning( - `Could not register exposed component with id '${id}'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'.` + pointIdLog.error( + `Could not register exposed component with '${id}'. Reason: The component id does not match the id naming convention. Id should be prefixed with plugin id. e.g 'myorg-basic-app/my-component-id/v1'.` ); continue; } if (!extensionPointEndsWithVersion(id)) { - logWarning( - `Exposed component with id '${id}' does not match the convention. It's recommended to suffix the id with the component version. e.g 'myorg-basic-app/my-component-id/v1'.` + pointIdLog.error( + `Exposed component does not match the convention. It's recommended to suffix the id with the component version. e.g 'myorg-basic-app/my-component-id/v1'.` ); } if (registry[id]) { - logWarning( - `Could not register exposed component with id '${id}'. Reason: An exposed component with the same id already exists.` + pointIdLog.error( + `Could not register exposed component with '${id}'. Reason: An exposed component with the same id already exists.` ); continue; } if (!title) { - logWarning(`Could not register exposed component with id '${id}'. Reason: Title is missing.`); + pointIdLog.error(`Could not register exposed component with id '${id}'. Reason: Title is missing.`); continue; } if (!description) { - logWarning(`Could not register exposed component with id '${id}'. Reason: Description is missing.`); + pointIdLog.error(`Could not register exposed component with id '${id}'. Reason: Description is missing.`); continue; } - if (pluginId !== 'grafana' && isGrafanaDevMode() && isExposedComponentMetaInfoMissing(pluginId, config)) { + if ( + pluginId !== 'grafana' && + isGrafanaDevMode() && + isExposedComponentMetaInfoMissing(pluginId, config, pointIdLog) + ) { continue; } + pointIdLog.debug(`Exposed component from '${pluginId}' to '${id}'`); + registry[id] = { ...config, pluginId }; } diff --git a/public/app/features/plugins/extensions/registry/Registry.ts b/public/app/features/plugins/extensions/registry/Registry.ts index 33470990e37..b2fbfbc09d8 100644 --- a/public/app/features/plugins/extensions/registry/Registry.ts +++ b/public/app/features/plugins/extensions/registry/Registry.ts @@ -1,5 +1,6 @@ import { Observable, ReplaySubject, Subject, firstValueFrom, map, scan, startWith } from 'rxjs'; +import { ExtensionsLog, log } from '../logs/log'; import { deepFreeze } from '../utils'; export const MSG_CANNOT_REGISTER_READ_ONLY = 'Cannot register to a read-only registry'; @@ -19,6 +20,7 @@ export abstract class Registry { private isReadOnly: boolean; // This is the subject that receives extension configs for a loaded plugin. private resultSubject: Subject>; + protected logger: ExtensionsLog; // This is the subject that we expose. // (It will buffer the last value on the stream - the registry - and emit it to new subscribers immediately.) protected registrySubject: ReplaySubject>; @@ -26,8 +28,10 @@ export abstract class Registry { constructor(options: { registrySubject?: ReplaySubject>; initialState?: RegistryType; + log?: ExtensionsLog; }) { this.resultSubject = new Subject>(); + this.logger = options.log ?? log; this.isReadOnly = false; // If the registry subject (observable) is provided, it means that all the registry updates are taken care of outside of this class -> it is read-only. @@ -41,7 +45,7 @@ export abstract class Registry { this.registrySubject = new ReplaySubject>(1); this.resultSubject .pipe( - scan(this.mapToRegistry, options.initialState ?? {}), + scan(this.mapToRegistry.bind(this), options.initialState ?? {}), // Emit an empty registry to start the stream (it is only going to do it once during construction, and then just passes down the values) startWith(options.initialState ?? {}), map((registry) => deepFreeze(registry)) diff --git a/public/app/features/plugins/extensions/usePluginComponent.test.tsx b/public/app/features/plugins/extensions/usePluginComponent.test.tsx index 7cf467b5e38..365dd520e91 100644 --- a/public/app/features/plugins/extensions/usePluginComponent.test.tsx +++ b/public/app/features/plugins/extensions/usePluginComponent.test.tsx @@ -5,6 +5,8 @@ import { PluginContextProvider, PluginLoadingStrategy, PluginMeta, PluginType } import { config } from '@grafana/runtime'; import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; +import { resetLogMock } from './logs/testUtils'; import { setupPluginExtensionRegistries } from './registry/setup'; import { PluginExtensionRegistries } from './registry/types'; import { usePluginComponent } from './usePluginComponent'; @@ -30,11 +32,20 @@ jest.mock('./utils', () => ({ wrapWithPluginContext: jest.fn().mockImplementation((_, component: React.ReactNode) => component), })); +jest.mock('./logs/log', () => { + const { createLogMock } = jest.requireActual('./logs/testUtils'); + const original = jest.requireActual('./logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('usePluginComponent()', () => { let registries: PluginExtensionRegistries; let wrapper: ({ children }: { children: React.ReactNode }) => JSX.Element; let pluginMeta: PluginMeta; - let consoleWarnSpy: jest.SpyInstance; const originalApps = config.apps; const pluginId = 'myorg-extensions-app'; const exposedComponentId = `${pluginId}/exposed-component/v1`; @@ -74,7 +85,7 @@ describe('usePluginComponent()', () => { beforeEach(() => { registries = setupPluginExtensionRegistries(); jest.mocked(isGrafanaDevMode).mockReturnValue(false); - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + resetLogMock(log); jest.mocked(wrapWithPluginContext).mockClear(); @@ -221,7 +232,7 @@ describe('usePluginComponent()', () => { // (No restrictions due to isGrafanaDevMode() = false) let { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper }); expect(result.current.component).not.toBe(null); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the meta-info in core Grafana', () => { @@ -245,7 +256,7 @@ describe('usePluginComponent()', () => { }); expect(result.current.component).not.toBe(null); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should validate the meta-info in dev mode and if inside a plugin', () => { @@ -277,7 +288,7 @@ describe('usePluginComponent()', () => { // Shouldn't return the component, as it's not present in the plugin.json dependencies let { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper }); expect(result.current.component).toBe(null); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should return the exposed component if the meta-info is correct and in dev mode', () => { @@ -307,6 +318,6 @@ describe('usePluginComponent()', () => { let { result } = renderHook(() => usePluginComponent(exposedComponentId), { wrapper }); expect(result.current.component).not.toBe(null); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/usePluginComponent.tsx b/public/app/features/plugins/extensions/usePluginComponent.tsx index 9ae43c27bbf..a32b6aa974c 100644 --- a/public/app/features/plugins/extensions/usePluginComponent.tsx +++ b/public/app/features/plugins/extensions/usePluginComponent.tsx @@ -2,9 +2,10 @@ import { useMemo } from 'react'; import { useObservable } from 'react-use'; import { usePluginContext } from '@grafana/data'; -import { logWarning, UsePluginComponentResult } from '@grafana/runtime'; +import { UsePluginComponentResult } from '@grafana/runtime'; import { useExposedComponentsRegistry } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; import { isExposedComponentDependencyMissing, isGrafanaDevMode, wrapWithPluginContext } from './utils'; // Returns a component exposed by a plugin. @@ -18,16 +19,6 @@ export function usePluginComponent(id: string): UsePl // For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana. const enableRestrictions = isGrafanaDevMode() && pluginContext; - if (enableRestrictions && isExposedComponentDependencyMissing(id, pluginContext)) { - logWarning( - `usePluginComponent("${id}") - The exposed component ("${id}") is missing from the dependencies[] in the "plugin.json" file.` - ); - return { - isLoading: false, - component: null, - }; - } - if (!registryState?.[id]) { return { isLoading: false, @@ -36,10 +27,25 @@ export function usePluginComponent(id: string): UsePl } const registryItem = registryState[id]; + const componentLog = log.child({ + title: registryItem.title, + description: registryItem.description, + pluginId: registryItem.pluginId, + }); + + if (enableRestrictions && isExposedComponentDependencyMissing(id, pluginContext, componentLog)) { + componentLog.warning( + `usePluginComponent("${id}") - The exposed component ("${id}") is missing from the dependencies[] in the "plugin.json" file.` + ); + return { + isLoading: false, + component: null, + }; + } return { isLoading: false, - component: wrapWithPluginContext(registryItem.pluginId, registryItem.component), + component: wrapWithPluginContext(registryItem.pluginId, registryItem.component, componentLog), }; }, [id, pluginContext, registryState]); } diff --git a/public/app/features/plugins/extensions/usePluginComponents.test.tsx b/public/app/features/plugins/extensions/usePluginComponents.test.tsx index 0c37e90e549..d174a871cc7 100644 --- a/public/app/features/plugins/extensions/usePluginComponents.test.tsx +++ b/public/app/features/plugins/extensions/usePluginComponents.test.tsx @@ -4,6 +4,8 @@ import { renderHook } from '@testing-library/react-hooks'; import { PluginContextProvider, PluginMeta, PluginType } from '@grafana/data'; import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; +import { resetLogMock } from './logs/testUtils'; import { setupPluginExtensionRegistries } from './registry/setup'; import { PluginExtensionRegistries } from './registry/types'; import { usePluginComponents } from './usePluginComponents'; @@ -29,18 +31,27 @@ jest.mock('./utils', () => ({ wrapWithPluginContext: jest.fn().mockImplementation((_, component: React.ReactNode) => component), })); +jest.mock('./logs/log', () => { + const { createLogMock } = jest.requireActual('./logs/testUtils'); + const original = jest.requireActual('./logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('usePluginComponents()', () => { let registries: PluginExtensionRegistries; let wrapper: ({ children }: { children: React.ReactNode }) => JSX.Element; let pluginMeta: PluginMeta; - let consoleWarnSpy: jest.SpyInstance; const pluginId = 'myorg-extensions-app'; const extensionPointId = `${pluginId}/extension-point/v1`; beforeEach(() => { jest.mocked(isGrafanaDevMode).mockReturnValue(false); + resetLogMock(log); registries = setupPluginExtensionRegistries(); - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); jest.mocked(wrapWithPluginContext).mockClear(); @@ -251,7 +262,7 @@ describe('usePluginComponents()', () => { // (No restrictions due to isGrafanaDevMode() = false) let { result } = renderHook(() => usePluginComponents({ extensionPointId }), { wrapper }); expect(result.current.components.length).toBe(1); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point id in production mode', () => { @@ -276,7 +287,7 @@ describe('usePluginComponents()', () => { wrapper, }); expect(result.current.components.length).toBe(0); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point meta-info if used in Grafana core (no plugin context)', () => { @@ -305,7 +316,7 @@ describe('usePluginComponents()', () => { wrapper, }); expect(result.current.components.length).toBe(1); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point id if used in Grafana core (no plugin context)', () => { @@ -321,7 +332,7 @@ describe('usePluginComponents()', () => { wrapper, }); expect(result.current.components.length).toBe(0); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should validate if the extension point meta-info is correct if in dev-mode and used by a plugin', () => { @@ -359,7 +370,7 @@ describe('usePluginComponents()', () => { // Trying to render an extension point that is not defined in the plugin meta let { result } = renderHook(() => usePluginComponents({ extensionPointId }), { wrapper }); expect(result.current.components.length).toBe(0); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should not log a warning if the extension point meta-info is correct if in dev-mode and used by a plugin', () => { @@ -403,6 +414,6 @@ describe('usePluginComponents()', () => { // Trying to render an extension point that is not defined in the plugin meta let { result } = renderHook(() => usePluginComponents({ extensionPointId }), { wrapper }); expect(result.current.components.length).toBe(0); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/usePluginComponents.tsx b/public/app/features/plugins/extensions/usePluginComponents.tsx index 92dbc4e676c..45a6c6d9487 100644 --- a/public/app/features/plugins/extensions/usePluginComponents.tsx +++ b/public/app/features/plugins/extensions/usePluginComponents.tsx @@ -8,7 +8,8 @@ import { } from '@grafana/runtime/src/services/pluginExtensions/getPluginExtensions'; import { useAddedComponentsRegistry } from './ExtensionRegistriesContext'; -import { isExtensionPointMetaInfoMissing, isGrafanaDevMode, logWarning } from './utils'; +import { log } from './logs/log'; +import { isExtensionPointMetaInfoMissing, isGrafanaDevMode } from './utils'; import { isExtensionPointIdValid } from './validators'; // Returns an array of component extensions for the given extension point @@ -26,9 +27,13 @@ export function usePluginComponents({ const components: Array> = []; const extensionsByPlugin: Record = {}; const pluginId = pluginContext?.meta.id ?? ''; + const pointLog = log.child({ + pluginId, + extensionPointId, + }); if (enableRestrictions && !isExtensionPointIdValid({ extensionPointId, pluginId })) { - logWarning( + pointLog.warning( `Extension point usePluginComponents("${extensionPointId}") - the id should be prefixed with your plugin id ("${pluginId}/").` ); return { @@ -37,8 +42,8 @@ export function usePluginComponents({ }; } - if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) { - logWarning( + if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, pointLog)) { + pointLog.warning( `usePluginComponents("${extensionPointId}") - The extension point is missing from the "plugin.json" file.` ); return { diff --git a/public/app/features/plugins/extensions/usePluginExtensions.tsx b/public/app/features/plugins/extensions/usePluginExtensions.tsx index 8b5c3e69f37..2ab882108de 100644 --- a/public/app/features/plugins/extensions/usePluginExtensions.tsx +++ b/public/app/features/plugins/extensions/usePluginExtensions.tsx @@ -6,8 +6,9 @@ import { GetPluginExtensionsOptions, UsePluginExtensionsResult } from '@grafana/ import { useSidecar } from 'app/core/context/SidecarContext'; import { getPluginExtensions } from './getPluginExtensions'; +import { log } from './logs/log'; import { PluginExtensionRegistries } from './registry/types'; -import { isExtensionPointMetaInfoMissing, isGrafanaDevMode, logWarning } from './utils'; +import { isExtensionPointMetaInfoMissing, isGrafanaDevMode } from './utils'; import { isExtensionPointIdValid } from './validators'; export function createUsePluginExtensions(registries: PluginExtensionRegistries) { @@ -25,13 +26,17 @@ export function createUsePluginExtensions(registries: PluginExtensionRegistries) // For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana. const enableRestrictions = isGrafanaDevMode() && pluginContext !== null; const pluginId = pluginContext?.meta.id ?? ''; + const pointLog = log.child({ + pluginId, + extensionPointId, + }); if (!addedLinksRegistry && !addedComponentsRegistry) { return { extensions: [], isLoading: false }; } if (enableRestrictions && !isExtensionPointIdValid({ extensionPointId, pluginId })) { - logWarning( + pointLog.warning( `Extension point usePluginExtensions("${extensionPointId}") - the id should be prefixed with your plugin id ("${pluginId}/").` ); return { @@ -40,8 +45,8 @@ export function createUsePluginExtensions(registries: PluginExtensionRegistries) }; } - if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) { - logWarning( + if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, pointLog)) { + pointLog.warning( `Invalid extension point. Reason: The extension point is not declared in the "plugin.json" file. ExtensionPointId: "${extensionPointId}"` ); return { diff --git a/public/app/features/plugins/extensions/usePluginLinks.test.tsx b/public/app/features/plugins/extensions/usePluginLinks.test.tsx index d4d0cc6a843..8fd92bd944a 100644 --- a/public/app/features/plugins/extensions/usePluginLinks.test.tsx +++ b/public/app/features/plugins/extensions/usePluginLinks.test.tsx @@ -4,6 +4,8 @@ import { renderHook } from '@testing-library/react-hooks'; import { PluginContextProvider, PluginMeta, PluginType } from '@grafana/data'; import { ExtensionRegistriesProvider } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; +import { resetLogMock } from './logs/testUtils'; import { setupPluginExtensionRegistries } from './registry/setup'; import { PluginExtensionRegistries } from './registry/types'; import { usePluginLinks } from './usePluginLinks'; @@ -28,18 +30,27 @@ jest.mock('./utils', () => ({ isGrafanaDevMode: jest.fn().mockReturnValue(false), })); +jest.mock('./logs/log', () => { + const { createLogMock } = jest.requireActual('./logs/testUtils'); + const original = jest.requireActual('./logs/log'); + + return { + ...original, + log: createLogMock(), + }; +}); + describe('usePluginLinks()', () => { let registries: PluginExtensionRegistries; let wrapper: ({ children }: { children: React.ReactNode }) => JSX.Element; let pluginMeta: PluginMeta; - let consoleWarnSpy: jest.SpyInstance; const pluginId = 'myorg-extensions-app'; const extensionPointId = `${pluginId}/extension-point/v1`; beforeEach(() => { jest.mocked(isGrafanaDevMode).mockReturnValue(false); registries = setupPluginExtensionRegistries(); - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); + resetLogMock(log); pluginMeta = { id: pluginId, @@ -194,7 +205,7 @@ describe('usePluginLinks()', () => { // (No restrictions due to isGrafanaDevMode() = false) let { result } = renderHook(() => usePluginLinks({ extensionPointId }), { wrapper }); expect(result.current.links.length).toBe(1); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point id in production mode', () => { @@ -217,7 +228,7 @@ describe('usePluginLinks()', () => { // (No restrictions due to isGrafanaDevMode() = false) let { result } = renderHook(() => usePluginLinks({ extensionPointId: 'invalid-extension-point-id' }), { wrapper }); expect(result.current.links.length).toBe(0); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point meta-info if used in Grafana core (no plugin context)', () => { @@ -244,7 +255,7 @@ describe('usePluginLinks()', () => { let { result } = renderHook(() => usePluginLinks({ extensionPointId: 'grafana/extension-point/v1' }), { wrapper }); expect(result.current.links.length).toBe(1); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should not validate the extension point id if used in Grafana core (no plugin context)', () => { @@ -258,7 +269,7 @@ describe('usePluginLinks()', () => { let { result } = renderHook(() => usePluginLinks({ extensionPointId: 'invalid-extension-point-id' }), { wrapper }); expect(result.current.links.length).toBe(0); - expect(consoleWarnSpy).not.toHaveBeenCalled(); + expect(log.warning).not.toHaveBeenCalled(); }); it('should validate if the extension point meta-info is correct if in dev-mode and used by a plugin', () => { @@ -296,7 +307,7 @@ describe('usePluginLinks()', () => { // Trying to render an extension point that is not defined in the plugin meta let { result } = renderHook(() => usePluginLinks({ extensionPointId }), { wrapper }); expect(result.current.links.length).toBe(0); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); it('should not log a warning if the extension point meta-info is correct if in dev-mode and used by a plugin', () => { @@ -334,6 +345,6 @@ describe('usePluginLinks()', () => { // Trying to render an extension point that is not defined in the plugin meta let { result } = renderHook(() => usePluginLinks({ extensionPointId }), { wrapper }); expect(result.current.links.length).toBe(0); - expect(consoleWarnSpy).toHaveBeenCalled(); + expect(log.warning).toHaveBeenCalled(); }); }); diff --git a/public/app/features/plugins/extensions/usePluginLinks.tsx b/public/app/features/plugins/extensions/usePluginLinks.tsx index 1f868b01c77..bd9bba9bcc9 100644 --- a/public/app/features/plugins/extensions/usePluginLinks.tsx +++ b/public/app/features/plugins/extensions/usePluginLinks.tsx @@ -9,6 +9,7 @@ import { } from '@grafana/runtime/src/services/pluginExtensions/getPluginExtensions'; import { useAddedLinksRegistry } from './ExtensionRegistriesContext'; +import { log } from './logs/log'; import { generateExtensionId, getLinkExtensionOnClick, @@ -17,7 +18,6 @@ import { getReadOnlyProxy, isExtensionPointMetaInfoMissing, isGrafanaDevMode, - logWarning, } from './utils'; import { isExtensionPointIdValid } from './validators'; @@ -35,9 +35,13 @@ export function usePluginLinks({ // For backwards compatibility we don't enable restrictions in production or when the hook is used in core Grafana. const enableRestrictions = isGrafanaDevMode() && pluginContext !== null; const pluginId = pluginContext?.meta.id ?? ''; + const pointLog = log.child({ + pluginId, + extensionPointId, + }); if (enableRestrictions && !isExtensionPointIdValid({ extensionPointId, pluginId })) { - logWarning( + pointLog.warning( `Extension point usePluginLinks("${extensionPointId}") - the id should be prefixed with your plugin id ("${pluginId}/").` ); return { @@ -46,8 +50,8 @@ export function usePluginLinks({ }; } - if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext)) { - logWarning( + if (enableRestrictions && isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, pointLog)) { + pointLog.warning( `Invalid extension point. Reason: The extension point is not declared in the "plugin.json" file. ExtensionPointId: "${extensionPointId}"` ); return { @@ -78,8 +82,14 @@ export function usePluginLinks({ extensionsByPlugin[pluginId] = 0; } + const linkLog = pointLog.child({ + path: addedLink.path ?? '', + title: addedLink.title, + description: addedLink.description, + onClick: typeof addedLink.onClick, + }); // Run the configure() function with the current context, and apply the ovverides - const overrides = getLinkExtensionOverrides(pluginId, addedLink, frozenContext); + const overrides = getLinkExtensionOverrides(pluginId, addedLink, linkLog, frozenContext); // configure() returned an `undefined` -> hide the extension if (addedLink.configure && overrides === undefined) { @@ -91,7 +101,7 @@ export function usePluginLinks({ id: generateExtensionId(pluginId, extensionPointId, addedLink.title), type: PluginExtensionTypes.link, pluginId: pluginId, - onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, frozenContext), + onClick: getLinkExtensionOnClick(pluginId, extensionPointId, addedLink, linkLog, frozenContext), // Configurable properties icon: overrides?.icon || addedLink.icon, diff --git a/public/app/features/plugins/extensions/utils.test.tsx b/public/app/features/plugins/extensions/utils.test.tsx index 3697fe0b71f..c090e2ac232 100644 --- a/public/app/features/plugins/extensions/utils.test.tsx +++ b/public/app/features/plugins/extensions/utils.test.tsx @@ -13,6 +13,8 @@ import { config } from '@grafana/runtime'; import appEvents from 'app/core/app_events'; import { ShowModalReactEvent } from 'app/types/events'; +import { log } from './logs/log'; +import { createLogMock } from './logs/testUtils'; import { deepFreeze, handleErrorsInFn, @@ -441,7 +443,7 @@ describe('Plugin Extensions / Utils', () => { it('should make the plugin context available for the wrapped component', async () => { const pluginId = 'grafana-worldmap-panel'; - const Component = wrapWithPluginContext(pluginId, ExampleComponent); + const Component = wrapWithPluginContext(pluginId, ExampleComponent, log); render(); @@ -451,7 +453,7 @@ describe('Plugin Extensions / Utils', () => { it('should pass the properties into the wrapped component', async () => { const pluginId = 'grafana-worldmap-panel'; - const Component = wrapWithPluginContext(pluginId, ExampleComponent); + const Component = wrapWithPluginContext(pluginId, ExampleComponent, log); render(); @@ -461,7 +463,6 @@ describe('Plugin Extensions / Utils', () => { }); describe('isAddedLinkMetaInfoMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; const originalApps = config.apps; const pluginId = 'myorg-extensions-app'; const appPluginConfig = { @@ -495,7 +496,6 @@ describe('Plugin Extensions / Utils', () => { }; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); config.apps = { [pluginId]: appPluginConfig, }; @@ -506,63 +506,75 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedLinks.push(extensionConfig); - const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the app config is not found', () => { + const log = createLogMock(); delete config.apps[pluginId]; - const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch("couldn't find app plugin"); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch("couldn't find app plugin"); }); it('should return TRUE and log a warning if the link has no meta-info in the plugin.json', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedLinks = []; - const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedLinkMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('not registered in the plugin.json'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('not registered in the plugin.json'); }); it('should return TRUE and log a warning if the "targets" do not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedLinks.push(extensionConfig); - const returnValue = isAddedLinkMetaInfoMissing(pluginId, { - ...extensionConfig, - targets: [PluginExtensionPoints.DashboardPanelMenu, PluginExtensionPoints.ExploreToolbarAction], - }); + const returnValue = isAddedLinkMetaInfoMissing( + pluginId, + { + ...extensionConfig, + targets: [PluginExtensionPoints.DashboardPanelMenu, PluginExtensionPoints.ExploreToolbarAction], + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"targets" don\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"targets" don\'t match'); }); it('should return TRUE and log a warning if the "description" does not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedLinks.push(extensionConfig); - const returnValue = isAddedLinkMetaInfoMissing(pluginId, { - ...extensionConfig, - description: 'Link description UPDATED', - }); + const returnValue = isAddedLinkMetaInfoMissing( + pluginId, + { + ...extensionConfig, + description: 'Link description UPDATED', + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"description" doesn\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"description" doesn\'t match'); }); }); describe('isAddedComponentMetaInfoMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; const originalApps = config.apps; const pluginId = 'myorg-extensions-app'; const appPluginConfig = { @@ -597,7 +609,6 @@ describe('Plugin Extensions / Utils', () => { }; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); config.apps = { [pluginId]: appPluginConfig, }; @@ -608,63 +619,75 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedComponents.push(extensionConfig); - const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the app config is not found', () => { + const log = createLogMock(); delete config.apps[pluginId]; - const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch("couldn't find app plugin"); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch("couldn't find app plugin"); }); it('should return TRUE and log a warning if the Component has no meta-info in the plugin.json', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedComponents = []; - const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig); + const returnValue = isAddedComponentMetaInfoMissing(pluginId, extensionConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('not registered in the plugin.json'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('not registered in the plugin.json'); }); it('should return TRUE and log a warning if the "targets" do not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedComponents.push(extensionConfig); - const returnValue = isAddedComponentMetaInfoMissing(pluginId, { - ...extensionConfig, - targets: [PluginExtensionPoints.ExploreToolbarAction], - }); + const returnValue = isAddedComponentMetaInfoMissing( + pluginId, + { + ...extensionConfig, + targets: [PluginExtensionPoints.ExploreToolbarAction], + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"targets" don\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"targets" don\'t match'); }); it('should return TRUE and log a warning if the "description" does not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.addedComponents.push(extensionConfig); - const returnValue = isAddedComponentMetaInfoMissing(pluginId, { - ...extensionConfig, - description: 'UPDATED', - }); + const returnValue = isAddedComponentMetaInfoMissing( + pluginId, + { + ...extensionConfig, + description: 'UPDATED', + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"description" doesn\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"description" doesn\'t match'); }); }); describe('isExposedComponentMetaInfoMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; const originalApps = config.apps; const pluginId = 'myorg-extensions-app'; const appPluginConfig = { @@ -699,7 +722,6 @@ describe('Plugin Extensions / Utils', () => { }; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); config.apps = { [pluginId]: appPluginConfig, }; @@ -710,69 +732,80 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); config.apps[pluginId].extensions.exposedComponents.push(exposedComponentConfig); - const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig); + const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the app config is not found', () => { + const log = createLogMock(); delete config.apps[pluginId]; - const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig); + const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch("couldn't find app plugin"); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch("couldn't find app plugin"); }); it('should return TRUE and log a warning if the exposed component has no meta-info in the plugin.json', () => { + const log = createLogMock(); config.apps[pluginId].extensions.exposedComponents = []; - const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig); + const returnValue = isExposedComponentMetaInfoMissing(pluginId, exposedComponentConfig, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('not registered in the plugin.json'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('not registered in the plugin.json'); }); it('should return TRUE and log a warning if the title does not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.exposedComponents.push(exposedComponentConfig); - const returnValue = isExposedComponentMetaInfoMissing(pluginId, { - ...exposedComponentConfig, - title: 'UPDATED', - }); + const returnValue = isExposedComponentMetaInfoMissing( + pluginId, + { + ...exposedComponentConfig, + title: 'UPDATED', + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"title" doesn\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"title" doesn\'t match'); }); it('should return TRUE and log a warning if the "description" does not match', () => { + const log = createLogMock(); config.apps[pluginId].extensions.exposedComponents.push(exposedComponentConfig); - const returnValue = isExposedComponentMetaInfoMissing(pluginId, { - ...exposedComponentConfig, - description: 'UPDATED', - }); + const returnValue = isExposedComponentMetaInfoMissing( + pluginId, + { + ...exposedComponentConfig, + description: 'UPDATED', + }, + log + ); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch('"description" doesn\'t match'); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch('"description" doesn\'t match'); }); }); describe('isExposedComponentDependencyMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; let pluginContext: PluginContextType; const pluginId = 'myorg-extensions-app'; const exposedComponentId = `${pluginId}/component/v1`; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); pluginContext = { meta: { id: pluginId, @@ -806,35 +839,37 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); pluginContext.meta.dependencies?.extensions.exposedComponents.push(exposedComponentId); - const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext); + const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the dependencies are missing', () => { + const log = createLogMock(); delete pluginContext.meta.dependencies; - const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext); + const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch(`Using exposed component "${exposedComponentId}"`); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch(`Using exposed component "${exposedComponentId}"`); }); it('should return TRUE and log a warning if the exposed component id is not specified in the list of dependencies', () => { - const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext); + const log = createLogMock(); + const returnValue = isExposedComponentDependencyMissing(exposedComponentId, pluginContext, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch(`Using exposed component "${exposedComponentId}"`); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch(`Using exposed component "${exposedComponentId}"`); }); }); describe('isExtensionPointMetaInfoMissing()', () => { - let consoleWarnSpy: jest.SpyInstance; let pluginContext: PluginContextType; const pluginId = 'myorg-extensions-app'; const extensionPointId = `${pluginId}/extension-point/v1`; @@ -845,7 +880,6 @@ describe('Plugin Extensions / Utils', () => { }; beforeEach(() => { - consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(); pluginContext = { meta: { id: pluginId, @@ -885,20 +919,22 @@ describe('Plugin Extensions / Utils', () => { }); it('should return FALSE if the meta-info in the plugin.json is correct', () => { + const log = createLogMock(); pluginContext.meta.extensions?.extensionPoints.push(extensionPointConfig); - const returnValue = isExtensionPointMetaInfoMissing(extensionPointId, pluginContext); + const returnValue = isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, log); expect(returnValue).toBe(false); - expect(consoleWarnSpy).toHaveBeenCalledTimes(0); + expect(log.warning).toHaveBeenCalledTimes(0); }); it('should return TRUE and log a warning if the extension point id is not recorded in the plugin.json', () => { - const returnValue = isExtensionPointMetaInfoMissing(extensionPointId, pluginContext); + const log = createLogMock(); + const returnValue = isExtensionPointMetaInfoMissing(extensionPointId, pluginContext, log); expect(returnValue).toBe(true); - expect(consoleWarnSpy).toHaveBeenCalledTimes(1); - expect(consoleWarnSpy.mock.calls[0][0]).toMatch(`Extension point "${extensionPointId}"`); + expect(log.warning).toHaveBeenCalledTimes(1); + expect(jest.mocked(log.warning).mock.calls[0][0]).toMatch(`Extension point "${extensionPointId}"`); }); }); }); diff --git a/public/app/features/plugins/extensions/utils.tsx b/public/app/features/plugins/extensions/utils.tsx index 0938c1df666..5f92e51b7f6 100644 --- a/public/app/features/plugins/extensions/utils.tsx +++ b/public/app/features/plugins/extensions/utils.tsx @@ -28,13 +28,10 @@ import { sidecarService } from 'app/core/services/SidecarService'; import { getPluginSettings } from 'app/features/plugins/pluginSettings'; import { ShowModalReactEvent } from 'app/types/events'; +import { ExtensionsLog, log } from './logs/log'; import { AddedLinkRegistryItem } from './registry/AddedLinksRegistry'; import { assertIsNotPromise, assertLinkPathIsValid, assertStringProps, isPromise } from './validators'; -export function logWarning(message: string) { - console.warn(`[Plugin Extensions] ${message}`); -} - export function isPluginExtensionLinkConfig( extension: PluginExtensionConfig | undefined ): extension is PluginExtensionLinkConfig { @@ -59,7 +56,11 @@ export function createOpenModalFunction(pluginId: string): PluginExtensionEventH appEvents.publish( new ShowModalReactEvent({ - component: wrapWithPluginContext(pluginId, getModalWrapper({ title, body, width, height })), + component: wrapWithPluginContext( + pluginId, + getModalWrapper({ title, body, width, height }), + log + ), }) ); }; @@ -69,7 +70,7 @@ type ModalWrapperProps = { onDismiss: () => void; }; -export const wrapWithPluginContext = (pluginId: string, Component: React.ComponentType) => { +export const wrapWithPluginContext = (pluginId: string, Component: React.ComponentType, log: ExtensionsLog) => { const WrappedExtensionComponent = (props: T & React.JSX.IntrinsicAttributes) => { const { error, @@ -82,12 +83,15 @@ export const wrapWithPluginContext = (pluginId: string, Component: React.Com } if (error) { - logWarning(`Could not fetch plugin meta information for "${pluginId}", aborting. (${error.message})`); + log.error(`Could not fetch plugin meta information for "${pluginId}", aborting. (${error.message})`, { + stack: error.stack ?? '', + message: error.message, + }); return null; } if (!pluginMeta) { - logWarning(`Fetched plugin meta information is empty for "${pluginId}", aborting.`); + log.error(`Fetched plugin meta information is empty for "${pluginId}", aborting.`); return null; } @@ -298,7 +302,12 @@ export function createExtensionSubMenu(extensions: PluginExtensionLink[]): Panel return subMenu; } -export function getLinkExtensionOverrides(pluginId: string, config: AddedLinkRegistryItem, context?: object) { +export function getLinkExtensionOverrides( + pluginId: string, + config: AddedLinkRegistryItem, + log: ExtensionsLog, + context?: object +) { try { const overrides = config.configure?.(context, { isAppOpened: () => isAppOpened(pluginId) }); @@ -325,7 +334,7 @@ export function getLinkExtensionOverrides(pluginId: string, config: AddedLinkReg assertStringProps({ title, description }, ['title', 'description']); if (Object.keys(rest).length > 0) { - logWarning( + log.warning( `Extension "${config.title}", is trying to override restricted properties: ${Object.keys(rest).join( ', ' )} which will be ignored.` @@ -341,7 +350,10 @@ export function getLinkExtensionOverrides(pluginId: string, config: AddedLinkReg }; } catch (error) { if (error instanceof Error) { - logWarning(error.message); + log.error(`Failed to configure link with title "${config.title}"`, { + stack: error.stack ?? '', + message: error.message, + }); } // If there is an error, we hide the extension @@ -354,6 +366,7 @@ export function getLinkExtensionOnClick( pluginId: string, extensionPointId: string, config: AddedLinkRegistryItem, + log: ExtensionsLog, context?: object ): ((event?: React.MouseEvent) => void) | undefined { const { onClick } = config; @@ -379,18 +392,25 @@ export function getLinkExtensionOnClick( closeAppInSideview: () => closeAppInSideview(pluginId), }; + log.debug(`onClick '${config.title}' at '${extensionPointId}'`); const result = onClick(event, helpers); if (isPromise(result)) { - result.catch((e) => { - if (e instanceof Error) { - logWarning(e.message); + result.catch((error) => { + if (error instanceof Error) { + log.error(error.message, { + message: error.message, + stack: error.stack ?? '', + }); } }); } } catch (error) { if (error instanceof Error) { - logWarning(error.message); + log.error(error.message, { + message: error.message, + stack: error.stack ?? '', + }); } } }; @@ -417,12 +437,16 @@ export const isAppOpened = (pluginId: string) => sidecarService.isAppOpened(plug export const isGrafanaDevMode = () => config.buildInfo.env === 'development'; // Checks if the meta information is missing from the plugin's plugin.json file -export const isExtensionPointMetaInfoMissing = (extensionPointId: string, pluginContext: PluginContextType) => { +export const isExtensionPointMetaInfoMissing = ( + extensionPointId: string, + pluginContext: PluginContextType, + log: ExtensionsLog +) => { const pluginId = pluginContext.meta?.id; const extensionPoints = pluginContext.meta?.extensions?.extensionPoints; if (!extensionPoints || !extensionPoints.some((ep) => ep.id === extensionPointId)) { - logWarning( + log.warning( `Extension point "${extensionPointId}" - it's not recorded in the "plugin.json" for "${pluginId}". Please add it under "extensions.extensionPoints[]".` ); return true; @@ -432,12 +456,16 @@ export const isExtensionPointMetaInfoMissing = (extensionPointId: string, plugin }; // Checks if an exposed component that the plugin is depending on is missing from the `dependencies` in the plugin.json file -export const isExposedComponentDependencyMissing = (id: string, pluginContext: PluginContextType) => { +export const isExposedComponentDependencyMissing = ( + id: string, + pluginContext: PluginContextType, + log: ExtensionsLog +) => { const pluginId = pluginContext.meta?.id; const exposedComponentsDependencies = pluginContext.meta?.dependencies?.extensions?.exposedComponents; if (!exposedComponentsDependencies || !exposedComponentsDependencies.includes(id)) { - logWarning( + log.warning( `Using exposed component "${id}" - it's not recorded in the "plugin.json" for "${pluginId}". Please add it under "dependencies.extensions.exposedComponents[]".` ); return true; @@ -446,31 +474,35 @@ export const isExposedComponentDependencyMissing = (id: string, pluginContext: P return false; }; -export const isAddedLinkMetaInfoMissing = (pluginId: string, metaInfo: PluginExtensionAddedLinkConfig) => { +export const isAddedLinkMetaInfoMissing = ( + pluginId: string, + metaInfo: PluginExtensionAddedLinkConfig, + log: ExtensionsLog +) => { const app = config.apps[pluginId]; const logPrefix = `Added-link "${metaInfo.title}" from "${pluginId}" -`; const pluginJsonMetaInfo = app ? app.extensions.addedLinks.find(({ title }) => title === metaInfo.title) : null; if (!app) { - logWarning(`${logPrefix} couldn't find app plugin "${pluginId}"`); + log.warning(`${logPrefix} couldn't find app plugin "${pluginId}"`); return true; } if (!pluginJsonMetaInfo) { - logWarning(`${logPrefix} not registered in the plugin.json under "extensions.addedLinks[]".`); + log.warning(`${logPrefix} not registered in the plugin.json under "extensions.addedLinks[]".`); return true; } const targets = Array.isArray(metaInfo.targets) ? metaInfo.targets : [metaInfo.targets]; if (!targets.every((target) => pluginJsonMetaInfo.targets.includes(target))) { - logWarning(`${logPrefix} the "targets" don't match with ones in the plugin.json under "extensions.addedLinks[]".`); + log.warning(`${logPrefix} the "targets" don't match with ones in the plugin.json under "extensions.addedLinks[]".`); return true; } if (pluginJsonMetaInfo.description !== metaInfo.description) { - logWarning( + log.warning( `${logPrefix} the "description" doesn't match with one in the plugin.json under "extensions.addedLinks[]".` ); @@ -480,25 +512,29 @@ export const isAddedLinkMetaInfoMissing = (pluginId: string, metaInfo: PluginExt return false; }; -export const isAddedComponentMetaInfoMissing = (pluginId: string, metaInfo: PluginExtensionAddedComponentConfig) => { +export const isAddedComponentMetaInfoMissing = ( + pluginId: string, + metaInfo: PluginExtensionAddedComponentConfig, + log: ExtensionsLog +) => { const app = config.apps[pluginId]; const logPrefix = `Added component "${metaInfo.title}" -`; const pluginJsonMetaInfo = app ? app.extensions.addedComponents.find(({ title }) => title === metaInfo.title) : null; if (!app) { - logWarning(`${logPrefix} couldn't find app plugin "${pluginId}"`); + log.warning(`${logPrefix} couldn't find app plugin "${pluginId}"`); return true; } if (!pluginJsonMetaInfo) { - logWarning(`${logPrefix} not registered in the plugin.json under "extensions.addedComponents[]".`); + log.warning(`${logPrefix} not registered in the plugin.json under "extensions.addedComponents[]".`); return true; } const targets = Array.isArray(metaInfo.targets) ? metaInfo.targets : [metaInfo.targets]; if (!targets.every((target) => pluginJsonMetaInfo.targets.includes(target))) { - logWarning( + log.warning( `${logPrefix} the "targets" don't match with ones in the plugin.json under "extensions.addedComponents[]".` ); @@ -506,7 +542,7 @@ export const isAddedComponentMetaInfoMissing = (pluginId: string, metaInfo: Plug } if (pluginJsonMetaInfo.description !== metaInfo.description) { - logWarning( + log.warning( `${logPrefix} the "description" doesn't match with one in the plugin.json under "extensions.addedComponents[]".` ); @@ -518,25 +554,26 @@ export const isAddedComponentMetaInfoMissing = (pluginId: string, metaInfo: Plug export const isExposedComponentMetaInfoMissing = ( pluginId: string, - metaInfo: PluginExtensionExposedComponentConfig + metaInfo: PluginExtensionExposedComponentConfig, + log: ExtensionsLog ) => { const app = config.apps[pluginId]; const logPrefix = `Exposed component "${metaInfo.id}" -`; const pluginJsonMetaInfo = app ? app.extensions.exposedComponents.find(({ id }) => id === metaInfo.id) : null; if (!app) { - logWarning(`${logPrefix} couldn't find app plugin: "${pluginId}"`); + log.warning(`${logPrefix} couldn't find app plugin: "${pluginId}"`); return true; } if (!pluginJsonMetaInfo) { - logWarning(`${logPrefix} not registered in the plugin.json under "extensions.exposedComponents[]".`); + log.warning(`${logPrefix} not registered in the plugin.json under "extensions.exposedComponents[]".`); return true; } if (pluginJsonMetaInfo.title !== metaInfo.title) { - logWarning( + log.warning( `${logPrefix} the "title" doesn't match with one in the plugin.json under "extensions.exposedComponents[]".` ); @@ -544,7 +581,7 @@ export const isExposedComponentMetaInfoMissing = ( } if (pluginJsonMetaInfo.description !== metaInfo.description) { - logWarning( + log.warning( `${logPrefix} the "description" doesn't match with one in the plugin.json under "extensions.exposedComponents[]".` ); diff --git a/public/app/features/sandbox/TestStuffPage.tsx b/public/app/features/sandbox/TestStuffPage.tsx index 7faa71465b7..36368f5b508 100644 --- a/public/app/features/sandbox/TestStuffPage.tsx +++ b/public/app/features/sandbox/TestStuffPage.tsx @@ -19,7 +19,6 @@ export const TestStuffPage = () => { - diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index 405c76ee35f..e5661244077 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -23,6 +23,7 @@ import { SafeDynamicImport } from '../core/components/DynamicImports/SafeDynamic import { RouteDescriptor } from '../core/navigation/types'; import { getPublicDashboardRoutes } from '../features/dashboard/routes'; +const isDevEnv = config.buildInfo.env === 'development'; export const extraRoutes: RouteDescriptor[] = []; export function getAppRoutes(): RouteDescriptor[] { @@ -198,6 +199,15 @@ export function getAppRoutes(): RouteDescriptor[] { path: '/admin/plugins', component: () => , }, + { + path: '/admin/extensions', + navId: 'extensions', + component: isDevEnv + ? SafeDynamicImport( + () => import(/* webpackChunkName: "PluginExtensionsLog" */ 'app/features/plugins/extensions/logs/LogViewer') + ) + : () => , + }, { path: '/admin/access', component: () => , diff --git a/public/test/jest-setup.ts b/public/test/jest-setup.ts index f47f43ecb60..17579226432 100644 --- a/public/test/jest-setup.ts +++ b/public/test/jest-setup.ts @@ -117,3 +117,12 @@ global.ResizeObserver = class ResizeObserver { disconnect() {} unobserve() {} }; + +global.BroadcastChannel = class BroadcastChannel { + onmessage() {} + onmessageerror() {} + postMessage(data: unknown) {} + close() {} + addEventListener() {} + removeEventListener() {} +}; diff --git a/yarn.lock b/yarn.lock index 5d25ed259d4..c25e0e29049 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4143,6 +4143,24 @@ __metadata: languageName: unknown linkType: soft +"@grafana/scenes-react@npm:5.19.1": + version: 5.19.1 + resolution: "@grafana/scenes-react@npm:5.19.1" + dependencies: + "@grafana/e2e-selectors": "npm:^11.0.0" + "@grafana/scenes": "npm:5.19.1" + react-use: "npm:17.4.0" + peerDependencies: + "@grafana/data": ^11.0.0 + "@grafana/runtime": ^11.0.0 + "@grafana/schema": ^11.0.0 + "@grafana/ui": ^11.0.0 + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10/a4efd256a02ba4d7418ca412e5f03439684ce531188606a762ce7252a5235f8a3dcf14fd51058b30b7c9c4510f5a7c931958f619811170684b97d9db9294dca1 + languageName: node + linkType: hard + "@grafana/scenes@npm:5.19.1": version: 5.19.1 resolution: "@grafana/scenes@npm:5.19.1" @@ -18949,6 +18967,7 @@ __metadata: "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" "@grafana/scenes": "npm:5.19.1" + "@grafana/scenes-react": "npm:5.19.1" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" @@ -24275,7 +24294,7 @@ __metadata: languageName: node linkType: hard -"nano-css@npm:^5.6.1, nano-css@npm:^5.6.2": +"nano-css@npm:^5.3.1, nano-css@npm:^5.6.1, nano-css@npm:^5.6.2": version: 5.6.2 resolution: "nano-css@npm:5.6.2" dependencies: @@ -28029,6 +28048,31 @@ __metadata: languageName: node linkType: hard +"react-use@npm:17.4.0": + version: 17.4.0 + resolution: "react-use@npm:17.4.0" + dependencies: + "@types/js-cookie": "npm:^2.2.6" + "@xobotyi/scrollbar-width": "npm:^1.9.5" + copy-to-clipboard: "npm:^3.3.1" + fast-deep-equal: "npm:^3.1.3" + fast-shallow-equal: "npm:^1.0.0" + js-cookie: "npm:^2.2.1" + nano-css: "npm:^5.3.1" + react-universal-interface: "npm:^0.6.2" + resize-observer-polyfill: "npm:^1.5.1" + screenfull: "npm:^5.1.0" + set-harmonic-interval: "npm:^1.0.1" + throttle-debounce: "npm:^3.0.1" + ts-easing: "npm:^0.2.0" + tslib: "npm:^2.1.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 10/98566c4817b00251107824743ea9dff41f167b548bd5f249f6eb9e2ec09388a2de1e89988e4432cead3f8aa83cf706e0255db8a20c0615768c670751973d2761 + languageName: node + linkType: hard + "react-use@npm:17.5.0": version: 17.5.0 resolution: "react-use@npm:17.5.0" From 9ece88d5852dceb90f83271e66902eece24f908f Mon Sep 17 00:00:00 2001 From: Karl Persson Date: Thu, 10 Oct 2024 09:07:40 +0200 Subject: [PATCH 025/110] Zanzana: bump openfga version (#94485) * Bump openfga * Remove internall sqlite implementation for openfga * Use sqlite implementation from openfga --- go.mod | 65 +- go.sum | 184 ++-- go.work.sum | 101 ++- pkg/aggregator/go.mod | 24 +- pkg/aggregator/go.sum | 48 +- pkg/apimachinery/go.mod | 4 +- pkg/apimachinery/go.sum | 8 +- pkg/apiserver/go.mod | 18 +- pkg/apiserver/go.sum | 36 +- pkg/build/go.mod | 18 +- pkg/build/go.sum | 32 +- pkg/promlib/go.mod | 20 +- pkg/promlib/go.sum | 40 +- pkg/services/authz/zanzana/logger/logger.go | 11 + .../authz/zanzana/store/assets/assets.go | 10 - .../sqlite/001_initialize_schema.sql | 56 -- .../002_add_authorization_model_version.sql | 5 - .../sqlite/003_add_reverse_lookup_index.sql | 5 - ...uthorization_model_serialized_protobuf.sql | 5 - .../sqlite/005_add_conditions_to_tuples.sql | 11 - .../authz/zanzana/store/migration/migrator.go | 8 +- .../authz/zanzana/store/sqlite/config.go | 15 - .../authz/zanzana/store/sqlite/store.go | 821 ------------------ .../authz/zanzana/store/sqlite/store_test.go | 295 ------- .../authz/zanzana/store/sqlite/write.go | 165 ---- pkg/services/authz/zanzana/store/store.go | 40 +- 26 files changed, 362 insertions(+), 1683 deletions(-) delete mode 100644 pkg/services/authz/zanzana/store/assets/assets.go delete mode 100644 pkg/services/authz/zanzana/store/assets/migrations/sqlite/001_initialize_schema.sql delete mode 100644 pkg/services/authz/zanzana/store/assets/migrations/sqlite/002_add_authorization_model_version.sql delete mode 100644 pkg/services/authz/zanzana/store/assets/migrations/sqlite/003_add_reverse_lookup_index.sql delete mode 100644 pkg/services/authz/zanzana/store/assets/migrations/sqlite/004_add_authorization_model_serialized_protobuf.sql delete mode 100644 pkg/services/authz/zanzana/store/assets/migrations/sqlite/005_add_conditions_to_tuples.sql delete mode 100644 pkg/services/authz/zanzana/store/sqlite/config.go delete mode 100644 pkg/services/authz/zanzana/store/sqlite/store.go delete mode 100644 pkg/services/authz/zanzana/store/sqlite/store_test.go delete mode 100644 pkg/services/authz/zanzana/store/sqlite/write.go diff --git a/go.mod b/go.mod index 7d9993fcdc4..0558eda7f7b 100644 --- a/go.mod +++ b/go.mod @@ -27,11 +27,10 @@ require ( github.com/Masterminds/semver v1.5.0 // @grafana/grafana-backend-group github.com/Masterminds/semver/v3 v3.2.0 // @grafana/grafana-release-guild github.com/Masterminds/sprig/v3 v3.2.3 // @grafana/grafana-backend-group - github.com/Masterminds/squirrel v1.5.4 // @grafana/identity-access-team github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // @grafana/plugins-platform-backend github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f // @grafana/grafana-backend-group github.com/alicebob/miniredis/v2 v2.33.0 // @grafana/alerting-backend - github.com/andybalholm/brotli v1.0.6 // @grafana/partner-datasources + github.com/andybalholm/brotli v1.1.0 // @grafana/partner-datasources github.com/apache/arrow/go/v15 v15.0.2 // @grafana/observability-metrics github.com/armon/go-radix v1.0.0 // @grafana/grafana-app-platform-squad github.com/aws/aws-sdk-go v1.55.5 // @grafana/aws-datasources @@ -123,21 +122,20 @@ require ( github.com/mattn/go-isatty v0.0.20 // @grafana/grafana-backend-group github.com/mattn/go-sqlite3 v1.14.22 // @grafana/grafana-backend-group github.com/matttproud/golang_protobuf_extensions v1.0.4 // @grafana/alerting-backend - github.com/microsoft/go-mssqldb v1.7.0 // @grafana/grafana-bi-squad + github.com/microsoft/go-mssqldb v1.7.2 // @grafana/grafana-bi-squad github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c //@grafana/identity-access-team github.com/mocktools/go-smtp-mock/v2 v2.3.1 // @grafana/grafana-backend-group github.com/modern-go/reflect2 v1.0.2 // @grafana/alerting-backend github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // @grafana/alerting-backend github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // @grafana/grafana-operator-experience-squad github.com/oapi-codegen/oapi-codegen/v2 v2.3.0 // @grafana/grafana-as-code - github.com/oklog/ulid/v2 v2.1.0 // @grafana/identity-access-team github.com/olekukonko/tablewriter v0.0.5 // @grafana/grafana-backend-group - github.com/openfga/api/proto v0.0.0-20240529184453-5b0b4941f3e0 // @grafana/identity-access-team - github.com/openfga/language/pkg/go v0.0.0-20240409225820-a53ea2892d6d // @grafana/identity-access-team - github.com/openfga/openfga v1.5.4 // @grafana/identity-access-team + github.com/openfga/api/proto v0.0.0-20240906203051-102620ef2a66 // @grafana/identity-access-team + github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20240926131254-992b301a003f // @grafana/identity-access-team + github.com/openfga/openfga v1.6.2 // @grafana/identity-access-team github.com/patrickmn/go-cache v2.1.0+incompatible // @grafana/alerting-backend github.com/prometheus/alertmanager v0.27.0 // @grafana/alerting-backend - github.com/prometheus/client_golang v1.20.3 // @grafana/alerting-backend + github.com/prometheus/client_golang v1.20.4 // @grafana/alerting-backend github.com/prometheus/client_model v0.6.1 // @grafana/grafana-backend-group github.com/prometheus/common v0.55.0 // @grafana/alerting-backend github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-backend @@ -158,21 +156,21 @@ require ( github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // @grafana/grafana-operator-experience-squad github.com/yudai/gojsondiff v1.0.0 // @grafana/grafana-backend-group go.opentelemetry.io/collector/pdata v1.6.0 // @grafana/grafana-backend-group - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // @grafana/plugins-platform-backend + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // @grafana/plugins-platform-backend go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 // @grafana/grafana-operator-experience-squad go.opentelemetry.io/contrib/propagators/jaeger v1.29.0 // @grafana/grafana-backend-group go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel v1.30.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/exporters/jaeger v1.17.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // @grafana/grafana-backend-group - go.opentelemetry.io/otel/sdk v1.29.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // @grafana/grafana-backend-group + go.opentelemetry.io/otel/sdk v1.30.0 // @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.30.0 // @grafana/grafana-backend-group go.uber.org/atomic v1.11.0 // @grafana/alerting-backend go.uber.org/goleak v1.3.0 // @grafana/grafana-search-and-storage gocloud.dev v0.39.0 // @grafana/grafana-app-platform-squad golang.org/x/crypto v0.27.0 // @grafana/grafana-backend-group - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // @grafana/alerting-backend + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // @grafana/alerting-backend golang.org/x/mod v0.20.0 // indirect; @grafana/grafana-backend-group golang.org/x/net v0.29.0 // @grafana/oss-big-tent @grafana/partner-datasources golang.org/x/oauth2 v0.23.0 // @grafana/identity-access-team @@ -180,9 +178,9 @@ require ( golang.org/x/text v0.18.0 // @grafana/grafana-backend-group golang.org/x/time v0.6.0 // @grafana/grafana-backend-group golang.org/x/tools v0.24.0 // @grafana/grafana-as-code - gonum.org/v1/gonum v0.14.0 // @grafana/observability-metrics + gonum.org/v1/gonum v0.15.1 // @grafana/observability-metrics google.golang.org/api v0.191.0 // @grafana/grafana-backend-group - google.golang.org/grpc v1.66.0 // @grafana/plugins-platform-backend + google.golang.org/grpc v1.67.0 // @grafana/plugins-platform-backend google.golang.org/protobuf v1.34.2 // @grafana/plugins-platform-backend gopkg.in/ini.v1 v1.67.0 // @grafana/alerting-backend gopkg.in/mail.v2 v2.3.1 // @grafana/grafana-backend-group @@ -224,12 +222,13 @@ require ( github.com/FZambia/eagle v0.1.0 // indirect github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/squirrel v1.5.4 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect github.com/RoaringBitmap/roaring v1.9.3 // indirect github.com/agext/levenshtein v1.2.1 // indirect github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apache/thrift v0.20.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect @@ -269,7 +268,7 @@ require ( github.com/elazarl/goproxy v0.0.0-20240726154733-8b0c20506380 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/proto v1.10.0 // indirect - github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect + github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect @@ -293,7 +292,7 @@ require ( github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/cel-go v0.20.1 // indirect + github.com/google/cel-go v0.21.0 // indirect github.com/google/flatbuffers v24.3.25+incompatible // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/gofuzz v1.2.0 // indirect @@ -306,10 +305,8 @@ require ( github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect; @grafana/plugins-platform-backend github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // @grafana/identity-access-team github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect - github.com/hashicorp/go-retryablehttp v0.7.5 // indirect github.com/hashicorp/go-sockaddr v1.0.6 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect @@ -321,9 +318,9 @@ require ( github.com/invopop/jsonschema v0.12.0 // indirect github.com/invopop/yaml v0.3.1 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect - github.com/jackc/pgx/v5 v5.5.5 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.7.1 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect @@ -377,15 +374,16 @@ require ( github.com/oapi-codegen/runtime v1.1.1 // indirect github.com/oklog/run v1.1.0 // indirect github.com/oklog/ulid v1.3.1 // indirect + github.com/oklog/ulid/v2 v2.1.0 // indirect github.com/opentracing-contrib/go-stdlib v1.0.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/pressly/goose/v3 v3.20.0 // indirect + github.com/pressly/goose/v3 v3.22.1 // indirect github.com/prometheus/common/sigv4 v0.1.0 // indirect github.com/prometheus/exporter-toolkit v0.11.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -401,14 +399,14 @@ require ( github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/encoding v0.4.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect - github.com/sethvargo/go-retry v0.2.4 // indirect - github.com/shopspring/decimal v1.3.1 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/shopspring/decimal v1.4.0 // indirect github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect github.com/spf13/cast v1.6.0 // indirect - github.com/spf13/viper v1.18.2 // indirect + github.com/spf13/viper v1.19.0 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/subosito/gotenv v1.6.0 // indirect @@ -440,8 +438,8 @@ require ( golang.org/x/term v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect; @grafana/grafana-backend-group - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect @@ -449,10 +447,10 @@ require ( gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/kms v0.31.1 // indirect modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 // indirect - modernc.org/libc v1.41.0 // indirect + modernc.org/libc v1.55.3 // indirect modernc.org/mathutil v1.6.0 // indirect - modernc.org/memory v1.7.2 // indirect - modernc.org/sqlite v1.29.6 // indirect + modernc.org/memory v1.8.0 // indirect + modernc.org/sqlite v1.33.1 // indirect modernc.org/strutil v1.2.0 // indirect modernc.org/token v1.1.0 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect @@ -484,6 +482,7 @@ require ( cloud.google.com/go/longrunning v0.5.12 // indirect github.com/at-wat/mqtt-go v0.19.4 // indirect github.com/dolthub/maphash v0.1.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect github.com/gammazero/deque v0.2.1 // indirect github.com/grafana/grafana-app-sdk v0.19.0 // indirect github.com/grafana/grafana/pkg/semconv v0.0.0-20240808213237-f4d2e064f435 // indirect diff --git a/go.sum b/go.sum index 58deaa35524..2b8c44b7bd6 100644 --- a/go.sum +++ b/go.sum @@ -1334,8 +1334,6 @@ cloud.google.com/go/workflows v1.12.1/go.mod h1:5A95OhD/edtOhQd/O741NSfIMezNTbCw cloud.google.com/go/workflows v1.12.2/go.mod h1:+OmBIgNqYJPVggnMo9nqmizW0qEXHhmnAzK/CnBqsHc= cloud.google.com/go/workflows v1.12.3/go.mod h1:fmOUeeqEwPzIU81foMjTRQIdwQHADi/vEr1cx9R1m5g= cloud.google.com/go/workflows v1.12.4/go.mod h1:yQ7HUqOkdJK4duVtMeBCAOPiN1ZF1E9pAMX51vpwB/w= -dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= -dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/age v1.1.1 h1:pIpO7l151hCnQ4BdyBujnGP2YlUo0uj6sAVNHGBvXHg= filippo.io/age v1.1.1/go.mod h1:l03SrzDUrBkdBx8+IILdnn2KZysqQdbEBUQ4p3sqEQE= @@ -1389,8 +1387,6 @@ github.com/Azure/azure-storage-blob-go v0.15.0 h1:rXtgp8tN1p29GvpGgfJetavIG0V7Og github.com/Azure/azure-storage-blob-go v0.15.0/go.mod h1:vbjsVbX0dlxnRc4FFMPsS9BsJWPcne7GB7onqlPvz58= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.29 h1:I4+HL/JDvErx2LjyzaVxllw2lRDB5/BT2Bm4g20iqYw= @@ -1453,8 +1449,6 @@ github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA4 github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8= -github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= @@ -1502,11 +1496,11 @@ github.com/alicebob/miniredis/v2 v2.33.0 h1:uvTF0EDeu9RLnUEG27Db5I68ESoIxTiXbNUi github.com/alicebob/miniredis/v2 v2.33.0/go.mod h1:MhP4a3EU7aENRi9aO+tHfTBZicLqQevyi/DJpoj6mi0= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= -github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= +github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/apache/arrow/go/arrow v0.0.0-20210223225224-5bea62493d91/go.mod h1:c9sxoIT3YgLxH4UhLOCKaBlEojuMhVYpk4Ntv3opUTQ= github.com/apache/arrow/go/v10 v10.0.1/go.mod h1:YvhnlEePVnBS4+0z3fhPfUy7W1Ikj0Ih0vcRo/gZ1M0= github.com/apache/arrow/go/v11 v11.0.0/go.mod h1:Eg5OsL5H+e299f7u5ssuXsuHQVEGC4xei5aX110hRiI= @@ -1723,8 +1717,8 @@ github.com/cncf/xds/go v0.0.0-20230428030218-4003588d1b74/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa/go.mod h1:x/1Gn8zydmfq8dk6e9PdstVsDgu9RuyIIJqAaF//0IM= -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw= -github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20 h1:N+3sFI5GUjRKBi+i0TxYVST9h4Ie192jJWpHvthBBgg= +github.com/cncf/xds/go v0.0.0-20240723142845-024c85f92f20/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E= github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= @@ -1734,9 +1728,6 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE github.com/containerd/cgroups/v3 v3.0.1/go.mod h1:/vtwk1VXrtoa5AaZLkypuOJgA/6DyPMZHJPGQNtlHnw= github.com/containerd/cgroups/v3 v3.0.3/go.mod h1:8HBe7V3aWGLFPd/k03swSIsGjZhHI2WzJmticMgVuz0= github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.7.12 h1:+KQsnv4VnzyxWcfO9mlxxELaoztsDEjOuCMPAuPqgU0= -github.com/containerd/containerd v1.7.12/go.mod h1:/5OMpE1p0ylxtEUGY8kuCYkDRzJm9NO1TFMWjUpdevk= -github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -1749,8 +1740,6 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E= -github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= @@ -1805,8 +1794,8 @@ github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4Kfc github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v26.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v26.0.2+incompatible h1:yGVmKUFGgcxA6PXWAokO0sQL22BrQ67cgVjko8tGdXE= -github.com/docker/docker v26.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI= +github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= @@ -1839,6 +1828,8 @@ github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxER github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -1854,8 +1845,8 @@ github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCw github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g= github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0= -github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155 h1:IgJPqnrlY2Mr4pYB6oaMKvFvwJ9H+X6CCY5x1vCTcpc= -github.com/envoyproxy/go-control-plane v0.12.1-0.20240621013728-1eb8caab5155/go.mod h1:5Wkq+JduFtdAXihLmeTJf+tRYIT4KBc2vPXDhwVo1pA= +github.com/envoyproxy/go-control-plane v0.13.0 h1:HzkeUz1Knt+3bK+8LG1bxOO/jzWZmdxpwC51i202les= +github.com/envoyproxy/go-control-plane v0.13.0/go.mod h1:GRaKG3dwvFoTg4nj7aXdZnvMg4d7nvT/wl9WgVXn3Q8= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= @@ -1863,8 +1854,9 @@ github.com/envoyproxy/protoc-gen-validate v0.10.0/go.mod h1:DRjgyB0I43LtJapqN6Ni github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= github.com/envoyproxy/protoc-gen-validate v1.0.1/go.mod h1:0vj8bNkYbSTNS2PIyH87KZaeN4x9zpL9Qt8fQC7d+vs= github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE= -github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew= +github.com/envoyproxy/protoc-gen-validate v1.1.0 h1:tntQDh69XqOCOZsDz0lVJQez/2L6Uu2PdjCQwWCJ3bM= +github.com/envoyproxy/protoc-gen-validate v1.1.0/go.mod h1:sXRDRVmzEbkM7CVcM06s9shE/m23dg3wzjl0UWqJ2q4= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM= @@ -1953,8 +1945,6 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/analysis v0.21.5/go.mod h1:25YcZosX9Lwz2wBsrFrrsL8bmjjXdlyP6zsr2AMy29M= github.com/go-openapi/analysis v0.22.0/go.mod h1:acDnkkCI2QxIo8sSIPgmp1wUlRohV7vfGtAIVae73b0= github.com/go-openapi/analysis v0.22.2/go.mod h1:pDF4UbZsQTo/oNuRfAWWd4dAh4yuYf//LYorPTjrpvo= @@ -2124,8 +2114,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= -github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v23.5.26+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= @@ -2396,8 +2386,8 @@ github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1 github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY= github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= -github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M= -github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= @@ -2490,13 +2480,13 @@ github.com/ionos-cloud/sdk-go/v6 v6.1.11/go.mod h1:EzEgRIDxBELvfoa/uBN0kOQaqovLj github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA= -github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= -github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= -github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= -github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= -github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs= +github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= @@ -2621,8 +2611,6 @@ github.com/linkedin/goavro/v2 v2.10.0 h1:eTBIRoInBM88gITGXYtUSqqxLTFXfOsJBiX8ZMW github.com/linkedin/goavro/v2 v2.10.0/go.mod h1:UgQUb2N/pmueQYH9bfqFioWxzYCZXSfF8Jw03O5sjqA= github.com/linode/linodego v1.32.0 h1:OmZzB3iON6uu84VtLFf64uKmAQqJJarvmsVguroioPI= github.com/linode/linodego v1.32.0/go.mod h1:y8GDP9uLVH4jTB9qyrgw79qfKdYJmNCGUOJmfuiOcmI= -github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY= -github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star v0.6.1/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= github.com/lyft/protoc-gen-star/v2 v2.0.1/go.mod h1:RcCdONR2ScXaYnQC5tUzxzlpA3WVYF7/opLeUgcQs/o= @@ -2690,8 +2678,8 @@ github.com/maypok86/otter v1.2.2 h1:jJi0y8ruR/ZcKmJ4FbQj3QQTqKwV+LNrSOo2S1zbF5M= github.com/maypok86/otter v1.2.2/go.mod h1:mKLfoI7v1HOmQMwFgX4QkRk23mX6ge3RDvjdHOWG4R4= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= -github.com/microsoft/go-mssqldb v1.7.0 h1:sgMPW0HA6Ihd37Yx0MzHyKD726C2kY/8KJsQtXHNaAs= -github.com/microsoft/go-mssqldb v1.7.0/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= +github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA= +github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -2750,18 +2738,10 @@ github.com/mithrandie/ternary v1.1.1 h1:k/joD6UGVYxHixYmSR8EGgDFNONBMqyD373xT4QR github.com/mithrandie/ternary v1.1.1/go.mod h1:0D9Ba3+09K2TdSZO7/bFCC0GjSXetCvYuYq0u8FY/1g= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= -github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= -github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8= github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= -github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= -github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= -github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= -github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/mocktools/go-smtp-mock/v2 v2.3.1 h1:wq75NDSsOy5oHo/gEQQT0fRRaYKRqr1IdkjhIPXxagM= github.com/mocktools/go-smtp-mock/v2 v2.3.1/go.mod h1:h9AOf/IXLSU2m/1u4zsjtOM/WddPwdOUBz56dV9f81M= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -2778,7 +2758,6 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de h1:D5x39vF5KCwKQaw+OC9ZPiLVHXz3UFw2+psEX+gYcto= github.com/mpvl/unique v0.0.0-20150818121801-cbe035fff7de/go.mod h1:kJun4WP5gFuHZgRjZUWWuH1DTxCtxbHDOIJsudS8jzY= @@ -2885,12 +2864,12 @@ github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zM github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/openfga/api/proto v0.0.0-20240529184453-5b0b4941f3e0 h1:tSJl/atdqsDACjRQQPCbXe2GfEvcOkhdrUNmDToAjTA= -github.com/openfga/api/proto v0.0.0-20240529184453-5b0b4941f3e0/go.mod h1:XnvYrdU//9i70Aou6n4H5DJ0bdRPB3IlmE/Vx6qhnm8= -github.com/openfga/language/pkg/go v0.0.0-20240409225820-a53ea2892d6d h1:n44DfITs+CLCYJIgsryJkG2ElwOZJ3huekPZKydPi7U= -github.com/openfga/language/pkg/go v0.0.0-20240409225820-a53ea2892d6d/go.mod h1:wkI4GcY3yNNuFMU2ncHPWqBaF7XylQTkJYfBi2pIpK8= -github.com/openfga/openfga v1.5.4 h1:mVrp0uB9jNWX/5+OtZLM6YOx5Y9Y4r/D/O+LNBF/FGQ= -github.com/openfga/openfga v1.5.4/go.mod h1:+PoZg9BJeq+h3L0eR52tqNTwghSapCFtmaVKHsUK7QM= +github.com/openfga/api/proto v0.0.0-20240906203051-102620ef2a66 h1:pAYrdyIKxPsMs/nRcTEnS9za2io11g7Rt7ng7HK82hk= +github.com/openfga/api/proto v0.0.0-20240906203051-102620ef2a66/go.mod h1:gil5LBD8tSdFQbUkCQdnXsoeU9kDJdJgbGdHkgJfcd0= +github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20240926131254-992b301a003f h1:ZMZ7ntMnaHIPZxvVQv/aqC4ctzLqH+9Fqn4uw35kQpk= +github.com/openfga/language/pkg/go v0.2.0-beta.2.0.20240926131254-992b301a003f/go.mod h1:ll/hN6kS4EE6B/7J/PbZqac9Nuv7ZHpI+Jfh36JLrbs= +github.com/openfga/openfga v1.6.2 h1:tHBAgiCPomCZb3IH0CFqOpTDVDA/Xb8kG87oX4JCbXc= +github.com/openfga/openfga v1.6.2/go.mod h1:jzbEpheazf6MFjtanQt1rpxexSRzfa9057F7JlkMv2I= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= github.com/opentracing-contrib/go-stdlib v1.0.0 h1:TBS7YuVotp8myLon4Pv7BtCBzOTo1DeZCld0Z63mW2w= github.com/opentracing-contrib/go-stdlib v1.0.0/go.mod h1:qtI1ogk+2JhVPIXVc6q+NHziSmy2W5GbdQZFUHADCBU= @@ -2919,8 +2898,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= @@ -2956,11 +2935,9 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= -github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI= -github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U= -github.com/pressly/goose/v3 v3.20.0 h1:uPJdOxF/Ipj7ABVNOAMJXSxwFXZGwMGHNqjC8e61VA0= -github.com/pressly/goose/v3 v3.20.0/go.mod h1:BRfF2GcG4FTG12QfdBVy3q1yveaf4ckL9vWwEcIO3lA= +github.com/pressly/goose/v3 v3.22.1 h1:2zICEfr1O3yTP9BRZMGPj7qFxQ+ik6yeo+z1LMuioLc= +github.com/pressly/goose/v3 v3.22.1/go.mod h1:xtMpbstWyCpyH+0cxLTMCENWBG+0CSxvTsXhW95d5eo= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -2977,8 +2954,8 @@ github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= -github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= -github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -3093,19 +3070,15 @@ github.com/segmentio/encoding v0.4.0 h1:MEBYvRqiUB2nfR2criEXWqwdY6HJOUrCn5hboVOV github.com/segmentio/encoding v0.4.0/go.mod h1:/d03Cd8PoaDeceuhUUUQWjU0KhWjrmYrWPgtJHYZSnI= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= -github.com/sethvargo/go-retry v0.2.4 h1:T+jHEQy/zKJf5s95UkguisicE0zuF9y7+/vgz08Ocec= -github.com/sethvargo/go-retry v0.2.4/go.mod h1:1afjQuvh7s4gflMObvjLPaWgluLLyhA1wmVZ6KLpICw= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b h1:SCYeryKXBVdW38167VyumGakH+7E4Wxe6b/zxmQxwyM= github.com/shadowspore/fossil-delta v0.0.0-20240102155221-e3a8590b820b/go.mod h1:daNLfX/GJKuZyN4HkMf0h8dVmTmgRbBSkd9bFQyGNIo= -github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= -github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v1.7.1/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= @@ -3159,8 +3132,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= -github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= -github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0= github.com/spyzhov/ajson v0.9.0/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= @@ -3200,19 +3173,9 @@ github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSW github.com/substrait-io/substrait-go v0.4.2/go.mod h1:qhpnLmrcvAnlZsUyPXZRqldiHapPTXC3t7xFgDi3aQg= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= -github.com/testcontainers/testcontainers-go v0.30.0 h1:jmn/XS22q4YRrcMwWg0pAwlClzs/abopbsBzrepyc4E= -github.com/testcontainers/testcontainers-go v0.30.0/go.mod h1:K+kHNGiM5zjklKjgTtcrEetF3uhWbMUyqAQoyoh8Pf0= -github.com/testcontainers/testcontainers-go/modules/mysql v0.30.0 h1:wrePvxfU/2HFALKyBqpNs6VoPPvThzHy9aN+PCxse9g= -github.com/testcontainers/testcontainers-go/modules/mysql v0.30.0/go.mod h1:Srnlf7wwA7s6K4sKKhjAoBHJcKorRINR/i5dCA4ZyGk= -github.com/testcontainers/testcontainers-go/modules/postgres v0.30.0 h1:D3HFqpZS90iRGAN7M85DFiuhPfvYvFNnx8urQ6mPAvo= -github.com/testcontainers/testcontainers-go/modules/postgres v0.30.0/go.mod h1:e1sKxwUOkqzvaqdHl/oV9mUtFmkDPTfBGp0po2tnWQU= github.com/thanos-io/objstore v0.0.0-20220809103346-8ef1f215e2bf h1:onQsPyHlq2yIWU+Nfl6yStuqnZuVQQN8FZ8sBb2wqtw= github.com/thanos-io/objstore v0.0.0-20220809103346-8ef1f215e2bf/go.mod h1:v0NhuxxxUFUPatQcVNSCUkBEVezXzl7LSdaBOZygq98= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= @@ -3282,8 +3245,6 @@ github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zclconf/go-cty v1.13.0 h1:It5dfKTTZHe9aeppbNOda3mN7Ag7sg6QkBNm6TkyFa0= github.com/zclconf/go-cty v1.13.0/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= @@ -3343,8 +3304,8 @@ go.opentelemetry.io/collector/semconv v0.98.0/go.mod h1:8ElcRZ8Cdw5JnvhTOQOdYizk go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.46.1/go.mod h1:4UoMYEZOC0yN/sPGH76KPkkU7zgiEWYWL9vwmbnTJPE= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 h1:sqmsIQ75l6lfZjjpnXXT9DFVtYEDg6CH0/Cn4/3A1Wg= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0/go.mod h1:rsg1EO8LXSs2po50PB5CeY/MSVlhghuKBgXlKnqm6ks= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.46.1/go.mod h1:sEGXWArGqc3tVa+ekntsN65DmVbVeW+7lTKTjZF3/Fo= @@ -3369,11 +3330,11 @@ go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDI go.opentelemetry.io/otel/exporters/jaeger v1.17.0 h1:D7UpUy2Xc2wsi1Ras6V40q806WM07rqoCWzXu7Sqy+4= go.opentelemetry.io/otel/exporters/jaeger v1.17.0/go.mod h1:nPCqOnEH9rNLKqH/+rrUjiMzHJdV1BlpKcTwRTyKkKI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.25.0/go.mod h1:h95q0LBGh7hlAC08X2DhSeyIG02YQ0UyioTCVAqRPmc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.25.0/go.mod h1:8GlBGcDk8KKi7n+2S4BT/CPZQYH3erLu0/k64r1MYgo= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0/go.mod h1:e7ciERRhZaOZXVjx5MiL8TK5+Xv7G5Gv5PA2ZDEJdL8= go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o= go.opentelemetry.io/otel/metric v1.19.0/go.mod h1:L5rUsV9kM1IxCj1MmSdS+JQAcVm319EUrDVLrt7jqt8= @@ -3388,8 +3349,8 @@ go.opentelemetry.io/otel/sdk v1.17.0/go.mod h1:U87sE0f5vQB7hwUoW98pW5Rz4ZDuCFBZF go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= go.opentelemetry.io/otel/sdk v1.25.0/go.mod h1:oFgzCM2zdsxKzz6zwpTZYLLQsFwc+K0daArPdIhuxkw= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY= go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= @@ -3509,8 +3470,8 @@ golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N0 golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -4048,8 +4009,8 @@ gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY= -gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= @@ -4347,8 +4308,8 @@ google.golang.org/genproto/googleapis/api v0.0.0-20240213162025-012b6fc9bca9/go. google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y= google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= google.golang.org/genproto/googleapis/bytestream v0.0.0-20230807174057-1744710a1577/go.mod h1:NjCQG/D8JandXxM57PZbAJL1DCNL6EypA0vPPwfsc7c= google.golang.org/genproto/googleapis/bytestream v0.0.0-20231030173426-d783a09b4405/go.mod h1:GRUCuLdzVqZte8+Dl/D4N25yLzcGqqWaYkeVOwulFqw= @@ -4393,8 +4354,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240311132316-a219d84964c2/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415141817-7cd4c1c1f9ec/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -4460,8 +4421,8 @@ google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJai google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= google.golang.org/grpc v1.63.0/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200910201057-6591123024b3/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -4591,6 +4552,8 @@ modernc.org/cc/v3 v3.36.3/go.mod h1:NFUHyPn4ekoC/JHeZFfZurN6ixxawE1BnVonP/oahEI= modernc.org/cc/v3 v3.37.0/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.38.1/go.mod h1:vtL+3mdHx/wcj3iEGz84rQa8vEqR6XM84v5Lcvfph20= modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v3 v3.0.0-20220428102840-41399a37e894/go.mod h1:eI31LL8EwEBKPpNpA4bU1/i+sKOwOrQy8D87zWUcRZc= modernc.org/ccgo/v3 v3.0.0-20220430103911-bc99d88307be/go.mod h1:bwdAnOoaIt8Ax9YdWGjxWsdkPcZyRPHqrOvJxaKAKGw= modernc.org/ccgo/v3 v3.0.0-20220904174949-82d86e1b6d56/go.mod h1:YSXjPL62P2AMSxBphRHPn7IkzhVHqkvOnRKAKh+W6ZI= @@ -4601,9 +4564,13 @@ modernc.org/ccgo/v3 v3.16.8/go.mod h1:zNjwkizS+fIFDrDjIAgBSCLkWbJuHF+ar3QRn+Z9aw modernc.org/ccgo/v3 v3.16.9/go.mod h1:zNMzC9A9xeNUepy6KuZBbugn3c0Mc9TeiJO4lgvkJDo= modernc.org/ccgo/v3 v3.16.13-0.20221017192402-261537637ce8/go.mod h1:fUB3Vn0nVPReA+7IG7yZDfjv1TMWjhQP8gCxrFAtL5g= modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI= modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= @@ -4622,8 +4589,8 @@ modernc.org/libc v1.21.2/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= modernc.org/libc v1.22.4/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= -modernc.org/libc v1.41.0 h1:g9YAc6BkKlgORsUWj+JwqoB1wU3o4DE3bM3yvA3k+Gk= -modernc.org/libc v1.41.0/go.mod h1:w0eszPsiXoOnoMJgrXjglgLuDy/bt5RR4y3QzUUeodY= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.4.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= @@ -4635,15 +4602,18 @@ modernc.org/memory v1.2.1/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.3.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= -modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E= -modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.18.1/go.mod h1:6ho+Gow7oX5V+OiOQ6Tr4xeqbx13UZ6t+Fw9IRUG4d4= modernc.org/sqlite v1.18.2/go.mod h1:kvrTLEWgxUcHa2GfHBQtanR1H9ht3hTJNtKpzH9k1u0= modernc.org/sqlite v1.21.2/go.mod h1:cxbLkB5WS32DnQqeH4h4o1B0eMr8W/y8/RGuxQ3JsC0= -modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4= -modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +modernc.org/sqlite v1.33.1 h1:trb6Z3YYoeM9eDL1O8do81kP+0ejv+YzgyFo+Gwy0nM= +modernc.org/sqlite v1.33.1/go.mod h1:pXV2xHxhzXZsgT/RtTFAPY6JJDEvOTcTdwADQCCWD4k= modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= diff --git a/go.work.sum b/go.work.sum index 59348c11716..eb78119b883 100644 --- a/go.work.sum +++ b/go.work.sum @@ -2,6 +2,8 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-2023080216373 buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20230802163732-1c33ebd9ecfa.1/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= cel.dev/expr v0.15.0 h1:O1jzfJCQBfL5BFoYktaxwIhuttaQPsVWerH9/EEKx0w= cel.dev/expr v0.15.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= +cel.dev/expr v0.16.0 h1:yloc84fytn4zmJX2GU3TkXGsaieaV7dQ057Qs4sIG2Y= +cel.dev/expr v0.16.0/go.mod h1:TRSuuV7DlVCE/uwv5QbAiW/v8l5O8C4eEPHeu7gf7Sg= cloud.google.com/go/accessapproval v1.7.11 h1:MgtE8CI+YJWPGGHnxQ9z1VQqV87h+vSGy2MeM/m0ggQ= cloud.google.com/go/accessapproval v1.7.11/go.mod h1:KGK3+CLDWm4BvjN0wFtZqdFUGhxlTvTF6PhAwQJGL4M= cloud.google.com/go/accesscontextmanager v1.8.11 h1:IQ3KLJmNKPgstN0ZcRw0niU4KfsiOZmzvcGCF+NT618= @@ -259,7 +261,19 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9 h1:VpgP7xuJadIUu docker.io/go-docker v1.0.0 h1:VdXS/aNYQxyA9wdLD5z8Q8Ro688/hG8HzKxYVEVbE6s= docker.io/go-docker v1.0.0/go.mod h1:7tiAn5a0LFmjbPDbyTPOaTTOuG1ZRNXdPA6RvKY+fpY= gioui.org v0.0.0-20210308172011-57750fc8a0a6 h1:K72hopUosKG3ntOPNG4OzzbuhxGuVf06fa2la1/H/Ho= +gioui.org v0.2.0 h1:RbzDn1h/pCVf/q44ImQSa/J3MIFpY3OWphzT/Tyei+w= +gioui.org v0.2.0/go.mod h1:1H72sKEk/fNFV+l0JNeM2Dt3co3Y4uaQcD+I+/GQ0e4= +gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7 h1:tNJdnP5CgM39PRc+KWmBRRYX/zJ+rd5XaYxY5d5veqA= +gioui.org/cpu v0.0.0-20220412190645-f1e9e8c3b1f7/go.mod h1:A8M0Cn5o+vY5LTMlnRoK3O5kG+rH0kWfJjeKd9QpBmQ= +gioui.org/shader v1.0.6 h1:cvZmU+eODFR2545X+/8XucgZdTtEjR3QWW6W65b0q5Y= +gioui.org/shader v1.0.6/go.mod h1:mWdiME581d/kV7/iEhLmUgUK5iZ09XR5XpduXzbePVM= +gioui.org/x v0.2.0 h1:/MbdjKH19F16auv19UiQxli2n6BYPw7eyh9XBOTgmEw= +gioui.org/x v0.2.0/go.mod h1:rCGN2nZ8ZHqrtseJoQxCMZpt2xrZUrdZ2WuMRLBJmYs= +git.sr.ht/~sbinet/cmpimg v0.1.0 h1:E0zPRk2muWuCqSKSVZIWsgtU9pjsw3eKHi8VmQeScxo= +git.sr.ht/~sbinet/cmpimg v0.1.0/go.mod h1:FU12psLbF4TfNXkKH2ZZQ29crIqoiqTZmeQ7dkp/pxE= git.sr.ht/~sbinet/gg v0.3.1 h1:LNhjNn8DerC8f9DHLz6lS0YYul/b602DUxDgGkd/Aik= +git.sr.ht/~sbinet/gg v0.5.0 h1:6V43j30HM623V329xA9Ntq+WJrMjDxRjuAB1LFWF5m8= +git.sr.ht/~sbinet/gg v0.5.0/go.mod h1:G2C0eRESqlKhS7ErsNey6HHrqU1PwsnCQlekFi9Q2Oo= github.com/99designs/basicauth-go v0.0.0-20160802081356-2a93ba0f464d h1:j6oB/WPCigdOkxtuPl1VSIiLpy7Mdsu6phQffbF19Ng= github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc= github.com/Azure/azure-amqp-common-go/v3 v3.2.3 h1:uDF62mbd9bypXWi19V1bN5NZEO84JqgmI5G73ibAmrk= @@ -281,8 +295,12 @@ github.com/Azure/go-autorest/autorest/azure/cli v0.4.5/go.mod h1:ADQAXrkgm7acgWV github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc= github.com/ClickHouse/ch-go v0.58.2 h1:jSm2szHbT9MCAB1rJ3WuCJqmGLi5UTjlNu+f530UTS0= github.com/ClickHouse/ch-go v0.58.2/go.mod h1:Ap/0bEmiLa14gYjCiRkYGbXvbe8vwdrfTYWhsuQ99aw= +github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4= +github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg= github.com/ClickHouse/clickhouse-go/v2 v2.17.1 h1:ZCmAYWpu75IyEi7+Yrs/uaAjiCGY5wfW5kXo64exkX4= github.com/ClickHouse/clickhouse-go/v2 v2.17.1/go.mod h1:rkGTvFDTLqLIm0ma+13xmcCfr/08Gvs7KmFt1tgiWHQ= +github.com/ClickHouse/clickhouse-go/v2 v2.28.3 h1:SkFzPULX6nzgfNZd1YD1XTECivjTMrCtD09ZPKcVLFQ= +github.com/ClickHouse/clickhouse-go/v2 v2.28.3/go.mod h1:vzn73hp+3JwxtFU4RjPCQ7r6fP2pMKVwdi8E1/Tkua8= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v6 v6.2.0 h1:EpcZ6SR9n28BUGtNJSvlBqf90IpjeFr36Tizxhn/oME= @@ -300,6 +318,8 @@ github.com/KimMachineGun/automemlimit v0.6.0 h1:p/BXkH+K40Hax+PuWWPQ478hPjsp9h1C github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o= github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw= +github.com/MicahParks/keyfunc/v2 v2.1.0 h1:6ZXKb9Rp6qp1bDbJefnG7cTH8yMN1IC/4nf+GVjO99k= +github.com/MicahParks/keyfunc/v2 v2.1.0/go.mod h1:rW42fi+xgLJ2FRRXAfNx9ZA8WpD4OeE/yHVMteCkw9k= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.6 h1:U68crOE3y3MPttCMQGywZOLrTeF5HHJ3/vDBCJn9/bA= github.com/OneOfOne/xxhash v1.2.6/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q= @@ -335,10 +355,10 @@ github.com/alexflint/go-scalar v1.0.0 h1:NGupf1XV/Xb04wXskDFzS0KWOLH632W/EO4fAFi github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw= github.com/alicebob/miniredis v2.5.0+incompatible h1:yBHoLpsyjupjz3NL3MhKMVkR41j82Yjf3KFv7ApYzUI= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= -github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= -github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0 h1:uF5Q/hWnDU1XZeT6CsrRSxHLroUSEYYO3kgES+yd+So= +github.com/andybalholm/stroke v0.0.0-20221221101821-bd29b49d73f0/go.mod h1:ccdDYaY5+gO+cbnQdFxEXqfy0RkoV25H3jLXUDNM3wg= github.com/antihax/optional v1.0.0 h1:xK2lYat7ZLaVVcIuj82J8kIro4V6kDe0AUDFboUCwcg= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 h1:goHVqTbFX3AIo0tzGr14pgfAW2ZfPChKO21Z9MGf/gk= github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM= @@ -395,6 +415,8 @@ github.com/bufbuild/protovalidate-go v0.2.1/go.mod h1:e7XXDtlxj5vlEyAgsrxpzayp4c github.com/bwesterb/go-ristretto v1.2.3 h1:1w53tCkGhCQ5djbat3+MH0BAQ5Kfgbt56UZQ/JMzngw= github.com/bytedance/sonic v1.10.0-rc3 h1:uNSnscRapXTwUgTyOF0GVljYD08p9X/Lbr9MweSV3V0= github.com/bytedance/sonic v1.10.0-rc3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/campoy/embedmd v1.0.0 h1:V4kI2qTJJLf4J29RzI/MAt2c3Bl4dQSYPuflzwFH2hY= +github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= github.com/casbin/casbin/v2 v2.37.0 h1:/poEwPSovi4bTOcP752/CsTQiRz2xycyVKFG7GUhbDw= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g= @@ -426,7 +448,11 @@ github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9D github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= +github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo= +github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs= github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= +github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= +github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= @@ -492,6 +518,7 @@ github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQ github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/expr-lang/expr v1.16.2 h1:JvMnzUs3LeVHBvGFcXYmXo+Q6DPDmzrlcSBO6Wy3w4s= github.com/expr-lang/expr v1.16.2/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= @@ -518,20 +545,36 @@ github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= github.com/go-fonts/dejavu v0.1.0 h1:JSajPXURYqpr+Cu8U9bt8K+XcACIHWqWrvWCKyeFmVQ= +github.com/go-fonts/dejavu v0.3.2 h1:3XlHi0JBYX+Cp8n98c6qSoHrxPa4AUKDMKdrh/0sUdk= +github.com/go-fonts/dejavu v0.3.2/go.mod h1:m+TzKY7ZEl09/a17t1593E4VYW8L1VaBXHzFZOIjGEY= github.com/go-fonts/latin-modern v0.2.0 h1:5/Tv1Ek/QCr20C6ZOz15vw3g7GELYL98KWr8Hgo+3vk= +github.com/go-fonts/latin-modern v0.3.2 h1:M+Sq24Dp0ZRPf3TctPnG1MZxRblqyWC/cRUL9WmdaFc= +github.com/go-fonts/latin-modern v0.3.2/go.mod h1:9odJt4NbRrbdj4UAMuLVd4zEukf6aAEKnDaQga0whqQ= github.com/go-fonts/liberation v0.3.0 h1:3BI2iaE7R/s6uUUtzNCjo3QijJu3aS4wmrMgfSpYQ+8= github.com/go-fonts/liberation v0.3.0/go.mod h1:jdJ+cqF+F4SUL2V+qxBth8fvBpBDS7yloUL5Fi8GTGY= +github.com/go-fonts/liberation v0.3.2 h1:XuwG0vGHFBPRRI8Qwbi5tIvR3cku9LUfZGq/Ar16wlQ= +github.com/go-fonts/liberation v0.3.2/go.mod h1:N0QsDLVUQPy3UYg9XAc3Uh3UDMp2Z7M1o4+X98dXkmI= github.com/go-fonts/stix v0.1.0 h1:UlZlgrvvmT/58o573ot7NFw0vZasZ5I6bcIft/oMdgg= +github.com/go-fonts/stix v0.2.2 h1:v9krocr13J1llaOHLEol1eaHsv8S43UuFX/1bFgEJJ4= +github.com/go-fonts/stix v0.2.2/go.mod h1:SUxggC9dxd/Q+rb5PkJuvfvTbOPtNc2Qaua00fIp9iU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4 h1:WtGNWLvXpe6ZudgnXrq0barxBImvnnJoMEhXAzcbM0I= github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU= github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9 h1:NxXI5pTAtpEaU49bpLpQoDsu1zrteW/vxzTz8Cd2UAs= github.com/go-latex/latex v0.0.0-20230307184459-12ec69307ad9/go.mod h1:gWuR/CrFDDeVRFQwHPvsv9soJVB/iqymhuZQuJ3a9OM= +github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea h1:DfZQkvEbdmOe+JK2TMtBM+0I9GSdzE2y/L1/AmD8xKc= +github.com/go-latex/latex v0.0.0-20231108140139-5c1ce85aa4ea/go.mod h1:Y7Vld91/HRbTBm7JwoI7HejdDB0u+e9AUBO9MB7yuZk= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.8/go.mod h1:6QT22icPLEqAM/z/TChgb4WAveCHF92+2gF0CNjHpPI= github.com/go-pdf/fpdf v0.6.0 h1:MlgtGIfsdMEEQJr2le6b/HNr1ZlQwxyWr77r2aj2U/8= +github.com/go-pdf/fpdf v0.9.0 h1:PPvSaUuo1iMi9KkaAn90NuKi+P4gwMedWPHhj8YlJQw= +github.com/go-pdf/fpdf v0.9.0/go.mod h1:oO8N111TkmKb9D7VvWGLvLJlaZUQVPM+6V42pp3iV4Y= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= @@ -539,6 +582,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k= github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372 h1:FQivqchis6bE2/9uF70M2gmmLpe82esEm2QadL0TEJo= +github.com/go-text/typesetting v0.0.0-20230803102845-24e03d8b5372/go.mod h1:evDBbvNR/KaVFZ2ZlDSOWWXIUKq0wCOEtzLxRM8SG3k= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c= github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= @@ -554,6 +599,8 @@ github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfE github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= +github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4= github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA= github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= @@ -687,8 +734,12 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1 h1:vi1F1IQ8N7hNWytK9DpJsUfQhGuNSc19z330K6vl4zk= github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4= github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc= +github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c h1:VtwQ41oftZwlMnOEbMWQtSEUgU64U4s+GHk7hZK+jtY= +github.com/lufia/plan9stats v0.0.0-20220913051719-115f729f3c8c/go.mod h1:JKx41uQRwqlTZabZc+kILPrO/3jlKnQ2Z8b7YiVw5cE= github.com/lyft/protoc-gen-star v0.6.1 h1:erE0rdztuaDq3bpGifD95wfoPrSZc95nGA6tbiNYh6M= github.com/lyft/protoc-gen-star/v2 v2.0.3 h1:/3+/2sWyXeMLzKd1bX+ixWKgEMsULrIivpDsuaF441o= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4 h1:sIXJOMrYnQZJu7OB7ANSF4MYri2fTEGIsRLz6LwI4xE= +github.com/lyft/protoc-gen-star/v2 v2.0.4-0.20230330145011-496ad1ac90a4/go.mod h1:amey7yeodaJhXSbf/TlLvWiqQfLOSpEk//mLlc+axEk= github.com/lyft/protoc-gen-validate v0.0.13 h1:KNt/RhmQTOLr7Aj8PsJ7mTronaFyx80mRTT9qF261dA= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= @@ -698,6 +749,8 @@ github.com/matryer/moq v0.5.0 h1:h2PJUYjZSiyEahzVogDRmrgL9Bsx9xYAl8l+LPfmwL8= github.com/matryer/moq v0.5.0/go.mod h1:39GTnrD0mVWHPvWdYj5ki/lxfhLQEtHcLh+tWoYF/iE= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/mfridman/xflag v0.0.0-20240825232106-efb77353e578 h1:CRrqlUmLebb/QjzRDWE0E66+YyN/v95+w6WyH9ju8/Y= +github.com/mfridman/xflag v0.0.0-20240825232106-efb77353e578/go.mod h1:/483ywM5ZO5SuMVjrIGquYNE5CzLrj5Ux/LxWWnjRaE= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE= github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME= @@ -720,8 +773,12 @@ github.com/nats-io/jwt/v2 v2.0.3 h1:i/O6cmIsjpcQyWDYNcq2JyZ3/VTF8SJ4JWluI5OhpvI= github.com/nats-io/nats-server/v2 v2.5.0 h1:wsnVaaXH9VRSg+A2MVg5Q727/CqxnmPLGFQ3YZYKTQg= github.com/nats-io/nats.go v1.31.0 h1:/WFBHEc/dOKBF6qf1TZhrdEfTmOZ5JzdJ+Y3m6Y/p7E= github.com/nats-io/nats.go v1.31.0/go.mod h1:di3Bm5MLsoB4Bx61CBTsxuarI36WbhAwOm8QrW39+i8= +github.com/nats-io/nats.go v1.34.0 h1:fnxnPCNiwIG5w08rlMcEKTUw4AV/nKyGCOJE8TdhSPk= +github.com/nats-io/nats.go v1.34.0/go.mod h1:Ubdu4Nh9exXdSz0RVWRFBbRfrbSxOYd26oF0wkWclB8= github.com/nats-io/nkeys v0.4.6 h1:IzVe95ru2CT6ta874rt9saQRkWfe2nFj1NtvYSLqMzY= github.com/nats-io/nkeys v0.4.6/go.mod h1:4DxZNzenSVd1cYQoAa8948QY3QDjrHfcfVADymtkpts= +github.com/nats-io/nkeys v0.4.7 h1:RwNJbbIdYCoClSDNY7QVKZlyb/wfT6ugvFCiKy6vDvI= +github.com/nats-io/nkeys v0.4.7/go.mod h1:kqXRgRDPlGy7nGaEDMuYzmiJCIAAWDK0IMBtDmGD0nc= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM= @@ -780,6 +837,8 @@ github.com/parquet-go/parquet-go v0.20.2-0.20240416173845-962b3c5827c3 h1:dHzXGq github.com/parquet-go/parquet-go v0.20.2-0.20240416173845-962b3c5827c3/go.mod h1:wMYanjuaE900FTDTNY00JU+67Oqh9uO0pYWRNoPGctQ= github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU= +github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30 h1:BHT1/DKsYDGkUgQ2jmMaozVcdk+sVfz0+1ZJq4zkWgw= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= @@ -798,6 +857,8 @@ github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDj github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c h1:NRoLoZvkBTKvR5gQLgA3e0hqjkY9u1wm+iOL45VN/qI= +github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/pquerna/cachecontrol v0.1.0 h1:yJMy84ti9h/+OEWa752kBTKv4XC30OtVVHYv/8cTqKc= github.com/pquerna/cachecontrol v0.1.0/go.mod h1:NrUG3Z7Rdu85UNR3vm7SOsl1nFIeSiQnrHV5K9mBcUI= github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= @@ -821,6 +882,8 @@ github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245 h1:K1Xf3bKttbF github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= github.com/sagikazarmark/crypt v0.17.0 h1:ZA/7pXyjkHoK4bW4mIdnCLvL8hd+Nrbiw7Dqk7D4qUk= github.com/sagikazarmark/crypt v0.17.0/go.mod h1:SMtHTvdmsZMuY/bpZoqokSoChIrcJ/epOxZN58PbZDg= +github.com/sagikazarmark/crypt v0.19.0 h1:WMyLTjHBo64UvNcWqpzY3pbZTYgnemZU8FBZigKc42E= +github.com/sagikazarmark/crypt v0.19.0/go.mod h1:c6vimRziqqERhtSe0MhIvzE1w54FrCHtrXb5NH/ja78= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= @@ -832,6 +895,10 @@ github.com/segmentio/fasthash v0.0.0-20180216231524-a72b379d632e/go.mod h1:tm/wZ github.com/sercand/kuberesolver/v5 v5.1.1 h1:CYH+d67G0sGBj7q5wLK61yzqJJ8gLLC8aeprPTHb6yY= github.com/sercand/kuberesolver/v5 v5.1.1/go.mod h1:Fs1KbKhVRnB2aDWN12NjKCB+RgYMWZJ294T3BtmVCpQ= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v3 v3.24.2 h1:kcR0erMbLg5/3LcInpw0X/rrPSqq4CDPyI6A6ZRC18Y= +github.com/shirou/gopsutil/v3 v3.24.2/go.mod h1:tSg/594BcA+8UdQU2XcW803GWYgdtauFFPgJCJKZlVk= +github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v1.7.1 h1:UJcjSAI3aUKx52kfcfhblgyhZceouhvvs3OYdWgn+PY= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= @@ -852,14 +919,22 @@ github.com/tdewolff/parse/v2 v2.6.8 h1:mhNZXYCx//xG7Yq2e/kVLNZw4YfYmeHbhx+Zc0OvF github.com/tdewolff/parse/v2 v2.6.8/go.mod h1:XHDhaU6IBgsryfdnpzUXBlT6leW/l25yrFBTEb4eIyM= github.com/tidwall/gjson v1.17.1 h1:wlYEnwqAHgzmhNUFfw7Xalt2JzQvsMx2Se4PcoFCT/U= github.com/tidwall/gjson v1.17.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tursodatabase/libsql-client-go v0.0.0-20240411070317-a1138d155304 h1:Y6cw8yjWCEJDy5Bll7HjTinkgTQU55AXiKSEe29SpgA= github.com/tursodatabase/libsql-client-go v0.0.0-20240411070317-a1138d155304/go.mod h1:2Fu26tjM011BLeR5+jwTfs6DX/fNMEWV/3CBZvggrA4= +github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d h1:dOMI4+zEbDI37KGb0TI44GUAwxHF9cMsIoDTJ7UmgfU= +github.com/tursodatabase/libsql-client-go v0.0.0-20240902231107-85af5b9d094d/go.mod h1:l8xTsYB90uaVdMHXMCxKKLSgw5wLYBwBKKefNIUnm9s= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926 h1:G3dpKMzFDjgEh2q1Z7zUUtKa8ViPtH+ocF0bE0g00O8= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= @@ -901,12 +976,18 @@ github.com/yalue/merged_fs v1.3.0 h1:qCeh9tMPNy/i8cwDsQTJ5bLr6IRxbs6meakNE5O+wyY github.com/yalue/merged_fs v1.3.0/go.mod h1:WqqchfVYQyclV2tnR7wtRhBddzBvLVR83Cjw9BKQw0M= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf h1:ckwNHVo4bv2tqNkgx3W3HANh3ta1j6TR5qw08J1A7Tw= github.com/ydb-platform/ydb-go-genproto v0.0.0-20240126124512-dbb0e1720dbf/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7 h1:nL8XwD6fSst7xFUirkaWJmE7kM0CdWRYgu6+YQer1d4= +github.com/ydb-platform/ydb-go-genproto v0.0.0-20240528144234-5d5a685e41f7/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I= github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1 h1:Ebo6J5AMXgJ3A438ECYotA0aK7ETqjQx9WoZvVxzKBE= github.com/ydb-platform/ydb-go-sdk/v3 v3.55.1/go.mod h1:udNPW8eupyH/EZocecFmaSNJacKKYjzQa7cVgX5U2nc= +github.com/ydb-platform/ydb-go-sdk/v3 v3.80.2 h1:qmZGJQCNx09/r0HDIT2cDDogiOvWikELy13ubM2CFS8= +github.com/ydb-platform/ydb-go-sdk/v3 v3.80.2/go.mod h1:IHwuXyolaAmGK2Dp7+dlhsnXphG1pwCoaP/OITT3+tU= github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+K0= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= github.com/zenazn/goji v1.0.1 h1:4lbD8Mx2h7IvloP7r2C0D6ltZP6Ufip8Hn0wmSK5LR8= @@ -1020,9 +1101,15 @@ golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5D golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= +golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp/shiny v0.0.0-20230801115018-d63ba01acd4b h1:sgkbz1SFTsoQIvzTIw45hccUcGocu00QM3qucBYV8b0= +golang.org/x/exp/shiny v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:UH99kUObWAZkDnWqppdQe5ZhPYESUw8I0zVV1uWBR+0= golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= +golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4= +golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -1047,17 +1134,20 @@ golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= +gonum.org/v1/plot v0.14.0 h1:+LBDVFYwFe4LHhdP8coW6296MBEY4nQ+Y4vuUpJopcE= +gonum.org/v1/plot v0.14.0/go.mod h1:MLdR9424SJed+5VqC6MsouEpig9pZX2VZ57H9ko2bXU= google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= google.golang.org/genproto v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:mCr1K1c8kX+1iSBREvU3Juo11CB+QOEWxbRS01wWl5M= google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU= @@ -1073,8 +1163,10 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20240722135656-d784300faade/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240730163845-b1a4ccb954bf/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/cheggaaa/pb.v1 v1.0.25 h1:Ev7yu1/f6+d+b3pi5vPdRPc6nNtP1umSfcWiEfRqv6I= @@ -1105,13 +1197,14 @@ k8s.io/gengo v0.0.0-20230829151522-9cce18d56c01 h1:pWEwq4Asjm4vjW7vcsmijwBhOr1/s k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 h1:NGrVE502P0s0/1hudf8zjgwki1X/TByhmAoILTarmzo= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= modernc.org/cc/v3 v3.41.0 h1:QoR1Sn3YWlmA1T4vLaKZfawdVtSiGx8H+cEojbC7v1Q= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= modernc.org/ccgo/v3 v3.16.15 h1:KbDR3ZAVU+wiLyMESPtbtE/Add4elztFyfsWoNTgxS0= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= -modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws= modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= diff --git a/pkg/aggregator/go.mod b/pkg/aggregator/go.mod index fd0a44d51e4..601011db495 100644 --- a/pkg/aggregator/go.mod +++ b/pkg/aggregator/go.mod @@ -23,7 +23,7 @@ require ( require ( github.com/BurntSushi/toml v1.4.0 // indirect github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/antlr4-go/antlr/v4 v4.13.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/apache/arrow/go/v15 v15.0.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -53,7 +53,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.2 // indirect - github.com/google/cel-go v0.20.1 // indirect + github.com/google/cel-go v0.21.0 // indirect github.com/google/flatbuffers v24.3.25+incompatible // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -98,7 +98,7 @@ require ( github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_golang v1.20.3 // indirect + github.com/prometheus/client_golang v1.20.4 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect @@ -118,21 +118,21 @@ require ( go.etcd.io/etcd/api/v3 v3.5.14 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect go.etcd.io/etcd/client/v3 v3.5.14 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.30.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect go.opentelemetry.io/otel/trace v1.30.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.27.0 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect golang.org/x/mod v0.20.0 // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect @@ -143,11 +143,11 @@ require ( golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect - gonum.org/v1/gonum v0.14.0 // indirect + gonum.org/v1/gonum v0.15.1 // indirect google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.66.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect diff --git a/pkg/aggregator/go.sum b/pkg/aggregator/go.sum index c52f20d6294..51002ade3a7 100644 --- a/pkg/aggregator/go.sum +++ b/pkg/aggregator/go.sum @@ -5,8 +5,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0 github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE= github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= @@ -106,8 +106,8 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU= github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84= -github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg= +github.com/google/cel-go v0.21.0 h1:cl6uW/gxN+Hy50tNYvI691+sXxioCnstFzLp2WO4GCI= +github.com/google/cel-go v0.21.0/go.mod h1:rHUlWCcBKgyEk+eV03RPdZUekPp6YcJwV0FxuUksYxc= github.com/google/flatbuffers v24.3.25+incompatible h1:CX395cjN9Kke9mmalRoL3d81AtFUxJM+yDthflgJGkI= github.com/google/flatbuffers v24.3.25+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -247,8 +247,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= -github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -342,8 +342,8 @@ go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 h1:sqmsIQ75l6lfZjjpnXXT9DFVtYEDg6CH0/Cn4/3A1Wg= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0/go.mod h1:rsg1EO8LXSs2po50PB5CeY/MSVlhghuKBgXlKnqm6ks= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= @@ -355,16 +355,16 @@ go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0/go.mod h1:1kbAgQa5lgYC go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= @@ -386,8 +386,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -462,8 +462,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -471,18 +471,18 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/apimachinery/go.mod b/pkg/apimachinery/go.mod index 481dbeb1b39..f0ab30ee9c0 100644 --- a/pkg/apimachinery/go.mod +++ b/pkg/apimachinery/go.mod @@ -39,8 +39,8 @@ require ( golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.25.0 // indirect golang.org/x/text v0.18.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.66.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/pkg/apimachinery/go.sum b/pkg/apimachinery/go.sum index 3e4397d878d..f2a1732dde0 100644 --- a/pkg/apimachinery/go.sum +++ b/pkg/apimachinery/go.sum @@ -135,10 +135,10 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 4ec79bb6de8..3a728b84ae9 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -6,7 +6,7 @@ require ( github.com/google/go-cmp v0.6.0 github.com/grafana/authlib/claims v0.0.0-20240903121118-16441568af1e github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240701135906-559738ce6ae1 - github.com/prometheus/client_golang v1.20.3 + github.com/prometheus/client_golang v1.20.4 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/contrib/propagators/jaeger v1.30.0 go.opentelemetry.io/otel v1.30.0 @@ -64,16 +64,16 @@ require ( go.etcd.io/etcd/api/v3 v3.5.14 // indirect go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect go.etcd.io/etcd/client/v3 v3.5.14 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect golang.org/x/net v0.29.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sys v0.25.0 // indirect @@ -82,9 +82,9 @@ require ( golang.org/x/time v0.6.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.66.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index ca022eacc5f..73455d71aea 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -132,8 +132,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= -github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= -github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -188,22 +188,22 @@ go.etcd.io/etcd/raft/v3 v3.5.13 h1:7r/NKAOups1YnKcfro2RvGGo2PTuizF/xh26Z2CTAzA= go.etcd.io/etcd/raft/v3 v3.5.13/go.mod h1:uUFibGLn2Ksm2URMxN1fICGhk8Wu96EfDQyuLhAcAmw= go.etcd.io/etcd/server/v3 v3.5.13 h1:V6KG+yMfMSqWt+lGnhFpP5z5dRUj1BDRJ5k1fQ9DFok= go.etcd.io/etcd/server/v3 v3.5.13/go.mod h1:K/8nbsGupHqmr5MkgaZpLlH1QdX1pcNQLAkODy44XcQ= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= go.opentelemetry.io/contrib/propagators/jaeger v1.30.0 h1:g8+Y+7lnhH1DB0THjPPthzQ+RlzAntmTz8+TH2sRU0k= go.opentelemetry.io/contrib/propagators/jaeger v1.30.0/go.mod h1:lRMaD/FjOQJ2yz/MwOHYxP/BTCMFodNW/wuYDkJvdA4= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= @@ -224,8 +224,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -290,18 +290,18 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/build/go.mod b/pkg/build/go.mod index eb21e9948a2..2d855a277ba 100644 --- a/pkg/build/go.mod +++ b/pkg/build/go.mod @@ -20,7 +20,7 @@ require ( github.com/Masterminds/semver/v3 v3.2.0 // @grafana/grafana-release-guild github.com/aws/aws-sdk-go v1.55.5 // @grafana/aws-datasources github.com/blang/semver/v4 v4.0.0 // @grafana/grafana-release-guild - github.com/docker/docker v26.0.2+incompatible // @grafana/grafana-release-guild + github.com/docker/docker v27.3.1+incompatible // @grafana/grafana-release-guild github.com/drone/drone-cli v1.6.1 // @grafana/grafana-release-guild github.com/gogo/protobuf v1.3.2 // indirect; @grafana/alerting-backend github.com/google/go-cmp v0.6.0 // @grafana/grafana-backend-group @@ -32,9 +32,9 @@ require ( github.com/stretchr/testify v1.9.0 // @grafana/grafana-backend-group github.com/urfave/cli v1.22.15 // @grafana/grafana-backend-group github.com/urfave/cli/v2 v2.27.1 // @grafana/grafana-backend-group - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect; @grafana/plugins-platform-backend + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect; @grafana/plugins-platform-backend go.opentelemetry.io/otel v1.30.0 // indirect; @grafana/grafana-backend-group - go.opentelemetry.io/otel/sdk v1.29.0 // indirect; @grafana/grafana-backend-group + go.opentelemetry.io/otel/sdk v1.30.0 // indirect; @grafana/grafana-backend-group go.opentelemetry.io/otel/trace v1.30.0 // indirect; @grafana/grafana-backend-group golang.org/x/crypto v0.27.0 // indirect; @grafana/grafana-backend-group golang.org/x/mod v0.20.0 // @grafana/grafana-backend-group @@ -45,7 +45,7 @@ require ( golang.org/x/time v0.6.0 // indirect; @grafana/grafana-backend-group golang.org/x/tools v0.24.0 // indirect; @grafana/grafana-as-code google.golang.org/api v0.191.0 // @grafana/grafana-backend-group - google.golang.org/grpc v1.66.0 // indirect; @grafana/plugins-platform-backend + google.golang.org/grpc v1.67.0 // indirect; @grafana/plugins-platform-backend google.golang.org/protobuf v1.34.2 // indirect; @grafana/plugins-platform-backend gopkg.in/yaml.v3 v3.0.1 // @grafana/alerting-backend ) @@ -87,8 +87,8 @@ require ( go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect golang.org/x/sys v0.25.0 // indirect google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 // indirect; @grafana/grafana-backend-group - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) @@ -110,13 +110,13 @@ require ( github.com/vektah/gqlparser/v2 v2.5.11 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88 // indirect go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.2.0-alpha // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 // indirect go.opentelemetry.io/otel/log v0.2.0-alpha // indirect go.opentelemetry.io/otel/sdk/log v0.2.0-alpha // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e // indirect gotest.tools/v3 v3.5.1 // indirect ) diff --git a/pkg/build/go.sum b/pkg/build/go.sum index e348659ac5e..3e6e7758deb 100644 --- a/pkg/build/go.sum +++ b/pkg/build/go.sum @@ -227,8 +227,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= @@ -237,18 +237,18 @@ go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-2024051809000 go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.0.0-20240518090000-14441aefdf88/go.mod h1:JGG8ebaMO5nXOPnvKEl+DiA4MGwFjCbjsxT1WHIEBPY= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.2.0-alpha h1:z2s6Zba+OUyayRv5m1AXWNUTGh57K1iMhy6emU5QT5Y= go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.2.0-alpha/go.mod h1:paOXXyUgPW6jYxYkP0pB47H2zHE1fPvMJ4E4G9LHOi0= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0 h1:JAv0Jwtl01UFiyWZEMiJZBiTlv5A50zNs8lsthXqIio= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.29.0/go.mod h1:QNKLmUEAq2QUbPQUfvw4fmv0bgbK7UlOSFCnXyfvSNc= go.opentelemetry.io/otel/log v0.2.0-alpha h1:ixOPvMzserpqA07SENHvRzkZOsnG0XbPr74hv1AQ+n0= go.opentelemetry.io/otel/log v0.2.0-alpha/go.mod h1:vbFZc65yq4c4ssvXY43y/nIqkNJLxORrqw0L85P59LA= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/sdk/log v0.2.0-alpha h1:jGTkL/jroJ31jnP6jDl34N/mDOfRGGYZHcHsCM+5kWA= go.opentelemetry.io/otel/sdk/log v0.2.0-alpha/go.mod h1:Hd8Lw9FPGUM3pfY7iGMRvFaC2Nyau4Ajb5WnQ9OdIho= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= @@ -266,8 +266,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -339,18 +339,18 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988 h1:CT2Thj5AuPV9phrYMtzX11k+XkzMGfRAet42PmoTATM= google.golang.org/genproto v0.0.0-20240812133136-8ffd90a71988/go.mod h1:7uvplUBj4RjHAxIZ//98LzOvrQ04JBkaixRmCMI29hc= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 8d79b5ff460..51e5bc9cc60 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -7,13 +7,13 @@ require ( github.com/grafana/grafana-plugin-sdk-go v0.251.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/prometheus/client_golang v1.20.3 + github.com/prometheus/client_golang v1.20.4 github.com/prometheus/common v0.55.0 github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 github.com/stretchr/testify v1.9.0 go.opentelemetry.io/otel v1.30.0 go.opentelemetry.io/otel/trace v1.30.0 - golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa + golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e k8s.io/apimachinery v0.31.1 ) @@ -101,14 +101,14 @@ require ( github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 // indirect go.opentelemetry.io/contrib/propagators/jaeger v1.30.0 // indirect go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 // indirect go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/sdk v1.29.0 // indirect + go.opentelemetry.io/otel/sdk v1.30.0 // indirect go.opentelemetry.io/proto/otlp v1.3.1 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/goleak v1.3.0 // indirect @@ -119,10 +119,10 @@ require ( golang.org/x/text v0.18.0 // indirect golang.org/x/tools v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect - gonum.org/v1/gonum v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd // indirect - google.golang.org/grpc v1.66.0 // indirect + gonum.org/v1/gonum v0.15.1 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/grpc v1.67.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/fsnotify/fsnotify.v1 v1.4.7 // indirect gopkg.in/inf.v0 v0.9.1 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 2d878a7363f..af656a4d453 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -199,8 +199,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.3 h1:oPksm4K8B+Vt35tUhw6GbSNSgVlVSBH0qELP/7u83l4= -github.com/prometheus/client_golang v1.20.3/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= @@ -262,8 +262,8 @@ github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0 h1:9G6E0TXzGFVfTnawRzrPl83iHOAV7L8NJiR8RSGYV1g= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.53.0/go.mod h1:azvtTADFQJA8mX80jIH/akaE7h+dbm/sVuaHqN13w74= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0 h1:hCq2hNMwsegUvPzI7sPOvtO9cqyy5GbWt/Ybp2xrx8Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.55.0/go.mod h1:LqaApwGx/oUmzsbqxkzuBvyoPpkxk3JQWnqfVrJ3wCA= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0 h1:sqmsIQ75l6lfZjjpnXXT9DFVtYEDg6CH0/Cn4/3A1Wg= go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.55.0/go.mod h1:rsg1EO8LXSs2po50PB5CeY/MSVlhghuKBgXlKnqm6ks= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= @@ -275,16 +275,16 @@ go.opentelemetry.io/contrib/samplers/jaegerremote v0.23.0/go.mod h1:1kbAgQa5lgYC go.opentelemetry.io/otel v1.21.0/go.mod h1:QZzNPQPm1zLX4gZK4cMi+71eaorMSGT3A4znnUvNNEo= go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0 h1:dIIDULZJpgdiHz5tXrTgKIMLkus6jEFa7x5SOKcyR7E= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.29.0/go.mod h1:jlRVBe7+Z1wyxFSUs48L6OBQZ5JwH2Hg/Vbl+t9rAgI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0 h1:nSiV3s7wiCam610XcLbYOmMfJxB9gO4uK3Xgv5gmTgg= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.29.0/go.mod h1:hKn/e/Nmd19/x1gvIHwtOwVWM+VhuITSWip3JUDghj0= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0 h1:m0yTiGDLUvVYaTFbAvCkVYIYcvwKt3G7OLoN77NUs/8= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.30.0/go.mod h1:wBQbT4UekBfegL2nx0Xk1vBcnzyBPsIVm9hRG4fYcr4= go.opentelemetry.io/otel/metric v1.21.0/go.mod h1:o1p3CA8nNHW8j5yuQLdc1eeqEaPfzug24uvsyIEJRWM= go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E= -go.opentelemetry.io/otel/sdk v1.29.0 h1:vkqKjk7gwhS8VaWb0POZKmIEDimRCMsopNYnriHyryo= -go.opentelemetry.io/otel/sdk v1.29.0/go.mod h1:pM8Dx5WKnvxLCb+8lG1PRNIDxu9g9b9g59Qr7hfAAok= +go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= +go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= go.opentelemetry.io/otel/trace v1.21.0/go.mod h1:LGbsEB0f9LGjN+OZaQQ26sohbOmiMR+BaslueVtS/qQ= go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= @@ -297,8 +297,8 @@ go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI= -golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e h1:I88y4caeGeuDQxgdoFPUq097j7kNfw6uvuiNxUBfcBk= +golang.org/x/exp v0.0.0-20240904232852-e7e105dedf7e/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= @@ -349,14 +349,14 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= -gonum.org/v1/gonum v0.14.0/go.mod h1:AoWeoz0becf9QMWtE8iWXNXc27fK4fNeHNf/oMejGfU= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd h1:BBOTEWLuuEGQy9n1y9MhVJ9Qt0BDu21X8qZs71/uPZo= -google.golang.org/genproto/googleapis/api v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:fO8wJzT2zbQbAjbIoos1285VfEIYKDDY+Dt+WpTkh6g= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd h1:6TEm2ZxXoQmFWFlt1vNxvVOa1Q0dXFQD1m/rYjXmS0E= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240822170219-fc7c04adadcd/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= -google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= +gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= +google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= +google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/services/authz/zanzana/logger/logger.go b/pkg/services/authz/zanzana/logger/logger.go index ac953ed4f15..1e1f2a6435d 100644 --- a/pkg/services/authz/zanzana/logger/logger.go +++ b/pkg/services/authz/zanzana/logger/logger.go @@ -4,10 +4,14 @@ import ( "context" "go.uber.org/zap" + "go.uber.org/zap/zapcore" "github.com/grafana/grafana/pkg/infra/log" + "github.com/openfga/openfga/pkg/logger" ) +var _ logger.Logger = (*ZanzanaLogger)(nil) + // ZanzanaLogger is a grafana logger wrapper compatible with OpenFGA logger interface type ZanzanaLogger struct { logger log.Logger @@ -36,6 +40,13 @@ func zapFieldsToArgs(fields []zap.Field) []any { return args } +// With implements logger.Logger. +func (l *ZanzanaLogger) With(fields ...zapcore.Field) logger.Logger { + return &ZanzanaLogger{ + logger: l.logger.New(zapFieldsToArgs(fields)...), + } +} + func (l *ZanzanaLogger) Debug(msg string, fields ...zap.Field) { l.logger.Debug(msg, zapFieldsToArgs(fields)...) } diff --git a/pkg/services/authz/zanzana/store/assets/assets.go b/pkg/services/authz/zanzana/store/assets/assets.go deleted file mode 100644 index fb55d16eef2..00000000000 --- a/pkg/services/authz/zanzana/store/assets/assets.go +++ /dev/null @@ -1,10 +0,0 @@ -package assets - -import "embed" - -// EmbedMigrations within the grafana binary. -// -//go:embed migrations/* -var EmbedMigrations embed.FS - -const SQLiteMigrationDir = "migrations/sqlite" diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/001_initialize_schema.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/001_initialize_schema.sql deleted file mode 100644 index ecceebfb58b..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/001_initialize_schema.sql +++ /dev/null @@ -1,56 +0,0 @@ --- +goose Up -CREATE TABLE tuple ( - store CHAR(26) NOT NULL, - object_type VARCHAR(128) NOT NULL, - object_id VARCHAR(128) NOT NULL, - relation VARCHAR(50) NOT NULL, - _user VARCHAR(256) NOT NULL, - user_type VARCHAR(7) NOT NULL, - ulid CHAR(26) NOT NULL, - inserted_at TIMESTAMP NOT NULL, - PRIMARY KEY (store, object_type, object_id, relation, _user) -); - -CREATE UNIQUE INDEX idx_tuple_ulid ON tuple (ulid); - -CREATE TABLE authorization_model ( - store CHAR(26) NOT NULL, - authorization_model_id CHAR(26) NOT NULL, - type VARCHAR(256) NOT NULL, - type_definition BLOB, - PRIMARY KEY (store, authorization_model_id, type) -); - -CREATE TABLE store ( - id CHAR(26) PRIMARY KEY, - name VARCHAR(64) NOT NULL, - created_at TIMESTAMP NOT NULL, - updated_at TIMESTAMP, - deleted_at TIMESTAMP -); - -CREATE TABLE assertion ( - store CHAR(26) NOT NULL, - authorization_model_id CHAR(26) NOT NULL, - assertions BLOB, - PRIMARY KEY (store, authorization_model_id) -); - -CREATE TABLE changelog ( - store CHAR(26) NOT NULL, - object_type VARCHAR(256) NOT NULL, - object_id VARCHAR(256) NOT NULL, - relation VARCHAR(50) NOT NULL, - _user VARCHAR(512) NOT NULL, - operation INTEGER NOT NULL, - ulid CHAR(26) NOT NULL, - inserted_at TIMESTAMP NOT NULL, - PRIMARY KEY (store, ulid, object_type) -); - --- +goose Down -DROP TABLE tuple; -DROP TABLE authorization_model; -DROP TABLE store; -DROP TABLE assertion; -DROP TABLE changelog; diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/002_add_authorization_model_version.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/002_add_authorization_model_version.sql deleted file mode 100644 index 26ce15ac18b..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/002_add_authorization_model_version.sql +++ /dev/null @@ -1,5 +0,0 @@ --- +goose Up -ALTER TABLE authorization_model ADD COLUMN schema_version VARCHAR(5) NOT NULL DEFAULT '1.0'; - --- +goose Down -ALTER TABLE authorization_model DROP COLUMN schema_version; diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/003_add_reverse_lookup_index.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/003_add_reverse_lookup_index.sql deleted file mode 100644 index 9a3ee2a82bc..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/003_add_reverse_lookup_index.sql +++ /dev/null @@ -1,5 +0,0 @@ --- +goose Up -CREATE INDEX idx_reverse_lookup_user on tuple (store, object_type, relation, _user); - --- +goose Down -DROP INDEX idx_reverse_lookup_user on tuple; diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/004_add_authorization_model_serialized_protobuf.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/004_add_authorization_model_serialized_protobuf.sql deleted file mode 100644 index 1e47f290e51..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/004_add_authorization_model_serialized_protobuf.sql +++ /dev/null @@ -1,5 +0,0 @@ --- +goose Up -ALTER TABLE authorization_model ADD COLUMN serialized_protobuf LONGBLOB; - --- +goose Down -ALTER TABLE authorization_model DROP COLUMN serialized_protobuf; diff --git a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/005_add_conditions_to_tuples.sql b/pkg/services/authz/zanzana/store/assets/migrations/sqlite/005_add_conditions_to_tuples.sql deleted file mode 100644 index 37d039ca799..00000000000 --- a/pkg/services/authz/zanzana/store/assets/migrations/sqlite/005_add_conditions_to_tuples.sql +++ /dev/null @@ -1,11 +0,0 @@ --- +goose Up -ALTER TABLE tuple ADD COLUMN condition_name VARCHAR(256); -ALTER TABLE tuple ADD COLUMN condition_context LONGBLOB; -ALTER TABLE changelog ADD COLUMN condition_name VARCHAR(256); -ALTER TABLE changelog ADD COLUMN condition_context LONGBLOB; - --- +goose Down -ALTER TABLE tuple DROP COLUMN condition_name; -ALTER TABLE tuple DROP COLUMN condition_context; -ALTER TABLE changelog DROP COLUMN condition_name; -ALTER TABLE changelog DROP COLUMN condition_context; diff --git a/pkg/services/authz/zanzana/store/migration/migrator.go b/pkg/services/authz/zanzana/store/migration/migrator.go index 3f0d13f7343..3a2db5aa8c9 100644 --- a/pkg/services/authz/zanzana/store/migration/migrator.go +++ b/pkg/services/authz/zanzana/store/migration/migrator.go @@ -15,13 +15,17 @@ import ( func Run(cfg *setting.Cfg, typ, connStr string, fs embed.FS, path string) error { engine, err := xorm.NewEngine(typ, connStr) if err != nil { - return fmt.Errorf("failed to parse database config: %w", err) + return fmt.Errorf("failed to create db engine: %w", err) } m := migrator.NewMigrator(engine, cfg) m.AddCreateMigration() - return RunWithMigrator(m, cfg, fs, path) + if err := RunWithMigrator(m, cfg, fs, path); err != nil { + return err + } + + return engine.Close() } func RunWithMigrator(m *migrator.Migrator, cfg *setting.Cfg, fs embed.FS, path string) error { diff --git a/pkg/services/authz/zanzana/store/sqlite/config.go b/pkg/services/authz/zanzana/store/sqlite/config.go deleted file mode 100644 index b47404d6028..00000000000 --- a/pkg/services/authz/zanzana/store/sqlite/config.go +++ /dev/null @@ -1,15 +0,0 @@ -package sqlite - -import "github.com/openfga/openfga/pkg/storage/sqlcommon" - -type Config struct { - *sqlcommon.Config - QueryRetries int -} - -func NewConfig() *Config { - return &Config{ - Config: sqlcommon.NewConfig(), - QueryRetries: 0, - } -} diff --git a/pkg/services/authz/zanzana/store/sqlite/store.go b/pkg/services/authz/zanzana/store/sqlite/store.go deleted file mode 100644 index 0860eb3b968..00000000000 --- a/pkg/services/authz/zanzana/store/sqlite/store.go +++ /dev/null @@ -1,821 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "encoding/json" - "errors" - "fmt" - "net/url" - "strings" - "time" - - sq "github.com/Masterminds/squirrel" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/collectors" - "go.opentelemetry.io/otel" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/structpb" - "google.golang.org/protobuf/types/known/timestamppb" - - // Pull in sqlite driver. - "github.com/mattn/go-sqlite3" - - openfgav1 "github.com/openfga/api/proto/openfga/v1" - "github.com/openfga/openfga/pkg/logger" - "github.com/openfga/openfga/pkg/storage" - "github.com/openfga/openfga/pkg/storage/sqlcommon" - tupleUtils "github.com/openfga/openfga/pkg/tuple" -) - -var tracer = otel.Tracer("openfga/pkg/storage/sqlite") - -// SQLite provides a SQLite based implementation of [storage.OpenFGADatastore]. -type SQLite struct { - stbl sq.StatementBuilderType - cfg *Config - db *sql.DB - dbInfo *sqlcommon.DBInfo - sqlTime sq.Sqlizer - logger logger.Logger - dbStatsCollector prometheus.Collector -} - -// Ensures that SQLite implements the OpenFGADatastore interface. -var _ storage.OpenFGADatastore = (*SQLite)(nil) - -// New creates a new [SQLite] storage. -func New(uri string, cfg *Config) (*SQLite, error) { - // Set journal mode and busy timeout pragmas if not specified. - query := url.Values{} - var err error - - if i := strings.Index(uri, "?"); i != -1 { - query, err = url.ParseQuery(uri[i+1:]) - if err != nil { - return nil, fmt.Errorf("error parsing dsn: %w", err) - } - - uri = uri[:i] - } - - foundJournalMode := false - foundBusyTimeout := false - for _, val := range query["_pragma"] { - if strings.HasPrefix(val, "journal_mode") { - foundJournalMode = true - } else if strings.HasPrefix(val, "busy_timeout") { - foundBusyTimeout = true - } - } - - if !foundJournalMode { - query.Add("_pragma", "journal_mode(WAL)") - } - if !foundBusyTimeout { - query.Add("_pragma", "busy_timeout(500)") - } - - uri += "?" + query.Encode() - - db, err := sql.Open("sqlite", uri) - if err != nil { - return nil, fmt.Errorf("initialize sqlite connection: %w", err) - } - - return NewWithDB(db, cfg) -} - -// NewWithDB creates a new [SQLite] storage using provided [*sql.DB] -func NewWithDB(db *sql.DB, cfg *Config) (*SQLite, error) { - var collector prometheus.Collector - if cfg.ExportMetrics { - collector = collectors.NewDBStatsCollector(db, "openfga") - if err := prometheus.Register(collector); err != nil { - return nil, fmt.Errorf("initialize metrics: %w", err) - } - } - - sqlTime := sq.Expr("datetime('subsec')") - stbl := sq.StatementBuilder.RunWith(db) - dbInfo := sqlcommon.NewDBInfo(db, stbl, sqlTime) - - return &SQLite{ - cfg: cfg, - stbl: stbl, - db: db, - sqlTime: sqlTime, - dbInfo: dbInfo, - logger: cfg.Logger, - dbStatsCollector: collector, - }, nil -} - -// Close see [storage.OpenFGADatastore].Close. -func (m *SQLite) Close() { - if m.dbStatsCollector != nil { - prometheus.Unregister(m.dbStatsCollector) - } - _ = m.db.Close() -} - -// Read see [storage.RelationshipTupleReader].Read. -func (m *SQLite) Read(ctx context.Context, store string, tupleKey *openfgav1.TupleKey) (storage.TupleIterator, error) { - ctx, span := tracer.Start(ctx, "sqlite.Read") - defer span.End() - - return m.read(ctx, store, tupleKey, nil) -} - -// ReadPage see [storage.RelationshipTupleReader].ReadPage. -func (m *SQLite) ReadPage( - ctx context.Context, - store string, - tupleKey *openfgav1.TupleKey, - opts storage.PaginationOptions, -) ([]*openfgav1.Tuple, []byte, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadPage") - defer span.End() - - iter, err := m.read(ctx, store, tupleKey, &opts) - if err != nil { - return nil, nil, err - } - defer iter.Stop() - - return iter.ToArray(opts) -} - -func (m *SQLite) read(ctx context.Context, store string, tupleKey *openfgav1.TupleKey, opts *storage.PaginationOptions) (*sqlcommon.SQLTupleIterator, error) { - ctx, span := tracer.Start(ctx, "sqlite.read") - defer span.End() - - sb := m.stbl. - Select( - "store", "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", "ulid", "inserted_at", - ). - From("tuple"). - Where(sq.Eq{"store": store}) - if opts != nil { - sb = sb.OrderBy("ulid") - } - objectType, objectID := tupleUtils.SplitObject(tupleKey.GetObject()) - if objectType != "" { - sb = sb.Where(sq.Eq{"object_type": objectType}) - } - if objectID != "" { - sb = sb.Where(sq.Eq{"object_id": objectID}) - } - if tupleKey.GetRelation() != "" { - sb = sb.Where(sq.Eq{"relation": tupleKey.GetRelation()}) - } - if tupleKey.GetUser() != "" { - sb = sb.Where(sq.Eq{"_user": tupleKey.GetUser()}) - } - if opts != nil && opts.From != "" { - token, err := sqlcommon.UnmarshallContToken(opts.From) - if err != nil { - return nil, err - } - sb = sb.Where(sq.GtOrEq{"ulid": token.Ulid}) - } - if opts != nil && opts.PageSize != 0 { - sb = sb.Limit(uint64(opts.PageSize + 1)) // + 1 is used to determine whether to return a continuation token. - } - - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, handleSQLError(err) - } - - return sqlcommon.NewSQLTupleIterator(rows), nil -} - -// Write see [storage.RelationshipTupleWriter].Write. -func (m *SQLite) Write(ctx context.Context, store string, deletes storage.Deletes, writes storage.Writes) error { - ctx, span := tracer.Start(ctx, "sqlite.Write") - defer span.End() - - if len(deletes)+len(writes) > m.MaxTuplesPerWrite() { - return storage.ErrExceededWriteBatchLimit - } - - return m.busyRetry(func() error { - now := time.Now().UTC() - return write(ctx, m.db, m.stbl, m.sqlTime, store, deletes, writes, now) - }) -} - -// ReadUserTuple see [storage.RelationshipTupleReader].ReadUserTuple. -func (m *SQLite) ReadUserTuple(ctx context.Context, store string, tupleKey *openfgav1.TupleKey) (*openfgav1.Tuple, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadUserTuple") - defer span.End() - - objectType, objectID := tupleUtils.SplitObject(tupleKey.GetObject()) - userType := tupleUtils.GetUserTypeFromUser(tupleKey.GetUser()) - - var conditionName sql.NullString - var conditionContext []byte - var record storage.TupleRecord - err := m.stbl. - Select( - "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", - ). - From("tuple"). - Where(sq.Eq{ - "store": store, - "object_type": objectType, - "object_id": objectID, - "relation": tupleKey.GetRelation(), - "_user": tupleKey.GetUser(), - "user_type": userType, - }). - QueryRowContext(ctx). - Scan( - &record.ObjectType, - &record.ObjectID, - &record.Relation, - &record.User, - &conditionName, - &conditionContext, - ) - if err != nil { - return nil, handleSQLError(err) - } - - if conditionName.String != "" { - record.ConditionName = conditionName.String - - if conditionContext != nil { - var conditionContextStruct structpb.Struct - if err := proto.Unmarshal(conditionContext, &conditionContextStruct); err != nil { - return nil, err - } - record.ConditionContext = &conditionContextStruct - } - } - - return record.AsTuple(), nil -} - -// ReadUsersetTuples see [storage.RelationshipTupleReader].ReadUsersetTuples. -func (m *SQLite) ReadUsersetTuples( - ctx context.Context, - store string, - filter storage.ReadUsersetTuplesFilter, -) (storage.TupleIterator, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadUsersetTuples") - defer span.End() - - sb := m.stbl. - Select( - "store", "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", "ulid", "inserted_at", - ). - From("tuple"). - Where(sq.Eq{"store": store}). - Where(sq.Eq{"user_type": tupleUtils.UserSet}) - - objectType, objectID := tupleUtils.SplitObject(filter.Object) - if objectType != "" { - sb = sb.Where(sq.Eq{"object_type": objectType}) - } - if objectID != "" { - sb = sb.Where(sq.Eq{"object_id": objectID}) - } - if filter.Relation != "" { - sb = sb.Where(sq.Eq{"relation": filter.Relation}) - } - if len(filter.AllowedUserTypeRestrictions) > 0 { - orConditions := sq.Or{} - for _, userset := range filter.AllowedUserTypeRestrictions { - if _, ok := userset.GetRelationOrWildcard().(*openfgav1.RelationReference_Relation); ok { - orConditions = append(orConditions, sq.Like{"_user": userset.GetType() + ":%#" + userset.GetRelation()}) - } - if _, ok := userset.GetRelationOrWildcard().(*openfgav1.RelationReference_Wildcard); ok { - orConditions = append(orConditions, sq.Eq{"_user": userset.GetType() + ":*"}) - } - } - sb = sb.Where(orConditions) - } - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, handleSQLError(err) - } - - return sqlcommon.NewSQLTupleIterator(rows), nil -} - -// ReadStartingWithUser see [storage.RelationshipTupleReader].ReadStartingWithUser. -func (m *SQLite) ReadStartingWithUser( - ctx context.Context, - store string, - opts storage.ReadStartingWithUserFilter, -) (storage.TupleIterator, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadStartingWithUser") - defer span.End() - - targetUsersArg := make([]string, 0, len(opts.UserFilter)) - for _, u := range opts.UserFilter { - targetUser := u.GetObject() - if u.GetRelation() != "" { - targetUser = strings.Join([]string{u.GetObject(), u.GetRelation()}, "#") - } - targetUsersArg = append(targetUsersArg, targetUser) - } - - rows, err := m.stbl. - Select( - "store", "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", "ulid", "inserted_at", - ). - From("tuple"). - Where(sq.Eq{ - "store": store, - "object_type": opts.ObjectType, - "relation": opts.Relation, - "_user": targetUsersArg, - }).QueryContext(ctx) - if err != nil { - return nil, handleSQLError(err) - } - - return sqlcommon.NewSQLTupleIterator(rows), nil -} - -// MaxTuplesPerWrite see [storage.RelationshipTupleWriter].MaxTuplesPerWrite. -func (m *SQLite) MaxTuplesPerWrite() int { - return m.cfg.MaxTuplesPerWriteField -} - -// ReadAuthorizationModel see [storage.AuthorizationModelReadBackend].ReadAuthorizationModel. -func (m *SQLite) ReadAuthorizationModel(ctx context.Context, store string, modelID string) (*openfgav1.AuthorizationModel, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadAuthorizationModel") - defer span.End() - - return sqlcommon.ReadAuthorizationModel(ctx, m.dbInfo, store, modelID) -} - -// ReadAuthorizationModels see [storage.AuthorizationModelReadBackend].ReadAuthorizationModels. -func (m *SQLite) ReadAuthorizationModels( - ctx context.Context, - store string, - opts storage.PaginationOptions, -) ([]*openfgav1.AuthorizationModel, []byte, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadAuthorizationModels") - defer span.End() - - sb := m.stbl.Select("authorization_model_id"). - Distinct(). - From("authorization_model"). - Where(sq.Eq{"store": store}). - OrderBy("authorization_model_id desc") - - if opts.From != "" { - token, err := sqlcommon.UnmarshallContToken(opts.From) - if err != nil { - return nil, nil, err - } - sb = sb.Where(sq.LtOrEq{"authorization_model_id": token.Ulid}) - } - if opts.PageSize > 0 { - sb = sb.Limit(uint64(opts.PageSize + 1)) // + 1 is used to determine whether to return a continuation token. - } - - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, nil, handleSQLError(err) - } - defer func() { _ = rows.Close() }() - - var modelIDs []string - var modelID string - - for rows.Next() { - err = rows.Scan(&modelID) - if err != nil { - return nil, nil, handleSQLError(err) - } - - modelIDs = append(modelIDs, modelID) - } - - if err := rows.Err(); err != nil { - return nil, nil, handleSQLError(err) - } - - var token []byte - numModelIDs := len(modelIDs) - if len(modelIDs) > opts.PageSize { - numModelIDs = opts.PageSize - token, err = json.Marshal(sqlcommon.NewContToken(modelID, "")) - if err != nil { - return nil, nil, err - } - } - - // TODO: make this concurrent with a maximum of 5 goroutines. This may be helpful: - // https://stackoverflow.com/questions/25306073/always-have-x-number-of-goroutines-running-at-any-time - models := make([]*openfgav1.AuthorizationModel, 0, numModelIDs) - // We use numModelIDs here to avoid retrieving possibly one extra model. - for i := 0; i < numModelIDs; i++ { - model, err := m.ReadAuthorizationModel(ctx, store, modelIDs[i]) - if err != nil { - return nil, nil, err - } - models = append(models, model) - } - - return models, token, nil -} - -// FindLatestAuthorizationModel see [storage.AuthorizationModelReadBackend].FindLatestAuthorizationModel. -func (m *SQLite) FindLatestAuthorizationModel(ctx context.Context, store string) (*openfgav1.AuthorizationModel, error) { - ctx, span := tracer.Start(ctx, "sqlite.FindLatestAuthorizationModel") - defer span.End() - - return sqlcommon.FindLatestAuthorizationModel(ctx, m.dbInfo, store) -} - -// MaxTypesPerAuthorizationModel see [storage.TypeDefinitionWriteBackend].MaxTypesPerAuthorizationModel. -func (m *SQLite) MaxTypesPerAuthorizationModel() int { - return m.cfg.MaxTypesPerModelField -} - -// WriteAuthorizationModel see [storage.TypeDefinitionWriteBackend].WriteAuthorizationModel. -func (m *SQLite) WriteAuthorizationModel(ctx context.Context, store string, model *openfgav1.AuthorizationModel) error { - ctx, span := tracer.Start(ctx, "sqlite.WriteAuthorizationModel") - defer span.End() - - typeDefinitions := model.GetTypeDefinitions() - - if len(typeDefinitions) > m.MaxTypesPerAuthorizationModel() { - return storage.ExceededMaxTypeDefinitionsLimitError(m.MaxTypesPerAuthorizationModel()) - } - - return m.busyRetry(func() error { - return sqlcommon.WriteAuthorizationModel(ctx, m.dbInfo, store, model) - }) -} - -// CreateStore adds a new store to the SQLite storage. -func (m *SQLite) CreateStore(ctx context.Context, store *openfgav1.Store) (*openfgav1.Store, error) { - ctx, span := tracer.Start(ctx, "sqlite.CreateStore") - defer span.End() - - txn, err := m.db.BeginTx(ctx, &sql.TxOptions{}) - if err != nil { - return nil, handleSQLError(err) - } - defer func() { - _ = txn.Rollback() - }() - - _, err = m.stbl. - Insert("store"). - Columns("id", "name", "created_at", "updated_at"). - Values(store.GetId(), store.GetName(), sq.Expr("datetime('subsec')"), sq.Expr("datetime('subsec')")). - RunWith(txn). - ExecContext(ctx) - if err != nil { - return nil, handleSQLError(err) - } - - var createdAt time.Time - var id, name string - err = m.stbl. - Select("id", "name", "created_at"). - From("store"). - Where(sq.Eq{"id": store.GetId()}). - RunWith(txn). - QueryRowContext(ctx). - Scan(&id, &name, &createdAt) - if err != nil { - return nil, handleSQLError(err) - } - - err = txn.Commit() - if err != nil { - return nil, handleSQLError(err) - } - - return &openfgav1.Store{ - Id: id, - Name: name, - CreatedAt: timestamppb.New(createdAt), - UpdatedAt: timestamppb.New(createdAt), - }, nil -} - -// GetStore retrieves the details of a specific store from the SQLite using its storeID. -func (m *SQLite) GetStore(ctx context.Context, id string) (*openfgav1.Store, error) { - ctx, span := tracer.Start(ctx, "sqlite.GetStore") - defer span.End() - - row := m.stbl. - Select("id", "name", "created_at", "updated_at"). - From("store"). - Where(sq.Eq{ - "id": id, - "deleted_at": nil, - }). - QueryRowContext(ctx) - - var storeID, name string - var createdAt, updatedAt time.Time - err := row.Scan(&storeID, &name, &createdAt, &updatedAt) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return nil, storage.ErrNotFound - } - return nil, handleSQLError(err) - } - - return &openfgav1.Store{ - Id: storeID, - Name: name, - CreatedAt: timestamppb.New(createdAt), - UpdatedAt: timestamppb.New(updatedAt), - }, nil -} - -// ListStores provides a paginated list of all stores present in the SQLite storage. -func (m *SQLite) ListStores(ctx context.Context, opts storage.PaginationOptions) ([]*openfgav1.Store, []byte, error) { - ctx, span := tracer.Start(ctx, "sqlite.ListStores") - defer span.End() - - sb := m.stbl. - Select("id", "name", "created_at", "updated_at"). - From("store"). - Where(sq.Eq{"deleted_at": nil}). - OrderBy("id") - - if opts.From != "" { - token, err := sqlcommon.UnmarshallContToken(opts.From) - if err != nil { - return nil, nil, err - } - sb = sb.Where(sq.GtOrEq{"id": token.Ulid}) - } - if opts.PageSize > 0 { - sb = sb.Limit(uint64(opts.PageSize + 1)) // + 1 is used to determine whether to return a continuation token. - } - - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, nil, handleSQLError(err) - } - defer func() { _ = rows.Close() }() - - var stores []*openfgav1.Store - var id string - for rows.Next() { - var name string - var createdAt, updatedAt time.Time - err := rows.Scan(&id, &name, &createdAt, &updatedAt) - if err != nil { - return nil, nil, handleSQLError(err) - } - - stores = append(stores, &openfgav1.Store{ - Id: id, - Name: name, - CreatedAt: timestamppb.New(createdAt), - UpdatedAt: timestamppb.New(updatedAt), - }) - } - - if err := rows.Err(); err != nil { - return nil, nil, handleSQLError(err) - } - - if len(stores) > opts.PageSize { - contToken, err := json.Marshal(sqlcommon.NewContToken(id, "")) - if err != nil { - return nil, nil, err - } - return stores[:opts.PageSize], contToken, nil - } - - return stores, nil, nil -} - -// DeleteStore removes a store from the SQLite storage. -func (m *SQLite) DeleteStore(ctx context.Context, id string) error { - ctx, span := tracer.Start(ctx, "sqlite.DeleteStore") - defer span.End() - - _, err := m.stbl. - Update("store"). - Set("deleted_at", sq.Expr("datetime('subsec')")). - Where(sq.Eq{"id": id}). - ExecContext(ctx) - if err != nil { - return handleSQLError(err) - } - - return nil -} - -// WriteAssertions see [storage.AssertionsBackend].WriteAssertions. -func (m *SQLite) WriteAssertions(ctx context.Context, store, modelID string, assertions []*openfgav1.Assertion) error { - ctx, span := tracer.Start(ctx, "sqlite.WriteAssertions") - defer span.End() - - marshalledAssertions, err := proto.Marshal(&openfgav1.Assertions{Assertions: assertions}) - if err != nil { - return err - } - - return m.busyRetry(func() error { - _, err = m.stbl. - Insert("assertion"). - Columns("store", "authorization_model_id", "assertions"). - Values(store, modelID, marshalledAssertions). - Suffix("ON CONFLICT(store,authorization_model_id) DO UPDATE SET assertions = ?", marshalledAssertions). - ExecContext(ctx) - if err != nil { - return handleSQLError(err) - } - - return nil - }) -} - -// ReadAssertions see [storage.AssertionsBackend].ReadAssertions. -func (m *SQLite) ReadAssertions(ctx context.Context, store, modelID string) ([]*openfgav1.Assertion, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadAssertions") - defer span.End() - - var marshalledAssertions []byte - err := m.stbl. - Select("assertions"). - From("assertion"). - Where(sq.Eq{ - "store": store, - "authorization_model_id": modelID, - }). - QueryRowContext(ctx). - Scan(&marshalledAssertions) - if err != nil { - if errors.Is(err, sql.ErrNoRows) { - return []*openfgav1.Assertion{}, nil - } - return nil, handleSQLError(err) - } - - var assertions openfgav1.Assertions - err = proto.Unmarshal(marshalledAssertions, &assertions) - if err != nil { - return nil, err - } - - return assertions.GetAssertions(), nil -} - -// ReadChanges see [storage.ChangelogBackend].ReadChanges. -func (m *SQLite) ReadChanges( - ctx context.Context, - store, objectTypeFilter string, - opts storage.PaginationOptions, - horizonOffset time.Duration, -) ([]*openfgav1.TupleChange, []byte, error) { - ctx, span := tracer.Start(ctx, "sqlite.ReadChanges") - defer span.End() - - sb := m.stbl. - Select( - "ulid", "object_type", "object_id", "relation", "_user", "operation", - "condition_name", "condition_context", "inserted_at", - ). - From("changelog"). - Where(sq.Eq{"store": store}). - Where(fmt.Sprintf("inserted_at <= datetime('subsec','-%f seconds')", horizonOffset.Seconds())). - OrderBy("ulid asc") - - if objectTypeFilter != "" { - sb = sb.Where(sq.Eq{"object_type": objectTypeFilter}) - } - if opts.From != "" { - token, err := sqlcommon.UnmarshallContToken(opts.From) - if err != nil { - return nil, nil, err - } - if token.ObjectType != objectTypeFilter { - return nil, nil, storage.ErrMismatchObjectType - } - - sb = sb.Where(sq.Gt{"ulid": token.Ulid}) // > as we always return a continuation token. - } - if opts.PageSize > 0 { - sb = sb.Limit(uint64(opts.PageSize)) // + 1 is NOT used here as we always return a continuation token. - } - - rows, err := sb.QueryContext(ctx) - if err != nil { - return nil, nil, handleSQLError(err) - } - defer func() { _ = rows.Close() }() - - var changes []*openfgav1.TupleChange - var ulid string - for rows.Next() { - var objectType, objectID, relation, user string - var operation int - var insertedAt time.Time - var conditionName sql.NullString - var conditionContext []byte - - err = rows.Scan( - &ulid, - &objectType, - &objectID, - &relation, - &user, - &operation, - &conditionName, - &conditionContext, - &insertedAt, - ) - if err != nil { - return nil, nil, handleSQLError(err) - } - - var conditionContextStruct structpb.Struct - if conditionName.String != "" { - if conditionContext != nil { - if err := proto.Unmarshal(conditionContext, &conditionContextStruct); err != nil { - return nil, nil, err - } - } - } - - tk := tupleUtils.NewTupleKeyWithCondition( - tupleUtils.BuildObject(objectType, objectID), - relation, - user, - conditionName.String, - &conditionContextStruct, - ) - - changes = append(changes, &openfgav1.TupleChange{ - TupleKey: tk, - Operation: openfgav1.TupleOperation(operation), - Timestamp: timestamppb.New(insertedAt.UTC()), - }) - } - - if len(changes) == 0 { - return nil, nil, storage.ErrNotFound - } - - contToken, err := json.Marshal(sqlcommon.NewContToken(ulid, objectTypeFilter)) - if err != nil { - return nil, nil, err - } - - return changes, contToken, nil -} - -func (m *SQLite) IsReady(ctx context.Context) (storage.ReadinessStatus, error) { - if err := m.db.PingContext(ctx); err != nil { - return storage.ReadinessStatus{}, err - } - return storage.ReadinessStatus{ - IsReady: true, - }, nil -} - -// SQLite will return an SQLITE_BUSY error when the database is locked rather than waiting for the lock. -// This function retries the operation up to 5 times before returning the error. -func (m *SQLite) busyRetry(fn func() error) error { - for retries := 0; ; retries++ { - err := fn() - if err == nil || retries == m.cfg.QueryRetries { - return err - } - - var sqliteErr *sqlite3.Error - if errors.As(err, &sqliteErr) && (sqliteErr.Code == sqlite3.ErrLocked || sqliteErr.Code == sqlite3.ErrBusy) { - time.Sleep(10 * time.Millisecond) - continue - } - - return err - } -} - -func handleSQLError(err error, args ...any) error { - if strings.Contains(err.Error(), "UNIQUE constraint failed:") { - if len(args) > 0 { - if tk, ok := args[0].(*openfgav1.TupleKey); ok { - return storage.InvalidWriteInputError(tk, openfgav1.TupleOperation_TUPLE_OPERATION_WRITE) - } - } - return storage.ErrCollision - } - - return sqlcommon.HandleSQLError(err, args...) -} diff --git a/pkg/services/authz/zanzana/store/sqlite/store_test.go b/pkg/services/authz/zanzana/store/sqlite/store_test.go deleted file mode 100644 index 5388114d188..00000000000 --- a/pkg/services/authz/zanzana/store/sqlite/store_test.go +++ /dev/null @@ -1,295 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "testing" - "time" - - sq "github.com/Masterminds/squirrel" - "github.com/oklog/ulid/v2" - openfgav1 "github.com/openfga/api/proto/openfga/v1" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - - "github.com/openfga/openfga/pkg/storage" - "github.com/openfga/openfga/pkg/storage/sqlcommon" - "github.com/openfga/openfga/pkg/storage/test" - "github.com/openfga/openfga/pkg/tuple" - "github.com/openfga/openfga/pkg/typesystem" - - "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" - "github.com/grafana/grafana/pkg/tests/testsuite" - - zassets "github.com/grafana/grafana/pkg/services/authz/zanzana/store/assets" - "github.com/grafana/grafana/pkg/services/authz/zanzana/store/migration" -) - -func TestMain(m *testing.M) { - testsuite.Run(m) -} - -// TestIntegrationDatastore runs open fga default datastore test suite -func TestIntegrationDatastore(t *testing.T) { - db := sqliteIntegrationTest(t) - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - test.RunAllTests(t, ds) -} - -// TestIntegrationReadEnsureNoOrder asserts that the read response is not ordered by ulid. -func TestIntegrationReadEnsureNoOrder(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - store := "store" - firstTuple := tuple.NewTupleKey("doc:object_id_1", "relation", "user:user_1") - secondTuple := tuple.NewTupleKey("doc:object_id_2", "relation", "user:user_2") - - err = sqlcommon.Write(ctx, - sqlcommon.NewDBInfo(ds.db, ds.stbl, sq.Expr("datetime('subsec')")), - store, - []*openfgav1.TupleKeyWithoutCondition{}, - []*openfgav1.TupleKey{firstTuple}, - time.Now()) - require.NoError(t, err) - - // Tweak time so that ULID is smaller. - err = sqlcommon.Write(ctx, - sqlcommon.NewDBInfo(ds.db, ds.stbl, sq.Expr("datetime('subsec')")), - store, - []*openfgav1.TupleKeyWithoutCondition{}, - []*openfgav1.TupleKey{secondTuple}, - time.Now().Add(time.Minute*-1)) - require.NoError(t, err) - - iter, err := ds.Read(ctx, - store, - tuple.NewTupleKey("doc:", "relation", "")) - defer iter.Stop() - - require.NoError(t, err) - - // We expect that objectID1 will return first because it is inserted first. - curTuple, err := iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, firstTuple, curTuple.GetKey()) - - curTuple, err = iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, secondTuple, curTuple.GetKey()) -} - -// TestIntegrationReadPageEnsureNoOrder asserts that the read page is ordered by ulid. -func TestIntegrationReadPageEnsureOrder(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - - store := "store" - firstTuple := tuple.NewTupleKey("doc:object_id_1", "relation", "user:user_1") - secondTuple := tuple.NewTupleKey("doc:object_id_2", "relation", "user:user_2") - - err = sqlcommon.Write(ctx, - sqlcommon.NewDBInfo(ds.db, ds.stbl, sq.Expr("datetime('subsec')")), - store, - []*openfgav1.TupleKeyWithoutCondition{}, - []*openfgav1.TupleKey{firstTuple}, - time.Now()) - require.NoError(t, err) - - // Tweak time so that ULID is smaller. - err = sqlcommon.Write(ctx, - sqlcommon.NewDBInfo(ds.db, ds.stbl, sq.Expr("datetime('subsec')")), - store, - []*openfgav1.TupleKeyWithoutCondition{}, - []*openfgav1.TupleKey{secondTuple}, - time.Now().Add(time.Minute*-1)) - require.NoError(t, err) - - tuples, _, err := ds.ReadPage(ctx, - store, - tuple.NewTupleKey("doc:", "relation", ""), - storage.NewPaginationOptions(0, "")) - require.NoError(t, err) - - require.Len(t, tuples, 2) - // We expect that objectID2 will return first because it has a smaller ulid. - require.Equal(t, secondTuple, tuples[0].GetKey()) - require.Equal(t, firstTuple, tuples[1].GetKey()) -} - -func TestIntegrationReadAuthorizationModelUnmarshallError(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - store := "store" - modelID := "foo" - schemaVersion := typesystem.SchemaVersion1_0 - - bytes, err := proto.Marshal(&openfgav1.TypeDefinition{Type: "document"}) - require.NoError(t, err) - pbdata := []byte{0x01, 0x02, 0x03} - - _, err = ds.db.ExecContext(ctx, "INSERT INTO authorization_model (store, authorization_model_id, schema_version, type, type_definition, serialized_protobuf) VALUES (?, ?, ?, ?, ?, ?)", store, modelID, schemaVersion, "document", bytes, pbdata) - require.NoError(t, err) - - _, err = ds.ReadAuthorizationModel(ctx, store, modelID) - require.Error(t, err) - require.Contains(t, err.Error(), "cannot parse invalid wire-format data") -} - -// TestIntegrationAllowNullCondition tests that tuple and changelog rows existing before -// migration 005_add_conditions_to_tuples can be successfully read. -func TestIntegrationAllowNullCondition(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - - stmt := ` - INSERT INTO tuple ( - store, object_type, object_id, relation, _user, user_type, ulid, - condition_name, condition_context, inserted_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('subsec')); - ` - _, err = ds.db.ExecContext( - ctx, stmt, "store", "folder", "2021-budget", "owner", "user:anne", "user", - ulid.Make().String(), nil, nil, - ) - require.NoError(t, err) - - tk := tuple.NewTupleKey("folder:2021-budget", "owner", "user:anne") - iter, err := ds.Read(ctx, "store", tk) - require.NoError(t, err) - defer iter.Stop() - - curTuple, err := iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, tk, curTuple.GetKey()) - - tuples, _, err := ds.ReadPage(ctx, "store", &openfgav1.TupleKey{}, storage.PaginationOptions{ - PageSize: 2, - }) - require.NoError(t, err) - require.Len(t, tuples, 1) - require.Equal(t, tk, tuples[0].GetKey()) - - userTuple, err := ds.ReadUserTuple(ctx, "store", tk) - require.NoError(t, err) - require.Equal(t, tk, userTuple.GetKey()) - - tk2 := tuple.NewTupleKey("folder:2022-budget", "viewer", "user:anne") - _, err = ds.db.ExecContext( - ctx, stmt, "store", "folder", "2022-budget", "viewer", "user:anne", "userset", - ulid.Make().String(), nil, nil, - ) - - require.NoError(t, err) - iter, err = ds.ReadUsersetTuples(ctx, "store", storage.ReadUsersetTuplesFilter{Object: "folder:2022-budget"}) - require.NoError(t, err) - defer iter.Stop() - - curTuple, err = iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, tk2, curTuple.GetKey()) - - iter, err = ds.ReadStartingWithUser(ctx, "store", storage.ReadStartingWithUserFilter{ - ObjectType: "folder", - Relation: "owner", - UserFilter: []*openfgav1.ObjectRelation{ - {Object: "user:anne"}, - }, - }) - require.NoError(t, err) - defer iter.Stop() - - curTuple, err = iter.Next(ctx) - require.NoError(t, err) - require.Equal(t, tk, curTuple.GetKey()) - - stmt = ` - INSERT INTO changelog ( - store, object_type, object_id, relation, _user, ulid, - condition_name, condition_context, inserted_at, operation - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('subsec'), ?); -` - _, err = ds.db.ExecContext( - ctx, stmt, "store", "folder", "2021-budget", "owner", "user:anne", - ulid.Make().String(), nil, nil, openfgav1.TupleOperation_TUPLE_OPERATION_WRITE, - ) - require.NoError(t, err) - - _, err = ds.db.ExecContext( - ctx, stmt, "store", "folder", "2021-budget", "owner", "user:anne", - ulid.Make().String(), nil, nil, openfgav1.TupleOperation_TUPLE_OPERATION_DELETE, - ) - require.NoError(t, err) - - changes, _, err := ds.ReadChanges(ctx, "store", "folder", storage.PaginationOptions{}, 0) - require.NoError(t, err) - require.Len(t, changes, 2) - require.Equal(t, tk, changes[0].GetTupleKey()) - require.Equal(t, tk, changes[1].GetTupleKey()) -} - -// TestIntegrationMarshalledAssertions tests that previously persisted marshalled -// assertions can be read back. In any case where the Assertions proto model -// needs to change, we'll likely need to introduce a series of data migrations. -func TestIntegrationMarshalledAssertions(t *testing.T) { - db := sqliteIntegrationTest(t) - - ds, err := NewWithDB(db, NewConfig()) - require.NoError(t, err) - - ctx := context.Background() - // Note: this represents an assertion written on v1.3.7. - stmt := ` - INSERT INTO assertion ( - store, authorization_model_id, assertions - ) VALUES (?, ?, UNHEX('0A2B0A270A12666F6C6465723A323032312D62756467657412056F776E65721A0A757365723A616E6E657A1001')); - ` - _, err = ds.db.ExecContext(ctx, stmt, "store", "model") - require.NoError(t, err) - - assertions, err := ds.ReadAssertions(ctx, "store", "model") - require.NoError(t, err) - - expectedAssertions := []*openfgav1.Assertion{ - { - TupleKey: &openfgav1.AssertionTupleKey{ - Object: "folder:2021-budget", - Relation: "owner", - User: "user:annez", - }, - Expectation: true, - }, - } - require.Equal(t, expectedAssertions, assertions) -} - -func sqliteIntegrationTest(tb testing.TB) *sql.DB { - if testing.Short() || !db.IsTestDbSQLite() { - tb.Skip("skipping integration test") - } - - db, cfg := db.InitTestDBWithCfg(tb) - - m := migrator.NewMigrator(db.GetEngine(), cfg) - err := migration.RunWithMigrator(m, cfg, zassets.EmbedMigrations, zassets.SQLiteMigrationDir) - require.NoError(tb, err) - - return db.GetEngine().DB().DB -} diff --git a/pkg/services/authz/zanzana/store/sqlite/write.go b/pkg/services/authz/zanzana/store/sqlite/write.go deleted file mode 100644 index 4e5dd79610b..00000000000 --- a/pkg/services/authz/zanzana/store/sqlite/write.go +++ /dev/null @@ -1,165 +0,0 @@ -package sqlite - -import ( - "context" - "database/sql" - "time" - - sq "github.com/Masterminds/squirrel" - "github.com/oklog/ulid/v2" - "google.golang.org/protobuf/proto" - - openfgav1 "github.com/openfga/api/proto/openfga/v1" - "github.com/openfga/openfga/pkg/storage" - tupleUtils "github.com/openfga/openfga/pkg/tuple" -) - -// write is copied from https://github.com/openfga/openfga/blob/main/pkg/storage/sqlcommon/sqlcommon.go#L330-L456 -// but uses custom handleSQLError. -func write( - ctx context.Context, - db *sql.DB, - stbl sq.StatementBuilderType, - sqlTime any, - store string, - deletes storage.Deletes, - writes storage.Writes, - now time.Time, -) error { - txn, err := db.BeginTx(ctx, nil) - if err != nil { - return handleSQLError(err) - } - defer func() { - _ = txn.Rollback() - }() - - changelogBuilder := stbl. - Insert("changelog"). - Columns( - "store", "object_type", "object_id", "relation", "_user", - "condition_name", "condition_context", "operation", "ulid", "inserted_at", - ) - - deleteBuilder := stbl.Delete("tuple") - - for _, tk := range deletes { - id := ulid.MustNew(ulid.Timestamp(now), ulid.DefaultEntropy()).String() - objectType, objectID := tupleUtils.SplitObject(tk.GetObject()) - - res, err := deleteBuilder. - Where(sq.Eq{ - "store": store, - "object_type": objectType, - "object_id": objectID, - "relation": tk.GetRelation(), - "_user": tk.GetUser(), - "user_type": tupleUtils.GetUserTypeFromUser(tk.GetUser()), - }). - RunWith(txn). // Part of a txn. - ExecContext(ctx) - if err != nil { - return handleSQLError(err, tk) - } - - rowsAffected, err := res.RowsAffected() - if err != nil { - return handleSQLError(err) - } - - if rowsAffected != 1 { - return storage.InvalidWriteInputError( - tk, - openfgav1.TupleOperation_TUPLE_OPERATION_DELETE, - ) - } - - changelogBuilder = changelogBuilder.Values( - store, objectType, objectID, - tk.GetRelation(), tk.GetUser(), - "", nil, // Redact condition info for deletes since we only need the base triplet (object, relation, user). - openfgav1.TupleOperation_TUPLE_OPERATION_DELETE, - id, sqlTime, - ) - } - - insertBuilder := stbl. - Insert("tuple"). - Columns( - "store", "object_type", "object_id", "relation", "_user", "user_type", - "condition_name", "condition_context", "ulid", "inserted_at", - ) - - for _, tk := range writes { - id := ulid.MustNew(ulid.Timestamp(now), ulid.DefaultEntropy()).String() - objectType, objectID := tupleUtils.SplitObject(tk.GetObject()) - - conditionName, conditionContext, err := marshalRelationshipCondition(tk.GetCondition()) - if err != nil { - return err - } - - _, err = insertBuilder. - Values( - store, - objectType, - objectID, - tk.GetRelation(), - tk.GetUser(), - tupleUtils.GetUserTypeFromUser(tk.GetUser()), - conditionName, - conditionContext, - id, - sqlTime, - ). - RunWith(txn). // Part of a txn. - ExecContext(ctx) - if err != nil { - return handleSQLError(err, tk) - } - - changelogBuilder = changelogBuilder.Values( - store, - objectType, - objectID, - tk.GetRelation(), - tk.GetUser(), - conditionName, - conditionContext, - openfgav1.TupleOperation_TUPLE_OPERATION_WRITE, - id, - sqlTime, - ) - } - - if len(writes) > 0 || len(deletes) > 0 { - _, err := changelogBuilder.RunWith(txn).ExecContext(ctx) // Part of a txn. - if err != nil { - return handleSQLError(err) - } - } - - if err := txn.Commit(); err != nil { - return handleSQLError(err) - } - - return nil -} - -// copied from https://github.com/openfga/openfga/blob/main/pkg/storage/sqlcommon/encoding.go#L8-L24 -func marshalRelationshipCondition( - rel *openfgav1.RelationshipCondition, -) (name string, context []byte, err error) { - if rel != nil { - if rel.GetContext() != nil && len(rel.GetContext().GetFields()) > 0 { - context, err = proto.Marshal(rel.GetContext()) - if err != nil { - return name, context, err - } - } - - return rel.GetName(), context, err - } - - return name, context, err -} diff --git a/pkg/services/authz/zanzana/store/store.go b/pkg/services/authz/zanzana/store/store.go index fe7aadc0cee..360e75c2dd4 100644 --- a/pkg/services/authz/zanzana/store/store.go +++ b/pkg/services/authz/zanzana/store/store.go @@ -2,15 +2,15 @@ package store import ( "fmt" + "strings" "time" - "xorm.io/xorm" - "github.com/openfga/openfga/assets" "github.com/openfga/openfga/pkg/storage" "github.com/openfga/openfga/pkg/storage/mysql" "github.com/openfga/openfga/pkg/storage/postgres" "github.com/openfga/openfga/pkg/storage/sqlcommon" + "github.com/openfga/openfga/pkg/storage/sqlite" "github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/log" @@ -19,9 +19,7 @@ import ( "github.com/grafana/grafana/pkg/setting" zlogger "github.com/grafana/grafana/pkg/services/authz/zanzana/logger" - zassets "github.com/grafana/grafana/pkg/services/authz/zanzana/store/assets" "github.com/grafana/grafana/pkg/services/authz/zanzana/store/migration" - "github.com/grafana/grafana/pkg/services/authz/zanzana/store/sqlite" ) func NewStore(cfg *setting.Cfg, logger log.Logger) (storage.OpenFGADatastore, error) { @@ -32,22 +30,12 @@ func NewStore(cfg *setting.Cfg, logger log.Logger) (storage.OpenFGADatastore, er switch grafanaDBCfg.Type { case migrator.SQLite: - connStr := grafanaDBCfg.ConnectionString - // Initilize connection using xorm engine so we can reuse it for both migrations and data store - engine, err := xorm.NewEngine(grafanaDBCfg.Type, connStr) - if err != nil { - return nil, fmt.Errorf("failed to connect to database: %w", err) - } - - m := migrator.NewMigrator(engine, cfg) - if err := migration.RunWithMigrator(m, cfg, zassets.EmbedMigrations, zassets.SQLiteMigrationDir); err != nil { + connStr := sqliteConnectionString(grafanaDBCfg.ConnectionString) + if err := migration.Run(cfg, migrator.SQLite, connStr, assets.EmbedMigrations, assets.SqliteMigrationDir); err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) } - return sqlite.NewWithDB(engine.DB().DB, &sqlite.Config{ - Config: zanzanaDBCfg, - QueryRetries: grafanaDBCfg.QueryRetries, - }) + return sqlite.New(connStr, zanzanaDBCfg) case migrator.MySQL: // For mysql we need to pass parseTime parameter in connection string connStr := grafanaDBCfg.ConnectionString + "&parseTime=true" @@ -75,20 +63,16 @@ func NewEmbeddedStore(cfg *setting.Cfg, db db.DB, logger log.Logger) (storage.Op return nil, fmt.Errorf("failed to parse database config: %w", err) } - m := migrator.NewMigrator(db.GetEngine(), cfg) - switch grafanaDBCfg.Type { case migrator.SQLite: - if err := migration.RunWithMigrator(m, cfg, zassets.EmbedMigrations, zassets.SQLiteMigrationDir); err != nil { + grafanaDBCfg.ConnectionString = sqliteConnectionString(grafanaDBCfg.ConnectionString) + if err := migration.Run(cfg, migrator.SQLite, grafanaDBCfg.ConnectionString, assets.EmbedMigrations, assets.SqliteMigrationDir); err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) } - // FIXME(kalleep): We should work on getting sqlite implemtation merged upstream and replace this one - return sqlite.NewWithDB(db.GetEngine().DB().DB, &sqlite.Config{ - Config: zanzanaDBCfg, - QueryRetries: grafanaDBCfg.QueryRetries, - }) + return sqlite.New(grafanaDBCfg.ConnectionString, zanzanaDBCfg) case migrator.MySQL: + m := migrator.NewMigrator(db.GetEngine(), cfg) if err := migration.RunWithMigrator(m, cfg, assets.EmbedMigrations, assets.MySQLMigrationDir); err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) } @@ -96,6 +80,7 @@ func NewEmbeddedStore(cfg *setting.Cfg, db db.DB, logger log.Logger) (storage.Op // For mysql we need to pass parseTime parameter in connection string return mysql.New(grafanaDBCfg.ConnectionString+"&parseTime=true", zanzanaDBCfg) case migrator.Postgres: + m := migrator.NewMigrator(db.GetEngine(), cfg) if err := migration.RunWithMigrator(m, cfg, assets.EmbedMigrations, assets.PostgresMigrationDir); err != nil { return nil, fmt.Errorf("failed to run migrations: %w", err) } @@ -126,3 +111,8 @@ func parseConfig(cfg *setting.Cfg, logger log.Logger) (*sqlstore.DatabaseConfig, return grafanaDBCfg, zanzanaDBCfg, nil } + +func sqliteConnectionString(v string) string { + // hardcode zanzana.db for now + return v[0:strings.LastIndex(v, "/")+1] + "zanzana.db" +} From 419598c7453523027f7668d59ca5b4239678ca3e Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:56:15 +0200 Subject: [PATCH 026/110] Alerting: Fix getSimpleConditionFromExpressions (#94516) fix getSimpleConditionFromExpressions --- .../query-and-alert-condition/SimpleCondition.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx index 7307675defd..1e745c46da6 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SimpleCondition.tsx @@ -218,11 +218,17 @@ export function getSimpleConditionFromExpressions(expressions: Array query.model.type === ExpressionQueryType.threshold && query.refId === SIMPLE_CONDITION_THRESHOLD_ID ); const conditionsFromThreshold = thresholdExpression?.model.conditions ?? []; + const whenField = reduceExpression?.model.reducer ?? ReducerID.last; + const params = conditionsFromThreshold[0]?.evaluator?.params + ? [...conditionsFromThreshold[0]?.evaluator?.params] + : [0]; + const type = conditionsFromThreshold[0]?.evaluator?.type ?? EvalFunction.IsAbove; + return { - whenField: reduceExpression?.model.reducer ?? ReducerID.last, + whenField: whenField, evaluator: { - params: [...conditionsFromThreshold[0]?.evaluator?.params] ?? [0], - type: conditionsFromThreshold[0]?.evaluator?.type ?? EvalFunction.IsAbove, + params: params, + type: type, }, }; } From 6dbd324ef974b26725f6bac086f6ed40acd8de0a Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Thu, 10 Oct 2024 10:53:31 +0200 Subject: [PATCH 027/110] Fix: Actually call the DedupOrgInLogin migration (#94520) --- pkg/services/sqlstore/migrations/user_mig.go | 3 +++ .../usermig/service_account_multiple_org_login_migrator.go | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/services/sqlstore/migrations/user_mig.go b/pkg/services/sqlstore/migrations/user_mig.go index d4d8384a4d3..68fe8ba9af4 100644 --- a/pkg/services/sqlstore/migrations/user_mig.go +++ b/pkg/services/sqlstore/migrations/user_mig.go @@ -158,6 +158,9 @@ func addUserMigrations(mg *Migrator) { // Service accounts login were not unique per org. this migration is part of making it unique per org // to be able to create service accounts that are unique per org mg.AddMigration(usermig.AllowSameLoginCrossOrgs, &usermig.ServiceAccountsSameLoginCrossOrgs{}) + // Before it was fixed, the previous migration introduced the org_id again in logins that already had it. + // This migration removes the duplicate org_id from the login. + mg.AddMigration(usermig.DedupOrgInLogin, &usermig.ServiceAccountsDeduplicateOrgInLogin{}) // Users login and email should be in lower case mg.AddMigration(usermig.LowerCaseUserLoginAndEmail, &usermig.UsersLowerCaseLoginAndEmail{}) diff --git a/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go b/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go index 0737091bda4..54b71250f1b 100644 --- a/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go +++ b/pkg/services/sqlstore/migrations/usermig/service_account_multiple_org_login_migrator.go @@ -16,9 +16,6 @@ const ( // to be able to create service accounts that are unique per org func AddServiceAccountsAllowSameLoginCrossOrgs(mg *migrator.Migrator) { mg.AddMigration(AllowSameLoginCrossOrgs, &ServiceAccountsSameLoginCrossOrgs{}) - // Before it was fixed, the previous migration introduced the org_id again in logins that already had it. - // This migration removes the duplicate org_id from the login. - mg.AddMigration(DedupOrgInLogin, &ServiceAccountsDeduplicateOrgInLogin{}) } var _ migrator.CodeMigration = new(ServiceAccountsSameLoginCrossOrgs) From 636d17c11157282abb736e6a16cfc2c4189a9b69 Mon Sep 17 00:00:00 2001 From: jjaychen <31304335+jjaychen1e@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:02:33 +0800 Subject: [PATCH 028/110] PanelQueryRunner: Fix diff between multiple errors (#89868) --- public/app/features/query/state/PanelQueryRunner.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/app/features/query/state/PanelQueryRunner.ts b/public/app/features/query/state/PanelQueryRunner.ts index 87bb9bf93f5..8853a9945a2 100644 --- a/public/app/features/query/state/PanelQueryRunner.ts +++ b/public/app/features/query/state/PanelQueryRunner.ts @@ -1,4 +1,4 @@ -import { cloneDeep, merge } from 'lodash'; +import { cloneDeep, merge, isEqual } from 'lodash'; import { Observable, of, ReplaySubject, Unsubscribable } from 'rxjs'; import { map, mergeMap, catchError } from 'rxjs/operators'; @@ -371,6 +371,7 @@ export class PanelQueryRunner { let sameSeries = compareArrayValues(last.series ?? [], next.series ?? [], (a, b) => a === b); let sameAnnotations = compareArrayValues(last.annotations ?? [], next.annotations ?? [], (a, b) => a === b); let sameState = last.state === next.state; + let sameErrors = compareArrayValues(last.errors ?? [], next.errors ?? [], (a, b) => isEqual(a, b)); if (sameSeries) { next.series = last.series; @@ -380,7 +381,7 @@ export class PanelQueryRunner { next.annotations = last.annotations; } - if (sameSeries && sameAnnotations && sameState) { + if (sameSeries && sameAnnotations && sameState && sameErrors) { return; } } From 3c376f137abe09f5d2c38952a71ec84aeea14110 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:10:04 +0100 Subject: [PATCH 029/110] Update dependency @react-types/button to v3.10.0 (#94498) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 17 +++-------------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index 589fa327342..d789f216fae 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "@manypkg/get-packages": "^2.2.0", "@playwright/test": "1.48.0", "@pmmmwh/react-refresh-webpack-plugin": "0.5.15", - "@react-types/button": "3.9.6", + "@react-types/button": "3.10.0", "@react-types/menu": "3.9.12", "@react-types/overlays": "3.8.10", "@react-types/shared": "3.24.1", diff --git a/yarn.lock b/yarn.lock index c25e0e29049..8f2911f11f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6881,18 +6881,7 @@ __metadata: languageName: node linkType: hard -"@react-types/button@npm:3.9.6": - version: 3.9.6 - resolution: "@react-types/button@npm:3.9.6" - dependencies: - "@react-types/shared": "npm:^3.24.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - checksum: 10/348096091b39b9cfeaf3b11b4ff262652954ea1793008aa2acc005ca32f299db550f08fe076498501547c2a9a06c46d2000f202fc0dbe853a1202d6523b71449 - languageName: node - linkType: hard - -"@react-types/button@npm:^3.10.0": +"@react-types/button@npm:3.10.0, @react-types/button@npm:^3.10.0": version: 3.10.0 resolution: "@react-types/button@npm:3.10.0" dependencies: @@ -6947,7 +6936,7 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:^3.24.1, @react-types/shared@npm:^3.25.0": +"@react-types/shared@npm:^3.25.0": version: 3.25.0 resolution: "@react-types/shared@npm:3.25.0" peerDependencies: @@ -18995,7 +18984,7 @@ __metadata: "@react-aria/overlays": "npm:3.23.3" "@react-aria/utils": "npm:3.25.3" "@react-awesome-query-builder/ui": "npm:6.6.3" - "@react-types/button": "npm:3.9.6" + "@react-types/button": "npm:3.10.0" "@react-types/menu": "npm:3.9.12" "@react-types/overlays": "npm:3.8.10" "@react-types/shared": "npm:3.24.1" From 152f70a6a4b178040247cc9f2944363fba8b8086 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:11:48 +0100 Subject: [PATCH 030/110] Update dependency @formatjs/intl-durationformat to ^0.3.0 (#94497) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 30 ++++++++++++++++++++---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index d789f216fae..e9788c04553 100644 --- a/package.json +++ b/package.json @@ -250,7 +250,7 @@ "@emotion/react": "11.13.3", "@fingerprintjs/fingerprintjs": "^3.4.2", "@floating-ui/react": "0.26.24", - "@formatjs/intl-durationformat": "^0.2.4", + "@formatjs/intl-durationformat": "^0.3.0", "@glideapps/glide-data-grid": "^6.0.0", "@grafana/aws-sdk": "0.5.0", "@grafana/azure-sdk": "0.0.3", diff --git a/yarn.lock b/yarn.lock index 8f2911f11f3..992de7beb63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3035,13 +3035,14 @@ __metadata: languageName: node linkType: hard -"@formatjs/ecma402-abstract@npm:2.0.0": - version: 2.0.0 - resolution: "@formatjs/ecma402-abstract@npm:2.0.0" +"@formatjs/ecma402-abstract@npm:2.1.0": + version: 2.1.0 + resolution: "@formatjs/ecma402-abstract@npm:2.1.0" dependencies: + "@formatjs/fast-memoize": "npm:2.2.0" "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: 10/41543ba509ea3c7d6530d57b888115f7ca242f13462a951fae4d1d1f28bae10c999f4dea28a71d2f08366d4889a3f5276cae3a16c6f6417b841a84fd314c2234 + checksum: 10/8e25d2739c03913c61f0ec12a71ab7baf03386cff540e7470bee73e78e4fe9f09c9dfa860223f1177acdcfd0fa341041d25b2fe4e0a77e7209d0680831982bac languageName: node linkType: hard @@ -3054,6 +3055,15 @@ __metadata: languageName: node linkType: hard +"@formatjs/fast-memoize@npm:2.2.0": + version: 2.2.0 + resolution: "@formatjs/fast-memoize@npm:2.2.0" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/8697fe72a7ece252d600a7d08105f2a2f758e2dd96f54ac0a4c508b1205a559fc08835635e1f8e5ca9dcc3ee61ce1fca4a0e7047b402f29fc96051e293a280ff + languageName: node + linkType: hard + "@formatjs/icu-messageformat-parser@npm:2.1.7": version: 2.1.7 resolution: "@formatjs/icu-messageformat-parser@npm:2.1.7" @@ -3075,14 +3085,14 @@ __metadata: languageName: node linkType: hard -"@formatjs/intl-durationformat@npm:^0.2.4": - version: 0.2.4 - resolution: "@formatjs/intl-durationformat@npm:0.2.4" +"@formatjs/intl-durationformat@npm:^0.3.0": + version: 0.3.0 + resolution: "@formatjs/intl-durationformat@npm:0.3.0" dependencies: - "@formatjs/ecma402-abstract": "npm:2.0.0" + "@formatjs/ecma402-abstract": "npm:2.1.0" "@formatjs/intl-localematcher": "npm:0.5.4" tslib: "npm:^2.4.0" - checksum: 10/5f500409a20d18967e17ffbc222f9b4c4bf7ef08cce20023c33f06d1989c2bc4cf700d1dd1d048748d0a36c882109d5375896a4964d6700f73ec18914c6de4ba + checksum: 10/96625ba190040e8233a8fd0b8c73347a1e780641868ba429910da046fc971b3c6116b1f93f6020e0eafc1d7ecda6ea1cd25f6048087a10300278fa31495534d3 languageName: node linkType: hard @@ -18934,7 +18944,7 @@ __metadata: "@emotion/react": "npm:11.13.3" "@fingerprintjs/fingerprintjs": "npm:^3.4.2" "@floating-ui/react": "npm:0.26.24" - "@formatjs/intl-durationformat": "npm:^0.2.4" + "@formatjs/intl-durationformat": "npm:^0.3.0" "@glideapps/glide-data-grid": "npm:^6.0.0" "@grafana/aws-sdk": "npm:0.5.0" "@grafana/azure-sdk": "npm:0.0.3" From f6abde33282dd45ffc745f8187656fc0c02a85b4 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 10 Oct 2024 10:18:25 +0100 Subject: [PATCH 031/110] Alerting: Remove `accesscontrol` license feature requirement for contact points RBAC (#94418) --- .../contact-points/ContactPoints.test.tsx | 34 +++++-------------- .../components/contact-points/utils.ts | 3 +- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx index 76d8b271183..fa70dacd4ab 100644 --- a/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx +++ b/public/app/features/alerting/unified/components/contact-points/ContactPoints.test.tsx @@ -3,11 +3,7 @@ import { ComponentProps, ReactNode } from 'react'; import { render, screen, userEvent, waitFor, waitForElementToBeRemoved, within } from 'test/test-utils'; import { selectors } from '@grafana/e2e-selectors'; -import { - flushMicrotasks, - testWithFeatureToggles, - testWithLicenseFeatures, -} from 'app/features/alerting/unified/test/test-utils'; +import { flushMicrotasks, testWithFeatureToggles } from 'app/features/alerting/unified/test/test-utils'; import { K8sAnnotations } from 'app/features/alerting/unified/utils/k8s/constants'; import { AlertManagerDataSourceJsonData, AlertManagerImplementation } from 'app/plugins/datasource/alertmanager/types'; import { AccessControlAction } from 'app/types'; @@ -533,32 +529,20 @@ describe('contact points', () => { ).toBeInTheDocument(); }); - it('does not show manage permissions', async () => { - renderGrafanaContactPoints(); + it('shows manage permissions and allows closing', async () => { + const { user } = renderGrafanaContactPoints(); await clickMoreActionsButton('lotsa-emails'); - expect(screen.queryByRole('menuitem', { name: /manage permissions/i })).not.toBeInTheDocument(); - }); + await user.click(await screen.findByRole('menuitem', { name: /manage permissions/i })); - describe('accesscontrol license feature enabled', () => { - testWithLicenseFeatures(['accesscontrol']); + const permissionsDialog = await screen.findByRole('dialog', { name: /drawer title manage permissions/i }); - it('shows manage permissions and allows closing', async () => { - const { user } = renderGrafanaContactPoints(); + expect(permissionsDialog).toBeInTheDocument(); + expect(await screen.findByRole('table')).toBeInTheDocument(); - await clickMoreActionsButton('lotsa-emails'); - - await user.click(await screen.findByRole('menuitem', { name: /manage permissions/i })); - - const permissionsDialog = await screen.findByRole('dialog', { name: /drawer title manage permissions/i }); - - expect(permissionsDialog).toBeInTheDocument(); - expect(await screen.findByRole('table')).toBeInTheDocument(); - - await user.click(within(permissionsDialog).getAllByRole('button', { name: /close/i })[0]); - expect(permissionsDialog).not.toBeInTheDocument(); - }); + await user.click(within(permissionsDialog).getAllByRole('button', { name: /close/i })[0]); + expect(permissionsDialog).not.toBeInTheDocument(); }); }); }); diff --git a/public/app/features/alerting/unified/components/contact-points/utils.ts b/public/app/features/alerting/unified/components/contact-points/utils.ts index 0f81ff496a9..5f6deb9c0b0 100644 --- a/public/app/features/alerting/unified/components/contact-points/utils.ts +++ b/public/app/features/alerting/unified/components/contact-points/utils.ts @@ -2,7 +2,6 @@ import { difference, groupBy, take, trim, upperFirst } from 'lodash'; import { ReactNode } from 'react'; import { config } from '@grafana/runtime'; -import { contextSrv } from 'app/core/core'; import { canAdminEntity, shouldUseK8sApi } from 'app/features/alerting/unified/utils/k8s/utils'; import { AlertManagerCortexConfig, @@ -210,4 +209,4 @@ function getNotifierMetadata(notifiers: NotifierDTO[], receiver: GrafanaManagedR } export const showManageContactPointPermissions = (alertmanager: string, contactPoint: GrafanaManagedContactPoint) => - shouldUseK8sApi(alertmanager) && contextSrv.licensedAccessControlEnabled() && canAdminEntity(contactPoint); + shouldUseK8sApi(alertmanager) && canAdminEntity(contactPoint); From 074831153aaeff6aa3641bdd8d3be57b6b88f0be Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Thu, 10 Oct 2024 10:18:36 +0100 Subject: [PATCH 032/110] Alerting: Config tracker - update link for default contact point (#94384) Co-authored-by: Sonia Aguilar --- .../configuration-tracker/alerting/hooks.ts | 17 +++-------------- .../gops/configuration-tracker/irmHooks.ts | 15 ++++++--------- 2 files changed, 9 insertions(+), 23 deletions(-) diff --git a/public/app/features/gops/configuration-tracker/alerting/hooks.ts b/public/app/features/gops/configuration-tracker/alerting/hooks.ts index 3dbc9844d18..f0cde843803 100644 --- a/public/app/features/gops/configuration-tracker/alerting/hooks.ts +++ b/public/app/features/gops/configuration-tracker/alerting/hooks.ts @@ -35,20 +35,9 @@ export function isOnCallContactPointReady(contactPoints: Receiver[]) { ); } -export function useGetContactPoints() { - const alertmanagerConfiguration = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useQuery( - GRAFANA_RULES_SOURCE_NAME, - { - refetchOnFocus: true, - refetchOnReconnect: true, - refetchOnMountOrArgChange: true, - } - ); - - const contactPoints = alertmanagerConfiguration.data?.alertmanager_config?.receivers ?? []; - return { contactPoints, isLoading: alertmanagerConfiguration.isLoading }; -} - +/** + * @deprecated Will be removed when notification policies is moved to k8s API. Do not use! + */ export function useGetDefaultContactPoint() { const alertmanagerConfiguration = alertmanagerApi.endpoints.getAlertmanagerConfiguration.useQuery( GRAFANA_RULES_SOURCE_NAME, diff --git a/public/app/features/gops/configuration-tracker/irmHooks.ts b/public/app/features/gops/configuration-tracker/irmHooks.ts index a18bc7fd35c..39121db0e6e 100644 --- a/public/app/features/gops/configuration-tracker/irmHooks.ts +++ b/public/app/features/gops/configuration-tracker/irmHooks.ts @@ -1,14 +1,10 @@ import { useMemo } from 'react'; import { locationService } from '@grafana/runtime'; +import { useGrafanaContactPoints } from 'app/features/alerting/unified/components/contact-points/useContactPoints'; import { RelativeUrl, createRelativeUrl } from 'app/features/alerting/unified/utils/url'; -import { - isOnCallContactPointReady, - useGetContactPoints, - useGetDefaultContactPoint, - useIsCreateAlertRuleDone, -} from './alerting/hooks'; +import { isOnCallContactPointReady, useGetDefaultContactPoint, useIsCreateAlertRuleDone } from './alerting/hooks'; import { isContactPointReady } from './alerting/utils'; import { ConfigurationStepsEnum, DataSourceConfigurationData, IrmCardConfiguration } from './components/ConfigureIRM'; import { useGetIncidentPluginConfig } from './incidents/hooks'; @@ -53,7 +49,8 @@ export interface EssentialsConfigurationData { function useGetConfigurationForApps() { // configuration checks for alerting - const { contactPoints, isLoading: isLoadingContactPoints } = useGetContactPoints(); + const { contactPoints, isLoading: isLoadingContactPoints } = useGrafanaContactPoints(); + // TODO: Switch to k8s API/refactored notification policies hook when available const { defaultContactpoint, isLoading: isLoadingDefaultContactPoint } = useGetDefaultContactPoint(); const { isDone: isCreateAlertRuleDone, isLoading: isLoadingAlertCreatedDone } = useIsCreateAlertRuleDone(); // configuration checks for incidents @@ -132,8 +129,8 @@ export function useGetEssentialsConfiguration(): EssentialsConfigurationData { button: { type: 'openLink', urlLink: { - url: `/alerting/notifications/receivers/${defaultContactpoint}/edit`, - queryParams: { alertmanager: 'grafana' }, + url: `/alerting/notifications`, + queryParams: { search: defaultContactpoint, alertmanager: 'grafana' }, }, label: 'Edit', labelOnDone: 'View', From 4e790ca24024378f6cdbb702a83d596f0950b6f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:51:14 +0100 Subject: [PATCH 033/110] Update dependency @react-types/shared to v3.25.0 (#94522) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index e9788c04553..d22a03c0f34 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "@react-types/button": "3.10.0", "@react-types/menu": "3.9.12", "@react-types/overlays": "3.8.10", - "@react-types/shared": "3.24.1", + "@react-types/shared": "3.25.0", "@rtk-query/codegen-openapi": "^1.2.0", "@rtsao/plugin-proposal-class-properties": "7.0.1-patch.1", "@swc/core": "1.4.2", diff --git a/yarn.lock b/yarn.lock index 992de7beb63..4558ca10bfa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6937,16 +6937,7 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:3.24.1": - version: 3.24.1 - resolution: "@react-types/shared@npm:3.24.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0 - checksum: 10/5472ae35f65b2ed7c12d5ea4459f34b4aec065d2633844031d27945495b6dca6fa9bf02b6392b901fac97252e58d9b91a4baf53f4c281397fb81ce85c73b8648 - languageName: node - linkType: hard - -"@react-types/shared@npm:^3.25.0": +"@react-types/shared@npm:3.25.0, @react-types/shared@npm:^3.25.0": version: 3.25.0 resolution: "@react-types/shared@npm:3.25.0" peerDependencies: @@ -18997,7 +18988,7 @@ __metadata: "@react-types/button": "npm:3.10.0" "@react-types/menu": "npm:3.9.12" "@react-types/overlays": "npm:3.8.10" - "@react-types/shared": "npm:3.24.1" + "@react-types/shared": "npm:3.25.0" "@reduxjs/toolkit": "npm:2.2.8" "@rtk-query/codegen-openapi": "npm:^1.2.0" "@rtsao/plugin-proposal-class-properties": "npm:7.0.1-patch.1" From e38c4c26ae4ea8681e86d6469343cea0ce11fc3b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:52:17 +0100 Subject: [PATCH 034/110] Update dependency esbuild-plugin-browserslist to ^0.15.0 (#94523) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 33 +++++++++++++-------------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index d22a03c0f34..10fc106e9bb 100644 --- a/package.json +++ b/package.json @@ -171,7 +171,7 @@ "cypress-recurse": "^1.35.3", "esbuild": "0.24.0", "esbuild-loader": "4.2.2", - "esbuild-plugin-browserslist": "^0.14.0", + "esbuild-plugin-browserslist": "^0.15.0", "eslint": "8.57.0", "eslint-config-prettier": "9.1.0", "eslint-plugin-import": "^2.26.0", diff --git a/yarn.lock b/yarn.lock index 4558ca10bfa..b1096283a89 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15392,15 +15392,15 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6": - version: 4.3.6 - resolution: "debug@npm:4.3.6" +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.6, debug@npm:^4.3.7": + version: 4.3.7 + resolution: "debug@npm:4.3.7" dependencies: - ms: "npm:2.1.2" + ms: "npm:^2.1.3" peerDependenciesMeta: supports-color: optional: true - checksum: 10/d3adb9af7d57a9e809a68f404490cf776122acca16e6359a2702c0f462e510e91f9765c07f707b8ab0d91e03bad57328f3256f5082631cefb5393d0394d50fb7 + checksum: 10/71168908b9a78227ab29d5d25fe03c5867750e31ce24bf2c44a86efc5af041758bb56569b0a3d48a9b5344c00a24a777e6f4100ed6dfd9534a42c1dde285125a languageName: node linkType: hard @@ -16518,16 +16518,16 @@ __metadata: languageName: node linkType: hard -"esbuild-plugin-browserslist@npm:^0.14.0": - version: 0.14.0 - resolution: "esbuild-plugin-browserslist@npm:0.14.0" +"esbuild-plugin-browserslist@npm:^0.15.0": + version: 0.15.0 + resolution: "esbuild-plugin-browserslist@npm:0.15.0" dependencies: - debug: "npm:^4.3.5" + debug: "npm:^4.3.7" zod: "npm:^3.23.8" peerDependencies: browserslist: ^4.21.8 - esbuild: ~0.23.0 - checksum: 10/634d8a562597dd3c21cad10785153968d7b80f97ee8d11bac9611e1aa524291084e1c5965213442f1a9d07a98fc8862bd00921aa7cb130716f67d2ac8c2a6cc2 + esbuild: ~0.24.0 + checksum: 10/b1afe26f5c013a37664ff6ca2f8190d50e36f719afca8aba2c0b18f0fd0f34de3334db1435154a70dbfeb5e91acf732cc20296bb3fd3cbdfe68ae37dadcffe32 languageName: node linkType: hard @@ -19099,7 +19099,7 @@ __metadata: diff: "npm:^5.1.0" esbuild: "npm:0.24.0" esbuild-loader: "npm:4.2.2" - esbuild-plugin-browserslist: "npm:^0.14.0" + esbuild-plugin-browserslist: "npm:^0.15.0" eslint: "npm:8.57.0" eslint-config-prettier: "npm:9.1.0" eslint-plugin-import: "npm:^2.26.0" @@ -24183,14 +24183,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.2": - version: 2.1.2 - resolution: "ms@npm:2.1.2" - checksum: 10/673cdb2c3133eb050c745908d8ce632ed2c02d85640e2edb3ace856a2266a813b30c613569bf3354fdf4ea7d1a1494add3bfa95e2713baa27d0c2c71fc44f58f - languageName: node - linkType: hard - -"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: 10/aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d From f18b3ca3401ab23a2a9e5508ee9a4603ddeddff0 Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:06:03 +0200 Subject: [PATCH 035/110] SAML: Add a screenshot for Graph API integration config (#94494) * Add a screenshot for Graph API config --- .../configure-authentication/saml/index.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md index 29ef9f04975..11b405c1959 100644 --- a/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md +++ b/docs/sources/setup-grafana/configure-security/configure-authentication/saml/index.md @@ -231,21 +231,34 @@ This app registration will be used as a Service Account to retrieve more informa 1. Go to the [Azure portal](https://portal.azure.com/#home) and sign in with your Azure AD account. 1. In the left-hand navigation pane, select the Azure Active Directory service, and then select **App registrations**. -1. Select **New registration**. +1. Click the **New registration** button. 1. In the **Register an application** pane, enter a name for the application. 1. In the **Supported account types** section, select the account types that can use the application. 1. In the **Redirect URI** section, select Web and enter `https://localhost/login/azuread`. -1. Select **Register**. +1. Click the **Register** button. #### Set up permissions for the application 1. In the overview pane, look for **API permissions** section and select **Add a permission**. 1. In the **Request API permissions** pane, select **Microsoft Graph**, and click **Application permissions**. 1. In the **Select permissions** pane, under the **GroupMember** section, select **GroupMember.Read.All**. +1. In the **Select permissions** pane, under the **User** section, select **User.Read.All**. +1. Click the **Add permissions** button at the bottom of the page. +1. In the **Request API permissions** pane, select **Microsoft Graph**, and click **Delegated permissions**. 1. In the **Select permissions** pane, under the **User** section, select **User.Read**. -1. Select **Add permissions** at the bottom of the page. +1. Click the **Add permissions** button at the bottom of the page. 1. In the **API permissions** section, select **Grant admin consent for **. +The following table shows what the permissions look like from the Azure AD portal: + +| Permissions name | Type | Admin consent required | Status | +| ---------------- | ----------- | ---------------------- | ------- | +| `Group.Read.All` | Application | Yes | Granted | +| `User.Read` | Delegated | No | Granted | +| `User.Read.All` | Application | Yes | Granted | + +{{< figure src="/media/docs/grafana/saml/graph-api-app-permissions.png" caption="Screen shot of the permissions listed in Azure AD for the App registration" >}} + #### Generate a client secret 1. In the **Overview** pane, select **Certificates & secrets**. From bf9e5ae056d1a07547480b87c31887b3553ec7bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:08:45 +0300 Subject: [PATCH 036/110] Update dependency @grafana/scenes to v5.20.0 (#94526) * Update dependency @grafana/scenes to v5.20.0 * bump scenes-react as well --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Ashley Harrison --- package.json | 4 ++-- yarn.lock | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index 10fc106e9bb..0e835827c62 100644 --- a/package.json +++ b/package.json @@ -268,8 +268,8 @@ "@grafana/prometheus": "workspace:*", "@grafana/runtime": "workspace:*", "@grafana/saga-icons": "workspace:*", - "@grafana/scenes": "5.19.1", - "@grafana/scenes-react": "5.19.1", + "@grafana/scenes": "5.20.0", + "@grafana/scenes-react": "5.20.0", "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", diff --git a/yarn.lock b/yarn.lock index b1096283a89..d395dd0888d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4153,12 +4153,12 @@ __metadata: languageName: unknown linkType: soft -"@grafana/scenes-react@npm:5.19.1": - version: 5.19.1 - resolution: "@grafana/scenes-react@npm:5.19.1" +"@grafana/scenes-react@npm:5.20.0": + version: 5.20.0 + resolution: "@grafana/scenes-react@npm:5.20.0" dependencies: "@grafana/e2e-selectors": "npm:^11.0.0" - "@grafana/scenes": "npm:5.19.1" + "@grafana/scenes": "npm:5.20.0" react-use: "npm:17.4.0" peerDependencies: "@grafana/data": ^11.0.0 @@ -4167,13 +4167,13 @@ __metadata: "@grafana/ui": ^11.0.0 react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/a4efd256a02ba4d7418ca412e5f03439684ce531188606a762ce7252a5235f8a3dcf14fd51058b30b7c9c4510f5a7c931958f619811170684b97d9db9294dca1 + checksum: 10/9e4b4f678ad4e072876eaa018e6c67e2fd19d125202b95f8a2f18f113d25693a4a2387ada17cd032f5f11ed2fd189ef471148a25e74f55cc620f22c5e6016a48 languageName: node linkType: hard -"@grafana/scenes@npm:5.19.1": - version: 5.19.1 - resolution: "@grafana/scenes@npm:5.19.1" +"@grafana/scenes@npm:5.20.0": + version: 5.20.0 + resolution: "@grafana/scenes@npm:5.20.0" dependencies: "@floating-ui/react": "npm:0.26.16" "@grafana/e2e-selectors": "npm:^11.0.0" @@ -4190,7 +4190,7 @@ __metadata: "@grafana/ui": ">=10.4" react: ^18.0.0 react-dom: ^18.0.0 - checksum: 10/b27dcae3ae03f4ad49815bcf445ef27f5ccf66099b837001b4e255cb37a637cd7f563dd99e4f00290668e60b8cc57a5f75c97d5577690034543fe4cfb0830c4a + checksum: 10/0bc36324f5c109175199b5e9523323397acd6b95c0be11856f54394bb2a51eb4e0720ecabd37acac9966a7de69f9ddcf330c1ed03e0c196b76e7188f65de88c1 languageName: node linkType: hard @@ -18956,8 +18956,8 @@ __metadata: "@grafana/prometheus": "workspace:*" "@grafana/runtime": "workspace:*" "@grafana/saga-icons": "workspace:*" - "@grafana/scenes": "npm:5.19.1" - "@grafana/scenes-react": "npm:5.19.1" + "@grafana/scenes": "npm:5.20.0" + "@grafana/scenes-react": "npm:5.20.0" "@grafana/schema": "workspace:*" "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" From 011978e81b986e581162841feec7423e577bc17e Mon Sep 17 00:00:00 2001 From: "Arati R." <33031346+suntala@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:22:57 +0200 Subject: [PATCH 037/110] K8s/Folders: Remove folder service from client (#94450) * Support getting full path of UIDs * Use full path to set parents field * Update get folder test * Add folder store test for getting with full path UIDs * Add test for parsing parent titles * Test nested folder create payload --- pkg/api/folder.go | 95 +++++++------------ pkg/apimachinery/utils/meta.go | 26 +++++ pkg/registry/apis/folders/conversions.go | 36 ++++++- pkg/registry/apis/folders/conversions_test.go | 18 ++++ pkg/services/folder/folderimpl/folder.go | 28 +++++- pkg/services/folder/folderimpl/folder_test.go | 45 ++++++--- pkg/services/folder/folderimpl/sqlstore.go | 6 +- .../folder/folderimpl/sqlstore_test.go | 18 ++++ pkg/services/folder/model.go | 11 ++- pkg/tests/apis/folder/folders_test.go | 63 ++++++++++++ 10 files changed, 262 insertions(+), 84 deletions(-) create mode 100644 pkg/registry/apis/folders/conversions_test.go diff --git a/pkg/api/folder.go b/pkg/api/folder.go index 12b2e6b2d41..c9f2963f120 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -20,6 +20,7 @@ import ( "github.com/grafana/grafana/pkg/api/routing" folderalpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/infra/slugify" internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders" "github.com/grafana/grafana/pkg/services/accesscontrol" grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver" @@ -635,8 +636,6 @@ type folderK8sHandler struct { // #TODO check if it makes more sense to move this to FolderAPIBuilder accesscontrolService accesscontrol.Service userService user.Service - // #TODO remove after we handle the nested folder case - folderService folder.Service } //----------------------------------------------------------------------------------------- @@ -650,7 +649,6 @@ func newFolderK8sHandler(hs *HTTPServer) *folderK8sHandler { clientConfigProvider: hs.clientConfigProvider, accesscontrolService: hs.accesscontrolService, userService: hs.userService, - folderService: hs.folderService, } } @@ -884,55 +882,38 @@ func (fk8s *folderK8sHandler) newToFolderDto(c *contextmodel.ReqContext, item un return dtos.Folder{}, err } - parents := []*folder.Folder{} - if folderDTO.ParentUID != "" { - parents, err = fk8s.folderService.GetParents( - c.Req.Context(), - folder.GetParentsQuery{ - UID: folderDTO.UID, - OrgID: folderDTO.OrgID, - }) - if err != nil { - return dtos.Folder{}, err - } + if len(f.Fullpath) == 0 || len(f.FullpathUIDs) == 0 { + return folderDTO, nil } - // #TODO refactor so that we have just one function for converting to folder DTO - toParentDTO := func(fold *folder.Folder, checkCanView bool) (dtos.Folder, error) { - g, err := guardian.NewByFolder(c.Req.Context(), fold, c.SignedInUser.GetOrgID(), c.SignedInUser) - if err != nil { - return dtos.Folder{}, err - } + parentsFullPath, err := internalfolders.GetParentTitles(f.Fullpath) + if err != nil { + return dtos.Folder{}, err + } + parentsFullPathUIDs := strings.Split(f.FullpathUIDs, "/") - if checkCanView { - canView, _ := g.CanView() - if !canView { - return dtos.Folder{ - UID: REDACTED, - Title: REDACTED, - }, nil - } - } - metrics.MFolderIDsAPICount.WithLabelValues(metrics.NewToFolderDTO).Inc() - - return dtos.Folder{ - UID: fold.UID, - Title: fold.Title, - URL: fold.URL, - }, nil + // The first part of the path is the newly created folder which we don't need to include + // in the parents field + if len(parentsFullPath) < 2 || len(parentsFullPathUIDs) < 2 { + return folderDTO, nil } - folderDTO.Parents = make([]dtos.Folder, 0, len(parents)) - for _, f := range parents { - DTO, err := toParentDTO(f, true) - if err != nil { - // #TODO add logging - // fk8s.log.Error("failed to convert folder to DTO", "folder", f.UID, "org", f.OrgID, "error", err) - continue - } - folderDTO.Parents = append(folderDTO.Parents, DTO) + parents := []dtos.Folder{} + for i, v := range parentsFullPath[1:] { + slug := slugify.Slugify(v) + uid := parentsFullPathUIDs[1:][i] + url := dashboards.GetFolderURL(uid, slug) + + parents = append(parents, dtos.Folder{ + UID: uid, + OrgID: c.SignedInUser.GetOrgID(), + Title: v, + URL: url, + }) } + folderDTO.Parents = parents + return folderDTO, nil } @@ -953,23 +934,19 @@ func (fk8s *folderK8sHandler) getFolderACMetadata(c *contextmodel.ReqContext, f return nil, nil } - var err error - parents := []*folder.Folder{} - if f.ParentUID != "" { - parents, err = fk8s.folderService.GetParents( - c.Req.Context(), - folder.GetParentsQuery{ - UID: f.UID, - OrgID: c.SignedInUser.GetOrgID(), - }) - if err != nil { - return nil, err - } + if len(f.FullpathUIDs) == 0 { + return map[string]bool{}, nil + } + + parentsFullPathUIDs := strings.Split(f.FullpathUIDs, "/") + // The first part of the path is the newly created folder which we don't need to check here + if len(parentsFullPathUIDs) < 2 { + return map[string]bool{}, nil } folderIDs := map[string]bool{f.UID: true} - for _, p := range parents { - folderIDs[p.UID] = true + for _, uid := range parentsFullPathUIDs[1:] { + folderIDs[uid] = true } allMetadata := getMultiAccessControlMetadata(c, dashboards.ScopeFoldersPrefix, folderIDs) diff --git a/pkg/apimachinery/utils/meta.go b/pkg/apimachinery/utils/meta.go index 3c16efc81ca..b0074c3eaf2 100644 --- a/pkg/apimachinery/utils/meta.go +++ b/pkg/apimachinery/utils/meta.go @@ -34,6 +34,11 @@ const AnnoKeyOriginPath = "grafana.app/originPath" const AnnoKeyOriginHash = "grafana.app/originHash" const AnnoKeyOriginTimestamp = "grafana.app/originTimestamp" +// #TODO revisit keeping these folder-specific annotations once we have complete support for mode 1 + +const AnnoKeyFullPath = "grafana.app/fullPath" +const AnnoKeyFullPathUIDs = "grafana.app/fullPathUIDs" + // ResourceOriginInfo is saved in annotations. This is used to identify where the resource came from // This object can model the same data as our existing provisioning table or a more general git sync type ResourceOriginInfo struct { @@ -101,6 +106,11 @@ type GrafanaMetaAccessor interface { // NOTE the type must match the existing value, or an error will be thrown SetStatus(any) error + GetFullPath() string + SetFullPath(path string) + GetFullPathUIDs() string + SetFullPathUIDs(path string) + // Find a title in the object // This will reflect the object and try to get: // * spec.title @@ -598,6 +608,22 @@ func (m *grafanaMetaAccessor) SetStatus(s any) (err error) { return } +func (m *grafanaMetaAccessor) GetFullPath() string { + return m.get(AnnoKeyFullPath) +} + +func (m *grafanaMetaAccessor) SetFullPath(path string) { + m.SetAnnotation(AnnoKeyFullPath, path) +} + +func (m *grafanaMetaAccessor) GetFullPathUIDs() string { + return m.get(AnnoKeyFullPathUIDs) +} + +func (m *grafanaMetaAccessor) SetFullPathUIDs(path string) { + m.SetAnnotation(AnnoKeyFullPathUIDs, path) +} + func (m *grafanaMetaAccessor) FindTitle(defaultTitle string) string { // look for Spec.Title or Spec.Name spec := m.r.FieldByName("Spec") diff --git a/pkg/registry/apis/folders/conversions.go b/pkg/registry/apis/folders/conversions.go index 0deabc23ed3..31e8c051f2b 100644 --- a/pkg/registry/apis/folders/conversions.go +++ b/pkg/registry/apis/folders/conversions.go @@ -2,6 +2,7 @@ package folders import ( "fmt" + "regexp" "strconv" "time" @@ -85,10 +86,12 @@ func UnstructuredToLegacyFolder(item unstructured.Unstructured, orgID int64) *fo // #TODO add created by field if necessary // CreatedBy: meta.GetCreatedBy(), // UpdatedBy: meta.GetCreatedBy(), - URL: getURL(meta, title), - Created: createdTime, - Updated: createdTime, - OrgID: orgID, + URL: getURL(meta, title), + Created: createdTime, + Updated: createdTime, + OrgID: orgID, + Fullpath: meta.GetFullPath(), + FullpathUIDs: meta.GetFullPathUIDs(), } return f } @@ -183,6 +186,12 @@ func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper) if v.ParentUID != "" { meta.SetFolder(v.ParentUID) } + if v.Fullpath != "" { + meta.SetFullPath(v.Fullpath) + } + if v.FullpathUIDs != "" { + meta.SetFullPathUIDs(v.FullpathUIDs) + } f.UID = gapiutil.CalculateClusterWideUID(f) return f, nil } @@ -226,3 +235,22 @@ func getCreated(meta utils.GrafanaMetaAccessor) (*time.Time, error) { } return created, nil } + +func GetParentTitles(fullPath string) ([]string, error) { + // Find all forward slashes which aren't escaped + r, err := regexp.Compile(`[^\\](/)`) + if err != nil { + return nil, err + } + indices := r.FindAllStringIndex(fullPath, -1) + + var start int + titles := []string{} + for _, i := range indices { + titles = append(titles, fullPath[start:i[0]+1]) + start = i[0] + 2 + } + + titles = append(titles, fullPath[start:]) + return titles, nil +} diff --git a/pkg/registry/apis/folders/conversions_test.go b/pkg/registry/apis/folders/conversions_test.go new file mode 100644 index 00000000000..70310d50968 --- /dev/null +++ b/pkg/registry/apis/folders/conversions_test.go @@ -0,0 +1,18 @@ +package folders + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestGetParentTitles(t *testing.T) { + path := "get\\/folder-folder-0/get\\/folder-folder-1/another" + + titles, err := GetParentTitles(path) + require.Nil(t, err) + require.Equal(t, 3, len(titles)) + require.Equal(t, "get\\/folder-folder-0", titles[0]) + require.Equal(t, "get\\/folder-folder-1", titles[1]) + require.Equal(t, "another", titles[2]) +} diff --git a/pkg/services/folder/folderimpl/folder.go b/pkg/services/folder/folderimpl/folder.go index 36f21e37170..a1608f85e71 100644 --- a/pkg/services/folder/folderimpl/folder.go +++ b/pkg/services/folder/folderimpl/folder.go @@ -260,6 +260,7 @@ func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Fo if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { dashFolder.Fullpath = dashFolder.Title + dashFolder.FullpathUIDs = dashFolder.UID return dashFolder, nil } @@ -282,7 +283,8 @@ func (s *Service) Get(ctx context.Context, q *folder.GetFolderQuery) (*folder.Fo f.Version = dashFolder.Version if !s.features.IsEnabled(ctx, featuremgmt.FlagNestedFolders) { - f.Fullpath = f.Title // set full path to the folder title (unescaped) + f.Fullpath = f.Title // set full path to the folder title (unescaped) + f.FullpathUIDs = f.UID // set full path to the folder UID } return f, err @@ -671,6 +673,30 @@ func (s *Service) Create(ctx context.Context, cmd *folder.CreateFolderCommand) ( return nil, err } + if s.features.IsEnabled(ctx, featuremgmt.FlagKubernetesFolders) { + if f.ParentUID == "" { + return f, nil + } + + // Fetch the parent since the permissions for fetching the newly created folder + // are not yet present for the user--this requires a call to ClearUserPermissionCache + parent, err := s.Get(ctx, &folder.GetFolderQuery{ + UID: &f.ParentUID, + OrgID: f.OrgID, + WithFullpath: true, + WithFullpathUIDs: true, + SignedInUser: user, + }) + if err != nil { + return nil, err + } + // #TODO revisit setting permissions so that we can centralise the logic for escaping slashes in titles + // Escape forward slashes in the title + title := strings.Replace(f.Title, "/", "\\/", -1) + f.Fullpath = title + "/" + parent.Fullpath + f.FullpathUIDs = f.UID + "/" + parent.FullpathUIDs + } + return f, nil } diff --git a/pkg/services/folder/folderimpl/folder_test.go b/pkg/services/folder/folderimpl/folder_test.go index 283dcb668c1..188c4f57fce 100644 --- a/pkg/services/folder/folderimpl/folder_test.go +++ b/pkg/services/folder/folderimpl/folder_test.go @@ -501,7 +501,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOn) require.NoError(t, err) - ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd) + ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOn", createCmd, true) parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestors[0].UID) require.NoError(t, err) @@ -584,7 +584,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOff) require.NoError(t, err) - ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd) + ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "getDescendantCountsOff", createCmd, true) parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestors[0].UID) require.NoError(t, err) @@ -725,7 +725,7 @@ func TestIntegrationNestedFolderService(t *testing.T) { alertStore, err := ngstore.ProvideDBStore(cfg, tc.featuresFlag, db, tc.service, dashSrv, ac) require.NoError(t, err) - ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, tc.depth, tc.prefix, createCmd) + ancestors := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, tc.depth, tc.prefix, createCmd, true) parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestors[0].UID) require.NoError(t, err) @@ -1536,8 +1536,8 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { t.Run("Should get folders shared with given user", func(t *testing.T) { depth := 3 - ancestorFoldersWithPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withPermissions", createCmd) - ancestorFoldersWithoutPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withoutPermissions", createCmd) + ancestorFoldersWithPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withPermissions", createCmd, true) + ancestorFoldersWithoutPermissions := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "withoutPermissions", createCmd, true) parent, err := serviceWithFlagOn.dashboardFolderStore.GetFolderByUID(context.Background(), orgID, ancestorFoldersWithoutPermissions[0].UID) require.NoError(t, err) @@ -1661,8 +1661,8 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) { // tree2-folder-0 // └──tree2-folder-1 // └──tree2-folder-2 - tree1 := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "tree1-", createCmd) - tree2 := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "tree2-", createCmd) + tree1 := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "tree1-", createCmd, true) + tree2 := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOn, depth, "tree2-", createCmd, true) signedInUser.Permissions[orgID][dashboards.ActionFoldersRead] = []string{ // Add permission to tree1-folder-0 @@ -1928,14 +1928,16 @@ func TestFolderServiceGetFolder(t *testing.T) { } depth := 3 - folders := CreateSubtreeInStore(t, folderSvcOn.store, &folderSvcOn, depth, "get/folder-", createCmd) + folders := CreateSubtreeInStore(t, folderSvcOn.store, &folderSvcOn, depth, "get/folder-", createCmd, false) f := folders[1] testCases := []struct { - name string - svc *Service - WithFullpath bool - expectedFullpath string + name string + svc *Service + WithFullpath bool + WithFullpathUIDs bool + expectedFullpath string + expectedFullpathUIDs string }{ { name: "when flag is off", @@ -1954,6 +1956,18 @@ func TestFolderServiceGetFolder(t *testing.T) { WithFullpath: true, expectedFullpath: "get\\/folder-folder-0/get\\/folder-folder-1", }, + { + name: "when flag is on and WithFullpathUIDs is false", + svc: &folderSvcOn, + WithFullpathUIDs: false, + expectedFullpathUIDs: "", + }, + { + name: "when flag is on and WithFullpathUIDs is true", + svc: &folderSvcOn, + WithFullpathUIDs: true, + expectedFullpathUIDs: "uidfor-0/uidfor-1", + }, } for _, tc := range testCases { @@ -2021,7 +2035,7 @@ func TestFolderServiceGetFolders(t *testing.T) { }) prefix := "getfolders/ff/off" - folders := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOff, 5, prefix, createCmd) + folders := CreateSubtreeInStore(t, nestedFolderStore, serviceWithFlagOff, 5, prefix, createCmd, true) f := folders[rand.Intn(len(folders))] t.Run("when flag is off", func(t *testing.T) { @@ -2510,7 +2524,7 @@ func TestSupportBundle(t *testing.T) { } } -func CreateSubtreeInStore(t *testing.T, store folder.Store, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand) []*folder.Folder { +func CreateSubtreeInStore(t *testing.T, store folder.Store, service *Service, depth int, prefix string, cmd folder.CreateFolderCommand, randomUID bool) []*folder.Folder { t.Helper() folders := make([]*folder.Folder, 0, depth) @@ -2518,6 +2532,9 @@ func CreateSubtreeInStore(t *testing.T, store folder.Store, service *Service, de title := fmt.Sprintf("%sfolder-%d", prefix, i) cmd.Title = title cmd.UID = util.GenerateShortUID() + if !randomUID { + cmd.UID = fmt.Sprintf("uidfor-%d", i) + } cmd.OrgID = orgID cmd.SignedInUser = &user.SignedInUser{OrgID: orgID, Permissions: map[int64]map[string][]string{orgID: {dashboards.ActionFoldersCreate: {dashboards.ScopeFoldersAll}}}} diff --git a/pkg/services/folder/folderimpl/sqlstore.go b/pkg/services/folder/folderimpl/sqlstore.go index 2e085fddccd..1428bd037c6 100644 --- a/pkg/services/folder/folderimpl/sqlstore.go +++ b/pkg/services/folder/folderimpl/sqlstore.go @@ -201,8 +201,11 @@ func (ss *FolderStoreImpl) Get(ctx context.Context, q folder.GetFolderQuery) (*f if q.WithFullpath { s.WriteString(fmt.Sprintf(`, %s AS fullpath`, getFullpathSQL(ss.db.GetDialect()))) } + if q.WithFullpathUIDs { + s.WriteString(fmt.Sprintf(`, %s AS fullpath_uids`, getFullapathUIDsSQL(ss.db.GetDialect()))) + } s.WriteString(" FROM folder f0") - if q.WithFullpath { + if q.WithFullpath || q.WithFullpathUIDs { s.WriteString(getFullpathJoinsSQL()) } switch { @@ -241,6 +244,7 @@ func (ss *FolderStoreImpl) Get(ctx context.Context, q folder.GetFolderQuery) (*f }) foldr.Fullpath = strings.TrimLeft(foldr.Fullpath, "/") + foldr.FullpathUIDs = strings.TrimLeft(foldr.FullpathUIDs, "/") return foldr.WithURL(), err } diff --git a/pkg/services/folder/folderimpl/sqlstore_test.go b/pkg/services/folder/folderimpl/sqlstore_test.go index dc60826d618..7d639b92e8b 100644 --- a/pkg/services/folder/folderimpl/sqlstore_test.go +++ b/pkg/services/folder/folderimpl/sqlstore_test.go @@ -485,6 +485,24 @@ func TestIntegrationGet(t *testing.T) { assert.NotEmpty(t, ff.Updated) assert.NotEmpty(t, ff.URL) }) + + t.Run("get folder withFullpathUIDs should set fullpathUIDs as expected", func(t *testing.T) { + ff, err := folderStore.Get(context.Background(), folder.GetFolderQuery{ + UID: &subfolderWithSameName.UID, + OrgID: orgID, + WithFullpathUIDs: true, + }) + require.NoError(t, err) + assert.Equal(t, subfolderWithSameName.UID, ff.UID) + assert.Equal(t, subfolderWithSameName.OrgID, ff.OrgID) + assert.Equal(t, subfolderWithSameName.Title, ff.Title) + assert.Equal(t, subfolderWithSameName.Description, ff.Description) + assert.Equal(t, path.Join(f.UID, subfolderWithSameName.UID), ff.FullpathUIDs) + assert.Equal(t, f.UID, ff.ParentUID) + assert.NotEmpty(t, ff.Created) + assert.NotEmpty(t, ff.Updated) + assert.NotEmpty(t, ff.URL) + }) } func TestIntegrationGetParents(t *testing.T) { diff --git a/pkg/services/folder/model.go b/pkg/services/folder/model.go index b0869713f30..378127516c0 100644 --- a/pkg/services/folder/model.go +++ b/pkg/services/folder/model.go @@ -148,11 +148,12 @@ type DeleteFolderCommand struct { type GetFolderQuery struct { UID *string // Deprecated: use FolderUID instead - ID *int64 - Title *string - ParentUID *string - OrgID int64 - WithFullpath bool + ID *int64 + Title *string + ParentUID *string + OrgID int64 + WithFullpath bool + WithFullpathUIDs bool SignedInUser identity.Requester `json:"-"` } diff --git a/pkg/tests/apis/folder/folders_test.go b/pkg/tests/apis/folder/folders_test.go index aea2069d750..9683eb03cbb 100644 --- a/pkg/tests/apis/folder/folders_test.go +++ b/pkg/tests/apis/folder/folders_test.go @@ -3,6 +3,7 @@ package playlist import ( "context" "encoding/json" + "fmt" "net/http" "slices" @@ -13,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "github.com/grafana/grafana/pkg/api/dtos" folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -286,6 +288,24 @@ func TestIntegrationFoldersApp(t *testing.T) { doFolderTests(t, helper) }) + + t.Run("with dual write (unified storage, mode 1, nested folders)", func(t *testing.T) { + checkNestedCreate(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + folderv0alpha1.RESOURCEGROUP: { + DualWriterMode: grafanarest.Mode1, + }, + }, + EnableFeatureToggles: []string{ + featuremgmt.FlagGrafanaAPIServerTestingWithExperimentalAPIs, + featuremgmt.FlagNestedFolders, + featuremgmt.FlagKubernetesFolders, + }, + })) + }) } func doFolderTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelper { @@ -368,6 +388,49 @@ func doFolderTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelper return helper } +func checkNestedCreate(t *testing.T, helper *apis.K8sTestHelper) { + client := helper.GetResourceClient(apis.ResourceClientArgs{ + User: helper.Org1.Admin, + GVR: gvr, + }) + + parentPayload := `{ + "title": "Test/parent", + "uid": "" + }` + parentCreate := apis.DoRequest(helper, apis.RequestParams{ + User: client.Args.User, + Method: http.MethodPost, + Path: "/api/folders", + Body: []byte(parentPayload), + }, &folder.Folder{}) + require.NotNil(t, parentCreate.Result) + parentUID := parentCreate.Result.UID + require.NotEmpty(t, parentUID) + + childPayload := fmt.Sprintf(`{ + "title": "Test/child", + "uid": "", + "parentUid": "%s" + }`, parentUID) + childCreate := apis.DoRequest(helper, apis.RequestParams{ + User: client.Args.User, + Method: http.MethodPost, + Path: "/api/folders", + Body: []byte(childPayload), + }, &dtos.Folder{}) + require.NotNil(t, childCreate.Result) + childUID := childCreate.Result.UID + require.NotEmpty(t, childUID) + require.Equal(t, "Test/child", childCreate.Result.Title) + require.Equal(t, 1, len(childCreate.Result.Parents)) + + parent := childCreate.Result.Parents[0] + require.Equal(t, parentUID, parent.UID) + require.Equal(t, "Test\\/parent", parent.Title) + require.Equal(t, parentCreate.Result.URL, parent.URL) +} + // This does a get with both k8s and legacy API, and verifies the results are the same func getFromBothAPIs(t *testing.T, helper *apis.K8sTestHelper, From be489062572497f22d3a92faeb3609c0458cfa98 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:53:03 +0100 Subject: [PATCH 038/110] Update dependency eslint-plugin-import to v2.31.0 (#94529) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index d395dd0888d..02ebbc81e39 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16961,21 +16961,21 @@ __metadata: languageName: node linkType: hard -"eslint-module-utils@npm:^2.9.0": - version: 2.9.0 - resolution: "eslint-module-utils@npm:2.9.0" +"eslint-module-utils@npm:^2.12.0": + version: 2.12.0 + resolution: "eslint-module-utils@npm:2.12.0" dependencies: debug: "npm:^3.2.7" peerDependenciesMeta: eslint: optional: true - checksum: 10/13e001c96a6ce8d3d7ad6798c9b86351820c9c4a9abc5a152e84b838d7937a781471b0128ee690d18def226741fc96e8c5cff78c059bdcafe9ab8625777fcf2a + checksum: 10/dd27791147eca17366afcb83f47d6825b6ce164abb256681e5de4ec1d7e87d8605641eb869298a0dbc70665e2446dbcc2f40d3e1631a9475dd64dd23d4ca5dee languageName: node linkType: hard "eslint-plugin-import@npm:^2.26.0": - version: 2.30.0 - resolution: "eslint-plugin-import@npm:2.30.0" + version: 2.31.0 + resolution: "eslint-plugin-import@npm:2.31.0" dependencies: "@rtsao/scc": "npm:^1.1.0" array-includes: "npm:^3.1.8" @@ -16985,7 +16985,7 @@ __metadata: debug: "npm:^3.2.7" doctrine: "npm:^2.1.0" eslint-import-resolver-node: "npm:^0.3.9" - eslint-module-utils: "npm:^2.9.0" + eslint-module-utils: "npm:^2.12.0" hasown: "npm:^2.0.2" is-core-module: "npm:^2.15.1" is-glob: "npm:^4.0.3" @@ -16994,10 +16994,11 @@ __metadata: object.groupby: "npm:^1.0.3" object.values: "npm:^1.2.0" semver: "npm:^6.3.1" + string.prototype.trimend: "npm:^1.0.8" tsconfig-paths: "npm:^3.15.0" peerDependencies: - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: 10/a5f85dfe76e27286c28a01d137769726ce3f758bcc03aa6b6f9e18700a40a08f57239f82e07efcab763c4b03a02d425edcc29fbecf40aad0124286978c6bc63c + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + checksum: 10/6b76bd009ac2db0615d9019699d18e2a51a86cb8c1d0855a35fb1b418be23b40239e6debdc6e8c92c59f1468ed0ea8d7b85c817117a113d5cc225be8a02ad31c languageName: node linkType: hard From 32845704ac49bfb70c44ff13890d0c48fdd35f6d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:53:35 +0100 Subject: [PATCH 039/110] Update dependency eslint-scope to v8.1.0 (#94530) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 02ebbc81e39..324f31202a1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17217,12 +17217,12 @@ __metadata: linkType: hard "eslint-scope@npm:^8.0.0": - version: 8.0.2 - resolution: "eslint-scope@npm:8.0.2" + version: 8.1.0 + resolution: "eslint-scope@npm:8.1.0" dependencies: esrecurse: "npm:^4.3.0" estraverse: "npm:^5.2.0" - checksum: 10/d17c2e1ff4d3a98911414a954531078db912e2747d6da8ea4cafd16d0526e32086c676ce9aeaffb3ca0ff695fc951ac3169d7f08a0b42962db683dff126cc95b + checksum: 10/4c34a12fbeb0677822a9e93e81f2027e39e6f27557c17bc1e5ff76debbd41e748c3673517561792bda9e276245f89fbfd9b0b24fcec3b33a04ee2196729b3489 languageName: node linkType: hard From 8e667c4165a9f8206d1c6b8c45a65153dc00cf3a Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Thu, 10 Oct 2024 06:43:16 -0600 Subject: [PATCH 040/110] Search POC: Fixes search request tenant id (#94511) fixes search request tenant id --- pkg/services/unifiedSearch/service.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/services/unifiedSearch/service.go b/pkg/services/unifiedSearch/service.go index 4874a62858a..6f2b29e97c5 100644 --- a/pkg/services/unifiedSearch/service.go +++ b/pkg/services/unifiedSearch/service.go @@ -3,6 +3,7 @@ package unifiedSearch import ( "context" "errors" + "fmt" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" @@ -158,7 +159,8 @@ func (s *StandardSearchService) doQuery(ctx context.Context, signedInUser *user. func (s *StandardSearchService) doSearchQuery(ctx context.Context, qry Query, _ string) *backend.DataResponse { response := &backend.DataResponse{} - req := &resource.SearchRequest{Tenant: s.cfg.StackID, Query: qry.Query, Limit: int64(qry.Limit), Offset: int64(qry.From)} + tenantId := fmt.Sprintf("stacks-%s", s.cfg.StackID) + req := &resource.SearchRequest{Tenant: tenantId, Query: qry.Query, Limit: int64(qry.Limit), Offset: int64(qry.From)} res, err := s.resourceClient.Search(ctx, req) if err != nil { s.logger.Error("Failed to search resources", "error", err) From 088cb66635d51c858b6b20508835d01940cd5bd6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:53:26 +0300 Subject: [PATCH 041/110] Update dependency knip to v5.33.3 (#94538) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/yarn.lock b/yarn.lock index 324f31202a1..d5a48448f63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21675,7 +21675,7 @@ __metadata: languageName: node linkType: hard -"jiti@npm:^1.20.0, jiti@npm:^1.21.6": +"jiti@npm:^1.20.0": version: 1.21.6 resolution: "jiti@npm:1.21.6" bin: @@ -21684,6 +21684,15 @@ __metadata: languageName: node linkType: hard +"jiti@npm:^2.3.3": + version: 2.3.3 + resolution: "jiti@npm:2.3.3" + bin: + jiti: lib/jiti-cli.mjs + checksum: 10/21d1e89d909101c702769537e75ad87b433f980bc6938a6f64a90d1fbe7cb1510a0d4b82d4020b3093b47cebff5568af5e39d883e26aa4413564ced43b8cfd84 + languageName: node + linkType: hard + "jju@npm:^1.4.0": version: 1.4.0 resolution: "jju@npm:1.4.0" @@ -22147,15 +22156,15 @@ __metadata: linkType: hard "knip@npm:^5.10.0": - version: 5.30.6 - resolution: "knip@npm:5.30.6" + version: 5.33.3 + resolution: "knip@npm:5.33.3" dependencies: "@nodelib/fs.walk": "npm:1.2.8" "@snyk/github-codeowners": "npm:1.1.0" easy-table: "npm:1.2.0" enhanced-resolve: "npm:^5.17.1" fast-glob: "npm:^3.3.2" - jiti: "npm:^1.21.6" + jiti: "npm:^2.3.3" js-yaml: "npm:^4.1.0" minimist: "npm:^1.2.8" picocolors: "npm:^1.0.0" @@ -22172,7 +22181,7 @@ __metadata: bin: knip: bin/knip.js knip-bun: bin/knip-bun.js - checksum: 10/42973ec1f5208017c63232dd5a8c29b32accc9bbc00712afbb247fe14b2e8687e27a66a91cf48250cdae30aff6c456d71271c82dda9136785f96520b8c8a049d + checksum: 10/d6227a43666ce9732fe6b797a0c348bc46f59603826766c2049f44682ce96c23f0fb85b659d243dd36f98098a83a4e500d1044f3114a32564a39cf92dce8e853 languageName: node linkType: hard From 844023ad922d2b478bec730474736c3f6f019687 Mon Sep 17 00:00:00 2001 From: Irene Rodriguez Date: Thu, 10 Oct 2024 15:11:14 +0200 Subject: [PATCH 042/110] remove whitespace (#94542) --- docs/sources/upgrade-guide/_index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/upgrade-guide/_index.md b/docs/sources/upgrade-guide/_index.md index 390c81e965c..874374740f4 100644 --- a/docs/sources/upgrade-guide/_index.md +++ b/docs/sources/upgrade-guide/_index.md @@ -22,7 +22,7 @@ We recommend that you upgrade Grafana often to stay current with the latest fixe Because Grafana upgrades are backward compatible, the upgrade process is straightforward, and dashboards and graphs will not change. -To learn what's available in a Grafana release, refer to [What's New ]({{< relref "../whatsnew" >}}). +To learn what's available in a Grafana release, refer to [What's New]({{< relref "../whatsnew" >}}). Refer to any of the following upgrade guides: From c41c8c20268f7a3ffa21128dbeb6fc89be7b42d0 Mon Sep 17 00:00:00 2001 From: Virginia Cepeda Date: Thu, 10 Oct 2024 10:18:28 -0300 Subject: [PATCH 043/110] Icons: add new k6 icons (#94439) Added 4 new icons: - api-endpoint - browser-alt - k6-rounded - multi-step These icons where added in https://github.com/grafana/synthetic-monitoring-app/issues/955 --- packages/grafana-data/src/types/icon.ts | 4 ++++ public/img/icons/unicons/api-endpoint.svg | 21 +++++++++++++++++++++ public/img/icons/unicons/browser-alt.svg | 17 +++++++++++++++++ public/img/icons/unicons/k6-rounded.svg | 11 +++++++++++ public/img/icons/unicons/multi-step.svg | 5 +++++ 5 files changed, 58 insertions(+) create mode 100644 public/img/icons/unicons/api-endpoint.svg create mode 100644 public/img/icons/unicons/browser-alt.svg create mode 100644 public/img/icons/unicons/k6-rounded.svg create mode 100644 public/img/icons/unicons/multi-step.svg diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts index 570437c943d..d88e76965a6 100644 --- a/packages/grafana-data/src/types/icon.ts +++ b/packages/grafana-data/src/types/icon.ts @@ -22,6 +22,7 @@ export const availableIconsIndex = { 'angle-up': true, 'align-left': true, 'align-right': true, + 'api-endpoint': true, 'application-observability': true, apps: true, 'archive-alt': true, @@ -49,6 +50,7 @@ export const availableIconsIndex = { bookmark: true, 'book-open': true, 'brackets-curly': true, + 'browser-alt': true, bug: true, building: true, 'calculator-alt': true, @@ -161,6 +163,7 @@ export const availableIconsIndex = { info: true, 'info-circle': true, k6: true, + 'k6-rounded': true, 'key-skeleton-alt': true, keyboard: true, kubernetes: true, @@ -184,6 +187,7 @@ export const availableIconsIndex = { 'minus-circle': true, 'mobile-android': true, monitor: true, + 'multi-step': true, palette: true, 'panel-add': true, paragraph: true, diff --git a/public/img/icons/unicons/api-endpoint.svg b/public/img/icons/unicons/api-endpoint.svg new file mode 100644 index 00000000000..1d300cffc63 --- /dev/null +++ b/public/img/icons/unicons/api-endpoint.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/img/icons/unicons/browser-alt.svg b/public/img/icons/unicons/browser-alt.svg new file mode 100644 index 00000000000..253275890d3 --- /dev/null +++ b/public/img/icons/unicons/browser-alt.svg @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/public/img/icons/unicons/k6-rounded.svg b/public/img/icons/unicons/k6-rounded.svg new file mode 100644 index 00000000000..101c269716e --- /dev/null +++ b/public/img/icons/unicons/k6-rounded.svg @@ -0,0 +1,11 @@ + + + + + diff --git a/public/img/icons/unicons/multi-step.svg b/public/img/icons/unicons/multi-step.svg new file mode 100644 index 00000000000..eea231cd406 --- /dev/null +++ b/public/img/icons/unicons/multi-step.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file From 747cdf938e640e1d95db4a316e176fa4824439d1 Mon Sep 17 00:00:00 2001 From: Bradley <12028233+bradleypettit@users.noreply.github.com> Date: Thu, 10 Oct 2024 23:30:54 +1000 Subject: [PATCH 044/110] Docs: Added instructions for configuring a private CA in Helm installs (#93249) * Docs: Added instructions for configuring a private CA in Helm installs Signed-off-by: Bradley Pettit <12028233+bradleypettit@users.noreply.github.com> * Docs: linted new instructions for private CA cert Signed-off-by: Bradley Pettit <12028233+bradleypettit@users.noreply.github.com> * Expanded to gem/gel/get Originally, it just referred to GEM --------- Signed-off-by: Bradley Pettit <12028233+bradleypettit@users.noreply.github.com> Co-authored-by: Jennifer Villa --- .../setup-grafana/installation/helm/index.md | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/docs/sources/setup-grafana/installation/helm/index.md b/docs/sources/setup-grafana/installation/helm/index.md index 566f73d5610..d34e5c35f0e 100644 --- a/docs/sources/setup-grafana/installation/helm/index.md +++ b/docs/sources/setup-grafana/installation/helm/index.md @@ -278,6 +278,67 @@ To install plugins in the Grafana Helm Charts, complete the following steps: 1. Search for the above plugins and they should be marked as installed. +### Configure a Private CA (Certificate Authority) + +In many enterprise networks, TLS certificates are issued by a private certificate authority and are not trusted by default (using the provided OS trust chain). + +If your Grafana instance needs to interact with services exposing certificates issued by these private CAs, then you need to ensure Grafana trusts the root certificate. + +You might need to configure this if you: + +- have plugins that require connectivity to other self hosted systems. For example, if you've installed the Grafana Enterprise Metrics, Logs, or Traces (GEM, GEL, GET) plugins, and your GEM (or GEL/GET) cluster is using a private certificate. +- want to connect to data sources which are listening on HTTPS with a private certificate. +- are using a backend database for persistence, or caching service that uses private certificates for encryption in transit. + +In some cases you can specify a self-signed certificate within Grafana (such as in some data sources), or choose to skip TLS certificate validation (this is not recommended unless absolutely necessary). + +A simple solution which should work across your entire instance (plugins, data sources, and backend connections) is to add your self-signed CA certificate to your Kubernetes deployment. + +1. Create a ConfigMap containing the certificate, and deploy it to your Kubernetes cluster + + ```yaml + # grafana-ca-configmap.yaml + --- + apiVersion: v1 + kind: ConfigMap + metadata: + name: grafana-ca-cert + data: + ca.pem: | + -----BEGIN CERTIFICATE----- + (rest of the CA cert) + -----END CERTIFICATE----- + ``` + + ```bash + kubectl apply --filename grafana-ca-configmap.yaml --namespace monitoring + ``` + +1. Open the Helm `values.yaml` file in your favorite editor. + +1. Find the line that says `extraConfigmapMounts:` and under that section, specify the additional ConfigMap that you want to mount. + + ```yaml + ....... + ............ + ...... + extraConfigmapMounts: + - name: ca-certs-configmap + mountPath: /etc/ssl/certs/ca.pem + subPath: ca.pem + configMap: grafana-ca-cert + readOnly: true + ....... + ............ + ...... + ``` + +1. Save the changes and use the `helm upgrade` command to update your Grafana deployment and mount the new ConfigMap: + + ```bash + helm upgrade my-grafana grafana/grafana --values values.yaml --namespace monitoring + ``` + ## Troubleshooting This section includes troubleshooting tips you might find helpful when deploying Grafana on Kubernetes via Helm. From c872cad879a0f4ac558012c3b924827140c8fc94 Mon Sep 17 00:00:00 2001 From: Misi Date: Thu, 10 Oct 2024 15:31:30 +0200 Subject: [PATCH 045/110] OrgSync: Do not set default Organization for a user to a non-existent Organization (#94537) Do not set default org for a user to a missing org Co-authored-by: Karl Persson --- pkg/services/authn/authnimpl/sync/org_sync.go | 11 ++++++-- .../authn/authnimpl/sync/org_sync_test.go | 25 +++++++++++-------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/pkg/services/authn/authnimpl/sync/org_sync.go b/pkg/services/authn/authnimpl/sync/org_sync.go index 87d79215ce5..8a50d4cd73f 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync.go +++ b/pkg/services/authn/authnimpl/sync/org_sync.go @@ -87,18 +87,25 @@ func (s *OrgSync) SyncOrgRolesHook(ctx context.Context, id *authn.Identity, _ *a orgIDs := make([]int64, 0, len(id.OrgRoles)) // add any new org roles for orgId, orgRole := range id.OrgRoles { - orgIDs = append(orgIDs, orgId) if _, exists := handledOrgIds[orgId]; exists { + orgIDs = append(orgIDs, orgId) continue } // add role cmd := &org.AddOrgUserCommand{UserID: userID, Role: orgRole, OrgID: orgId} err := s.orgService.AddOrgUser(ctx, cmd) - if err != nil && !errors.Is(err, org.ErrOrgNotFound) { + + if errors.Is(err, org.ErrOrgNotFound) { + continue + } + + if err != nil { ctxLogger.Error("Failed to update active org for user", "error", err) return err } + + orgIDs = append(orgIDs, orgId) } // delete any removed org roles diff --git a/pkg/services/authn/authnimpl/sync/org_sync_test.go b/pkg/services/authn/authnimpl/sync/org_sync_test.go index b62bcbcbf00..246aec07399 100644 --- a/pkg/services/authn/authnimpl/sync/org_sync_test.go +++ b/pkg/services/authn/authnimpl/sync/org_sync_test.go @@ -24,7 +24,8 @@ import ( ) func TestOrgSync_SyncOrgRolesHook(t *testing.T) { - orgService := &orgtest.FakeOrgService{ExpectedUserOrgDTO: []*org.UserOrgDTO{ + orgService := &orgtest.MockService{} + orgService.On("GetUserOrgList", mock.Anything, mock.Anything).Return([]*org.UserOrgDTO{ { OrgID: 1, Role: org.RoleEditor, @@ -33,14 +34,16 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { OrgID: 3, Role: org.RoleViewer, }, - }, - ExpectedOrgListResponse: orgtest.OrgListResponse{ - { - OrgID: 3, - Response: nil, - }, - }, - } + }, nil) + orgService.On("RemoveOrgUser", mock.Anything, mock.MatchedBy(func(cmd *org.RemoveOrgUserCommand) bool { + return cmd.OrgID == 3 && cmd.UserID == 1 + })).Return(nil) + orgService.On("UpdateOrgUser", mock.Anything, mock.MatchedBy(func(cmd *org.UpdateOrgUserCommand) bool { + return cmd.OrgID == 1 && cmd.UserID == 1 && cmd.Role == org.RoleAdmin + })).Return(nil) + orgService.On("AddOrgUser", mock.Anything, mock.MatchedBy(func(cmd *org.AddOrgUserCommand) bool { + return cmd.OrgID == 2 && cmd.UserID == 1 && cmd.Role == org.RoleEditor + })).Return(org.ErrOrgNotFound) acService := &actest.FakeService{} userService := &usertest.FakeUserService{ExpectedUser: &user.User{ ID: 1, @@ -67,7 +70,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { wantID *authn.Identity }{ { - name: "add user to multiple orgs", + name: "add user to multiple orgs, should not set the user's default orgID to an org that does not exist", fields: fields{ userService: userService, orgService: orgService, @@ -100,7 +103,7 @@ func TestOrgSync_SyncOrgRolesHook(t *testing.T) { Name: "test", Email: "test", OrgRoles: map[int64]identity.RoleType{1: org.RoleAdmin, 2: org.RoleEditor}, - OrgID: 1, //set using org + OrgID: 1, // set using org IsGrafanaAdmin: ptrBool(false), ClientParams: authn.ClientParams{ SyncOrgRoles: true, From 315778227b010f2d929ebbf9d8617154f4f1373c Mon Sep 17 00:00:00 2001 From: Oscar Kilhed Date: Thu, 10 Oct 2024 15:54:48 +0200 Subject: [PATCH 046/110] Dashboards: Links to explore should respect subpath (#94525) * Links to explore should respect subpath * Change to using assureBaseUrl * Change back to normal single quotes --- public/app/core/utils/explore.test.ts | 26 +++++++++++++++++++++++++- public/app/core/utils/explore.ts | 3 ++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 82bac2e5764..75da07f4577 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -1,8 +1,11 @@ -import { DataSourceApi, dateTime, ExploreUrlState, LogsSortOrder } from '@grafana/data'; +import { DataSourceApi, dateTime, ExploreUrlState, GrafanaConfig, locationUtil, LogsSortOrder } from '@grafana/data'; import { serializeStateToUrlParam } from '@grafana/data/src/utils/url'; +import { config } from '@grafana/runtime'; import { DataQuery } from '@grafana/schema'; import { RefreshPicker } from '@grafana/ui'; +import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { DEFAULT_RANGE } from 'app/features/explore/state/utils'; +import { getVariablesUrlParams } from 'app/features/variables/getAllVariableValuesForUrl'; import { DatasourceSrvMock, MockDataSourceApi } from '../../../test/mocks/datasource_srv'; @@ -152,6 +155,27 @@ describe('getExploreUrl', () => { expect(interpolateMockLoki).toBeCalled(); expect(interpolateMockProm).toBeCalled(); }); + + describe('subpath', () => { + beforeAll(() => { + locationUtil.initialize({ + config: { appSubUrl: '/subpath' } as GrafanaConfig, + getVariablesUrlParams: jest.fn(), + getTimeRangeForUrl: jest.fn(), + }); + }); + afterAll(() => { + // Reset locationUtil + locationUtil.initialize({ + config, + getTimeRangeForUrl: getTimeSrv().timeRangeForUrl, + getVariablesUrlParams: getVariablesUrlParams, + }); + }); + it('should work with sub path', async () => { + expect(await getExploreUrl(args)).toMatch(/subpath\/explore/g); + }); + }); }); describe('hasNonEmptyQuery', () => { diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts index af320c4c6ec..cb22e191a77 100644 --- a/public/app/core/utils/explore.ts +++ b/public/app/core/utils/explore.ts @@ -12,6 +12,7 @@ import { DefaultTimeZone, getNextRefId, IntervalValues, + locationUtil, LogsDedupStrategy, LogsSortOrder, rangeUtil, @@ -94,7 +95,7 @@ export async function getExploreUrl(args: GetExploreUrlArguments): Promise Date: Thu, 10 Oct 2024 16:47:31 +0200 Subject: [PATCH 047/110] RBAC: Add legacy authorization checks to teams (#94524) * Setup team authorization for teams * Add list filter for teams --- pkg/apis/iam/v0alpha1/types_team.go | 9 ++ pkg/registry/apis/iam/authorizer.go | 13 ++ pkg/registry/apis/iam/legacy/sql.go | 1 + pkg/registry/apis/iam/legacy/team.go | 74 +++++++++++ .../apis/iam/legacy/team_internal_id.sql | 5 + pkg/registry/apis/iam/register.go | 2 +- pkg/registry/apis/iam/team/store.go | 121 ++++++++++-------- 7 files changed, 170 insertions(+), 55 deletions(-) create mode 100644 pkg/registry/apis/iam/legacy/team_internal_id.sql diff --git a/pkg/apis/iam/v0alpha1/types_team.go b/pkg/apis/iam/v0alpha1/types_team.go index 9def089d78f..eff4e770f84 100644 --- a/pkg/apis/iam/v0alpha1/types_team.go +++ b/pkg/apis/iam/v0alpha1/types_team.go @@ -1,6 +1,8 @@ package v0alpha1 import ( + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -15,6 +17,13 @@ type Team struct { type TeamSpec struct { Title string `json:"title,omitempty"` Email string `json:"email,omitempty"` + + // This is currently used for authorization checks but we don't want to expose it + InternalID int64 `json:"-"` +} + +func (t Team) AuthID() string { + return fmt.Sprintf("%d", t.Spec.InternalID) } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/pkg/registry/apis/iam/authorizer.go b/pkg/registry/apis/iam/authorizer.go index 8cc8cb68aba..6ea63508d4c 100644 --- a/pkg/registry/apis/iam/authorizer.go +++ b/pkg/registry/apis/iam/authorizer.go @@ -54,6 +54,19 @@ func newLegacyAuthorizer(ac accesscontrol.AccessControl, store legacy.LegacyIden return []string{fmt.Sprintf("serviceaccounts:id:%d", res.ID)}, nil }), }, + accesscontrol.ResourceAuthorizerOptions{ + Resource: iamv0.TeamResourceInfo.GetName(), + Attr: "id", + Resolver: accesscontrol.ResourceResolverFunc(func(ctx context.Context, ns claims.NamespaceInfo, name string) ([]string, error) { + res, err := store.GetTeamInternalID(ctx, ns, legacy.GetTeamInternalIDQuery{ + UID: name, + }) + if err != nil { + return nil, err + } + return []string{fmt.Sprintf("teams:id:%d", res.ID)}, nil + }), + }, ) return gfauthorizer.NewResourceAuthorizer(client), client diff --git a/pkg/registry/apis/iam/legacy/sql.go b/pkg/registry/apis/iam/legacy/sql.go index 5e0e3f470ed..cdabeb6ac7c 100644 --- a/pkg/registry/apis/iam/legacy/sql.go +++ b/pkg/registry/apis/iam/legacy/sql.go @@ -22,6 +22,7 @@ type LegacyIdentityStore interface { ListServiceAccounts(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountsQuery) (*ListServiceAccountResult, error) ListServiceAccountTokens(ctx context.Context, ns claims.NamespaceInfo, query ListServiceAccountTokenQuery) (*ListServiceAccountTokenResult, error) + GetTeamInternalID(ctx context.Context, ns claims.NamespaceInfo, query GetTeamInternalIDQuery) (*GetTeamInternalIDResult, error) ListTeams(ctx context.Context, ns claims.NamespaceInfo, query ListTeamQuery) (*ListTeamResult, error) ListTeamBindings(ctx context.Context, ns claims.NamespaceInfo, query ListTeamBindingsQuery) (*ListTeamBindingsResult, error) ListTeamMembers(ctx context.Context, ns claims.NamespaceInfo, query ListTeamMembersQuery) (*ListTeamMembersResult, error) diff --git a/pkg/registry/apis/iam/legacy/team.go b/pkg/registry/apis/iam/legacy/team.go index b5e1e5d0e12..be7cb9ab61a 100644 --- a/pkg/registry/apis/iam/legacy/team.go +++ b/pkg/registry/apis/iam/legacy/team.go @@ -3,6 +3,7 @@ package legacy import ( "context" "database/sql" + "errors" "fmt" "time" @@ -14,6 +15,79 @@ import ( "github.com/grafana/grafana/pkg/storage/unified/sql/sqltemplate" ) +type GetTeamInternalIDQuery struct { + OrgID int64 + UID string +} + +type GetTeamInternalIDResult struct { + ID int64 +} + +var sqlQueryTeamInternalIDTemplate = mustTemplate("team_internal_id.sql") + +func newGetTeamInternalID(sql *legacysql.LegacyDatabaseHelper, q *GetTeamInternalIDQuery) getTeamInternalIDQuery { + return getTeamInternalIDQuery{ + SQLTemplate: sqltemplate.New(sql.DialectForDriver()), + TeamTable: sql.Table("team"), + Query: q, + } +} + +type getTeamInternalIDQuery struct { + sqltemplate.SQLTemplate + TeamTable string + Query *GetTeamInternalIDQuery +} + +func (r getTeamInternalIDQuery) Validate() error { return nil } + +func (s *legacySQLStore) GetTeamInternalID( + ctx context.Context, + ns claims.NamespaceInfo, + query GetTeamInternalIDQuery, +) (*GetTeamInternalIDResult, error) { + query.OrgID = ns.OrgID + if query.OrgID == 0 { + return nil, fmt.Errorf("expected non zero org id") + } + + sql, err := s.sql(ctx) + if err != nil { + return nil, err + } + + req := newGetTeamInternalID(sql, &query) + q, err := sqltemplate.Execute(sqlQueryTeamInternalIDTemplate, req) + if err != nil { + return nil, fmt.Errorf("execute template %q: %w", sqlQueryTeamInternalIDTemplate.Name(), err) + } + + rows, err := sql.DB.GetSqlxSession().Query(ctx, q, req.GetArgs()...) + defer func() { + if rows != nil { + _ = rows.Close() + } + }() + + if err != nil { + return nil, err + } + + if !rows.Next() { + return nil, errors.New("team not found") + } + + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + + return &GetTeamInternalIDResult{ + id, + }, nil +} + type ListTeamQuery struct { OrgID int64 UID string diff --git a/pkg/registry/apis/iam/legacy/team_internal_id.sql b/pkg/registry/apis/iam/legacy/team_internal_id.sql new file mode 100644 index 00000000000..f2f4e3e5fad --- /dev/null +++ b/pkg/registry/apis/iam/legacy/team_internal_id.sql @@ -0,0 +1,5 @@ +SELECT t.id +FROM {{ .Ident .TeamTable }} as t +WHERE t.org_id = {{ .Arg .Query.OrgID }} +AND t.uid = {{ .Arg .Query.UID }} +LIMIT 1; diff --git a/pkg/registry/apis/iam/register.go b/pkg/registry/apis/iam/register.go index 1a69dcf136c..1ef15561291 100644 --- a/pkg/registry/apis/iam/register.go +++ b/pkg/registry/apis/iam/register.go @@ -97,7 +97,7 @@ func (b *IdentityAccessManagementAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *ge storage := map[string]rest.Storage{} teamResource := iamv0.TeamResourceInfo - storage[teamResource.StoragePath()] = team.NewLegacyStore(b.store) + storage[teamResource.StoragePath()] = team.NewLegacyStore(b.store, b.accessClient) storage[teamResource.StoragePath("members")] = team.NewLegacyTeamMemberREST(b.store) teamBindingResource := iamv0.TeamBindingResourceInfo diff --git a/pkg/registry/apis/iam/team/store.go b/pkg/registry/apis/iam/team/store.go index ae6f78291fd..864e5a36df8 100644 --- a/pkg/registry/apis/iam/team/store.go +++ b/pkg/registry/apis/iam/team/store.go @@ -2,6 +2,7 @@ package team import ( "context" + "fmt" "strconv" "k8s.io/apimachinery/pkg/apis/meta/internalversion" @@ -15,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/iam/common" "github.com/grafana/grafana/pkg/registry/apis/iam/legacy" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/team" ) var ( @@ -27,12 +29,13 @@ var ( var resource = iamv0.TeamResourceInfo -func NewLegacyStore(store legacy.LegacyIdentityStore) *LegacyStore { - return &LegacyStore{store} +func NewLegacyStore(store legacy.LegacyIdentityStore, ac claims.AccessClient) *LegacyStore { + return &LegacyStore{store, ac} } type LegacyStore struct { store legacy.LegacyIdentityStore + ac claims.AccessClient } func (s *LegacyStore) New() runtime.Object { @@ -58,74 +61,84 @@ func (s *LegacyStore) ConvertToTable(ctx context.Context, object runtime.Object, return resource.TableConverter().ConvertToTable(ctx, object, tableOptions) } -func (s *LegacyStore) doList(ctx context.Context, ns claims.NamespaceInfo, query legacy.ListTeamQuery) (*iamv0.TeamList, error) { - rsp, err := s.store.ListTeams(ctx, ns, query) - if err != nil { - return nil, err - } - list := &iamv0.TeamList{ - ListMeta: metav1.ListMeta{ - ResourceVersion: strconv.FormatInt(rsp.RV, 10), +func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { + res, err := common.List( + ctx, resource.GetName(), s.ac, common.PaginationFromListOptions(options), + func(ctx context.Context, ns claims.NamespaceInfo, p common.Pagination) (*common.ListResponse[iamv0.Team], error) { + found, err := s.store.ListTeams(ctx, ns, legacy.ListTeamQuery{ + Pagination: p, + }) + + if err != nil { + return nil, err + } + + teams := make([]iamv0.Team, 0, len(found.Teams)) + for _, t := range found.Teams { + teams = append(teams, toTeamObject(t, ns)) + } + + return &common.ListResponse[iamv0.Team]{ + Items: teams, + RV: found.RV, + Continue: found.Continue, + }, nil }, - } - for _, team := range rsp.Teams { - item := iamv0.Team{ - ObjectMeta: metav1.ObjectMeta{ - Name: team.UID, - Namespace: ns.Value, - CreationTimestamp: metav1.NewTime(team.Created), - ResourceVersion: strconv.FormatInt(team.Updated.UnixMilli(), 10), - }, - Spec: iamv0.TeamSpec{ - Title: team.Name, - Email: team.Email, - }, - } - meta, err := utils.MetaAccessor(&item) - if err != nil { - return nil, err - } - meta.SetUpdatedTimestamp(&team.Updated) - meta.SetOriginInfo(&utils.ResourceOriginInfo{ - Name: "SQL", - Path: strconv.FormatInt(team.ID, 10), - }) - list.Items = append(list.Items, item) + ) + + if err != nil { + return nil, fmt.Errorf("failed to list teams: %w", err) } - list.ListMeta.Continue = common.OptionalFormatInt(rsp.Continue) - list.ListMeta.ResourceVersion = common.OptionalFormatInt(rsp.RV) + list := &iamv0.TeamList{Items: res.Items} + list.ListMeta.Continue = common.OptionalFormatInt(res.Continue) + list.ListMeta.ResourceVersion = common.OptionalFormatInt(res.RV) return list, nil } -func (s *LegacyStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { - ns, err := request.NamespaceInfoFrom(ctx, true) - if err != nil { - return nil, err - } - - return s.doList(ctx, ns, legacy.ListTeamQuery{ - OrgID: ns.OrgID, - Pagination: common.PaginationFromListOptions(options), - }) -} - func (s *LegacyStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { ns, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err } - rsp, err := s.doList(ctx, ns, legacy.ListTeamQuery{ + + found, err := s.store.ListTeams(ctx, ns, legacy.ListTeamQuery{ OrgID: ns.OrgID, UID: name, Pagination: common.Pagination{Limit: 1}, }) - if err != nil { - return nil, err + if found == nil || err != nil { + return nil, resource.NewNotFound(name) } - if len(rsp.Items) > 0 { - return &rsp.Items[0], nil + if len(found.Teams) < 1 { + return nil, resource.NewNotFound(name) } - return nil, resource.NewNotFound(name) + + obj := toTeamObject(found.Teams[0], ns) + return &obj, nil +} + +func toTeamObject(t team.Team, ns claims.NamespaceInfo) iamv0.Team { + obj := iamv0.Team{ + ObjectMeta: metav1.ObjectMeta{ + Name: t.UID, + Namespace: ns.Value, + CreationTimestamp: metav1.NewTime(t.Created), + ResourceVersion: strconv.FormatInt(t.Updated.UnixMilli(), 10), + }, + Spec: iamv0.TeamSpec{ + Title: t.Name, + Email: t.Email, + InternalID: t.ID, + }, + } + meta, _ := utils.MetaAccessor(&obj) + meta.SetUpdatedTimestamp(&t.Updated) + meta.SetOriginInfo(&utils.ResourceOriginInfo{ + Name: "SQL", + Path: strconv.FormatInt(t.ID, 10), + }) + + return obj } From 42eb033b036177c2119c54b39ab68f727db21e77 Mon Sep 17 00:00:00 2001 From: kay delaney <45561153+kaydelaney@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:49:48 +0100 Subject: [PATCH 048/110] Chore: Change VariableEditorList overflow styling & tidy markup (#94394) Chore: Channge VariableEditorList overflow styling & tidy markup --- .../settings/variables/VariableEditorList.tsx | 112 ++++++++---------- 1 file changed, 51 insertions(+), 61 deletions(-) diff --git a/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx b/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx index 8686eb8bcb9..8a579c1d792 100644 --- a/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx +++ b/public/app/features/dashboard-scene/settings/variables/VariableEditorList.tsx @@ -1,5 +1,6 @@ import { css } from '@emotion/css'; import { DragDropContext, Droppable, DropResult } from '@hello-pangea/dnd'; +import classNames from 'classnames'; import { ReactElement } from 'react'; import { selectors } from '@grafana/e2e-selectors'; @@ -30,76 +31,66 @@ export function VariableEditorList({ onEdit, }: Props): ReactElement { const styles = useStyles2(getStyles); + const onDragEnd = (result: DropResult) => { if (!result.destination || !result.source) { return; } + reportInteraction('Variable drag and drop'); onChangeOrder(result.source.index, result.destination.index); }; - return ( -
-
- {variables.length === 0 && } - - {variables.length > 0 && ( - -
- - - - - - - - - - {(provided) => ( - - {variables.map((variableScene, index) => { - const variableState = variableScene.state; - return ( - - ); - })} - {provided.placeholder} - - )} - - -
VariableDefinition -
-
- - - - -
- )} -
-
+ return variables.length <= 0 ? ( + + ) : ( + + + + + + + + + + + {(provided) => ( + + {variables.map((variableScene, index) => { + const variableState = variableScene.state; + return ( + + ); + })} + {provided.placeholder} + + )} + + +
VariableDefinition +
+ + + + +
); } -function EmptyVariablesList({ onAdd }: { onAdd: () => void }): ReactElement { +function EmptyVariablesList({ onAdd }: { onAdd: () => void }) { return ( void }): ReactElement { const getStyles = () => ({ tableContainer: css({ - overflow: 'scroll', - width: '100%', + overflow: 'auto', }), }); From a268a56acbd0cf2c3ba8c30abb0b294806a08f70 Mon Sep 17 00:00:00 2001 From: Joey <90795735+joey-grafana@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:51:43 +0100 Subject: [PATCH 049/110] Tempo: Put trace results data frame first when streaming (#93739) * Put trace results dataframe first * Add comment --- public/app/plugins/datasource/tempo/streaming.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/public/app/plugins/datasource/tempo/streaming.ts b/public/app/plugins/datasource/tempo/streaming.ts index 71c336edf2f..333faca7388 100644 --- a/public/app/plugins/datasource/tempo/streaming.ts +++ b/public/app/plugins/datasource/tempo/streaming.ts @@ -84,9 +84,12 @@ export function doTempoChannelStream( throw new Error(error); } + // The order of the frames is important. The metrics frame should always be the last frame. + // This is because the metrics frame is used to display the progress of the streaming query + // and we would like to display the results first. frames = [ - metricsDataFrame(metrics, frameState, elapsedTime), ...formatTraceQLResponse(traces, instanceSettings, query.tableType), + metricsDataFrame(metrics, frameState, elapsedTime), ]; } return { From a112c9487bcff85c23fbb65a85109b56c3d294c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:54:35 +0100 Subject: [PATCH 050/110] Update dependency rollup to v4.24.0 (#94539) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 134 +++++++++++++++++++++++++++--------------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/yarn.lock b/yarn.lock index d5a48448f63..37e17be3484 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7070,114 +7070,114 @@ __metadata: languageName: node linkType: hard -"@rollup/rollup-android-arm-eabi@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-android-arm-eabi@npm:4.22.5" +"@rollup/rollup-android-arm-eabi@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.24.0" conditions: os=android & cpu=arm languageName: node linkType: hard -"@rollup/rollup-android-arm64@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-android-arm64@npm:4.22.5" +"@rollup/rollup-android-arm64@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-android-arm64@npm:4.24.0" conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-arm64@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-darwin-arm64@npm:4.22.5" +"@rollup/rollup-darwin-arm64@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-darwin-arm64@npm:4.24.0" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-darwin-x64@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-darwin-x64@npm:4.22.5" +"@rollup/rollup-darwin-x64@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-darwin-x64@npm:4.24.0" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@rollup/rollup-linux-arm-gnueabihf@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.22.5" +"@rollup/rollup-linux-arm-gnueabihf@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.24.0" conditions: os=linux & cpu=arm & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm-musleabihf@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.22.5" +"@rollup/rollup-linux-arm-musleabihf@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.24.0" conditions: os=linux & cpu=arm & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-arm64-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.22.5" +"@rollup/rollup-linux-arm64-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.24.0" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-arm64-musl@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-arm64-musl@npm:4.22.5" +"@rollup/rollup-linux-arm64-musl@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.24.0" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-linux-powerpc64le-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.22.5" +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.24.0" conditions: os=linux & cpu=ppc64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-riscv64-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.22.5" +"@rollup/rollup-linux-riscv64-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.24.0" conditions: os=linux & cpu=riscv64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-s390x-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.22.5" +"@rollup/rollup-linux-s390x-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.24.0" conditions: os=linux & cpu=s390x & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-gnu@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-x64-gnu@npm:4.22.5" +"@rollup/rollup-linux-x64-gnu@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.24.0" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@rollup/rollup-linux-x64-musl@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-linux-x64-musl@npm:4.22.5" +"@rollup/rollup-linux-x64-musl@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.24.0" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@rollup/rollup-win32-arm64-msvc@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.22.5" +"@rollup/rollup-win32-arm64-msvc@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.24.0" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@rollup/rollup-win32-ia32-msvc@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.22.5" +"@rollup/rollup-win32-ia32-msvc@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.24.0" conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@rollup/rollup-win32-x64-msvc@npm:4.22.5": - version: 4.22.5 - resolution: "@rollup/rollup-win32-x64-msvc@npm:4.22.5" +"@rollup/rollup-win32-x64-msvc@npm:4.24.0": + version: 4.24.0 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.24.0" conditions: os=win32 & cpu=x64 languageName: node linkType: hard @@ -29010,25 +29010,25 @@ __metadata: linkType: hard "rollup@npm:^4.22.4": - version: 4.22.5 - resolution: "rollup@npm:4.22.5" + version: 4.24.0 + resolution: "rollup@npm:4.24.0" dependencies: - "@rollup/rollup-android-arm-eabi": "npm:4.22.5" - "@rollup/rollup-android-arm64": "npm:4.22.5" - "@rollup/rollup-darwin-arm64": "npm:4.22.5" - "@rollup/rollup-darwin-x64": "npm:4.22.5" - "@rollup/rollup-linux-arm-gnueabihf": "npm:4.22.5" - "@rollup/rollup-linux-arm-musleabihf": "npm:4.22.5" - "@rollup/rollup-linux-arm64-gnu": "npm:4.22.5" - "@rollup/rollup-linux-arm64-musl": "npm:4.22.5" - "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.22.5" - "@rollup/rollup-linux-riscv64-gnu": "npm:4.22.5" - "@rollup/rollup-linux-s390x-gnu": "npm:4.22.5" - "@rollup/rollup-linux-x64-gnu": "npm:4.22.5" - "@rollup/rollup-linux-x64-musl": "npm:4.22.5" - "@rollup/rollup-win32-arm64-msvc": "npm:4.22.5" - "@rollup/rollup-win32-ia32-msvc": "npm:4.22.5" - "@rollup/rollup-win32-x64-msvc": "npm:4.22.5" + "@rollup/rollup-android-arm-eabi": "npm:4.24.0" + "@rollup/rollup-android-arm64": "npm:4.24.0" + "@rollup/rollup-darwin-arm64": "npm:4.24.0" + "@rollup/rollup-darwin-x64": "npm:4.24.0" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.24.0" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.24.0" + "@rollup/rollup-linux-arm64-gnu": "npm:4.24.0" + "@rollup/rollup-linux-arm64-musl": "npm:4.24.0" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.24.0" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.24.0" + "@rollup/rollup-linux-s390x-gnu": "npm:4.24.0" + "@rollup/rollup-linux-x64-gnu": "npm:4.24.0" + "@rollup/rollup-linux-x64-musl": "npm:4.24.0" + "@rollup/rollup-win32-arm64-msvc": "npm:4.24.0" + "@rollup/rollup-win32-ia32-msvc": "npm:4.24.0" + "@rollup/rollup-win32-x64-msvc": "npm:4.24.0" "@types/estree": "npm:1.0.6" fsevents: "npm:~2.3.2" dependenciesMeta: @@ -29068,7 +29068,7 @@ __metadata: optional: true bin: rollup: dist/bin/rollup - checksum: 10/f34812fa982442ab71410b649630c24434b2dc02485e543607734766eb7211ce7e0a79102f27210f337af00f3617006adebb4f87fb2e9d24cac7100d0e599352 + checksum: 10/291dce8f180628a73d6749119a3e50aa917c416075302bc6f6ac655affc7f0ce9d7f025bef7318d424d0c5623dcb83e360f9ea0125273b6a2285c232172800cc languageName: node linkType: hard From e30c3980876cdcb8f4d91c81dc0254a04cb03650 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 15:56:59 +0100 Subject: [PATCH 051/110] Update dependency @hello-pangea/dnd to v17 (#94543) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 28 +++++++++++++++++++----- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 0e835827c62..0543a9fbc17 100644 --- a/package.json +++ b/package.json @@ -273,7 +273,7 @@ "@grafana/schema": "workspace:*", "@grafana/sql": "workspace:*", "@grafana/ui": "workspace:*", - "@hello-pangea/dnd": "16.6.0", + "@hello-pangea/dnd": "17.0.0", "@kusto/monaco-kusto": "^10.0.0", "@leeoniya/ufuzzy": "1.0.14", "@lezer/common": "1.2.2", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 942c0f721a8..f0492ea2fe1 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -44,7 +44,7 @@ "@grafana/runtime": "11.3.0-pre", "@grafana/schema": "11.3.0-pre", "@grafana/ui": "11.3.0-pre", - "@hello-pangea/dnd": "16.6.0", + "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.14", "@lezer/common": "1.2.2", "@lezer/highlight": "1.2.1", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 233095b9f89..2164f47f7e1 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -55,7 +55,7 @@ "@grafana/e2e-selectors": "11.3.0-pre", "@grafana/faro-web-sdk": "^1.3.6", "@grafana/schema": "11.3.0-pre", - "@hello-pangea/dnd": "16.6.0", + "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.14", "@monaco-editor/react": "4.6.0", "@popperjs/core": "2.11.8", diff --git a/yarn.lock b/yarn.lock index 37e17be3484..e9d05fd50e3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1554,7 +1554,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:7.25.7, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:7.25.7, @babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.11.1, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.1, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.10, @babel/runtime@npm:^7.14.0, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.0, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.20.0, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.23.2, @babel/runtime@npm:^7.23.9, @babel/runtime@npm:^7.24.1, @babel/runtime@npm:^7.24.5, @babel/runtime@npm:^7.25.6, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": version: 7.25.7 resolution: "@babel/runtime@npm:7.25.7" dependencies: @@ -3980,7 +3980,7 @@ __metadata: "@grafana/schema": "npm:11.3.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@grafana/ui": "npm:11.3.0-pre" - "@hello-pangea/dnd": "npm:16.6.0" + "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.14" "@lezer/common": "npm:1.2.2" "@lezer/highlight": "npm:1.2.1" @@ -4287,7 +4287,7 @@ __metadata: "@grafana/faro-web-sdk": "npm:^1.3.6" "@grafana/schema": "npm:11.3.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@hello-pangea/dnd": "npm:16.6.0" + "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.14" "@monaco-editor/react": "npm:4.6.0" "@popperjs/core": "npm:2.11.8" @@ -4429,7 +4429,25 @@ __metadata: languageName: node linkType: hard -"@hello-pangea/dnd@npm:16.6.0, @hello-pangea/dnd@npm:^16.6.0": +"@hello-pangea/dnd@npm:17.0.0": + version: 17.0.0 + resolution: "@hello-pangea/dnd@npm:17.0.0" + dependencies: + "@babel/runtime": "npm:^7.25.6" + css-box-model: "npm:^1.2.1" + memoize-one: "npm:^6.0.0" + raf-schd: "npm:^4.0.3" + react-redux: "npm:^9.1.2" + redux: "npm:^5.0.1" + use-memo-one: "npm:^1.1.3" + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 10/4795063e249a818c60e223f3527797878cb546ef007a52a7dd6c1a01094d3b2107820476a10fc83c0ba9dc4387c1ae49e70c8f8cff9722636219773caad19372 + languageName: node + linkType: hard + +"@hello-pangea/dnd@npm:^16.6.0": version: 16.6.0 resolution: "@hello-pangea/dnd@npm:16.6.0" dependencies: @@ -18963,7 +18981,7 @@ __metadata: "@grafana/sql": "workspace:*" "@grafana/tsconfig": "npm:^2.0.0" "@grafana/ui": "workspace:*" - "@hello-pangea/dnd": "npm:16.6.0" + "@hello-pangea/dnd": "npm:17.0.0" "@kusto/monaco-kusto": "npm:^10.0.0" "@leeoniya/ufuzzy": "npm:1.0.14" "@lezer/common": "npm:1.2.2" From 21d26de4d8e97f363866a9efeff5bc8221af08f0 Mon Sep 17 00:00:00 2001 From: linoman <2051016+linoman@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:57:34 +0200 Subject: [PATCH 052/110] Session Refactor: Add SAMLSession (#94490) * add saml session struct * resolve saml session * Add NameID --------- Co-authored-by: Mihaly Gyongyosi --- pkg/services/authn/authnimpl/service.go | 28 ++++++++++++++++--------- pkg/services/authn/identity.go | 2 ++ pkg/services/login/model.go | 6 ++++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/pkg/services/authn/authnimpl/service.go b/pkg/services/authn/authnimpl/service.go index e7182dcab52..644adebc386 100644 --- a/pkg/services/authn/authnimpl/service.go +++ b/pkg/services/authn/authnimpl/service.go @@ -491,7 +491,7 @@ func orgIDFromHeader(req *http.Request) int64 { } func (s *Service) resolveExternalSessionFromIdentity(ctx context.Context, identity *authn.Identity, userID int64) *auth.ExternalSession { - if identity.OAuthToken == nil { + if identity.OAuthToken == nil && identity.SAMLSession == nil { return nil } @@ -506,18 +506,26 @@ func (s *Service) resolveExternalSessionFromIdentity(ctx context.Context, identi UserAuthID: info.Id, UserID: userID, } - extSession.AccessToken = identity.OAuthToken.AccessToken - extSession.RefreshToken = identity.OAuthToken.RefreshToken - extSession.ExpiresAt = identity.OAuthToken.Expiry - if idToken, ok := identity.OAuthToken.Extra("id_token").(string); ok && idToken != "" { - extSession.IDToken = idToken + if identity.OAuthToken != nil { + extSession.AccessToken = identity.OAuthToken.AccessToken + extSession.RefreshToken = identity.OAuthToken.RefreshToken + extSession.ExpiresAt = identity.OAuthToken.Expiry + + if idToken, ok := identity.OAuthToken.Extra("id_token").(string); ok && idToken != "" { + extSession.IDToken = idToken + } + + // As of https://openid.net/specs/openid-connect-session-1_0.html + if sessionState, ok := identity.OAuthToken.Extra("session_state").(string); ok && sessionState != "" { + extSession.SessionID = sessionState + } + + return extSession } - // As of https://openid.net/specs/openid-connect-session-1_0.html - if sessionState, ok := identity.OAuthToken.Extra("session_state").(string); ok && sessionState != "" { - extSession.SessionID = sessionState - } + extSession.SessionID = identity.SAMLSession.SessionIndex + extSession.NameID = identity.SAMLSession.NameID return extSession } diff --git a/pkg/services/authn/identity.go b/pkg/services/authn/identity.go index e137809c1d2..a072ee7d807 100644 --- a/pkg/services/authn/identity.go +++ b/pkg/services/authn/identity.go @@ -64,6 +64,8 @@ type Identity struct { Groups []string // OAuthToken is the OAuth token used to authenticate the entity. OAuthToken *oauth2.Token + // SAMLSession is the SAML session information. + SAMLSession *login.SAMLSession // SessionToken is the session token used to authenticate the entity. SessionToken *usertoken.UserToken // ClientParams are hints for the auth service on how to handle the identity. diff --git a/pkg/services/login/model.go b/pkg/services/login/model.go index b11ec2161f3..3d755a80301 100644 --- a/pkg/services/login/model.go +++ b/pkg/services/login/model.go @@ -27,6 +27,7 @@ type UserAuth struct { type ExternalUserInfo struct { OAuthToken *oauth2.Token + SAMLSession *SAMLSession AuthModule string AuthId string UserId int64 @@ -40,6 +41,11 @@ type ExternalUserInfo struct { SkipTeamSync bool } +type SAMLSession struct { + NameID string + SessionIndex string +} + func (e *ExternalUserInfo) String() string { isGrafanaAdmin := "nil" if e.IsGrafanaAdmin != nil { From 5c03c14b25ada948b0bb61e8a22abe5c7ca55dcd Mon Sep 17 00:00:00 2001 From: Prem Saraswat Date: Thu, 10 Oct 2024 20:33:18 +0530 Subject: [PATCH 053/110] resource-api: Loosen name validation to match K8s requirements (#93404) * resource-api: Loosen name validation to match K8s requirements This patch modifies some of the requirements for name validation of objects in Resource API to match Kubernetes. The limit we have on characters in name is 64, but some resources allow upto 253 characters. Similarly we also include `:` in the regex, as many objects in default K8s setup use it in the name (the group `system:masters` for example) Signed-off-by: Prem Kumar * Update the name column length in migrator and update e2e test to verify --------- Signed-off-by: Prem Kumar --- pkg/storage/unified/resource/validation.go | 4 ++-- .../unified/resource/validation_test.go | 8 ++++---- .../unified/sql/db/migrations/resource_mig.go | 4 ++-- pkg/tests/apis/scopes/scopes_test.go | 20 +++++++++++++++++++ .../apis/scopes/testdata/example-scope3.yaml | 14 +++++++++++++ 5 files changed, 42 insertions(+), 8 deletions(-) create mode 100644 pkg/tests/apis/scopes/testdata/example-scope3.yaml diff --git a/pkg/storage/unified/resource/validation.go b/pkg/storage/unified/resource/validation.go index dce25a1b8fe..066ac42c2af 100644 --- a/pkg/storage/unified/resource/validation.go +++ b/pkg/storage/unified/resource/validation.go @@ -4,14 +4,14 @@ import ( "regexp" ) -var validNameCharPattern = `a-zA-Z0-9\-\_\.` +var validNameCharPattern = `a-zA-Z0-9:\-\_\.` var validNamePattern = regexp.MustCompile(`^[` + validNameCharPattern + `]*$`).MatchString func validateName(name string) *ErrorResult { if len(name) == 0 { return NewBadRequestError("name is too short") } - if len(name) > 64 { + if len(name) > 253 { return NewBadRequestError("name is too long") } if !validNamePattern(name) { diff --git a/pkg/storage/unified/resource/validation_test.go b/pkg/storage/unified/resource/validation_test.go index b9f9a51d7b5..bc0e4bd8bda 100644 --- a/pkg/storage/unified/resource/validation_test.go +++ b/pkg/storage/unified/resource/validation_test.go @@ -1,22 +1,22 @@ package resource import ( + "strings" "testing" "github.com/stretchr/testify/require" ) func TestNameValidation(t *testing.T) { - require.NotNil(t, validateName("")) // too short - require.NotNil(t, validateName( // too long (max 64) - "0123456789012345678901234567890123456789012345678901234567890123456789", - )) + require.NotNil(t, validateName("")) // too short + require.NotNil(t, validateName(strings.Repeat("0", 254))) // too long (max 253) // OK require.Nil(t, validateName("a")) require.Nil(t, validateName("hello-world")) require.Nil(t, validateName("hello.world")) require.Nil(t, validateName("hello_world")) + require.Nil(t, validateName("hello:world")) // Bad characters require.NotNil(t, validateName("hello world")) diff --git a/pkg/storage/unified/sql/db/migrations/resource_mig.go b/pkg/storage/unified/sql/db/migrations/resource_mig.go index 15e13c08a38..fcaddaacc80 100644 --- a/pkg/storage/unified/sql/db/migrations/resource_mig.go +++ b/pkg/storage/unified/sql/db/migrations/resource_mig.go @@ -22,7 +22,7 @@ func initResourceTables(mg *migrator.Migrator) string { {Name: "group", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, {Name: "resource", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, {Name: "namespace", Type: migrator.DB_NVarchar, Length: 63, Nullable: false}, - {Name: "name", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + {Name: "name", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, {Name: "value", Type: migrator.DB_LongText, Nullable: true}, {Name: "action", Type: migrator.DB_Int, Nullable: false}, // 1: create, 2: update, 3: delete @@ -44,7 +44,7 @@ func initResourceTables(mg *migrator.Migrator) string { {Name: "group", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, {Name: "resource", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, {Name: "namespace", Type: migrator.DB_NVarchar, Length: 63, Nullable: false}, - {Name: "name", Type: migrator.DB_NVarchar, Length: 190, Nullable: false}, + {Name: "name", Type: migrator.DB_NVarchar, Length: 253, Nullable: false}, {Name: "value", Type: migrator.DB_LongText, Nullable: true}, {Name: "action", Type: migrator.DB_Int, Nullable: false}, // 1: create, 2: update, 3: delete diff --git a/pkg/tests/apis/scopes/scopes_test.go b/pkg/tests/apis/scopes/scopes_test.go index 87e8abb1318..eb380937dff 100644 --- a/pkg/tests/apis/scopes/scopes_test.go +++ b/pkg/tests/apis/scopes/scopes_test.go @@ -3,6 +3,7 @@ package scopes import ( "context" "encoding/json" + "strings" "testing" "github.com/stretchr/testify/require" @@ -159,6 +160,25 @@ func TestIntegrationScopes(t *testing.T) { ) require.NoError(t, err) + // Name length test + scope3 := helper.LoadYAMLOrJSONFile("testdata/example-scope3.yaml") + + // Name too long (>253) + scope3.SetName(strings.Repeat("0", 254)) + _, err = scopeClient.Resource.Create(ctx, + scope3, + createOptions, + ) + require.Error(t, err) + + // Maximum allowed length for name (253) + scope3.SetName(strings.Repeat("0", 253)) + _, err = scopeClient.Resource.Create(ctx, + scope3, + createOptions, + ) + require.NoError(t, err) + // Field Selector test found, err := scopeClient.Resource.List(ctx, metav1.ListOptions{ FieldSelector: "spec.title=foo-scope", diff --git a/pkg/tests/apis/scopes/testdata/example-scope3.yaml b/pkg/tests/apis/scopes/testdata/example-scope3.yaml new file mode 100644 index 00000000000..e1f36c39d87 --- /dev/null +++ b/pkg/tests/apis/scopes/testdata/example-scope3.yaml @@ -0,0 +1,14 @@ +apiVersion: scope.grafana.app/v0alpha1 +kind: Scope +metadata: + name: example-long +spec: + title: baz-scope + description: Longer description for a scope + filters: + - key: aaa + operator: equals + value: eee + - key: ccc + operator: not-equals + value: fff From e642e1a804d4b9c4260dfcaa1a01105efe3aad30 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Thu, 10 Oct 2024 17:38:15 +0200 Subject: [PATCH 054/110] Zanzana: Pass parent folder for the checks in search queries (#94541) * Pass parent folder as a contextual tuple in Check request * Search by listing folders and dashboards * skip dashboards listing if limit reached * remove unused * add some comments * only add ContextualTuples if parent provided * Remove parent relation for dashboards from schema and perform separate checks --- .../accesscontrol/acimpl/accesscontrol.go | 40 +++++- .../accesscontrol/migrator/zanzana.go | 43 +------ pkg/services/accesscontrol/models.go | 9 +- .../authz/zanzana/schema/dashboard.fga | 23 ++-- pkg/services/authz/zanzana/zanzana.go | 5 + pkg/services/dashboards/service/zanzana.go | 119 +++++++----------- 6 files changed, 109 insertions(+), 130 deletions(-) diff --git a/pkg/services/accesscontrol/acimpl/accesscontrol.go b/pkg/services/accesscontrol/acimpl/accesscontrol.go index d540474abfc..ed3ee6eba45 100644 --- a/pkg/services/accesscontrol/acimpl/accesscontrol.go +++ b/pkg/services/accesscontrol/acimpl/accesscontrol.go @@ -3,12 +3,15 @@ package acimpl import ( "context" "errors" + "strconv" "time" openfgav1 "github.com/openfga/api/proto/openfga/v1" "github.com/prometheus/client_golang/prometheus" "go.opentelemetry.io/otel" + "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/apimachinery/identity" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/metrics" @@ -127,6 +130,7 @@ func (a *AccessControl) evaluateZanzana(ctx context.Context, user identity.Reque a.log.Debug("evaluating zanzana", "user", tupleKey.User, "relation", tupleKey.Relation, "object", tupleKey.Object) allowed, err := a.Check(ctx, accesscontrol.CheckRequest{ + // Namespace: claims.OrgNamespaceFormatter(user.GetOrgID()), User: tupleKey.User, Relation: tupleKey.Relation, Object: tupleKey.Object, @@ -226,12 +230,44 @@ func (a *AccessControl) Check(ctx context.Context, req accesscontrol.CheckReques Relation: req.Relation, Object: req.Object, } - in := &openfgav1.CheckRequest{TupleKey: key} + + in := &openfgav1.CheckRequest{ + TupleKey: key, + } + + // Check direct access to resource first res, err := a.zclient.Check(ctx, in) if err != nil { return false, err } - return res.Allowed, err + + // no need to check folder access + if res.Allowed || req.Parent == "" { + return res.Allowed, nil + } + + // Check access through the parent folder + ns, err := claims.ParseNamespace(req.Namespace) + if err != nil { + return false, err + } + + folderKey := &openfgav1.CheckRequestTupleKey{ + User: req.User, + Relation: zanzana.TranslateToFolderRelation(req.Relation, req.ObjectType), + Object: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, req.Parent, "", strconv.FormatInt(ns.OrgID, 10)), + } + + folderReq := &openfgav1.CheckRequest{ + TupleKey: folderKey, + } + + folderRes, err := a.zclient.Check(ctx, folderReq) + if err != nil { + return false, err + } + + return folderRes.Allowed, nil } func (a *AccessControl) ListObjects(ctx context.Context, req accesscontrol.ListObjectsRequest) ([]string, error) { diff --git a/pkg/services/accesscontrol/migrator/zanzana.go b/pkg/services/accesscontrol/migrator/zanzana.go index f27316f2630..7c27f31b624 100644 --- a/pkg/services/accesscontrol/migrator/zanzana.go +++ b/pkg/services/accesscontrol/migrator/zanzana.go @@ -39,7 +39,6 @@ func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...Tu teamMembershipCollector(store), managedPermissionsCollector(store), folderTreeCollector(store), - dashboardFolderCollector(store), basicRolesCollector(store), customRolesCollector(store), basicRoleAssignemtCollector(store), @@ -58,6 +57,7 @@ func NewZanzanaSynchroniser(client zanzana.Client, store db.DB, collectors ...Tu // Sync runs all collectors and tries to write all collected tuples. // It will skip over any "sync group" that has already been written. func (z *ZanzanaSynchroniser) Sync(ctx context.Context) error { + z.log.Info("Starting zanzana permissions sync") ctx, span := tracer.Start(ctx, "accesscontrol.migrator.Sync") defer span.End() @@ -246,47 +246,6 @@ func folderTreeCollector(store db.DB) TupleCollector { } } -// dashboardFolderCollector collects information about dashboards parent folders -func dashboardFolderCollector(store db.DB) TupleCollector { - return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { - ctx, span := tracer.Start(ctx, "accesscontrol.migrator.dashboardFolderCollector") - defer span.End() - - const collectorID = "folder" - query := ` - SELECT org_id, uid, folder_uid, is_folder FROM dashboard - WHERE is_folder = ` + store.GetDialect().BooleanStr(false) + ` - AND folder_uid IS NOT NULL - ` - type dashboard struct { - OrgID int64 `xorm:"org_id"` - UID string `xorm:"uid"` - ParentUID string `xorm:"folder_uid"` - } - - var dashboards []dashboard - err := store.WithDbSession(ctx, func(sess *db.Session) error { - return sess.SQL(query).Find(&dashboards) - }) - - if err != nil { - return err - } - - for _, d := range dashboards { - tuple := &openfgav1.TupleKey{ - User: zanzana.NewScopedTupleEntry(zanzana.TypeFolder, d.ParentUID, "", strconv.FormatInt(d.OrgID, 10)), - Object: zanzana.NewScopedTupleEntry(zanzana.TypeDashboard, d.UID, "", strconv.FormatInt(d.OrgID, 10)), - Relation: zanzana.RelationParent, - } - - tuples[collectorID] = append(tuples[collectorID], tuple) - } - - return nil - } -} - // basicRolesCollector migrates basic roles to OpenFGA tuples func basicRolesCollector(store db.DB) TupleCollector { return func(ctx context.Context, tuples map[string][]*openfgav1.TupleKey) error { diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index c28a01429e1..9ee384540c1 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -589,9 +589,12 @@ type QueryWithOrg struct { } type CheckRequest struct { - User string - Relation string - Object string + Namespace string + User string + Relation string + Object string + ObjectType string + Parent string } type ListObjectsRequest struct { diff --git a/pkg/services/authz/zanzana/schema/dashboard.fga b/pkg/services/authz/zanzana/schema/dashboard.fga index ee08372ac7f..b9e77da81a1 100644 --- a/pkg/services/authz/zanzana/schema/dashboard.fga +++ b/pkg/services/authz/zanzana/schema/dashboard.fga @@ -28,18 +28,17 @@ extend type org type dashboard relations define org: [org] - define parent: [folder] - define read: [user, team#member, role#assignee] or dashboard_read from parent or dashboard_read from org - define write: [user, team#member, role#assignee] or dashboard_write from parent or dashboard_write from org - define delete: [user, team#member, role#assignee] or dashboard_delete from parent or dashboard_delete from org - define create: [user, team#member, role#assignee] or dashboard_create from parent or dashboard_create from org - define permissions_read: [user, team#member, role#assignee] or dashboard_permissions_read from parent or dashboard_permissions_read from org - define permissions_write: [user, team#member, role#assignee] or dashboard_permissions_write from parent or dashboard_permissions_write from org + define read: [user, team#member, role#assignee] or dashboard_read from org + define write: [user, team#member, role#assignee] or dashboard_write from org + define delete: [user, team#member, role#assignee] or dashboard_delete from org + define create: [user, team#member, role#assignee] or dashboard_create from org + define permissions_read: [user, team#member, role#assignee] or dashboard_permissions_read from org + define permissions_write: [user, team#member, role#assignee] or dashboard_permissions_write from org - define public_write: [user, team#member, role#assignee] or dashboard_public_write from parent or dashboard_public_write from org or write - define annotations_create: [user, team#member, role#assignee] or dashboard_annotations_create from parent or dashboard_annotations_create from org - define annotations_read: [user, team#member, role#assignee] or dashboard_annotations_read from parent or dashboard_annotations_read from org - define annotations_write: [user, team#member, role#assignee] or dashboard_annotations_write from parent or dashboard_annotations_write from org - define annotations_delete: [user, team#member, role#assignee] or dashboard_annotations_delete from parent or dashboard_annotations_delete from org + define public_write: [user, team#member, role#assignee] or dashboard_public_write from org or write + define annotations_create: [user, team#member, role#assignee] or dashboard_annotations_create from org + define annotations_read: [user, team#member, role#assignee] or dashboard_annotations_read from org + define annotations_write: [user, team#member, role#assignee] or dashboard_annotations_write from org + define annotations_delete: [user, team#member, role#assignee] or dashboard_annotations_delete from org diff --git a/pkg/services/authz/zanzana/zanzana.go b/pkg/services/authz/zanzana/zanzana.go index 03bfe9f36ed..ece56e98179 100644 --- a/pkg/services/authz/zanzana/zanzana.go +++ b/pkg/services/authz/zanzana/zanzana.go @@ -118,3 +118,8 @@ func TranslateFixedRole(role string) string { role = strings.ReplaceAll(role, ".", "_") return role } + +// Translate "read" for the dashboard into "dashboard_read" for folder +func TranslateToFolderRelation(relation, objectType string) string { + return fmt.Sprintf("%s_%s", objectType, relation) +} diff --git a/pkg/services/dashboards/service/zanzana.go b/pkg/services/dashboards/service/zanzana.go index f7bfb4d23f6..7441df302cf 100644 --- a/pkg/services/dashboards/service/zanzana.go +++ b/pkg/services/dashboards/service/zanzana.go @@ -10,10 +10,11 @@ import ( "github.com/prometheus/client_golang/prometheus" + "github.com/grafana/authlib/claims" + "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/authz/zanzana" "github.com/grafana/grafana/pkg/services/dashboards" - "github.com/grafana/grafana/pkg/services/sqlstore/searchstore" ) const ( @@ -193,9 +194,16 @@ func (dr *DashboardServiceImpl) checkDashboards(ctx context.Context, query dashb } req := accesscontrol.CheckRequest{ - User: query.SignedInUser.GetUID(), - Relation: "read", - Object: zanzana.NewScopedTupleEntry(objectType, d.UID, "", strconv.FormatInt(orgId, 10)), + Namespace: claims.OrgNamespaceFormatter(orgId), + User: query.SignedInUser.GetUID(), + Relation: "read", + Object: zanzana.NewScopedTupleEntry(objectType, d.UID, "", strconv.FormatInt(orgId, 10)), + } + + if objectType != zanzana.TypeFolder { + // Pass parentn folder for the correct check + req.Parent = d.FolderUID + req.ObjectType = objectType } allowed, err := dr.ac.Check(ctx, req) @@ -238,45 +246,47 @@ func (dr *DashboardServiceImpl) findDashboardsZanzanaList(ctx context.Context, q ctx, span := tracer.Start(ctx, "dashboards.service.findDashboardsZanzanaList") defer span.End() - resourceUIDs, err := dr.listUserResources(ctx, query) - if err != nil { - return nil, err - } - if len(resourceUIDs) == 0 { - return []dashboards.DashboardSearchProjection{}, nil - } + var result []dashboards.DashboardSearchProjection - query.DashboardUIDs = resourceUIDs - query.SkipAccessControlFilter = true - return dr.dashboardStore.FindDashboards(ctx, &query) -} - -func (dr *DashboardServiceImpl) listUserResources(ctx context.Context, query dashboards.FindPersistedDashboardsQuery) ([]string, error) { - tasks := make([]func() ([]string, error), 0) - var resourceTypes []string - - // For some search types we need dashboards or folders only - switch query.Type { - case searchstore.TypeDashboard: - resourceTypes = []string{zanzana.TypeDashboard} - case searchstore.TypeFolder, searchstore.TypeAlertFolder: - resourceTypes = []string{zanzana.TypeFolder} - default: - resourceTypes = []string{zanzana.TypeDashboard, zanzana.TypeFolder} - } - - for _, resourceType := range resourceTypes { - tasks = append(tasks, func() ([]string, error) { - return dr.listAllowedResources(ctx, query, resourceType) - }) - } - - uids, err := runBatch(tasks) + allowedFolders, err := dr.listAllowedResources(ctx, query, zanzana.TypeFolder) if err != nil { return nil, err } - return uids, nil + if len(allowedFolders) > 0 { + // Find dashboards in folders that user has access to + query.SkipAccessControlFilter = true + query.FolderUIDs = allowedFolders + result, err = dr.dashboardStore.FindDashboards(ctx, &query) + if err != nil { + return nil, err + } + } + + // skip if limit reached + rest := query.Limit - int64(len(result)) + if rest <= 0 { + return result, nil + } + + // Run second query to find dashboards with direct permission assignments + allowedDashboards, err := dr.listAllowedResources(ctx, query, zanzana.TypeDashboard) + if err != nil { + return nil, err + } + + if len(allowedDashboards) > 0 { + query.FolderUIDs = []string{} + query.DashboardUIDs = allowedDashboards + query.Limit = rest + dashboardRes, err := dr.dashboardStore.FindDashboards(ctx, &query) + if err != nil { + return nil, err + } + result = append(result, dashboardRes...) + } + + return result, err } func (dr *DashboardServiceImpl) listAllowedResources(ctx context.Context, query dashboards.FindPersistedDashboardsQuery, resourceType string) ([]string, error) { @@ -307,36 +317,3 @@ func (dr *DashboardServiceImpl) listAllowedResources(ctx context.Context, query return resourceUIDs, nil } - -func runBatch(tasks []func() ([]string, error)) ([]string, error) { - var wg sync.WaitGroup - tasksNum := len(tasks) - resChan := make(chan []string, tasksNum) - errChan := make(chan error, tasksNum) - - for _, task := range tasks { - wg.Add(1) - go func() { - defer wg.Done() - res, err := task() - resChan <- res - errChan <- err - }() - } - - wg.Wait() - close(resChan) - close(errChan) - - for err := range errChan { - if err != nil { - return nil, err - } - } - - result := make([]string, 0) - for res := range resChan { - result = append(result, res...) - } - return result, nil -} From e32caccc155a63510a2abe1e61f85a0ed7db4148 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Oct 2024 14:59:19 +0000 Subject: [PATCH 055/110] Update babel monorepo to v7.25.8 --- package.json | 4 +- packages/grafana-flamegraph/package.json | 4 +- packages/grafana-icons/package.json | 2 +- packages/grafana-ui/package.json | 2 +- yarn.lock | 239 +++++++++-------------- 5 files changed, 101 insertions(+), 150 deletions(-) diff --git a/package.json b/package.json index 0543a9fbc17..6bf41f692ec 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "releaseNotesUrl": "https://grafana.com/docs/grafana/next/release-notes/" }, "devDependencies": { - "@babel/core": "7.25.7", - "@babel/preset-env": "7.25.7", + "@babel/core": "7.25.8", + "@babel/preset-env": "7.25.8", "@babel/runtime": "7.25.7", "@betterer/betterer": "5.4.0", "@betterer/cli": "5.4.0", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 3ccd87c0a02..4767950153e 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -56,8 +56,8 @@ "tslib": "2.7.0" }, "devDependencies": { - "@babel/core": "7.25.7", - "@babel/preset-env": "7.25.7", + "@babel/core": "7.25.8", + "@babel/preset-env": "7.25.8", "@babel/preset-react": "7.25.7", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "15.3.0", diff --git a/packages/grafana-icons/package.json b/packages/grafana-icons/package.json index 7313c7fe3ed..fd2741ca1b1 100644 --- a/packages/grafana-icons/package.json +++ b/packages/grafana-icons/package.json @@ -34,7 +34,7 @@ "build": "yarn generate && rollup -c rollup.config.ts --configPlugin esbuild" }, "devDependencies": { - "@babel/core": "7.25.7", + "@babel/core": "7.25.8", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "^15.3.0", "@rollup/plugin-typescript": "^12.1.0", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 2164f47f7e1..1ce22d07abd 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -115,7 +115,7 @@ "uuid": "9.0.1" }, "devDependencies": { - "@babel/core": "7.25.7", + "@babel/core": "7.25.8", "@faker-js/faker": "^9.0.0", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-node-resolve": "15.3.0", diff --git a/yarn.lock b/yarn.lock index e9d05fd50e3..22f8b8cc7db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -92,16 +92,16 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/compat-data@npm:7.25.7" - checksum: 10/8fdc451e0ed9e22d1324d504b84d4452ba6f4a806b0f5c364996ee4c2a77293f79ecf4da03033acb625c90bac115c61617eb6c894c2b88486724bcbe3af1a6eb +"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.7, @babel/compat-data@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/compat-data@npm:7.25.8" + checksum: 10/269fcb0d89e02e36c8a11e0c1b960a6b4204e88f59f20c374d28f8e318f4cd5ded42dfedc4b54162065e6a10f71c0de651f5ed3f9b45d3a4b52240196df85726 languageName: node linkType: hard -"@babel/core@npm:7.25.7, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9, @babel/core@npm:^7.23.0, @babel/core@npm:^7.24.4": - version: 7.25.7 - resolution: "@babel/core@npm:7.25.7" +"@babel/core@npm:7.25.8, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.18.9, @babel/core@npm:^7.21.3, @babel/core@npm:^7.22.9, @babel/core@npm:^7.23.0, @babel/core@npm:^7.24.4": + version: 7.25.8 + resolution: "@babel/core@npm:7.25.8" dependencies: "@ampproject/remapping": "npm:^2.2.0" "@babel/code-frame": "npm:^7.25.7" @@ -109,16 +109,16 @@ __metadata: "@babel/helper-compilation-targets": "npm:^7.25.7" "@babel/helper-module-transforms": "npm:^7.25.7" "@babel/helpers": "npm:^7.25.7" - "@babel/parser": "npm:^7.25.7" + "@babel/parser": "npm:^7.25.8" "@babel/template": "npm:^7.25.7" "@babel/traverse": "npm:^7.25.7" - "@babel/types": "npm:^7.25.7" + "@babel/types": "npm:^7.25.8" convert-source-map: "npm:^2.0.0" debug: "npm:^4.1.0" gensync: "npm:^1.0.0-beta.2" json5: "npm:^2.2.3" semver: "npm:^6.3.1" - checksum: 10/f5fb7fb1e3ce357485cb33fe7984051a2d416472370b33144ae809df86a4663192b58cf0d828d40674d30f485790f3dd5aaf72eb659487673a4dc4be47cb3575 + checksum: 10/31eb1a8ca1a3cc0026060720eb290e68205d95c5c00fbd831e69ddc0810f5920b8eb2749db1889ac0a0312b6eddbf321d18a996a88858f3b75c9582bef9ec1e4 languageName: node linkType: hard @@ -371,14 +371,14 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/parser@npm:7.25.7" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.23.0, @babel/parser@npm:^7.24.4, @babel/parser@npm:^7.25.7, @babel/parser@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/parser@npm:7.25.8" dependencies: - "@babel/types": "npm:^7.25.7" + "@babel/types": "npm:^7.25.8" bin: parser: ./bin/babel-parser.js - checksum: 10/98eaa81bd378734a5f2790f02c7c076ecaba0839217445b4b84f45a7b391d640c34034253231a5bb2b2daf8204796f03584c3f94c10d46b004369bbb426a418f + checksum: 10/0396eb71e379903cedb43862f84ebb1bec809c41e82b4894d2e6e83b8e8bc636ba6eff45382e615baefdb2399ede76ca82247ecc3a9877ac16eb3140074a3276 languageName: node linkType: hard @@ -484,7 +484,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-properties@npm:^7.0.0, @babel/plugin-syntax-class-properties@npm:^7.12.13, @babel/plugin-syntax-class-properties@npm:^7.8.3": +"@babel/plugin-syntax-class-properties@npm:^7.0.0, @babel/plugin-syntax-class-properties@npm:^7.8.3": version: 7.12.13 resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" dependencies: @@ -495,17 +495,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-class-static-block@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-class-static-block@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/3e80814b5b6d4fe17826093918680a351c2d34398a914ce6e55d8083d72a9bdde4fbaf6a2dcea0e23a03de26dc2917ae3efd603d27099e2b98380345703bf948 - languageName: node - linkType: hard - "@babel/plugin-syntax-dynamic-import@npm:^7.8.3": version: 7.8.3 resolution: "@babel/plugin-syntax-dynamic-import@npm:7.8.3" @@ -561,7 +550,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-meta@npm:^7.10.4, @babel/plugin-syntax-import-meta@npm:^7.8.3": +"@babel/plugin-syntax-import-meta@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" dependencies: @@ -594,7 +583,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-logical-assignment-operators@npm:^7.10.4, @babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" dependencies: @@ -616,7 +605,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-numeric-separator@npm:^7.10.4, @babel/plugin-syntax-numeric-separator@npm:^7.8.3": +"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": version: 7.10.4 resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" dependencies: @@ -660,18 +649,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-private-property-in-object@npm:^7.14.5": - version: 7.14.5 - resolution: "@babel/plugin-syntax-private-property-in-object@npm:7.14.5" - dependencies: - "@babel/helper-plugin-utils": "npm:^7.14.5" - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 10/b317174783e6e96029b743ccff2a67d63d38756876e7e5d0ba53a322e38d9ca452c13354a57de1ad476b4c066dbae699e0ca157441da611117a47af88985ecda - languageName: node - linkType: hard - -"@babel/plugin-syntax-top-level-await@npm:^7.14.5, @babel/plugin-syntax-top-level-await@npm:^7.8.3": +"@babel/plugin-syntax-top-level-await@npm:^7.8.3": version: 7.14.5 resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" dependencies: @@ -716,17 +694,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-generator-functions@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.7" +"@babel/plugin-transform-async-generator-functions@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" "@babel/helper-remap-async-to-generator": "npm:^7.25.7" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" "@babel/traverse": "npm:^7.25.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/f5e14796bcb04db7f045833d695e49bb1178162c74754f67f25fd6934ebbd0e57a59784d76a23b3f472bbd3e5a0c33d433ab60e7c6a5c7ca240b54d8ca231baa + checksum: 10/ab3f74664fc03af357e8450711de60ec77149be668059dbc0c0d616d85253117aec0e5ffb2eccda3449d0099d5fba5ef32f0e6e12a52af5f72fbca437372ece5 languageName: node linkType: hard @@ -777,16 +754,15 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-class-static-block@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-class-static-block@npm:7.25.7" +"@babel/plugin-transform-class-static-block@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-class-static-block@npm:7.25.8" dependencies: "@babel/helper-create-class-features-plugin": "npm:^7.25.7" "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" peerDependencies: "@babel/core": ^7.12.0 - checksum: 10/d6fa7132071860d4b6e58baa43c8efdd241d5b4ff3d31b0b6593390c7b39bf17ab549e427c08db550c84b0fb02eaad41fc96c2d299fbee4c0a6030315b0a5296 + checksum: 10/160d5f9d1dbe4dc12c2998227b51b1ccfe9f4d11b1031d0698f34403961d5b9bb995cc86acf1855102b9be365370c97d8cea243802b73c7ba7b2b18b2ac3aae9 languageName: node linkType: hard @@ -864,15 +840,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dynamic-import@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-dynamic-import@npm:7.25.7" +"@babel/plugin-transform-dynamic-import@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-dynamic-import@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/a153f2a8b10a733f884a0ba0e680387d0ba643d40774885c26cceef90cc61b8e2f946a64e1764605189498a9a04e1d4da4e886c3e26a850e7c4e5ea5fb4b3f50 + checksum: 10/cf2c105143461876f418d21893ac8f7f2b0a3c3cefb4374c3cd6338a19d3a0deed3565049f7436b94452c6471622958ef9248c7bdfeb34d2917710ac74431203 languageName: node linkType: hard @@ -888,15 +863,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-export-namespace-from@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-export-namespace-from@npm:7.25.7" +"@babel/plugin-transform-export-namespace-from@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-export-namespace-from@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/e3ec589906380d1daa3349c358f8a56c9adc1c5003b4069321172e1e8160f2a497e9ae112ad1ac68e26dce33eb19019932cf938ad411493441ad202db0e72c2b + checksum: 10/439aac4ca1c7dbb63f021142e7abcd746049bf0d44cc5d2eb469ae3b75d90e076a43ff77190b74d8139402b53eea625b08c68651d3ce1d0a0915f5643450b3de languageName: node linkType: hard @@ -937,15 +911,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-json-strings@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-json-strings@npm:7.25.7" +"@babel/plugin-transform-json-strings@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-json-strings@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/99e7b19d5b8f0df78eb2eaef2c1c8734e634b08910b47d5c2f61e43e75bd94a9f8e51c8baf2d2fa181bf0a7424c08b0aee29a95ae5f9e59f50e51e188dc943a1 + checksum: 10/adbc6a5a77b96db0f7e168c5fd2e56941df649808ce960f12447c1ba5d3893e9d458e7e14e3a5bd725ac5f3432ac1b3cf62b7413bbf7168a7c656dce51db711a languageName: node linkType: hard @@ -960,15 +933,14 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-logical-assignment-operators@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.25.7" +"@babel/plugin-transform-logical-assignment-operators@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/28d00bae5637564243585e4fa044b9bc1da4b5c2ac2859e848e2db5f1c11c24dbc1ecc690755a00f593815a40d9cd99df2b31110ab68626840b22598ecf08d93 + checksum: 10/7af0e4ad63c1a59f24894b64330040966204963b75287752a2d56703c7924d3a883a3c2497e1f03c4b1792f8664e0650cf6687010dc5483444c077de1daae9f5 languageName: node linkType: hard @@ -1057,41 +1029,38 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.11, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.25.7" +"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.11, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/af4d2d17eb0bdfa4a0414eb02a402f65226363ea3cc6d781028fd0051893918eeb3cf13aaceef8ef58ef35b36362dbcc25cd018e0c24b5b441226e086bf3b58f + checksum: 10/d742fedc1abf404d7f40065cdff9afc521236607f0d06c48d1e471f43d3a7471010d1651ba4758d80c73347a39dc278d86c43a9c814382ded4e9c7c519ace021 languageName: node linkType: hard -"@babel/plugin-transform-numeric-separator@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-numeric-separator@npm:7.25.7" +"@babel/plugin-transform-numeric-separator@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-numeric-separator@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/963ff47d27b7e61e0b346a29e2ff86ccf4fb779f7069b54c09e93376e6ed769523d81b9ef4ef60ae2d8fe35f3411a25d4b2e8d0a23850afa821f47f33c7b510c + checksum: 10/e27779a309dbc5fdba71d7eae0eac5506547632b0cbf8f0add8215797bbda4f4e61595750236fee3292600cc2d13892f133beccc52b2998534e0b10c668db857 languageName: node linkType: hard -"@babel/plugin-transform-object-rest-spread@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-object-rest-spread@npm:7.25.7" +"@babel/plugin-transform-object-rest-spread@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-object-rest-spread@npm:7.25.8" dependencies: "@babel/helper-compilation-targets": "npm:^7.25.7" "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" "@babel/plugin-transform-parameters": "npm:^7.25.7" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b831c56921cb4818956941fd6eb54ba75fa51e5e5c1dc00466d70c79fea2a73d166a6ff02e8a27d63e40238de49f966af2bd243a8b6d281f784672f1a285d47c + checksum: 10/38f0fab8321a0b1e44784b7371f8bd5601eb885a7e9d88d7904dedda33a72f500d84792758c47e1541336c1b7592b6d956a85c2fd8e2e294f34c0303cc73442c languageName: node linkType: hard @@ -1107,28 +1076,26 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-optional-catch-binding@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.25.7" +"@babel/plugin-transform-optional-catch-binding@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/b195680e047bd2e0e36debfb75e5649f0fd8c88185e33c627c5418c68f9990cb4d96faccfbf47a2fdf9ff848a9e013ca077647616aa9dc1f78fa250ef1521437 + checksum: 10/9ecf32accc5b12b83ce2f6537c9eac87f2b0f89abfe91a8a8c87ea5ece05820988415271d0fdaf7f565e2c0c837afb24fc644779029b98b1401782d9c0d73642 languageName: node linkType: hard -"@babel/plugin-transform-optional-chaining@npm:^7.23.0, @babel/plugin-transform-optional-chaining@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-optional-chaining@npm:7.25.7" +"@babel/plugin-transform-optional-chaining@npm:^7.23.0, @babel/plugin-transform-optional-chaining@npm:^7.25.7, @babel/plugin-transform-optional-chaining@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-optional-chaining@npm:7.25.8" dependencies: "@babel/helper-plugin-utils": "npm:^7.25.7" "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/9c0a742eb3111f250b95065cd7d27b31c724d3f3c29b1de3c6655632172f69d5aa658d3ebff76d3b530b6792b6f4cf4845f4132ac8a1fea6e57ff19e3ec6f531 + checksum: 10/ffb5d81e6dbb28907d5346c8e12a1ed1ea0e30170fbe609d48d0466cdbc9d11b5774c8781682693f7cf7bd39da6111980e54813af96c6b3086dc769369c67d28 languageName: node linkType: hard @@ -1155,17 +1122,16 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-private-property-in-object@npm:^7.25.7": - version: 7.25.7 - resolution: "@babel/plugin-transform-private-property-in-object@npm:7.25.7" +"@babel/plugin-transform-private-property-in-object@npm:^7.25.8": + version: 7.25.8 + resolution: "@babel/plugin-transform-private-property-in-object@npm:7.25.8" dependencies: "@babel/helper-annotate-as-pure": "npm:^7.25.7" "@babel/helper-create-class-features-plugin": "npm:^7.25.7" "@babel/helper-plugin-utils": "npm:^7.25.7" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/c23786b9c123cceb0f69ab074377236825d76db090413db0b92d1a412b0e488e7ebde3e6155883ddbce6616114c54693485274a5d2b8a9a280b40781f80bd343 + checksum: 10/c612023879930c951e3a993104bbc3b78169aef6c38233758ee3358a7ab76954b41880bca67635df218dc6893aabad138f3783d508dc715419e62c8d1fad9088 languageName: node linkType: hard @@ -1379,11 +1345,11 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:7.25.7, @babel/preset-env@npm:^7.24.4": - version: 7.25.7 - resolution: "@babel/preset-env@npm:7.25.7" +"@babel/preset-env@npm:7.25.8, @babel/preset-env@npm:^7.24.4": + version: 7.25.8 + resolution: "@babel/preset-env@npm:7.25.8" dependencies: - "@babel/compat-data": "npm:^7.25.7" + "@babel/compat-data": "npm:^7.25.8" "@babel/helper-compilation-targets": "npm:^7.25.7" "@babel/helper-plugin-utils": "npm:^7.25.7" "@babel/helper-validator-option": "npm:^7.25.7" @@ -1393,45 +1359,30 @@ __metadata: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.25.7" "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.7" "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2" - "@babel/plugin-syntax-async-generators": "npm:^7.8.4" - "@babel/plugin-syntax-class-properties": "npm:^7.12.13" - "@babel/plugin-syntax-class-static-block": "npm:^7.14.5" - "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3" - "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3" "@babel/plugin-syntax-import-assertions": "npm:^7.25.7" "@babel/plugin-syntax-import-attributes": "npm:^7.25.7" - "@babel/plugin-syntax-import-meta": "npm:^7.10.4" - "@babel/plugin-syntax-json-strings": "npm:^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4" - "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3" - "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4" - "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3" - "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3" - "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3" - "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5" - "@babel/plugin-syntax-top-level-await": "npm:^7.14.5" "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6" "@babel/plugin-transform-arrow-functions": "npm:^7.25.7" - "@babel/plugin-transform-async-generator-functions": "npm:^7.25.7" + "@babel/plugin-transform-async-generator-functions": "npm:^7.25.8" "@babel/plugin-transform-async-to-generator": "npm:^7.25.7" "@babel/plugin-transform-block-scoped-functions": "npm:^7.25.7" "@babel/plugin-transform-block-scoping": "npm:^7.25.7" "@babel/plugin-transform-class-properties": "npm:^7.25.7" - "@babel/plugin-transform-class-static-block": "npm:^7.25.7" + "@babel/plugin-transform-class-static-block": "npm:^7.25.8" "@babel/plugin-transform-classes": "npm:^7.25.7" "@babel/plugin-transform-computed-properties": "npm:^7.25.7" "@babel/plugin-transform-destructuring": "npm:^7.25.7" "@babel/plugin-transform-dotall-regex": "npm:^7.25.7" "@babel/plugin-transform-duplicate-keys": "npm:^7.25.7" "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.7" - "@babel/plugin-transform-dynamic-import": "npm:^7.25.7" + "@babel/plugin-transform-dynamic-import": "npm:^7.25.8" "@babel/plugin-transform-exponentiation-operator": "npm:^7.25.7" - "@babel/plugin-transform-export-namespace-from": "npm:^7.25.7" + "@babel/plugin-transform-export-namespace-from": "npm:^7.25.8" "@babel/plugin-transform-for-of": "npm:^7.25.7" "@babel/plugin-transform-function-name": "npm:^7.25.7" - "@babel/plugin-transform-json-strings": "npm:^7.25.7" + "@babel/plugin-transform-json-strings": "npm:^7.25.8" "@babel/plugin-transform-literals": "npm:^7.25.7" - "@babel/plugin-transform-logical-assignment-operators": "npm:^7.25.7" + "@babel/plugin-transform-logical-assignment-operators": "npm:^7.25.8" "@babel/plugin-transform-member-expression-literals": "npm:^7.25.7" "@babel/plugin-transform-modules-amd": "npm:^7.25.7" "@babel/plugin-transform-modules-commonjs": "npm:^7.25.7" @@ -1439,15 +1390,15 @@ __metadata: "@babel/plugin-transform-modules-umd": "npm:^7.25.7" "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.25.7" "@babel/plugin-transform-new-target": "npm:^7.25.7" - "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.25.7" - "@babel/plugin-transform-numeric-separator": "npm:^7.25.7" - "@babel/plugin-transform-object-rest-spread": "npm:^7.25.7" + "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.25.8" + "@babel/plugin-transform-numeric-separator": "npm:^7.25.8" + "@babel/plugin-transform-object-rest-spread": "npm:^7.25.8" "@babel/plugin-transform-object-super": "npm:^7.25.7" - "@babel/plugin-transform-optional-catch-binding": "npm:^7.25.7" - "@babel/plugin-transform-optional-chaining": "npm:^7.25.7" + "@babel/plugin-transform-optional-catch-binding": "npm:^7.25.8" + "@babel/plugin-transform-optional-chaining": "npm:^7.25.8" "@babel/plugin-transform-parameters": "npm:^7.25.7" "@babel/plugin-transform-private-methods": "npm:^7.25.7" - "@babel/plugin-transform-private-property-in-object": "npm:^7.25.7" + "@babel/plugin-transform-private-property-in-object": "npm:^7.25.8" "@babel/plugin-transform-property-literals": "npm:^7.25.7" "@babel/plugin-transform-regenerator": "npm:^7.25.7" "@babel/plugin-transform-reserved-words": "npm:^7.25.7" @@ -1468,7 +1419,7 @@ __metadata: semver: "npm:^6.3.1" peerDependencies: "@babel/core": ^7.0.0-0 - checksum: 10/2d09aa2cb765a9a1c8a7566e22948e69d69de6b9b763322790bd72ec0bd5bcadf7b02abbc6ad1bab917e1f6d60b1a7f4606778d88cc7d7e1388cc52d292db97d + checksum: 10/501d78f56df8bf6f98a42da5db475db183048c4280b3292cf988b6baf01843915161f3b341ed525e2fcafcc47726798532b0e1dc7eb80aa29cc88c9d6f94ee6e languageName: node linkType: hard @@ -1589,14 +1540,14 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.7, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": - version: 7.25.7 - resolution: "@babel/types@npm:7.25.7" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.9, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.3, @babel/types@npm:^7.22.5, @babel/types@npm:^7.24.0, @babel/types@npm:^7.24.7, @babel/types@npm:^7.25.7, @babel/types@npm:^7.25.8, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": + version: 7.25.8 + resolution: "@babel/types@npm:7.25.8" dependencies: "@babel/helper-string-parser": "npm:^7.25.7" "@babel/helper-validator-identifier": "npm:^7.25.7" to-fast-properties: "npm:^2.0.0" - checksum: 10/4504e16a95b6a67d50cfaa389bcbc0621019084cff73784ad4797f82d1bb76c870cb0abb6d9881d5776eb06b4607419a2b1205a08c3e87b152d74bd0884b822a + checksum: 10/973108dbb189916bb87360f2beff43ae97f1b08f1c071bc6499d363cce48b3c71674bf3b59dfd617f8c5062d1c76dc2a64232bc07b6ccef831fd0c06162d44d9 languageName: node linkType: hard @@ -3813,8 +3764,8 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana/flamegraph@workspace:packages/grafana-flamegraph" dependencies: - "@babel/core": "npm:7.25.7" - "@babel/preset-env": "npm:7.25.7" + "@babel/core": "npm:7.25.8" + "@babel/preset-env": "npm:7.25.8" "@babel/preset-react": "npm:7.25.7" "@emotion/css": "npm:11.13.4" "@grafana/data": "npm:11.3.0-pre" @@ -4122,7 +4073,7 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana/saga-icons@workspace:packages/grafana-icons" dependencies: - "@babel/core": "npm:7.25.7" + "@babel/core": "npm:7.25.8" "@grafana/tsconfig": "npm:^2.0.0" "@rollup/plugin-node-resolve": "npm:^15.3.0" "@rollup/plugin-typescript": "npm:^12.1.0" @@ -4276,7 +4227,7 @@ __metadata: version: 0.0.0-use.local resolution: "@grafana/ui@workspace:packages/grafana-ui" dependencies: - "@babel/core": "npm:7.25.7" + "@babel/core": "npm:7.25.8" "@emotion/css": "npm:11.13.4" "@emotion/react": "npm:11.13.3" "@emotion/serialize": "npm:1.3.2" @@ -18942,8 +18893,8 @@ __metadata: version: 0.0.0-use.local resolution: "grafana@workspace:." dependencies: - "@babel/core": "npm:7.25.7" - "@babel/preset-env": "npm:7.25.7" + "@babel/core": "npm:7.25.8" + "@babel/preset-env": "npm:7.25.8" "@babel/runtime": "npm:7.25.7" "@betterer/betterer": "npm:5.4.0" "@betterer/cli": "npm:5.4.0" From 516e0cf7e22fab3a522366aef97df1705928d0bf Mon Sep 17 00:00:00 2001 From: Victor Marin <36818606+mdvictor@users.noreply.github.com> Date: Thu, 10 Oct 2024 19:11:58 +0300 Subject: [PATCH 056/110] DashboardScene: Update timerange in datalinks on change (#94419) * fix types * mods * refactor * refactor --- .../scene/DashboardControls.test.tsx | 77 +++++++++++++++---- .../scene/DashboardControls.tsx | 3 +- 2 files changed, 66 insertions(+), 14 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx index aefb2953e2b..34c5f3fe057 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.test.tsx @@ -1,22 +1,40 @@ -import { render } from '@testing-library/react'; +import { act, render } from '@testing-library/react'; +import { toUtc } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; import { SceneDataLayerControls, SceneVariableSet, TextBoxVariable, VariableValueSelectors } from '@grafana/scenes'; import { DashboardControls, DashboardControlsState } from './DashboardControls'; import { DashboardScene } from './DashboardScene'; +const mockGetAnchorInfo = jest.fn((link) => ({ + href: `/dashboard/${link.title}`, + title: link.title, + tooltip: link.tooltip || null, +})); + +// Mock the getLinkSrv function +jest.mock('app/features/panel/panellinks/link_srv', () => ({ + getLinkSrv: jest.fn(() => ({ + getAnchorInfo: mockGetAnchorInfo, + })), +})); + describe('DashboardControls', () => { describe('Given a standard scene', () => { it('should initialize with default values', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); expect(scene.state.variableControls).toEqual([]); expect(scene.state.timePicker).toBeDefined(); expect(scene.state.refreshPicker).toBeDefined(); }); it('should return if time controls are hidden', () => { - const scene = buildTestScene({ hideTimeControls: false, hideVariableControls: false, hideLinksControls: false }); + const { controls: scene } = buildTestScene({ + hideTimeControls: false, + hideVariableControls: false, + hideLinksControls: false, + }); expect(scene.hasControls()).toBeTruthy(); scene.setState({ hideTimeControls: true }); expect(scene.hasControls()).toBeTruthy(); @@ -27,14 +45,14 @@ describe('DashboardControls', () => { describe('Component', () => { it('should render', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); expect(() => { render(); }).not.toThrow(); }); it('should render visible controls', async () => { - const scene = buildTestScene({ + const { controls: scene } = buildTestScene({ variableControls: [new VariableValueSelectors({}), new SceneDataLayerControls()], }); const renderer = render(); @@ -47,7 +65,7 @@ describe('DashboardControls', () => { }); it('should render with hidden controls', async () => { - const scene = buildTestScene({ + const { controls: scene } = buildTestScene({ hideTimeControls: true, hideVariableControls: true, hideLinksControls: true, @@ -61,13 +79,13 @@ describe('DashboardControls', () => { describe('UrlSync', () => { it('should return keys', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); // @ts-expect-error expect(scene._urlSync.getKeys()).toEqual(['_dash.hideTimePicker', '_dash.hideVariables', '_dash.hideLinks']); }); it('should not return url state for hide flags', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); expect(scene.getUrlState()).toEqual({}); scene.setState({ hideTimeControls: true, @@ -78,7 +96,7 @@ describe('DashboardControls', () => { }); it('should update from url', () => { - const scene = buildTestScene(); + const { controls: scene } = buildTestScene(); scene.updateFromUrl({ '_dash.hideTimePicker': 'true', '_dash.hideVariables': 'true', @@ -98,7 +116,11 @@ describe('DashboardControls', () => { }); it('should not override state if no new state comes from url', () => { - const scene = buildTestScene({ hideTimeControls: true, hideVariableControls: true, hideLinksControls: true }); + const { controls: scene } = buildTestScene({ + hideTimeControls: true, + hideVariableControls: true, + hideLinksControls: true, + }); scene.updateFromUrl({}); expect(scene.state.hideTimeControls).toBeTruthy(); expect(scene.state.hideVariableControls).toBeTruthy(); @@ -106,7 +128,11 @@ describe('DashboardControls', () => { }); it('should not call setState if no changes', () => { - const scene = buildTestScene({ hideTimeControls: true, hideVariableControls: true, hideLinksControls: true }); + const { controls: scene } = buildTestScene({ + hideTimeControls: true, + hideVariableControls: true, + hideLinksControls: true, + }); const setState = jest.spyOn(scene, 'setState'); scene.updateFromUrl({ @@ -118,9 +144,34 @@ describe('DashboardControls', () => { expect(setState).toHaveBeenCalledTimes(0); }); }); + + it('Should update link hrefs when time range changes', () => { + const { controls, dashboard } = buildTestScene(); + render(); + + //clear initial calls to getAnchorInfo + mockGetAnchorInfo.mockClear(); + + act(() => { + // Update time range + dashboard.state.$timeRange?.setState({ + value: { + from: toUtc('2021-01-01'), + to: toUtc('2021-01-02'), + raw: { from: toUtc('2020-01-01'), to: toUtc('2020-01-02') }, + }, + }); + }); + + //expect getAnchorInfo to be called after time range change + expect(mockGetAnchorInfo).toHaveBeenCalledTimes(1); + }); }); -function buildTestScene(state?: Partial): DashboardControls { +function buildTestScene(state?: Partial): { + dashboard: DashboardScene; + controls: DashboardControls; +} { const variable = new TextBoxVariable({ name: 'A', label: 'A', @@ -155,5 +206,5 @@ function buildTestScene(state?: Partial): DashboardContr dashboard.activate(); variable.activate(); - return dashboard.state.controls as DashboardControls; + return { dashboard, controls: dashboard.state.controls as DashboardControls }; } diff --git a/public/app/features/dashboard-scene/scene/DashboardControls.tsx b/public/app/features/dashboard-scene/scene/DashboardControls.tsx index 3805792f93b..41e125957a5 100644 --- a/public/app/features/dashboard-scene/scene/DashboardControls.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardControls.tsx @@ -122,9 +122,10 @@ function DashboardControlsRenderer({ model }: SceneComponentProps Date: Thu, 10 Oct 2024 19:12:26 +0300 Subject: [PATCH 057/110] Scopes: Open dashboard list when a scope is selected (#94464) * Open dashboard list when a scope is selected * refactor * test * remove localstorage key * add checks on open/close methods * remove redundant statement * improve dashboards listing --- .../core/components/AppChrome/AppChrome.tsx | 4 +- .../scopes/internal/ScopesDashboardsScene.tsx | 96 +++++++++++++------ public/app/features/scopes/internal/const.ts | 1 - .../scopes/tests/dashboardsList.test.ts | 17 +++- .../features/scopes/tests/utils/assertions.ts | 2 + .../features/scopes/tests/utils/render.tsx | 2 - .../features/scopes/tests/utils/selectors.ts | 1 + public/app/features/scopes/utils.ts | 4 +- 8 files changed, 88 insertions(+), 39 deletions(-) delete mode 100644 public/app/features/scopes/internal/const.ts diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index 133deae94d3..9c323a7ba42 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -34,7 +34,9 @@ export function AppChrome({ children }: Props) { const dockedMenuLocalStorageState = store.getBool(DOCKED_LOCAL_STORAGE_KEY, true); const menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen; const scopesDashboardsState = useScopesDashboardsState(); - const isScopesDashboardsOpen = Boolean(scopesDashboardsState?.isEnabled && scopesDashboardsState?.isPanelOpened); + const isScopesDashboardsOpen = Boolean( + scopesDashboardsState?.isEnabled && scopesDashboardsState?.isPanelOpened && !scopesDashboardsState?.isReadOnly + ); const isSingleTopNav = config.featureToggles.singleTopNav; useMediaQueryChange({ breakpoint: dockedMenuBreakpoint, diff --git a/public/app/features/scopes/internal/ScopesDashboardsScene.tsx b/public/app/features/scopes/internal/ScopesDashboardsScene.tsx index e838286df3f..b5450af8630 100644 --- a/public/app/features/scopes/internal/ScopesDashboardsScene.tsx +++ b/public/app/features/scopes/internal/ScopesDashboardsScene.tsx @@ -1,5 +1,6 @@ import { css, cx } from '@emotion/css'; import { isEqual } from 'lodash'; +import { finalize, from, Subscription } from 'rxjs'; import { GrafanaTheme2, ScopeDashboardBinding } from '@grafana/data'; import { SceneComponentProps, SceneObjectBase, SceneObjectRef, SceneObjectState } from '@grafana/scenes'; @@ -10,7 +11,6 @@ import { ScopesDashboardsTree } from './ScopesDashboardsTree'; import { ScopesDashboardsTreeSearch } from './ScopesDashboardsTreeSearch'; import { ScopesSelectorScene } from './ScopesSelectorScene'; import { fetchDashboards } from './api'; -import { DASHBOARDS_OPENED_KEY } from './const'; import { SuggestedDashboardsFoldersMap } from './types'; import { filterFolders, getScopeNamesFromSelectedScopes, groupDashboards } from './utils'; @@ -26,6 +26,7 @@ export interface ScopesDashboardsSceneState extends SceneObjectState { isLoading: boolean; isPanelOpened: boolean; isEnabled: boolean; + isReadOnly: boolean; scopesSelected: boolean; searchQuery: string; } @@ -36,8 +37,9 @@ export const getInitialDashboardsState: () => Omit Omit { static Component = ScopesDashboardsSceneRenderer; + private dashboardsFetchingSub: Subscription | undefined; + constructor() { super({ selector: null, @@ -52,35 +56,44 @@ export class ScopesDashboardsScene extends SceneObjectBase { - if (this.state.isEnabled && this.state.isPanelOpened) { - this.fetchDashboards(); - } - const resolvedSelector = this.state.selector?.resolve(); + if (resolvedSelector?.state.scopes.length ?? 0 > 0) { + this.fetchDashboards(); + this.openPanel(); + } + if (resolvedSelector) { this._subs.add( resolvedSelector.subscribeToState((newState, prevState) => { - if ( - this.state.isEnabled && - this.state.isPanelOpened && - !newState.isLoadingScopes && - (prevState.isLoadingScopes || newState.scopes !== prevState.scopes) - ) { + const newScopeNames = getScopeNamesFromSelectedScopes(newState.scopes ?? []); + const oldScopeNames = getScopeNamesFromSelectedScopes(prevState.scopes ?? []); + + if (!isEqual(newScopeNames, oldScopeNames)) { this.fetchDashboards(); + + if (newState.scopes.length > 0) { + this.openPanel(); + } else { + this.closePanel(); + } } }) ); } + + return () => { + this.dashboardsFetchingSub?.unsubscribe(); + }; }); } public async fetchDashboards() { const scopeNames = getScopeNamesFromSelectedScopes(this.state.selector?.resolve().state.scopes ?? []); - if (isEqual(scopeNames, this.state.forScopeNames)) { - return; - } + this.dashboardsFetchingSub?.unsubscribe(); + + this.setState({ forScopeNames: scopeNames }); if (scopeNames.length === 0) { return this.setState({ @@ -95,18 +108,26 @@ export class ScopesDashboardsScene extends SceneObjectBase { + this.setState({ isLoading: false }); + }) + ) + .subscribe((dashboards) => { + const folders = groupDashboards(dashboards); + const filteredFolders = filterFolders(folders, this.state.searchQuery); - this.setState({ - dashboards, - folders, - filteredFolders, - forScopeNames: scopeNames, - isLoading: false, - scopesSelected: scopeNames.length > 0, - }); + this.setState({ + dashboards, + folders, + filteredFolders, + isLoading: false, + scopesSelected: scopeNames.length > 0, + }); + + this.dashboardsFetchingSub?.unsubscribe(); + }); } public changeSearchQuery(searchQuery: string) { @@ -148,14 +169,19 @@ export class ScopesDashboardsScene extends SceneObjectBase) { - const { dashboards, filteredFolders, isLoading, isPanelOpened, isEnabled, searchQuery, scopesSelected } = + const { dashboards, filteredFolders, isLoading, isPanelOpened, isEnabled, isReadOnly, searchQuery, scopesSelected } = model.useState(); const styles = useStyles2(getStyles); - if (!isEnabled || !isPanelOpened) { + if (!isEnabled || !isPanelOpened || isReadOnly) { return null; } diff --git a/public/app/features/scopes/internal/const.ts b/public/app/features/scopes/internal/const.ts deleted file mode 100644 index 643a106960d..00000000000 --- a/public/app/features/scopes/internal/const.ts +++ /dev/null @@ -1 +0,0 @@ -export const DASHBOARDS_OPENED_KEY = 'grafana.scopes.dashboards.opened'; diff --git a/public/app/features/scopes/tests/dashboardsList.test.ts b/public/app/features/scopes/tests/dashboardsList.test.ts index e5215a89f86..3b5f2fe1885 100644 --- a/public/app/features/scopes/tests/dashboardsList.test.ts +++ b/public/app/features/scopes/tests/dashboardsList.test.ts @@ -12,7 +12,9 @@ import { expectDashboardInDocument, expectDashboardLength, expectDashboardNotInDocument, + expectDashboardsClosed, expectDashboardSearchValue, + expectDashboardsOpen, expectDashboardsSearch, expectNoDashboardsForFilter, expectNoDashboardsForScope, @@ -45,9 +47,20 @@ describe('Dashboards list', () => { await resetScenes(); }); - it('Does not fetch dashboards list when the list is not expanded', async () => { + it('Opens container and fetches dashboards list when a scope is selected', async () => { + expectDashboardsClosed(); await updateScopes(['mimir']); - expect(fetchDashboardsSpy).not.toHaveBeenCalled(); + expectDashboardsOpen(); + expect(fetchDashboardsSpy).toHaveBeenCalled(); + }); + + it('Closes container when no scopes are selected', async () => { + await updateScopes(['mimir']); + expectDashboardsOpen(); + await updateScopes(['mimir', 'loki']); + expectDashboardsOpen(); + await updateScopes([]); + expectDashboardsClosed(); }); it('Fetches dashboards list when the list is expanded', async () => { diff --git a/public/app/features/scopes/tests/utils/assertions.ts b/public/app/features/scopes/tests/utils/assertions.ts index 785f72e005f..cfd54c4ad78 100644 --- a/public/app/features/scopes/tests/utils/assertions.ts +++ b/public/app/features/scopes/tests/utils/assertions.ts @@ -1,6 +1,7 @@ import { getMock, locationReloadSpy } from './mocks'; import { getDashboard, + getDashboardsContainer, getDashboardsExpand, getDashboardsSearch, getNotFoundForFilter, @@ -62,6 +63,7 @@ export const expectResultCloudOpsNotSelected = () => expectRadioNotChecked(getRe export const expectDashboardsDisabled = () => expectDisabled(getDashboardsExpand); export const expectDashboardsClosed = () => expectNotInDocument(queryDashboardsContainer); +export const expectDashboardsOpen = () => expectInDocument(getDashboardsContainer); export const expectNoDashboardsSearch = () => expectNotInDocument(queryDashboardsSearch); export const expectDashboardsSearch = () => expectInDocument(getDashboardsSearch); export const expectNoDashboardsNoScopes = () => expectInDocument(getNotFoundNoScopes); diff --git a/public/app/features/scopes/tests/utils/render.tsx b/public/app/features/scopes/tests/utils/render.tsx index 538ed293770..71d60ff6492 100644 --- a/public/app/features/scopes/tests/utils/render.tsx +++ b/public/app/features/scopes/tests/utils/render.tsx @@ -12,7 +12,6 @@ import { DashboardDataDTO, DashboardDTO, DashboardMeta } from 'app/types'; import { initializeScopes, scopesDashboardsScene, scopesSelectorScene } from '../../instance'; import { getInitialDashboardsState } from '../../internal/ScopesDashboardsScene'; import { initialSelectorState } from '../../internal/ScopesSelectorScene'; -import { DASHBOARDS_OPENED_KEY } from '../../internal/const'; import { clearMocks } from './actions'; @@ -160,7 +159,6 @@ export async function resetScenes() { await jest.runOnlyPendingTimersAsync(); jest.useRealTimers(); scopesSelectorScene?.setState(initialSelectorState); - localStorage.removeItem(DASHBOARDS_OPENED_KEY); scopesDashboardsScene?.setState(getInitialDashboardsState()); cleanup(); } diff --git a/public/app/features/scopes/tests/utils/selectors.ts b/public/app/features/scopes/tests/utils/selectors.ts index 44f24b5fbac..b3174c0c6e3 100644 --- a/public/app/features/scopes/tests/utils/selectors.ts +++ b/public/app/features/scopes/tests/utils/selectors.ts @@ -38,6 +38,7 @@ export const getSelectorApply = () => screen.getByTestId(selectors.selector.appl export const getSelectorCancel = () => screen.getByTestId(selectors.selector.cancel); export const getDashboardsExpand = () => screen.getByTestId(selectors.dashboards.expand); +export const getDashboardsContainer = () => screen.getByTestId(selectors.dashboards.container); export const queryDashboardsContainer = () => screen.queryByTestId(selectors.dashboards.container); export const queryDashboardsSearch = () => screen.queryByTestId(selectors.dashboards.search); export const getDashboardsSearch = () => screen.getByTestId(selectors.dashboards.search); diff --git a/public/app/features/scopes/utils.ts b/public/app/features/scopes/utils.ts index 85d01626fa8..29ddd616382 100644 --- a/public/app/features/scopes/utils.ts +++ b/public/app/features/scopes/utils.ts @@ -25,12 +25,12 @@ export function disableScopes() { export function exitScopesReadOnly() { scopesSelectorScene?.exitReadOnly(); - scopesDashboardsScene?.enable(); + scopesDashboardsScene?.exitReadOnly(); } export function enterScopesReadOnly() { scopesSelectorScene?.enterReadOnly(); - scopesDashboardsScene?.disable(); + scopesDashboardsScene?.enterReadOnly(); } export function getClosestScopesFacade(scene: SceneObject): ScopesFacade | null { From ce857c2680d9e77fd49745e0b63a726e4a7c17ff Mon Sep 17 00:00:00 2001 From: Brendan O'Handley Date: Thu, 10 Oct 2024 11:49:24 -0500 Subject: [PATCH 058/110] Explore metrics: set options directly because of scenes error when options not set (#94284) * scenes error when options not set * add all of the options that are updated in onOptionsChange * add options to all the other places we are building timeseries panels * Update public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts Co-authored-by: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> * remove activation handlers and unused code * Update public/app/features/trails/AutomaticMetricQueries/graph-builders/simple.ts Co-authored-by: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> * Update public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts Co-authored-by: Sven Grossmann --------- Co-authored-by: Nick Richmond <5732000+NWRichmond@users.noreply.github.com> Co-authored-by: Sven Grossmann --- .../graph-builders/percentiles.ts | 8 ++++++-- .../graph-builders/simple.ts | 3 +++ .../trails/Breakdown/LabelBreakdownScene.tsx | 19 ++++++++----------- .../features/trails/Breakdown/panelConfigs.ts | 8 -------- 4 files changed, 17 insertions(+), 21 deletions(-) delete mode 100644 public/app/features/trails/Breakdown/panelConfigs.ts diff --git a/public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts b/public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts index 9f40180d68d..36578e56ffb 100644 --- a/public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts +++ b/public/app/features/trails/AutomaticMetricQueries/graph-builders/percentiles.ts @@ -1,10 +1,14 @@ import { PanelBuilders } from '@grafana/scenes'; +import { SortOrder } from '@grafana/schema'; +import { TooltipDisplayMode } from '@grafana/ui'; import { CommonVizParams } from './types'; export function percentilesGraphBuilder({ title, unit }: CommonVizParams) { - return PanelBuilders.timeseries() // + return PanelBuilders.timeseries() .setTitle(title) .setUnit(unit) - .setCustomFieldConfig('fillOpacity', 9); + .setCustomFieldConfig('fillOpacity', 9) + .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) + .setOption('legend', { showLegend: false }); } diff --git a/public/app/features/trails/AutomaticMetricQueries/graph-builders/simple.ts b/public/app/features/trails/AutomaticMetricQueries/graph-builders/simple.ts index 5eab7d70bfd..d04766f23e4 100644 --- a/public/app/features/trails/AutomaticMetricQueries/graph-builders/simple.ts +++ b/public/app/features/trails/AutomaticMetricQueries/graph-builders/simple.ts @@ -1,4 +1,6 @@ import { PanelBuilders } from '@grafana/scenes'; +import { SortOrder } from '@grafana/schema'; +import { TooltipDisplayMode } from '@grafana/ui'; import { CommonVizParams } from './types'; @@ -7,5 +9,6 @@ export function simpleGraphBuilder({ title, unit }: CommonVizParams) { .setTitle(title) .setUnit(unit) .setOption('legend', { showLegend: false }) + .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) .setCustomFieldConfig('fillOpacity', 9); } diff --git a/public/app/features/trails/Breakdown/LabelBreakdownScene.tsx b/public/app/features/trails/Breakdown/LabelBreakdownScene.tsx index 26b04788dda..b908c25f801 100644 --- a/public/app/features/trails/Breakdown/LabelBreakdownScene.tsx +++ b/public/app/features/trails/Breakdown/LabelBreakdownScene.tsx @@ -21,7 +21,7 @@ import { VariableDependencyConfig, VizPanel, } from '@grafana/scenes'; -import { DataQuery } from '@grafana/schema'; +import { DataQuery, SortOrder, TooltipDisplayMode } from '@grafana/schema'; import { Button, Field, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; import { Trans } from 'app/core/internationalization'; @@ -39,7 +39,6 @@ import { AddToFiltersGraphAction } from './AddToFiltersGraphAction'; import { BreakdownSearchReset, BreakdownSearchScene } from './BreakdownSearchScene'; import { ByFrameRepeater } from './ByFrameRepeater'; import { LayoutSwitcher } from './LayoutSwitcher'; -import { breakdownPanelOptions } from './panelConfigs'; import { BreakdownLayoutChangeCallback, BreakdownLayoutType } from './types'; import { getLabelOptions } from './utils'; import { BreakdownAxisChangeEvent, yAxisSyncBehavior } from './yAxisSyncBehavior'; @@ -300,6 +299,8 @@ export function buildAllLayout( const unit = queryDef.unit; const vizPanel = PanelBuilders.timeseries() + .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) + .setOption('legend', { showLegend: false }) .setTitle(option.label!) .setData( new SceneQueryRunner({ @@ -319,10 +320,6 @@ export function buildAllLayout( .setBehaviors([fixLegendForUnspecifiedLabelValueBehavior]) .build(); - vizPanel.addActivationHandler(() => { - vizPanel.onOptionsChange(breakdownPanelOptions); - }); - children.push( new SceneCSSGridItem({ $behaviors: [yAxisSyncBehavior], @@ -382,10 +379,6 @@ function buildNormalLayout( isHidden, }); - vizPanel.addActivationHandler(() => { - vizPanel.onOptionsChange(breakdownPanelOptions); - }); - return item; } @@ -409,7 +402,11 @@ function buildNormalLayout( children: [ new SceneFlexItem({ minHeight: 300, - body: PanelBuilders.timeseries().setTitle('$metric').build(), + body: PanelBuilders.timeseries() + .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) + .setOption('legend', { showLegend: false }) + .setTitle('$metric') + .build(), }), ], }), diff --git a/public/app/features/trails/Breakdown/panelConfigs.ts b/public/app/features/trails/Breakdown/panelConfigs.ts deleted file mode 100644 index 23d796fdf8e..00000000000 --- a/public/app/features/trails/Breakdown/panelConfigs.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { PanelOptionsBuilders } from '@grafana/scenes'; -import { SortOrder } from '@grafana/schema/dist/esm/index'; -import { TooltipDisplayMode } from '@grafana/ui'; - -export const breakdownPanelOptions = PanelOptionsBuilders.timeseries() - .setOption('tooltip', { mode: TooltipDisplayMode.Multi, sort: SortOrder.Descending }) - .setOption('legend', { showLegend: false }) - .build(); From d96baaa878045268051e841bc02566074ef80979 Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Thu, 10 Oct 2024 11:34:57 -0600 Subject: [PATCH 059/110] Search PoC: Add logging (#94567) --- pkg/storage/unified/resource/index.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/pkg/storage/unified/resource/index.go b/pkg/storage/unified/resource/index.go index 08988e4c128..bc806800871 100644 --- a/pkg/storage/unified/resource/index.go +++ b/pkg/storage/unified/resource/index.go @@ -4,7 +4,7 @@ import ( "context" "encoding/json" "fmt" - "log" + golog "log" "os" "strings" @@ -12,6 +12,7 @@ import ( "github.com/blevesearch/bleve/v2/analysis/lang/en" "github.com/blevesearch/bleve/v2/mapping" "github.com/google/uuid" + "github.com/grafana/grafana/pkg/infra/log" "golang.org/x/exp/slices" ) @@ -25,6 +26,7 @@ type Index struct { shards map[string]Shard opts Opts s *server + log log.Logger } func NewIndex(s *server, opts Opts) *Index { @@ -32,6 +34,7 @@ func NewIndex(s *server, opts Opts) *Index { s: s, opts: opts, shards: make(map[string]Shard), + log: log.New("unifiedstorage.search.index"), } return idx } @@ -44,6 +47,7 @@ func (i *Index) Init(ctx context.Context) error { if err != nil { return err } + i.log.Info("initial indexing resources", "count", len(list.Items)) for _, obj := range list.Items { res, err := getResource(obj.Value) @@ -56,6 +60,8 @@ func (i *Index) Init(ctx context.Context) error { return err } + i.log.Info("indexing resource for tenant", "res", res, "tenant", tenant(res)) + var jsonDoc interface{} err = json.Unmarshal(obj.Value, &jsonDoc) if err != nil { @@ -85,6 +91,7 @@ func (i *Index) Index(ctx context.Context, data *Data) error { return err } tenant := tenant(res) + i.log.Info("indexing resource for tenant", "res", res, "tenant", tenant) shard, err := i.getShard(tenant) if err != nil { return err @@ -121,6 +128,11 @@ func (i *Index) Search(ctx context.Context, tenant string, query string, limit i if err != nil { return nil, err } + docCount, err := shard.index.DocCount() + if err != nil { + return nil, err + } + i.log.Info("got index for tenant", "tenant", tenant, "docCount", docCount) // use 10 as a default limit for now if limit <= 0 { @@ -133,12 +145,15 @@ func (i *Index) Search(ctx context.Context, tenant string, query string, limit i req.Fields = []string{"*"} // return all indexed fields in search results + i.log.Info("searching index", "query", query, "tenant", tenant) res, err := shard.index.Search(req) if err != nil { return nil, err } hits := res.Hits + i.log.Info("got search results", "hits", hits) + results := make([]SearchSummary, len(hits)) for resKey, hit := range hits { searchSummary := SearchSummary{} @@ -203,7 +218,7 @@ func createFileIndex() (bleve.Index, string, error) { indexPath := fmt.Sprintf("%s%s.bleve", os.TempDir(), uuid.New().String()) index, err := bleve.New(indexPath, createIndexMappings()) if err != nil { - log.Fatalf("Failed to create index: %v", err) + golog.Fatalf("Failed to create index: %v", err) } return index, indexPath, err } From 79614eabdf580f2cd0fb9bbea9e3345e42fa4b4a Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Thu, 10 Oct 2024 19:11:51 +0100 Subject: [PATCH 060/110] Remove "Add to docs project" workflow (#93476) --- .github/commands.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/commands.json b/.github/commands.json index af047be4341..cbbdea3813b 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -59,14 +59,6 @@ "url": "https://github.com/orgs/grafana/projects/76" } }, - { - "type": "label", - "name": "type/docs", - "action": "addToProject", - "addToProject": { - "url": "https://github.com/orgs/grafana/projects/69" - } - }, { "type": "label", "name": "datasource/Azure", From 0418a7bc0a633dda401b66f8323296b39dc3b98f Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Thu, 10 Oct 2024 14:40:16 -0400 Subject: [PATCH 061/110] Chore: Turn off apiserver tracing when embedded in Grafana (#94574) turn off apiserver tracing when embedded in Grafana to make it reuse Grafana initiated trace context --- pkg/services/apiserver/options/extra.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/services/apiserver/options/extra.go b/pkg/services/apiserver/options/extra.go index aaf1052f3b0..715c319a62f 100644 --- a/pkg/services/apiserver/options/extra.go +++ b/pkg/services/apiserver/options/extra.go @@ -4,12 +4,15 @@ import ( "log/slog" "strconv" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/infra/log/slogadapter" "github.com/spf13/pflag" + genericfeatures "k8s.io/apiserver/pkg/features" genericapiserver "k8s.io/apiserver/pkg/server" + utilfeature "k8s.io/apiserver/pkg/util/feature" "k8s.io/component-base/logs" "k8s.io/klog/v2" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/log/slogadapter" ) type ExtraOptions struct { @@ -40,7 +43,11 @@ func (o *ExtraOptions) Validate() []error { func (o *ExtraOptions) ApplyTo(c *genericapiserver.RecommendedConfig) error { handler := slogadapter.New(log.New("grafana-apiserver")) logger := slog.New(handler) - + if err := utilfeature.DefaultMutableFeatureGate.SetFromMap(map[string]bool{ + string(genericfeatures.APIServerTracing): false, + }); err != nil { + return err + } klog.SetSlogLogger(logger) if _, err := logs.GlogSetter(strconv.Itoa(o.Verbosity)); err != nil { logger.Error("failed to set log level", "error", err) From 75d42d82a3486925dd108f00843168e33fceb08e Mon Sep 17 00:00:00 2001 From: Santiago Date: Thu, 10 Oct 2024 21:30:16 +0200 Subject: [PATCH 062/110] Alerting: Make Google Chat URL a secure field (#94499) --- go.mod | 2 +- go.sum | 4 ++-- .../ngalert/api/tooling/definitions/contact_points.go | 2 +- .../ngalert/notifier/channels_config/available_channels.go | 1 + .../notifier/channels_config/available_channels_test.go | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 0558eda7f7b..5ab6b92c69d 100644 --- a/go.mod +++ b/go.mod @@ -72,7 +72,7 @@ require ( github.com/googleapis/gax-go/v2 v2.13.0 // @grafana/grafana-backend-group github.com/gorilla/mux v1.8.1 // @grafana/grafana-backend-group github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad - github.com/grafana/alerting v0.0.0-20240930154843-22cee00b280e // @grafana/alerting-backend + github.com/grafana/alerting v0.0.0-20241010165806-807ddf183724 // @grafana/alerting-backend github.com/grafana/authlib v0.0.0-20240919120951-58259833c564 // @grafana/identity-access-team github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd // @grafana/identity-access-team github.com/grafana/codejen v0.0.3 // @grafana/dataviz-squad diff --git a/go.sum b/go.sum index 2b8c44b7bd6..f707fdc6534 100644 --- a/go.sum +++ b/go.sum @@ -2247,8 +2247,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/alerting v0.0.0-20240930154843-22cee00b280e h1:RttFYx5+RTNuMPlaftx8i9f91kwUi9LdxsoPLHnticU= -github.com/grafana/alerting v0.0.0-20240930154843-22cee00b280e/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= +github.com/grafana/alerting v0.0.0-20241010165806-807ddf183724 h1:u+ZM5TLkdeEoSWXgYWxc4XRfPHhXpR63MyHXJxbBLrc= +github.com/grafana/alerting v0.0.0-20241010165806-807ddf183724/go.mod h1:QsnoKX/iYZxA4Cv+H+wC7uxutBD8qi8ZW5UJvD2TYmU= github.com/grafana/authlib v0.0.0-20240919120951-58259833c564 h1:zYF/RBulpvMqPYR3gbzJZ8t/j/Eymn5FNidSYkueNCA= github.com/grafana/authlib v0.0.0-20240919120951-58259833c564/go.mod h1:PFzXbCrn0GIpN4KwT6NP1l5Z1CPLfmKHnYx8rZzQcyY= github.com/grafana/authlib/claims v0.0.0-20240827210201-19d5347dd8dd h1:sIlR7n38/MnZvX2qxDEszywXdI5soCwQ78aTDSARvus= diff --git a/pkg/services/ngalert/api/tooling/definitions/contact_points.go b/pkg/services/ngalert/api/tooling/definitions/contact_points.go index 0cd8f792e4e..4aec132e837 100644 --- a/pkg/services/ngalert/api/tooling/definitions/contact_points.go +++ b/pkg/services/ngalert/api/tooling/definitions/contact_points.go @@ -56,7 +56,7 @@ type EmailIntegration struct { type GooglechatIntegration struct { DisableResolveMessage *bool `json:"-" yaml:"-" hcl:"disable_resolve_message"` - URL string `json:"url" yaml:"url" hcl:"url"` + URL Secret `json:"url" yaml:"url" hcl:"url"` Title *string `json:"title,omitempty" yaml:"title,omitempty" hcl:"title"` Message *string `json:"message,omitempty" yaml:"message,omitempty" hcl:"message"` diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels.go b/pkg/services/ngalert/notifier/channels_config/available_channels.go index 4a6433ebb5e..ffe7b757edc 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels.go @@ -1141,6 +1141,7 @@ func GetAvailableNotifiers() []*NotifierPlugin { Placeholder: "Google Chat incoming webhook url", PropertyName: "url", Required: true, + Secure: true, }, { Label: "Title", diff --git a/pkg/services/ngalert/notifier/channels_config/available_channels_test.go b/pkg/services/ngalert/notifier/channels_config/available_channels_test.go index f3290b3956a..2171b2c17fe 100644 --- a/pkg/services/ngalert/notifier/channels_config/available_channels_test.go +++ b/pkg/services/ngalert/notifier/channels_config/available_channels_test.go @@ -26,7 +26,7 @@ func TestGetSecretKeysForContactPointType(t *testing.T) { {receiverType: "wecom", expectedSecretFields: []string{"url", "secret"}}, {receiverType: "prometheus-alertmanager", expectedSecretFields: []string{"basicAuthPassword"}}, {receiverType: "discord", expectedSecretFields: []string{"url"}}, - {receiverType: "googlechat", expectedSecretFields: []string{}}, + {receiverType: "googlechat", expectedSecretFields: []string{"url"}}, {receiverType: "line", expectedSecretFields: []string{"token"}}, {receiverType: "threema", expectedSecretFields: []string{"api_secret"}}, {receiverType: "opsgenie", expectedSecretFields: []string{"apiKey"}}, From 27c44f4709ad203010f004134f58149b45c38cfb Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Thu, 10 Oct 2024 16:26:30 -0400 Subject: [PATCH 063/110] Alerting: Update notification policy service to check provenance status (#94359) * update ResetPolicyTree to accept provenance status * update methods to check for provenance status use relaxed validation --- pkg/services/ngalert/api/api_provisioning.go | 11 +- .../ngalert/api/api_provisioning_test.go | 8 +- .../provisioning/notification_policies.go | 24 +++- .../notification_policies_test.go | 103 ++++++++++++++---- .../notification_policy_provisioner.go | 2 +- 5 files changed, 115 insertions(+), 33 deletions(-) diff --git a/pkg/services/ngalert/api/api_provisioning.go b/pkg/services/ngalert/api/api_provisioning.go index f22cf445ce5..ccdfde76c75 100644 --- a/pkg/services/ngalert/api/api_provisioning.go +++ b/pkg/services/ngalert/api/api_provisioning.go @@ -53,7 +53,7 @@ type TemplateService interface { type NotificationPolicyService interface { GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, string, error) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p alerting_models.Provenance, version string) error - ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) + ResetPolicyTree(ctx context.Context, orgID int64, provenance alerting_models.Provenance) (definitions.Route, error) } type MuteTimingService interface { @@ -84,7 +84,7 @@ func (srv *ProvisioningSrv) RouteGetPolicyTree(c *contextmodel.ReqContext) respo return ErrResp(http.StatusNotFound, err, "") } if err != nil { - return ErrResp(http.StatusInternalServerError, err, "") + return response.ErrOrFallback(http.StatusInternalServerError, "failed to get notification policy tree", err) } return response.JSON(http.StatusOK, policies) @@ -117,16 +117,17 @@ func (srv *ProvisioningSrv) RoutePutPolicyTree(c *contextmodel.ReqContext, tree return ErrResp(http.StatusBadRequest, err, "") } if err != nil { - return response.ErrOrFallback(http.StatusInternalServerError, "", err) + return response.ErrOrFallback(http.StatusInternalServerError, "failed to update notification policy tree", err) } return response.JSON(http.StatusAccepted, util.DynMap{"message": "policies updated"}) } func (srv *ProvisioningSrv) RouteResetPolicyTree(c *contextmodel.ReqContext) response.Response { - tree, err := srv.policies.ResetPolicyTree(c.Req.Context(), c.SignedInUser.GetOrgID()) + provenance := determineProvenance(c) + tree, err := srv.policies.ResetPolicyTree(c.Req.Context(), c.SignedInUser.GetOrgID(), alerting_models.Provenance(provenance)) if err != nil { - return ErrResp(http.StatusInternalServerError, err, "") + return response.ErrOrFallback(http.StatusInternalServerError, "failed to reset notification policy tree", err) } return response.JSON(http.StatusAccepted, tree) } diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index e6747915ae6..6b418e3b6e4 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -140,7 +140,6 @@ func TestProvisioningApi(t *testing.T) { require.Equal(t, 500, response.Status()) require.NotEmpty(t, response.Body()) - require.Contains(t, string(response.Body()), "something went wrong") }) t.Run("PUT returns 500", func(t *testing.T) { @@ -164,7 +163,6 @@ func TestProvisioningApi(t *testing.T) { require.Equal(t, 500, response.Status()) require.NotEmpty(t, response.Body()) - require.Contains(t, string(response.Body()), "something went wrong") }) }) }) @@ -2002,7 +2000,7 @@ func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, or return nil } -func (f *fakeNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) { +func (f *fakeNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) { f.tree = definitions.Route{} // TODO return f.tree, nil } @@ -2017,7 +2015,7 @@ func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Cont return fmt.Errorf("something went wrong") } -func (f *fakeFailingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) { +func (f *fakeFailingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) { return definitions.Route{}, fmt.Errorf("something went wrong") } @@ -2031,7 +2029,7 @@ func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Co return fmt.Errorf("%w: invalid policy tree", provisioning.ErrValidation) } -func (f *fakeRejectingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) { +func (f *fakeRejectingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) { return definitions.Route{}, nil } diff --git a/pkg/services/ngalert/provisioning/notification_policies.go b/pkg/services/ngalert/provisioning/notification_policies.go index 8eeeeff5211..afba5f0c0d9 100644 --- a/pkg/services/ngalert/provisioning/notification_policies.go +++ b/pkg/services/ngalert/provisioning/notification_policies.go @@ -16,6 +16,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage" + "github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation" "github.com/grafana/grafana/pkg/setting" ) @@ -25,6 +26,7 @@ type NotificationPolicyService struct { xact TransactionManager log log.Logger settings setting.UnifiedAlertingSettings + validator validation.ProvenanceStatusTransitionValidator } func NewNotificationPolicyService(am alertmanagerConfigStore, prov ProvisioningStore, @@ -35,6 +37,7 @@ func NewNotificationPolicyService(am alertmanagerConfigStore, prov ProvisioningS xact: xact, log: log, settings: settings, + validator: validation.ValidateProvenanceRelaxed, } } @@ -69,11 +72,20 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI return err } - err = nps.checkOptimisticConcurrency(*revision.Config.AlertmanagerConfig.Route, models.Provenance(tree.Provenance), version, "update") + err = nps.checkOptimisticConcurrency(*revision.Config.AlertmanagerConfig.Route, p, version, "update") if err != nil { return err } + // check that provenance is not changed in an invalid way + storedProvenance, err := nps.provenanceStore.GetProvenance(ctx, &tree, orgID) + if err != nil { + return err + } + if err := nps.validator(storedProvenance, p); err != nil { + return err + } + receivers, err := nps.receiversToMap(revision.Config.AlertmanagerConfig.Receivers) if err != nil { return err @@ -107,7 +119,15 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI }) } -func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) { +func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) { + storedProvenance, err := nps.provenanceStore.GetProvenance(ctx, &definitions.Route{}, orgID) + if err != nil { + return definitions.Route{}, err + } + if err := nps.validator(storedProvenance, provenance); err != nil { + return definitions.Route{}, err + } + defaultCfg, err := legacy_storage.DeserializeAlertmanagerConfig([]byte(nps.settings.DefaultConfiguration)) if err != nil { nps.log.Error("Failed to parse default alertmanager config: %w", err) diff --git a/pkg/services/ngalert/provisioning/notification_policies_test.go b/pkg/services/ngalert/provisioning/notification_policies_test.go index e67fb83be32..9fa49c8f5f1 100644 --- a/pkg/services/ngalert/provisioning/notification_policies_test.go +++ b/pkg/services/ngalert/provisioning/notification_policies_test.go @@ -2,6 +2,7 @@ package provisioning import ( "context" + "errors" "testing" "github.com/grafana/alerting/definition" @@ -102,7 +103,10 @@ func TestUpdatePolicyTree(t *testing.T) { t.Run("ErrValidation if referenced receiver does not exist", func(t *testing.T) { rev := getDefaultConfigRevision() - sut, store, _ := createNotificationPolicyServiceSut() + sut, store, prov := createNotificationPolicyServiceSut() + prov.GetProvenanceFunc = func(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) { + return models.ProvenanceNone, nil + } store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { return &rev, nil } @@ -137,6 +141,35 @@ func TestUpdatePolicyTree(t *testing.T) { require.ErrorIs(t, err, ErrVersionConflict) }) + t.Run("Error if provenance validation fails", func(t *testing.T) { + sut, store, prov := createNotificationPolicyServiceSut() + prov.GetProvenanceFunc = func(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) { + return models.ProvenanceAPI, nil + } + store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { + return &rev, nil + } + expectedRev := getDefaultConfigRevision() + route := newRoute + expectedRev.ConcurrencyToken = rev.ConcurrencyToken + expectedRev.Config.AlertmanagerConfig.Route = &route + + expectedErr := errors.New("test") + sut.validator = func(from, to models.Provenance) error { + assert.Equal(t, models.ProvenanceAPI, from) + assert.Equal(t, models.ProvenanceNone, to) + return expectedErr + } + + err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion) + require.ErrorIs(t, err, expectedErr) + + assert.Len(t, prov.Calls, 1) + assert.Equal(t, "GetProvenance", prov.Calls[0].MethodName) + assert.IsType(t, &definitions.Route{}, prov.Calls[0].Arguments[1]) + assert.Equal(t, orgID, prov.Calls[0].Arguments[2].(int64)) + }) + t.Run("updates Route and sets provenance in transaction if route is valid and version matches", func(t *testing.T) { sut, store, prov := createNotificationPolicyServiceSut() store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) { @@ -155,12 +188,16 @@ func TestUpdatePolicyTree(t *testing.T) { assertInTransaction(t, store.Calls[1].Args[0].(context.Context)) assert.Equal(t, &expectedRev, store.Calls[1].Args[1]) - assert.Len(t, prov.Calls, 1) - assert.Equal(t, "SetProvenance", prov.Calls[0].MethodName) - assertInTransaction(t, prov.Calls[0].Arguments[0].(context.Context)) - assert.IsType(t, &definitions.Route{}, prov.Calls[0].Arguments[1]) - assert.Equal(t, orgID, prov.Calls[0].Arguments[2].(int64)) - assert.Equal(t, models.ProvenanceAPI, prov.Calls[0].Arguments[3].(models.Provenance)) + c := prov.Calls[0] + assert.Equal(t, "GetProvenance", c.MethodName) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2].(int64)) + c = prov.Calls[1] + assert.Equal(t, "SetProvenance", c.MethodName) + assertInTransaction(t, c.Arguments[0].(context.Context)) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2].(int64)) + assert.Equal(t, models.ProvenanceAPI, c.Arguments[3].(models.Provenance)) }) t.Run("bypasses optimistic concurrency if provided version is empty", func(t *testing.T) { @@ -181,12 +218,13 @@ func TestUpdatePolicyTree(t *testing.T) { assertInTransaction(t, store.Calls[1].Args[0].(context.Context)) assert.Equal(t, &expectedRev, store.Calls[1].Args[1]) - assert.Len(t, prov.Calls, 1) - assert.Equal(t, "SetProvenance", prov.Calls[0].MethodName) - assertInTransaction(t, prov.Calls[0].Arguments[0].(context.Context)) - assert.IsType(t, &definitions.Route{}, prov.Calls[0].Arguments[1]) - assert.Equal(t, orgID, prov.Calls[0].Arguments[2].(int64)) - assert.Equal(t, models.ProvenanceAPI, prov.Calls[0].Arguments[3].(models.Provenance)) + assert.Len(t, prov.Calls, 2) + c := prov.Calls[1] + assert.Equal(t, "SetProvenance", c.MethodName) + assertInTransaction(t, c.Arguments[0].(context.Context)) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2].(int64)) + assert.Equal(t, models.ProvenanceAPI, c.Arguments[3].(models.Provenance)) }) } @@ -223,10 +261,27 @@ func TestResetPolicyTree(t *testing.T) { sut.settings = setting.UnifiedAlertingSettings{ DefaultConfiguration: "{", } - _, err := sut.ResetPolicyTree(context.Background(), orgID) + _, err := sut.ResetPolicyTree(context.Background(), orgID, models.ProvenanceNone) require.ErrorContains(t, err, "failed to parse default alertmanager config") }) + t.Run("Error if provenance validation fails", func(t *testing.T) { + sut, _, prov := createNotificationPolicyServiceSut() + prov.GetProvenanceFunc = func(ctx context.Context, o models.Provisionable, org int64) (models.Provenance, error) { + return models.ProvenanceAPI, nil + } + + expectedErr := errors.New("test") + sut.validator = func(from, to models.Provenance) error { + assert.Equal(t, models.ProvenanceAPI, from) + assert.Equal(t, models.ProvenanceNone, to) + return expectedErr + } + + _, err := sut.ResetPolicyTree(context.Background(), orgID, models.ProvenanceNone) + require.ErrorIs(t, err, expectedErr) + }) + t.Run("replaces route with one from the default config and copies receivers if do not exist", func(t *testing.T) { defaultConfig := getDefaultConfigRevision().Config data, err := legacy_storage.SerializeAlertmanagerConfig(*defaultConfig) @@ -252,7 +307,7 @@ func TestResetPolicyTree(t *testing.T) { expectedRev.Config.AlertmanagerConfig.Route = getDefaultConfigRevision().Config.AlertmanagerConfig.Route expectedRev.Config.AlertmanagerConfig.Receivers = append(expectedRev.Config.AlertmanagerConfig.Receivers, getDefaultConfigRevision().Config.AlertmanagerConfig.Receivers[0]) - tree, err := sut.ResetPolicyTree(context.Background(), orgID) + tree, err := sut.ResetPolicyTree(context.Background(), orgID, models.ProvenanceNone) require.NoError(t, err) assert.Equal(t, *defaultConfig.AlertmanagerConfig.Route, tree) @@ -262,11 +317,16 @@ func TestResetPolicyTree(t *testing.T) { resetRev := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision) assert.Equal(t, expectedRev.Config.AlertmanagerConfig, resetRev.Config.AlertmanagerConfig) - assert.Len(t, prov.Calls, 1) - assert.Equal(t, "DeleteProvenance", prov.Calls[0].MethodName) - assertInTransaction(t, prov.Calls[0].Arguments[0].(context.Context)) - assert.IsType(t, &definitions.Route{}, prov.Calls[0].Arguments[1]) - assert.Equal(t, orgID, prov.Calls[0].Arguments[2]) + assert.Len(t, prov.Calls, 2) + c := prov.Calls[0] + assert.Equal(t, "GetProvenance", c.MethodName) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2].(int64)) + c = prov.Calls[1] + assert.Equal(t, "DeleteProvenance", c.MethodName) + assertInTransaction(t, c.Arguments[0].(context.Context)) + assert.IsType(t, &definitions.Route{}, c.Arguments[1]) + assert.Equal(t, orgID, c.Arguments[2]) }) } @@ -286,6 +346,9 @@ func createNotificationPolicyServiceSut() (*NotificationPolicyService, *legacy_s settings: setting.UnifiedAlertingSettings{ DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration(), }, + validator: func(from, to models.Provenance) error { + return nil + }, }, configStore, prov } diff --git a/pkg/services/provisioning/alerting/notification_policy_provisioner.go b/pkg/services/provisioning/alerting/notification_policy_provisioner.go index 55f3dd2ae9d..63f6cbe9ce3 100644 --- a/pkg/services/provisioning/alerting/notification_policy_provisioner.go +++ b/pkg/services/provisioning/alerting/notification_policy_provisioner.go @@ -45,7 +45,7 @@ func (c *defaultNotificationPolicyProvisioner) Unprovision(ctx context.Context, files []*AlertingFile) error { for _, file := range files { for _, orgID := range file.ResetPolicies { - _, err := c.notificationPolicyService.ResetPolicyTree(ctx, int64(orgID)) + _, err := c.notificationPolicyService.ResetPolicyTree(ctx, int64(orgID), models.ProvenanceFile) if err != nil { return fmt.Errorf("%s: %w", file.Filename, err) } From f8748f07240bb1c8e06ac6f9db00c38417380cad Mon Sep 17 00:00:00 2001 From: Syerikjan Kh Date: Thu, 10 Oct 2024 20:30:56 -0400 Subject: [PATCH 064/110] ref: pass tracer to plugin factory func (#93701) * ref: pass tracer to plugin factory func * fix: add tracer to coreplugin * test: fix test, generate wire * test: ignore trace field in loader_test * ref: pass tracer as dependency, don't store in plugin * ref: wrap tracer with tracer provider to satisfy WithTracerProvider * ref: use otel trace.Tracer type for tracer --- pkg/plugins/backendplugin/backendplugin.go | 3 ++- .../backendplugin/coreplugin/core_plugin.go | 3 ++- .../coreplugin/core_plugin_test.go | 5 ++-- .../backendplugin/coreplugin/registry.go | 2 +- .../backendplugin/grpcplugin/client.go | 25 +++++++++++++++++-- .../backendplugin/grpcplugin/client_proto.go | 3 +++ .../backendplugin/grpcplugin/grpc_plugin.go | 9 ++++--- pkg/plugins/manager/fakes/fakes.go | 12 ++++++++- .../manager/pipeline/initialization/steps.go | 11 +++++--- .../pipeline/initialization/steps_test.go | 15 +++++------ .../pluginsintegration/loader/loader_test.go | 5 ++-- .../pluginsintegration/pipeline/pipeline.go | 2 +- .../pluginsintegration/renderer/renderer.go | 10 +++++--- 13 files changed, 75 insertions(+), 30 deletions(-) diff --git a/pkg/plugins/backendplugin/backendplugin.go b/pkg/plugins/backendplugin/backendplugin.go index 2d8f6de2c27..baf755f88fa 100644 --- a/pkg/plugins/backendplugin/backendplugin.go +++ b/pkg/plugins/backendplugin/backendplugin.go @@ -3,7 +3,8 @@ package backendplugin import ( "github.com/grafana/grafana/pkg/plugins/log" + "go.opentelemetry.io/otel/trace" ) // PluginFactoryFunc is a function type for creating a Plugin. -type PluginFactoryFunc func(pluginID string, logger log.Logger, env func() []string) (Plugin, error) +type PluginFactoryFunc func(pluginID string, logger log.Logger, tracer trace.Tracer, env func() []string) (Plugin, error) diff --git a/pkg/plugins/backendplugin/coreplugin/core_plugin.go b/pkg/plugins/backendplugin/coreplugin/core_plugin.go index 6010b2650c5..0cb85b38c89 100644 --- a/pkg/plugins/backendplugin/coreplugin/core_plugin.go +++ b/pkg/plugins/backendplugin/coreplugin/core_plugin.go @@ -4,6 +4,7 @@ import ( "context" "github.com/grafana/grafana-plugin-sdk-go/backend" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" @@ -24,7 +25,7 @@ type corePlugin struct { // New returns a new backendplugin.PluginFactoryFunc for creating a core (built-in) backendplugin.Plugin. func New(opts backend.ServeOpts) backendplugin.PluginFactoryFunc { - return func(pluginID string, logger log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, logger log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { return &corePlugin{ pluginID: pluginID, logger: logger, diff --git a/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go b/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go index c3243c52ae9..6b4cc5a385a 100644 --- a/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go +++ b/pkg/plugins/backendplugin/coreplugin/core_plugin_test.go @@ -8,13 +8,14 @@ import ( "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin" "github.com/grafana/grafana/pkg/plugins/log" + "github.com/grafana/grafana/pkg/plugins/manager/fakes" "github.com/stretchr/testify/require" ) func TestCorePlugin(t *testing.T) { t.Run("New core plugin with empty opts should return expected values", func(t *testing.T) { factory := coreplugin.New(backend.ServeOpts{}) - p, err := factory("plugin", log.New("test"), nil) + p, err := factory("plugin", log.New("test"), fakes.InitializeNoopTracerForTest(), nil) require.NoError(t, err) require.NotNil(t, p) require.NoError(t, p.Start(context.Background())) @@ -47,7 +48,7 @@ func TestCorePlugin(t *testing.T) { return nil }), }) - p, err := factory("plugin", log.New("test"), nil) + p, err := factory("plugin", log.New("test"), fakes.InitializeNoopTracerForTest(), nil) require.NoError(t, err) require.NotNil(t, p) require.NoError(t, p.Start(context.Background())) diff --git a/pkg/plugins/backendplugin/coreplugin/registry.go b/pkg/plugins/backendplugin/coreplugin/registry.go index 23dc4524f75..423d74ac29d 100644 --- a/pkg/plugins/backendplugin/coreplugin/registry.go +++ b/pkg/plugins/backendplugin/coreplugin/registry.go @@ -254,7 +254,7 @@ func NewPlugin(pluginID string, cfg *setting.Cfg, httpClientProvider *httpclient if backendFactory == nil { return nil, ErrCorePluginNotFound } - bp, err := backendFactory(p.ID, p.Logger(), nil) + bp, err := backendFactory(p.ID, p.Logger(), tracer, nil) if err != nil { return nil, err } diff --git a/pkg/plugins/backendplugin/grpcplugin/client.go b/pkg/plugins/backendplugin/grpcplugin/client.go index 5ce776ec757..35920fef2cf 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client.go +++ b/pkg/plugins/backendplugin/grpcplugin/client.go @@ -6,6 +6,8 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend/grpcplugin" goplugin "github.com/hashicorp/go-plugin" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + trace "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/embedded" "google.golang.org/grpc" "github.com/grafana/grafana/pkg/plugins/backendplugin" @@ -40,7 +42,20 @@ var pluginSet = map[int]goplugin.PluginSet{ }, } -func newClientConfig(executablePath string, args []string, env []string, skipHostEnvVars bool, logger log.Logger, +type clientTracerProvider struct { + tracer trace.Tracer + embedded.TracerProvider +} + +func (ctp *clientTracerProvider) Tracer(instrumentationName string, opts ...trace.TracerOption) trace.Tracer { + return ctp.tracer +} + +func newClientTracerProvider(tracer trace.Tracer) trace.TracerProvider { + return &clientTracerProvider{tracer: tracer} +} + +func newClientConfig(executablePath string, args []string, env []string, skipHostEnvVars bool, logger log.Logger, tracer trace.Tracer, versionedPlugins map[int]goplugin.PluginSet) *goplugin.ClientConfig { // We can ignore gosec G201 here, since the dynamic part of executablePath comes from the plugin definition // nolint:gosec @@ -55,7 +70,13 @@ func newClientConfig(executablePath string, args []string, env []string, skipHos Logger: logWrapper{Logger: logger}, AllowedProtocols: []goplugin.Protocol{goplugin.ProtocolGRPC}, GRPCDialOptions: []grpc.DialOption{ - grpc.WithStatsHandler(otelgrpc.NewClientHandler()), + // https://github.com/grafana/app-platform-wg/issues/140 + // external plugins are loaded before k8s API server + // configures the tracing service thus failing to + // record trace span in the middleware. + // With code below we are passing the same tracer that k8s API server + // uses so that middleware is configured with tracer. + grpc.WithStatsHandler(otelgrpc.NewClientHandler(otelgrpc.WithTracerProvider(newClientTracerProvider(tracer)))), }, } } diff --git a/pkg/plugins/backendplugin/grpcplugin/client_proto.go b/pkg/plugins/backendplugin/grpcplugin/client_proto.go index 19c83d0c7dd..9922258f9e2 100644 --- a/pkg/plugins/backendplugin/grpcplugin/client_proto.go +++ b/pkg/plugins/backendplugin/grpcplugin/client_proto.go @@ -4,6 +4,7 @@ import ( "context" "errors" + trace "go.opentelemetry.io/otel/trace" "google.golang.org/grpc" "github.com/grafana/grafana-plugin-sdk-go/genproto/pluginv2" @@ -44,6 +45,7 @@ type ProtoClientOpts struct { ExecutableArgs []string Env []string Logger log.Logger + Tracer trace.Tracer } func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { @@ -56,6 +58,7 @@ func NewProtoClient(opts ProtoClientOpts) (ProtoClient, error) { versionedPlugins: pluginSet, }, opts.Logger, + opts.Tracer, func() []string { return opts.Env }, ) diff --git a/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go b/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go index bce188928aa..d1c6152a0ef 100644 --- a/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go +++ b/pkg/plugins/backendplugin/grpcplugin/grpc_plugin.go @@ -7,6 +7,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/hashicorp/go-plugin" + trace "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/process" "github.com/grafana/grafana/pkg/plugins" @@ -37,17 +38,17 @@ const ( // newPlugin allocates and returns a new gRPC (external) backendplugin.Plugin. func newPlugin(descriptor PluginDescriptor) backendplugin.PluginFactoryFunc { - return func(pluginID string, logger log.Logger, env func() []string) (backendplugin.Plugin, error) { - return newGrpcPlugin(descriptor, logger, env), nil + return func(pluginID string, logger log.Logger, tracer trace.Tracer, env func() []string) (backendplugin.Plugin, error) { + return newGrpcPlugin(descriptor, logger, tracer, env), nil } } -func newGrpcPlugin(descriptor PluginDescriptor, logger log.Logger, env func() []string) *grpcPlugin { +func newGrpcPlugin(descriptor PluginDescriptor, logger log.Logger, tracer trace.Tracer, env func() []string) *grpcPlugin { return &grpcPlugin{ descriptor: descriptor, logger: logger, clientFactory: func() *plugin.Client { - return plugin.NewClient(newClientConfig(descriptor.executablePath, descriptor.executableArgs, env(), descriptor.skipHostEnvVars, logger, descriptor.versionedPlugins)) + return plugin.NewClient(newClientConfig(descriptor.executablePath, descriptor.executableArgs, env(), descriptor.skipHostEnvVars, logger, tracer, descriptor.versionedPlugins)) }, state: pluginStateNotStarted, } diff --git a/pkg/plugins/manager/fakes/fakes.go b/pkg/plugins/manager/fakes/fakes.go index 2ba324f311d..f25daddc504 100644 --- a/pkg/plugins/manager/fakes/fakes.go +++ b/pkg/plugins/manager/fakes/fakes.go @@ -8,6 +8,8 @@ import ( "sync" "github.com/grafana/grafana-plugin-sdk-go/backend" + "go.opentelemetry.io/otel/trace" + "go.opentelemetry.io/otel/trace/noop" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/auth" @@ -267,6 +269,14 @@ func (r *FakePluginRepo) PluginVersion(ctx context.Context, pluginID, version st return repo.VersionData{}, nil } +type fakeTracerProvider struct { + noop.TracerProvider +} + +func InitializeNoopTracerForTest() trace.Tracer { + return fakeTracerProvider{}.Tracer("test") +} + type FakePluginStorage struct { ExtractFunc func(_ context.Context, pluginID string, dirNameFunc storage.DirNameGeneratorFunc, z *zip.ReadCloser) (*storage.ExtractedPluginArchive, error) } @@ -340,7 +350,7 @@ func NewFakeBackendProcessProvider() *FakeBackendProcessProvider { } f.BackendFactoryFunc = func(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { f.Requested[p.ID]++ - return func(pluginID string, _ log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, _ log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { f.Invoked[pluginID]++ return &FakePluginClient{}, nil } diff --git a/pkg/plugins/manager/pipeline/initialization/steps.go b/pkg/plugins/manager/pipeline/initialization/steps.go index 2df09d1144a..02573f6ba20 100644 --- a/pkg/plugins/manager/pipeline/initialization/steps.go +++ b/pkg/plugins/manager/pipeline/initialization/steps.go @@ -9,6 +9,7 @@ import ( "github.com/grafana/grafana/pkg/plugins/log" "github.com/grafana/grafana/pkg/plugins/manager/process" "github.com/grafana/grafana/pkg/plugins/manager/registry" + "go.opentelemetry.io/otel/trace" ) // BackendClientInit implements an InitializeFunc for initializing a backend plugin process. @@ -21,20 +22,22 @@ type BackendClientInit struct { envVarProvider envvars.Provider backendProvider plugins.BackendFactoryProvider log log.Logger + tracer trace.Tracer } // BackendClientInitStep returns a new InitializeFunc for registering a backend plugin process. func BackendClientInitStep(envVarProvider envvars.Provider, - backendProvider plugins.BackendFactoryProvider) InitializeFunc { - return newBackendProcessRegistration(envVarProvider, backendProvider).Initialize + backendProvider plugins.BackendFactoryProvider, tracer trace.Tracer) InitializeFunc { + return newBackendProcessRegistration(envVarProvider, backendProvider, tracer).Initialize } func newBackendProcessRegistration(envVarProvider envvars.Provider, - backendProvider plugins.BackendFactoryProvider) *BackendClientInit { + backendProvider plugins.BackendFactoryProvider, tracer trace.Tracer) *BackendClientInit { return &BackendClientInit{ backendProvider: backendProvider, envVarProvider: envVarProvider, log: log.New("plugins.backend.registration"), + tracer: tracer, } } @@ -49,7 +52,7 @@ func (b *BackendClientInit) Initialize(ctx context.Context, p *plugins.Plugin) ( // this will ensure that the env variables are calculated every time a plugin is started envFunc := func() []string { return b.envVarProvider.PluginEnvVars(ctx, p) } - if backendClient, err := backendFactory(p.ID, p.Logger(), envFunc); err != nil { + if backendClient, err := backendFactory(p.ID, p.Logger(), b.tracer, envFunc); err != nil { return nil, err } else { p.RegisterClient(backendClient) diff --git a/pkg/plugins/manager/pipeline/initialization/steps_test.go b/pkg/plugins/manager/pipeline/initialization/steps_test.go index b5e43195e71..da00444e93e 100644 --- a/pkg/plugins/manager/pipeline/initialization/steps_test.go +++ b/pkg/plugins/manager/pipeline/initialization/steps_test.go @@ -4,11 +4,12 @@ import ( "context" "testing" - "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/log" + "github.com/grafana/grafana/pkg/plugins/manager/fakes" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" ) func TestInitializer_Initialize(t *testing.T) { @@ -28,7 +29,7 @@ func TestInitializer_Initialize(t *testing.T) { Class: plugins.ClassCore, } - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}) + stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) var err error p, err = stepFunc(context.Background(), p) @@ -52,7 +53,7 @@ func TestInitializer_Initialize(t *testing.T) { Class: plugins.ClassExternal, } - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}) + stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) var err error p, err = stepFunc(context.Background(), p) @@ -76,7 +77,7 @@ func TestInitializer_Initialize(t *testing.T) { Class: plugins.ClassExternal, } - stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}) + stepFunc := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{plugin: p}, fakes.InitializeNoopTracerForTest()) var err error p, err = stepFunc(context.Background(), p) @@ -96,7 +97,7 @@ func TestInitializer_Initialize(t *testing.T) { i := BackendClientInitStep(&fakeEnvVarsProvider{}, &fakeBackendProvider{ plugin: p, - }) + }, fakes.InitializeNoopTracerForTest()) var err error p, err = i(context.Background(), p) @@ -115,7 +116,7 @@ type fakeBackendProvider struct { } func (f *fakeBackendProvider) BackendFactory(_ context.Context, _ *plugins.Plugin) backendplugin.PluginFactoryFunc { - return func(_ string, _ log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(_ string, _ log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { return f.plugin, nil } } diff --git a/pkg/services/pluginsintegration/loader/loader_test.go b/pkg/services/pluginsintegration/loader/loader_test.go index 4eafd7db235..677b07bcb27 100644 --- a/pkg/services/pluginsintegration/loader/loader_test.go +++ b/pkg/services/pluginsintegration/loader/loader_test.go @@ -10,6 +10,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" @@ -584,7 +585,7 @@ func TestLoader_Load_ExternalRegistration(t *testing.T) { backendFactoryProvider := fakes.NewFakeBackendProcessProvider() backendFactoryProvider.BackendFactoryFunc = func(ctx context.Context, plugin *plugins.Plugin) backendplugin.PluginFactoryFunc { - return func(pluginID string, logger log.Logger, env func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, logger log.Logger, tracer trace.Tracer, env func() []string) (backendplugin.Plugin, error) { require.Equal(t, "grafana-test-datasource", pluginID) return &fakes.FakeBackendPlugin{}, nil } @@ -1133,7 +1134,7 @@ func TestLoader_Load_SkipUninitializedPlugins(t *testing.T) { procPrvdr := fakes.NewFakeBackendProcessProvider() // Cause an initialization error procPrvdr.BackendFactoryFunc = func(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { - return func(pluginID string, _ log.Logger, _ func() []string) (backendplugin.Plugin, error) { + return func(pluginID string, _ log.Logger, _ trace.Tracer, _ func() []string) (backendplugin.Plugin, error) { if pluginID == "test-datasource" { return nil, errors.New("failed to initialize") } diff --git a/pkg/services/pluginsintegration/pipeline/pipeline.go b/pkg/services/pluginsintegration/pipeline/pipeline.go index 6422c7ddd61..a590d8c810a 100644 --- a/pkg/services/pluginsintegration/pipeline/pipeline.go +++ b/pkg/services/pluginsintegration/pipeline/pipeline.go @@ -68,7 +68,7 @@ func ProvideInitializationStage(cfg *config.PluginManagementCfg, pr registry.Ser return initialization.New(cfg, initialization.Opts{ InitializeFuncs: []initialization.InitializeFunc{ ExternalServiceRegistrationStep(cfg, externalServiceRegistry, tracer), - initialization.BackendClientInitStep(pluginEnvProvider, bp), + initialization.BackendClientInitStep(pluginEnvProvider, bp, tracer), initialization.BackendProcessStartStep(pm), RegisterPluginRolesStep(roleRegistry), RegisterActionSetsStep(actionSetRegistry), diff --git a/pkg/services/pluginsintegration/renderer/renderer.go b/pkg/services/pluginsintegration/renderer/renderer.go index 9eb71fc7d7d..ff8003b957c 100644 --- a/pkg/services/pluginsintegration/renderer/renderer.go +++ b/pkg/services/pluginsintegration/renderer/renderer.go @@ -5,6 +5,7 @@ import ( "errors" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" "github.com/grafana/grafana/pkg/plugins/backendplugin/provider" @@ -22,11 +23,12 @@ import ( "github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs" "github.com/grafana/grafana/pkg/services/rendering" + "go.opentelemetry.io/otel/trace" ) func ProvideService(cfg *config.PluginManagementCfg, pluginEnvProvider envvars.Provider, - registry registry.Service) (*Manager, error) { - l, err := createLoader(cfg, pluginEnvProvider, registry) + registry registry.Service, tracer tracing.Tracer) (*Manager, error) { + l, err := createLoader(cfg, pluginEnvProvider, registry, tracer) if err != nil { return nil, err } @@ -105,7 +107,7 @@ func (m *Manager) Renderer(ctx context.Context) (rendering.Plugin, bool) { } func createLoader(cfg *config.PluginManagementCfg, pluginEnvProvider envvars.Provider, - pr registry.Service) (loader.Service, error) { + pr registry.Service, tracer trace.Tracer) (loader.Service, error) { d := discovery.New(cfg, discovery.Opts{ FindFilterFuncs: []discovery.FindFilterFunc{ discovery.NewPermittedPluginTypesFilterStep([]plugins.Type{plugins.TypeRenderer}), @@ -124,7 +126,7 @@ func createLoader(cfg *config.PluginManagementCfg, pluginEnvProvider envvars.Pro }) i := initialization.New(cfg, initialization.Opts{ InitializeFuncs: []initialization.InitializeFunc{ - initialization.BackendClientInitStep(pluginEnvProvider, provider.New(provider.RendererProvider)), + initialization.BackendClientInitStep(pluginEnvProvider, provider.New(provider.RendererProvider), tracer), initialization.PluginRegistrationStep(pr), }, }) From 4eab10eaa1351fe8ec21bf8f812b6376fd4ea8ce Mon Sep 17 00:00:00 2001 From: Misi Date: Fri, 11 Oct 2024 08:45:36 +0200 Subject: [PATCH 065/110] Auth: Add missing Name property to SAML strategy (#94565) Add Name to SAML strategy --- pkg/services/ssosettings/strategies/saml_strategy.go | 1 + pkg/services/ssosettings/strategies/saml_strategy_test.go | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/services/ssosettings/strategies/saml_strategy.go b/pkg/services/ssosettings/strategies/saml_strategy.go index e6e820474fc..740d86ee2c0 100644 --- a/pkg/services/ssosettings/strategies/saml_strategy.go +++ b/pkg/services/ssosettings/strategies/saml_strategy.go @@ -33,6 +33,7 @@ func (s *SAMLStrategy) loadSAMLSettings() map[string]any { section := s.settingsProvider.Section("auth.saml") result := map[string]any{ "enabled": section.KeyValue("enabled").MustBool(false), + "name": section.KeyValue("name").MustString("SAML"), "single_logout": section.KeyValue("single_logout").MustBool(false), "allow_sign_up": section.KeyValue("allow_sign_up").MustBool(false), "auto_login": section.KeyValue("auto_login").MustBool(false), diff --git a/pkg/services/ssosettings/strategies/saml_strategy_test.go b/pkg/services/ssosettings/strategies/saml_strategy_test.go index fb61b54893e..d076f708400 100644 --- a/pkg/services/ssosettings/strategies/saml_strategy_test.go +++ b/pkg/services/ssosettings/strategies/saml_strategy_test.go @@ -16,8 +16,9 @@ var ( [auth.saml] enabled = true single_logout = true + name = "SAML Test" allow_sign_up = true - auto_login = false + auto_login = true certificate = devenv/docker/blocks/auth/saml-enterprise/cert.crt certificate_path = /path/to/cert private_key = dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0 @@ -55,7 +56,8 @@ var ( "enabled": true, "single_logout": true, "allow_sign_up": true, - "auto_login": false, + "auto_login": true, + "name": "SAML Test", "certificate": "devenv/docker/blocks/auth/saml-enterprise/cert.crt", "certificate_path": "/path/to/cert", "private_key": "dGhpcyBpcyBteSBwcml2YXRlIGtleSB0aGF0IEkgd2FudCB0byBnZXQgZW5jb2RlZCBpbiBiYXNlIDY0", From 3d59d3b40f83190da739a843b2f849c623716d77 Mon Sep 17 00:00:00 2001 From: owensmallwood Date: Fri, 11 Oct 2024 02:13:10 -0600 Subject: [PATCH 066/110] Search poc: Initial index paginates list response (#94582) --- pkg/services/unifiedSearch/service.go | 10 ++-- pkg/storage/unified/resource/index.go | 83 ++++++++++++++++----------- 2 files changed, 57 insertions(+), 36 deletions(-) diff --git a/pkg/services/unifiedSearch/service.go b/pkg/services/unifiedSearch/service.go index 6f2b29e97c5..7db7a0bffb5 100644 --- a/pkg/services/unifiedSearch/service.go +++ b/pkg/services/unifiedSearch/service.go @@ -3,7 +3,6 @@ package unifiedSearch import ( "context" "errors" - "fmt" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" @@ -12,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/registry" "github.com/grafana/grafana/pkg/services/accesscontrol" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/org" @@ -152,14 +152,16 @@ func (s *StandardSearchService) DoQuery(ctx context.Context, user *backend.User, } func (s *StandardSearchService) doQuery(ctx context.Context, signedInUser *user.SignedInUser, orgID int64, q Query) *backend.DataResponse { - response := s.doSearchQuery(ctx, q, s.cfg.AppSubURL) + response := s.doSearchQuery(ctx, q, s.cfg.AppSubURL, orgID) return response } -func (s *StandardSearchService) doSearchQuery(ctx context.Context, qry Query, _ string) *backend.DataResponse { +func (s *StandardSearchService) doSearchQuery(ctx context.Context, qry Query, _ string, orgID int64) *backend.DataResponse { response := &backend.DataResponse{} - tenantId := fmt.Sprintf("stacks-%s", s.cfg.StackID) + // will use stack id for cloud and org id for on-prem + tenantId := request.GetNamespaceMapper(s.cfg)(orgID) + req := &resource.SearchRequest{Tenant: tenantId, Query: qry.Query, Limit: int64(qry.Limit), Offset: int64(qry.From)} res, err := s.resourceClient.Search(ctx, req) if err != nil { diff --git a/pkg/storage/unified/resource/index.go b/pkg/storage/unified/resource/index.go index bc806800871..33abc5e92c7 100644 --- a/pkg/storage/unified/resource/index.go +++ b/pkg/storage/unified/resource/index.go @@ -39,46 +39,65 @@ func NewIndex(s *server, opts Opts) *Index { return idx } -func (i *Index) Init(ctx context.Context) error { - resourceTypes := fetchResourceTypes() - for _, rt := range resourceTypes { - r := &ListRequest{Options: rt} - list, err := i.s.List(ctx, r) +func (i *Index) IndexBatch(list *ListResponse, kind string) error { + for _, obj := range list.Items { + res, err := getResource(obj.Value) if err != nil { return err } - i.log.Info("initial indexing resources", "count", len(list.Items)) - for _, obj := range list.Items { - res, err := getResource(obj.Value) - if err != nil { - return err - } - - shard, err := i.getShard(tenant(res)) - if err != nil { - return err - } - - i.log.Info("indexing resource for tenant", "res", res, "tenant", tenant(res)) - - var jsonDoc interface{} - err = json.Unmarshal(obj.Value, &jsonDoc) - if err != nil { - return err - } - err = shard.batch.Index(res.Metadata.Uid, jsonDoc) - if err != nil { - return err - } + shard, err := i.getShard(tenant(res)) + if err != nil { + return err } + i.log.Debug("initial indexing resources batch", "count", len(list.Items), "kind", kind, "tenant", tenant(res)) - for _, shard := range i.shards { - err := shard.index.Batch(shard.batch) + var jsonDoc interface{} + err = json.Unmarshal(obj.Value, &jsonDoc) + if err != nil { + return err + } + err = shard.batch.Index(res.Metadata.Uid, jsonDoc) + if err != nil { + return err + } + } + + for _, shard := range i.shards { + err := shard.index.Batch(shard.batch) + if err != nil { + return err + } + shard.batch.Reset() + } + + return nil +} + +func (i *Index) Init(ctx context.Context) error { + resourceTypes := fetchResourceTypes() + for _, rt := range resourceTypes { + i.log.Info("indexing resource", "kind", rt.Key.Resource) + r := &ListRequest{Options: rt, Limit: 100} + + // Paginate through the list of resources and index each page + for { + list, err := i.s.List(ctx, r) if err != nil { return err } - shard.batch.Reset() + + // Index current page + err = i.IndexBatch(list, rt.Key.Resource) + if err != nil { + return err + } + + if list.NextPageToken == "" { + break + } + + r.NextPageToken = list.NextPageToken } } @@ -91,7 +110,7 @@ func (i *Index) Index(ctx context.Context, data *Data) error { return err } tenant := tenant(res) - i.log.Info("indexing resource for tenant", "res", res, "tenant", tenant) + i.log.Debug("indexing resource for tenant", "res", res, "tenant", tenant) shard, err := i.getShard(tenant) if err != nil { return err From 4092741f243b331a5636822bcaf1cd84a4cc8f3a Mon Sep 17 00:00:00 2001 From: Laura Benz <48948963+L-M-K-B@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:29:58 +0200 Subject: [PATCH 067/110] RestoreDashboards: Merge both feature toggles to dashboardRestore (#94412) * refactor: remove FE feat toggle from BE * refactor: remove FE toggle and adjust roles * refactor: replace feat toggle in tracking events * refactor: remove FE feat toggle * refactor: remove FE feat toggle * fix: autogenerated file --- .../configure-grafana/feature-toggles/index.md | 3 +-- packages/grafana-data/src/types/featureToggles.gen.ts | 1 - pkg/services/featuremgmt/registry.go | 10 ++-------- pkg/services/featuremgmt/toggles_gen.csv | 1 - pkg/services/featuremgmt/toggles_gen.go | 6 +----- pkg/services/featuremgmt/toggles_gen.json | 10 ++++++---- pkg/services/navtree/navtreeimpl/navtree.go | 2 +- .../browse-dashboards/BrowseDashboardsPage.tsx | 2 +- .../components/BrowseActions/BrowseActions.tsx | 2 +- .../components/BrowseActions/DeleteModal.tsx | 2 +- public/app/features/dashboard-scene/saving/shared.tsx | 2 +- .../dashboard-scene/settings/DeleteDashboardButton.tsx | 2 +- .../DeleteDashboard/DeleteDashboardModal.tsx | 2 +- .../SaveDashboard/SaveDashboardErrorProxy.tsx | 2 +- public/app/routes/routes.tsx | 4 ++-- 15 files changed, 20 insertions(+), 31 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index ef8bc6a3396..46c13858bb3 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -191,11 +191,10 @@ Experimental features might be changed or removed without prior notice. | `queryLibrary` | Enables Query Library feature in Explore | | `logsExploreTableDefaultVisualization` | Sets the logs table as default visualisation in logs explore | | `alertingListViewV2` | Enables the new alert list view design | -| `dashboardRestore` | Enables deleted dashboard restore feature (backend only) | +| `dashboardRestore` | Enables deleted dashboard restore feature | | `alertingCentralAlertHistory` | Enables the new central alert history. | | `failWrongDSUID` | Throws an error if a datasource has an invalid UIDs | | `alertingApiServer` | Register Alerting APIs with the K8s API server | -| `dashboardRestoreUI` | Enables the frontend to be able to restore a recently deleted dashboard | | `dataplaneAggregator` | Enable grafana dataplane aggregator | | `newFiltersUI` | Enables new combobox style UI for the Ad hoc filters variable in scenes architecture | | `lokiSendDashboardPanelNames` | Send dashboard and panel names to Loki when querying | diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index b8124c22120..6283847aa1a 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -198,7 +198,6 @@ export interface FeatureToggles { zanzana?: boolean; passScopeToDashboardApi?: boolean; alertingApiServer?: boolean; - dashboardRestoreUI?: boolean; cloudWatchRoundUpEndTime?: boolean; cloudwatchMetricInsightsCrossAccount?: boolean; prometheusAzureOverrideAudience?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index ba68086a0af..1d289b98596 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1255,10 +1255,11 @@ var ( }, { Name: "dashboardRestore", - Description: "Enables deleted dashboard restore feature (backend only)", + Description: "Enables deleted dashboard restore feature", Stage: FeatureStageExperimental, Owner: grafanaSearchAndStorageSquad, HideFromAdminPage: true, + Expression: "false", // enabled by default }, { Name: "datasourceProxyDisableRBAC", @@ -1367,13 +1368,6 @@ var ( Owner: grafanaAlertingSquad, RequiresRestart: true, }, - { - Name: "dashboardRestoreUI", - Description: "Enables the frontend to be able to restore a recently deleted dashboard", - Stage: FeatureStageExperimental, - Owner: grafanaFrontendPlatformSquad, - Expression: "false", // enabled by default - }, { Name: "cloudWatchRoundUpEndTime", Description: "Round up end time for metric queries to the next minute to avoid missing data", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 27dc4bcdf2a..3dd5ee565ce 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -179,7 +179,6 @@ failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false zanzana,experimental,@grafana/identity-access-team,false,false,false passScopeToDashboardApi,experimental,@grafana/dashboards-squad,false,false,false alertingApiServer,experimental,@grafana/alerting-squad,false,true,false -dashboardRestoreUI,experimental,@grafana/grafana-frontend-platform,false,false,false cloudWatchRoundUpEndTime,GA,@grafana/aws-datasources,false,false,false cloudwatchMetricInsightsCrossAccount,preview,@grafana/aws-datasources,false,false,true prometheusAzureOverrideAudience,deprecated,@grafana/partner-datasources,false,false,false diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 147d0f4681e..1aee2335aee 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -668,7 +668,7 @@ const ( FlagNotificationBanner = "notificationBanner" // FlagDashboardRestore - // Enables deleted dashboard restore feature (backend only) + // Enables deleted dashboard restore feature FlagDashboardRestore = "dashboardRestore" // FlagDatasourceProxyDisableRBAC @@ -727,10 +727,6 @@ const ( // Register Alerting APIs with the K8s API server FlagAlertingApiServer = "alertingApiServer" - // FlagDashboardRestoreUI - // Enables the frontend to be able to restore a recently deleted dashboard - FlagDashboardRestoreUI = "dashboardRestoreUI" - // FlagCloudWatchRoundUpEndTime // Round up end time for metric queries to the next minute to avoid missing data FlagCloudWatchRoundUpEndTime = "cloudWatchRoundUpEndTime" diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 797f974e49d..52088db76da 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -808,17 +808,18 @@ { "metadata": { "name": "dashboardRestore", - "resourceVersion": "1719321877748", + "resourceVersion": "1728397491294", "creationTimestamp": "2024-05-16T17:36:26Z", "annotations": { - "grafana.app/updatedTimestamp": "2024-06-25 13:24:37.748284 +0000 UTC" + "grafana.app/updatedTimestamp": "2024-10-08 14:24:51.294668 +0000 UTC" } }, "spec": { - "description": "Enables deleted dashboard restore feature (backend only)", + "description": "Enables deleted dashboard restore feature", "stage": "experimental", "codeowner": "@grafana/search-and-storage", - "hideFromAdminPage": true + "hideFromAdminPage": true, + "expression": "false" } }, { @@ -826,6 +827,7 @@ "name": "dashboardRestoreUI", "resourceVersion": "1720021873452", "creationTimestamp": "2024-06-25T14:43:13Z", + "deletionTimestamp": "2024-10-08T14:24:51Z", "annotations": { "grafana.app/updatedTimestamp": "2024-07-03 15:51:13.452477 +0000 UTC" } diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index e4bdfcf2bfc..4441b75d658 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -389,7 +389,7 @@ func (s *ServiceImpl) buildDashboardNavLinks(c *contextmodel.ReqContext) []*navt }) } - if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagDashboardRestoreUI) && (c.SignedInUser.GetOrgRole() == org.RoleAdmin || c.IsGrafanaAdmin) { + if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagDashboardRestore) && (c.SignedInUser.GetOrgRole() == org.RoleAdmin || c.IsGrafanaAdmin) { dashboardChildNavs = append(dashboardChildNavs, &navtree.NavLink{ Text: "Recently deleted", SubTitle: "Any items listed here for more than 30 days will be automatically deleted.", diff --git a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx index 6d8d2b14e26..cb56a761ab9 100644 --- a/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx +++ b/public/app/features/browse-dashboards/BrowseDashboardsPage.tsx @@ -121,7 +121,7 @@ const BrowseDashboardsPage = memo(() => { onEditTitle={showEditTitle ? onEditTitle : undefined} actions={ <> - {config.featureToggles.dashboardRestore && config.featureToggles.dashboardRestoreUI && hasAdminRights && ( + {config.featureToggles.dashboardRestore && hasAdminRights && (

diff --git a/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx b/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx index 19494d5663d..de55bc59a64 100644 --- a/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx +++ b/public/app/features/dashboard-scene/settings/DeleteDashboardButton.tsx @@ -33,7 +33,7 @@ export function DeleteDashboardButton({ dashboard }: ButtonProps) { dashboard: 1, }, source: 'dashboard_scene_settings', - restore_enabled: config.featureToggles.dashboardRestoreUI, + restore_enabled: config.featureToggles.dashboardRestore, }); toggleModal(); if (dashboard.state.uid) { diff --git a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx index d82f4fd56a0..2d777ba0bc2 100644 --- a/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx +++ b/public/app/features/dashboard/components/DeleteDashboard/DeleteDashboardModal.tsx @@ -34,7 +34,7 @@ const DeleteDashboardModalUnconnected = ({ hideModal, cleanUpDashboardAndVariabl dashboard: 1, }, source: 'dashboard_settings', - restore_enabled: config.featureToggles.dashboardRestoreUI, + restore_enabled: config.featureToggles.dashboardRestore, }); await deleteItems({ selectedItems: { diff --git a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx index 2a5941c82e9..d65ad7a9cb0 100644 --- a/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx +++ b/public/app/features/dashboard/components/SaveDashboard/SaveDashboardErrorProxy.tsx @@ -31,7 +31,7 @@ export const SaveDashboardErrorProxy = ({ setErrorIsHandled, }: SaveDashboardErrorProxyProps) => { const { onDashboardSave } = useDashboardSave(); - const isRestoreDashboardsEnabled = config.featureToggles.dashboardRestore && config.featureToggles.dashboardRestoreUI; + const isRestoreDashboardsEnabled = config.featureToggles.dashboardRestore; return ( <> {error.data && error.data.status === 'version-mismatch' && ( diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index e5661244077..3355616189d 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -444,9 +444,9 @@ export function getAppRoutes(): RouteDescriptor[] { () => import(/* webpackChunkName: "SnapshotListPage" */ 'app/features/manage-dashboards/SnapshotListPage') ), }, - config.featureToggles.dashboardRestoreUI && { + config.featureToggles.dashboardRestore && { path: '/dashboard/recently-deleted', - roles: () => ['Admin'], + roles: () => ['Admin', 'ServerAdmin'], component: SafeDynamicImport( () => import(/* webpackChunkName: "RecentlyDeletedPage" */ 'app/features/browse-dashboards/RecentlyDeletedPage') ), From 0bd3ad1d5a15a879fff3fe4f83af5edb2521c807 Mon Sep 17 00:00:00 2001 From: Mihai Doarna Date: Fri, 11 Oct 2024 12:09:34 +0300 Subject: [PATCH 068/110] SSO: Fix client side validations for LDAP (#94561) * fix client side validations for LDAP * add translations for new messages * simplify code in isInvalidField() --- .../features/admin/ldap/LdapSettingsPage.tsx | 29 ++++++++++++++----- public/locales/en-US/grafana.json | 9 ++++-- public/locales/pseudo-LOCALE/grafana.json | 9 ++++-- 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/public/app/features/admin/ldap/LdapSettingsPage.tsx b/public/app/features/admin/ldap/LdapSettingsPage.tsx index 6c518cb973b..fd84d4f0dcd 100644 --- a/public/app/features/admin/ldap/LdapSettingsPage.tsx +++ b/public/app/features/admin/ldap/LdapSettingsPage.tsx @@ -108,7 +108,7 @@ export const LdapSettingsPage = () => { const methods = useForm({ defaultValues: emptySettings }); const { control, - formState: { isDirty }, + formState: { isDirty, errors }, getValues, setValue, handleSubmit, @@ -216,7 +216,7 @@ export const LdapSettingsPage = () => { /** * Button's Actions */ - const submitAndEnableLdapSettings = async (payload: LdapPayload) => { + const submitFormAndToggleSettings = async (payload: LdapPayload) => { payload.settings.enabled = !payload.settings.enabled; await putPayload(payload); reportInteraction('authentication_ldap_enabled'); @@ -254,6 +254,11 @@ export const LdapSettingsPage = () => { reportInteraction('authentication_ldap_abandoned'); }; + const isInvalidField = (field: string) => { + const err = errors?.settings?.config?.servers?.[0]; + return typeof err === 'object' && field in err; + }; + const subTitle = ( The LDAP integration in Grafana allows your Grafana users to log in with their LDAP credentials. Find out more in @@ -282,7 +287,7 @@ export const LdapSettingsPage = () => { {config.disableLoginForm && disabledFormAlert} -

+ {isLoading && } {!isLoading && ( @@ -291,7 +296,10 @@ export const LdapSettingsPage = () => { Basic Settings { /> { /> !!value?.length }} name={`${serverConfig}.search_base_dns`} control={control} render={({ field: { onChange, ref, ...field } }) => ( @@ -397,7 +412,7 @@ export const LdapSettingsPage = () => { Disable )} - diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 2b6cff36111..5519b8dc79d 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1225,7 +1225,8 @@ "documentation": "documentation", "host": { "description": "Hostname or IP address of the LDAP server you wish to connect to.", - "label": "Server host *", + "error": "Server host is a required field", + "label": "Server host", "placeholder": "example: 127.0.0.1" }, "login-form-alert": { @@ -1234,12 +1235,14 @@ }, "search_filter": { "description": "LDAP search filter used to locate specific entries within the directory.", - "label": "Search filter *", + "error": "Search filter is a required field", + "label": "Search filter", "placeholder": "example: cn=%s" }, "search-base-dns": { "description": "An array of base dns to search through.", - "label": "Search base DNS *", + "error": "Search base DNS is a required field", + "label": "Search base DNS", "placeholder": "example: dc=grafana,dc=org" }, "subtitle": "The LDAP integration in Grafana allows your Grafana users to log in with their LDAP credentials. Find out more in our <2><0>documentation.", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index c5c78519a42..ee7d5df34d9 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1225,7 +1225,8 @@ "documentation": "đőčūmęʼnŧäŧįőʼn", "host": { "description": "Ħőşŧʼnämę őř ĨP äđđřęşş őƒ ŧĥę ĿĐÅP şęřvęř yőū ŵįşĥ ŧő čőʼnʼnęčŧ ŧő.", - "label": "Ŝęřvęř ĥőşŧ *", + "error": "Ŝęřvęř ĥőşŧ įş ä řęqūįřęđ ƒįęľđ", + "label": "Ŝęřvęř ĥőşŧ", "placeholder": "ęχämpľę: 127.0.0.1" }, "login-form-alert": { @@ -1234,12 +1235,14 @@ }, "search_filter": { "description": "ĿĐÅP şęäřčĥ ƒįľŧęř ūşęđ ŧő ľőčäŧę şpęčįƒįč ęʼnŧřįęş ŵįŧĥįʼn ŧĥę đįřęčŧőřy.", - "label": "Ŝęäřčĥ ƒįľŧęř *", + "error": "Ŝęäřčĥ ƒįľŧęř įş ä řęqūįřęđ ƒįęľđ", + "label": "Ŝęäřčĥ ƒįľŧęř", "placeholder": "ęχämpľę: čʼn=%ş" }, "search-base-dns": { "description": "Åʼn äřřäy őƒ þäşę đʼnş ŧő şęäřčĥ ŧĥřőūģĥ.", - "label": "Ŝęäřčĥ þäşę ĐŃŜ *", + "error": "Ŝęäřčĥ þäşę ĐŃŜ įş ä řęqūįřęđ ƒįęľđ", + "label": "Ŝęäřčĥ þäşę ĐŃŜ", "placeholder": "ęχämpľę: đč=ģřäƒäʼnä,đč=őřģ" }, "subtitle": "Ŧĥę ĿĐÅP įʼnŧęģřäŧįőʼn įʼn Ğřäƒäʼnä äľľőŵş yőūř Ğřäƒäʼnä ūşęřş ŧő ľőģ įʼn ŵįŧĥ ŧĥęįř ĿĐÅP čřęđęʼnŧįäľş. Fįʼnđ őūŧ mőřę įʼn őūř <2><0>đőčūmęʼnŧäŧįőʼn.", From d999b415df78ceba5302e3c0f98975a870cce8c8 Mon Sep 17 00:00:00 2001 From: Georges Chaudy Date: Fri, 11 Oct 2024 11:11:33 +0200 Subject: [PATCH 069/110] UniStore: Use epoch with microsecond resolution as RV (#92638) * Use epoch with microsecond resolution as RV * fix backend tests * Add solution for when the clock goes back * Add solution for when the clock goes back * generate mocks * go lint * remove comment * Use Greatest instead of max in msyql and postgres * update tests * Update pkg/storage/unified/sql/sqltemplate/dialect_sqlite.go Co-authored-by: Diego Augusto Molina * cast to bigint * add additional round trip * increment the RV using 2 sql round trips instead of 3 * cleanup comments * cast unix timestamp to integer * fix postgres query * remove old increment test data * remove greatest * cast unix_timestamp to signed * Use statement_timestamp instead of clock_timestamp --------- Co-authored-by: Diego Augusto Molina --- pkg/storage/unified/sql/backend.go | 92 ++++++++--------- pkg/storage/unified/sql/backend_test.go | 99 +++++++++---------- .../unified/sql/data/resource_version_get.sql | 3 +- .../sql/data/resource_version_insert.sql | 2 +- ...on_inc.sql => resource_version_update.sql} | 2 +- pkg/storage/unified/sql/queries.go | 31 ++++-- pkg/storage/unified/sql/queries_test.go | 48 ++++++--- .../unified/sql/sqltemplate/dialect.go | 3 + .../unified/sql/sqltemplate/dialect_mysql.go | 4 + .../sql/sqltemplate/dialect_postgresql.go | 4 + .../unified/sql/sqltemplate/dialect_sqlite.go | 5 + .../sql/sqltemplate/mocks/SQLTemplateIface.go | 45 +++++++++ .../sql/sqltemplate/mocks/WithResults.go | 45 +++++++++ .../unified/sql/test/integration_test.go | 92 ++++++++++------- ...ry_insert-insert into resource_history.sql | 8 +- ...resource_read-without_resource_version.sql | 8 +- .../mysql--resource_update-single path.sql | 8 +- ...ysql--resource_version_get-single path.sql | 7 +- ...version_inc-increment resource version.sql | 7 -- ...l--resource_version_insert-single path.sql | 2 +- ...sion_update-increment resource version.sql | 7 ++ ...ry_insert-insert into resource_history.sql | 8 +- ...resource_read-without_resource_version.sql | 8 +- .../postgres--resource_update-single path.sql | 8 +- ...gres--resource_version_get-single path.sql | 7 +- ...version_inc-increment resource version.sql | 7 -- ...s--resource_version_insert-single path.sql | 2 +- ...sion_update-increment resource version.sql | 7 ++ ...ry_insert-insert into resource_history.sql | 8 +- ...resource_read-without_resource_version.sql | 8 +- .../sqlite--resource_update-single path.sql | 8 +- ...lite--resource_version_get-single path.sql | 7 +- ...version_inc-increment resource version.sql | 7 -- ...e--resource_version_insert-single path.sql | 2 +- ...sion_update-increment resource version.sql | 7 ++ 35 files changed, 391 insertions(+), 225 deletions(-) rename pkg/storage/unified/sql/data/{resource_version_inc.sql => resource_version_update.sql} (71%) delete mode 100755 pkg/storage/unified/sql/testdata/mysql--resource_version_inc-increment resource version.sql create mode 100755 pkg/storage/unified/sql/testdata/mysql--resource_version_update-increment resource version.sql delete mode 100755 pkg/storage/unified/sql/testdata/postgres--resource_version_inc-increment resource version.sql create mode 100755 pkg/storage/unified/sql/testdata/postgres--resource_version_update-increment resource version.sql delete mode 100755 pkg/storage/unified/sql/testdata/sqlite--resource_version_inc-increment resource version.sql create mode 100755 pkg/storage/unified/sql/testdata/sqlite--resource_version_update-increment resource version.sql diff --git a/pkg/storage/unified/sql/backend.go b/pkg/storage/unified/sql/backend.go index 556c860bbb7..950814ba9d8 100644 --- a/pkg/storage/unified/sql/backend.go +++ b/pkg/storage/unified/sql/backend.go @@ -593,12 +593,12 @@ func (b *backend) listLatestRVs(ctx context.Context) (groupResourceRV, error) { // fetchLatestRV returns the current maximum RV in the resource table func fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, group, resource string) (int64, error) { - res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionRequest{ - SQLTemplate: sqltemplate.New(d), - Group: group, - Resource: resource, - ReadOnly: true, - resourceVersion: new(resourceVersion), + res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{ + SQLTemplate: sqltemplate.New(d), + Group: group, + Resource: resource, + ReadOnly: true, + Response: new(resourceVersionResponse), }) if errors.Is(err, sql.ErrNoRows) { return 1, nil @@ -611,7 +611,6 @@ func fetchLatestRV(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialec func (b *backend) poll(ctx context.Context, grp string, res string, since int64, stream chan<- *resource.WrittenEvent) (int64, error) { ctx, span := b.tracer.Start(ctx, tracePrefix+"poll") defer span.End() - var records []*historyPollResponse err := b.db.WithTx(ctx, ReadCommittedRO, func(ctx context.Context, tx db.Tx) error { var err error @@ -658,53 +657,58 @@ func (b *backend) poll(ctx context.Context, grp string, res string, since int64, return nextRV, nil } -// resourceVersionAtomicInc atomically increases the version of a kind within a -// transaction. +// resourceVersionAtomicInc atomically increases the version of a kind within a transaction. // TODO: Ideally we should attempt to update the RV in the resource and resource_history tables // in a single roundtrip. This would reduce the latency of the operation, and also increase the // throughput of the system. This is a good candidate for a future optimization. func resourceVersionAtomicInc(ctx context.Context, x db.ContextExecer, d sqltemplate.Dialect, key *resource.ResourceKey) (newVersion int64, err error) { - // TODO: refactor this code to run in a multi-statement transaction in order to minimize the number of round trips. - // 1 Lock the row for update - rv, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionRequest{ - SQLTemplate: sqltemplate.New(d), - Group: key.Group, - Resource: key.Resource, - resourceVersion: new(resourceVersion), - }) - - if errors.Is(err, sql.ErrNoRows) { - // if there wasn't a row associated with the given resource, we create one with - // version 2 to match the etcd behavior. - if _, err = dbutil.Exec(ctx, x, sqlResourceVersionInsert, sqlResourceVersionRequest{ - SQLTemplate: sqltemplate.New(d), - Group: key.Group, - Resource: key.Resource, - resourceVersion: &resourceVersion{1}, - }); err != nil { - return 0, fmt.Errorf("insert into resource_version: %w", err) - } - return 2, nil - } - - if err != nil { - return 0, fmt.Errorf("get current resource version: %w", err) - } - nextRV := rv.ResourceVersion + 1 - - // 2. Increment the resource version - _, err = dbutil.Exec(ctx, x, sqlResourceVersionInc, sqlResourceVersionRequest{ + // 1. Lock to row and prevent concurrent updates until the transaction is committed. + res, err := dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{ SQLTemplate: sqltemplate.New(d), Group: key.Group, Resource: key.Resource, - resourceVersion: &resourceVersion{ - ResourceVersion: nextRV, - }, + + Response: new(resourceVersionResponse), ReadOnly: false, // This locks the row for update + }) + + if errors.Is(err, sql.ErrNoRows) { + // if there wasn't a row associated with the given resource, then we create it. + if _, err = dbutil.Exec(ctx, x, sqlResourceVersionInsert, sqlResourceVersionUpsertRequest{ + SQLTemplate: sqltemplate.New(d), + Group: key.Group, + Resource: key.Resource, + }); err != nil { + return 0, fmt.Errorf("insert into resource_version: %w", err) + } + res, err = dbutil.QueryRow(ctx, x, sqlResourceVersionGet, sqlResourceVersionGetRequest{ + SQLTemplate: sqltemplate.New(d), + Group: key.Group, + Resource: key.Resource, + Response: new(resourceVersionResponse), + ReadOnly: true, // This locks the row for update + }) + if err != nil { + return 0, fmt.Errorf("fetching RV after read") + } + return res.ResourceVersion, nil + } else if err != nil { + return 0, fmt.Errorf("lock the resource version: %w", err) + } + + // 2. Update the RV + // Most times, the RV is the current microsecond timestamp generated on the sql server (to avoid clock skew). + // In rare occasion, the server clock might go back in time. In those cases, we simply increment the + // previous RV until the clock catches up. + nextRV := max(res.CurrentEpoch, res.ResourceVersion+1) + + _, err = dbutil.Exec(ctx, x, sqlResourceVersionUpdate, sqlResourceVersionUpsertRequest{ + SQLTemplate: sqltemplate.New(d), + Group: key.Group, + Resource: key.Resource, + ResourceVersion: nextRV, }) if err != nil { return 0, fmt.Errorf("increase resource version: %w", err) } - - // 3. Return the incremented value return nextRV, nil } diff --git a/pkg/storage/unified/sql/backend_test.go b/pkg/storage/unified/sql/backend_test.go index 33b7bab7d6a..0d96d73896d 100644 --- a/pkg/storage/unified/sql/backend_test.go +++ b/pkg/storage/unified/sql/backend_test.go @@ -36,8 +36,8 @@ type testBackend struct { test.TestDBProvider } -func (b testBackend) ExecWithResult(expectedSQL string) { - b.SQLMock.ExpectExec(expectedSQL).WillReturnResult(sqlmock.NewResult(0, 0)) +func (b testBackend) ExecWithResult(expectedSQL string, lastInsertID int64, rowsAffected int64) { + b.SQLMock.ExpectExec(expectedSQL).WillReturnResult(sqlmock.NewResult(lastInsertID, rowsAffected)) } func (b testBackend) ExecWithErr(expectedSQL string, err error) { @@ -204,8 +204,8 @@ func TestBackend_IsHealthy(t *testing.T) { // expectSuccessfulResourceVersionAtomicInc sets up expectations for calling // resourceVersionAtomicInc, where the returned RV will be 1. func expectSuccessfulResourceVersionAtomicInc(t *testing.T, b testBackend) { - b.QueryWithResult("select resource_version for update", 0, nil) - b.ExecWithResult("insert resource_version") + b.QueryWithResult("select resource_version for update", 2, Rows{{12345, 23456}}) + b.ExecWithResult("update resource_version set resource_version", 0, 0) } // expectUnsuccessfulResourceVersionAtomicInc sets up expectations for calling @@ -227,7 +227,7 @@ func TestResourceVersionAtomicInc(t *testing.T) { v, err := resourceVersionAtomicInc(ctx, b.DB, dialect, resKey) require.NoError(t, err) - require.Equal(t, int64(2), v) + require.Equal(t, int64(23456), v) }) t.Run("happy path - update existing row", func(t *testing.T) { @@ -235,24 +235,23 @@ func TestResourceVersionAtomicInc(t *testing.T) { b, ctx := setupBackendTest(t) - b.QueryWithResult("select resource_version for update", 1, Rows{{2}}) - b.ExecWithResult("update resource_version") + b.QueryWithResult("select resource_version for update", 2, Rows{{12345, 23456}}) + b.ExecWithResult("update resource_version", 0, 1) v, err := resourceVersionAtomicInc(ctx, b.DB, dialect, resKey) require.NoError(t, err) - require.Equal(t, int64(3), v) + require.Equal(t, int64(23456), v) }) t.Run("error getting current version", func(t *testing.T) { t.Parallel() b, ctx := setupBackendTest(t) - b.QueryWithErr("select resource_version for update", errTest) v, err := resourceVersionAtomicInc(ctx, b.DB, dialect, resKey) require.Zero(t, v) require.Error(t, err) - require.ErrorContains(t, err, "get current resource version") + require.ErrorContains(t, err, "lock the resource version") }) t.Run("error inserting new row", func(t *testing.T) { @@ -260,7 +259,7 @@ func TestResourceVersionAtomicInc(t *testing.T) { b, ctx := setupBackendTest(t) - b.QueryWithResult("select resource_version for update", 0, nil) + b.QueryWithResult("select resource_version", 0, Rows{}) b.ExecWithErr("insert resource_version", errTest) v, err := resourceVersionAtomicInc(ctx, b.DB, dialect, resKey) @@ -273,7 +272,7 @@ func TestResourceVersionAtomicInc(t *testing.T) { t.Parallel() b, ctx := setupBackendTest(t) - b.QueryWithResult("select resource_version for update", 1, Rows{{2}}) + b.QueryWithResult("select resource_version for update", 2, Rows{{12345, 23456}}) b.ExecWithErr("update resource_version", errTest) v, err := resourceVersionAtomicInc(ctx, b.DB, dialect, resKey) @@ -295,16 +294,16 @@ func TestBackend_create(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("insert resource") - b.ExecWithResult("insert resource_history") + b.ExecWithResult("insert resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) expectSuccessfulResourceVersionAtomicInc(t, b) // returns RV=1 - b.ExecWithResult("update resource_history") - b.ExecWithResult("update resource") + b.ExecWithResult("update resource_history", 0, 1) + b.ExecWithResult("update resource", 0, 1) b.SQLMock.ExpectCommit() v, err := b.create(ctx, event) require.NoError(t, err) - require.Equal(t, int64(2), v) + require.Equal(t, int64(23456), v) }) t.Run("error inserting into resource", func(t *testing.T) { @@ -326,7 +325,7 @@ func TestBackend_create(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("insert resource") + b.ExecWithResult("insert resource", 0, 1) b.ExecWithErr("insert resource_history", errTest) b.SQLMock.ExpectRollback() @@ -341,8 +340,8 @@ func TestBackend_create(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("insert resource") - b.ExecWithResult("insert resource_history") + b.ExecWithResult("insert resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) expectUnsuccessfulResourceVersionAtomicInc(t, b, errTest) b.SQLMock.ExpectRollback() @@ -357,8 +356,8 @@ func TestBackend_create(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("insert resource") - b.ExecWithResult("insert resource_history") + b.ExecWithResult("insert resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) expectSuccessfulResourceVersionAtomicInc(t, b) b.ExecWithErr("update resource_history", errTest) b.SQLMock.ExpectRollback() @@ -366,7 +365,7 @@ func TestBackend_create(t *testing.T) { v, err := b.create(ctx, event) require.Zero(t, v) require.Error(t, err) - require.ErrorContains(t, err, "update resource_history") + require.ErrorContains(t, err, "update resource_history", 0, 1) }) t.Run("error updating resource", func(t *testing.T) { @@ -374,10 +373,10 @@ func TestBackend_create(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("insert resource") - b.ExecWithResult("insert resource_history") + b.ExecWithResult("insert resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) expectSuccessfulResourceVersionAtomicInc(t, b) - b.ExecWithResult("update resource_history") + b.ExecWithResult("update resource_history", 0, 1) b.ExecWithErr("update resource", errTest) b.SQLMock.ExpectRollback() @@ -400,16 +399,16 @@ func TestBackend_update(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("update resource") - b.ExecWithResult("insert resource_history") - expectSuccessfulResourceVersionAtomicInc(t, b) // returns RV=1 - b.ExecWithResult("update resource_history") - b.ExecWithResult("update resource") + b.ExecWithResult("update resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) + expectSuccessfulResourceVersionAtomicInc(t, b) + b.ExecWithResult("update resource_history", 0, 1) + b.ExecWithResult("update resource", 0, 1) b.SQLMock.ExpectCommit() v, err := b.update(ctx, event) require.NoError(t, err) - require.Equal(t, int64(2), v) + require.Equal(t, int64(23456), v) }) t.Run("error in first update to resource", func(t *testing.T) { @@ -431,7 +430,7 @@ func TestBackend_update(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("update resource") + b.ExecWithResult("update resource", 0, 1) b.ExecWithErr("insert resource_history", errTest) b.SQLMock.ExpectRollback() @@ -446,8 +445,8 @@ func TestBackend_update(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("update resource") - b.ExecWithResult("insert resource_history") + b.ExecWithResult("update resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) expectUnsuccessfulResourceVersionAtomicInc(t, b, errTest) b.SQLMock.ExpectRollback() @@ -462,8 +461,8 @@ func TestBackend_update(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("update resource") - b.ExecWithResult("insert resource_history") + b.ExecWithResult("update resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) expectSuccessfulResourceVersionAtomicInc(t, b) // returns RV=1 b.ExecWithErr("update resource_history", errTest) b.SQLMock.ExpectRollback() @@ -479,10 +478,10 @@ func TestBackend_update(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("update resource") - b.ExecWithResult("insert resource_history") + b.ExecWithResult("update resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) expectSuccessfulResourceVersionAtomicInc(t, b) // returns RV=1 - b.ExecWithResult("update resource_history") + b.ExecWithResult("update resource_history", 0, 1) b.ExecWithErr("update resource", errTest) b.SQLMock.ExpectRollback() @@ -505,15 +504,15 @@ func TestBackend_delete(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("delete resource") - b.ExecWithResult("insert resource_history") - expectSuccessfulResourceVersionAtomicInc(t, b) // returns RV=1 - b.ExecWithResult("update resource_history") + b.ExecWithResult("delete resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) + expectSuccessfulResourceVersionAtomicInc(t, b) + b.ExecWithResult("update resource_history", 0, 1) b.SQLMock.ExpectCommit() v, err := b.delete(ctx, event) require.NoError(t, err) - require.Equal(t, int64(2), v) + require.Equal(t, int64(23456), v) }) t.Run("error deleting resource", func(t *testing.T) { @@ -535,7 +534,7 @@ func TestBackend_delete(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("delete resource") + b.ExecWithResult("delete resource", 0, 1) b.ExecWithErr("insert resource_history", errTest) b.SQLMock.ExpectCommit() @@ -550,8 +549,8 @@ func TestBackend_delete(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("delete resource") - b.ExecWithResult("insert resource_history") + b.ExecWithResult("delete resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) expectUnsuccessfulResourceVersionAtomicInc(t, b, errTest) b.SQLMock.ExpectCommit() @@ -566,8 +565,8 @@ func TestBackend_delete(t *testing.T) { b, ctx := setupBackendTest(t) b.SQLMock.ExpectBegin() - b.ExecWithResult("delete resource") - b.ExecWithResult("insert resource_history") + b.ExecWithResult("delete resource", 0, 1) + b.ExecWithResult("insert resource_history", 0, 1) expectSuccessfulResourceVersionAtomicInc(t, b) // returns RV=1 b.ExecWithErr("update resource_history", errTest) b.SQLMock.ExpectCommit() diff --git a/pkg/storage/unified/sql/data/resource_version_get.sql b/pkg/storage/unified/sql/data/resource_version_get.sql index e63f0bb34de..8228179e50e 100644 --- a/pkg/storage/unified/sql/data/resource_version_get.sql +++ b/pkg/storage/unified/sql/data/resource_version_get.sql @@ -1,5 +1,6 @@ SELECT - {{ .Ident "resource_version" | .Into .ResourceVersion }} + {{ .Ident "resource_version" | .Into .Response.ResourceVersion }}, + {{ .CurrentEpoch | .Into .Response.CurrentEpoch }} FROM {{ .Ident "resource_version" }} WHERE 1 = 1 AND {{ .Ident "group" }} = {{ .Arg .Group }} diff --git a/pkg/storage/unified/sql/data/resource_version_insert.sql b/pkg/storage/unified/sql/data/resource_version_insert.sql index 6c3aab0dcd4..d1ee612d9a0 100644 --- a/pkg/storage/unified/sql/data/resource_version_insert.sql +++ b/pkg/storage/unified/sql/data/resource_version_insert.sql @@ -8,6 +8,6 @@ INSERT INTO {{ .Ident "resource_version" }} VALUES ( {{ .Arg .Group }}, {{ .Arg .Resource }}, - 2 + {{ .CurrentEpoch }} ) ; diff --git a/pkg/storage/unified/sql/data/resource_version_inc.sql b/pkg/storage/unified/sql/data/resource_version_update.sql similarity index 71% rename from pkg/storage/unified/sql/data/resource_version_inc.sql rename to pkg/storage/unified/sql/data/resource_version_update.sql index e7bf52fd1eb..8cfec671aab 100644 --- a/pkg/storage/unified/sql/data/resource_version_inc.sql +++ b/pkg/storage/unified/sql/data/resource_version_update.sql @@ -1,6 +1,6 @@ UPDATE {{ .Ident "resource_version" }} SET - {{ .Ident "resource_version" }} = {{ .Arg .ResourceVersion}} + {{ .Ident "resource_version" }} = {{ .Arg .ResourceVersion }} WHERE 1 = 1 AND {{ .Ident "group" }} = {{ .Arg .Group }} AND {{ .Ident "resource" }} = {{ .Arg .Resource }} diff --git a/pkg/storage/unified/sql/queries.go b/pkg/storage/unified/sql/queries.go index dc31b7726c4..999d001a7e9 100644 --- a/pkg/storage/unified/sql/queries.go +++ b/pkg/storage/unified/sql/queries.go @@ -41,7 +41,7 @@ var ( // sqlResourceLabelsInsert = mustTemplate("resource_labels_insert.sql") sqlResourceVersionGet = mustTemplate("resource_version_get.sql") - sqlResourceVersionInc = mustTemplate("resource_version_inc.sql") + sqlResourceVersionUpdate = mustTemplate("resource_version_update.sql") sqlResourceVersionInsert = mustTemplate("resource_version_insert.sql") sqlResourceVersionList = mustTemplate("resource_version_list.sql") ) @@ -191,8 +191,13 @@ func (r sqlResourceUpdateRVRequest) Validate() error { } // resource_version table requests. -type resourceVersion struct { +type resourceVersionResponse struct { ResourceVersion int64 + CurrentEpoch int64 +} + +func (r *resourceVersionResponse) Results() (*resourceVersionResponse, error) { + return r, nil } type groupResourceVersion struct { @@ -200,20 +205,32 @@ type groupResourceVersion struct { ResourceVersion int64 } -func (r *resourceVersion) Results() (*resourceVersion, error) { - return r, nil +type sqlResourceVersionUpsertRequest struct { + sqltemplate.SQLTemplate + Group, Resource string + ResourceVersion int64 } -type sqlResourceVersionRequest struct { +func (r sqlResourceVersionUpsertRequest) Validate() error { + return nil // TODO +} + +type sqlResourceVersionGetRequest struct { sqltemplate.SQLTemplate Group, Resource string ReadOnly bool - *resourceVersion + Response *resourceVersionResponse } -func (r sqlResourceVersionRequest) Validate() error { +func (r sqlResourceVersionGetRequest) Validate() error { return nil // TODO } +func (r sqlResourceVersionGetRequest) Results() (*resourceVersionResponse, error) { + return &resourceVersionResponse{ + ResourceVersion: r.Response.ResourceVersion, + CurrentEpoch: r.Response.CurrentEpoch, + }, nil +} type sqlResourceVersionListRequest struct { sqltemplate.SQLTemplate diff --git a/pkg/storage/unified/sql/queries_test.go b/pkg/storage/unified/sql/queries_test.go index df7ed9167f7..5e2557af8c4 100644 --- a/pkg/storage/unified/sql/queries_test.go +++ b/pkg/storage/unified/sql/queries_test.go @@ -52,7 +52,12 @@ func TestUnifiedStorageQueries(t *testing.T) { Data: &sqlResourceRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), WriteEvent: resource.WriteEvent{ - Key: &resource.ResourceKey{}, + Key: &resource.ResourceKey{ + Namespace: "nn", + Group: "gg", + Resource: "rr", + Name: "name", + }, }, }, }, @@ -63,7 +68,12 @@ func TestUnifiedStorageQueries(t *testing.T) { Data: &sqlResourceReadRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), Request: &resource.ReadRequest{ - Key: &resource.ResourceKey{}, + Key: &resource.ResourceKey{ + Namespace: "nn", + Group: "gg", + Resource: "rr", + Name: "name", + }, }, readResponse: new(readResponse), }, @@ -155,7 +165,12 @@ func TestUnifiedStorageQueries(t *testing.T) { Data: &sqlResourceRequest{ SQLTemplate: mocks.NewTestingSQLTemplate(), WriteEvent: resource.WriteEvent{ - Key: &resource.ResourceKey{}, + Key: &resource.ResourceKey{ + Namespace: "nn", + Group: "gg", + Resource: "rr", + Name: "name", + }, PreviousRV: 1234, }, }, @@ -165,22 +180,24 @@ func TestUnifiedStorageQueries(t *testing.T) { sqlResourceVersionGet: { { Name: "single path", - Data: &sqlResourceVersionRequest{ - SQLTemplate: mocks.NewTestingSQLTemplate(), - resourceVersion: new(resourceVersion), - ReadOnly: false, + Data: &sqlResourceVersionGetRequest{ + SQLTemplate: mocks.NewTestingSQLTemplate(), + Resource: "resource", + Group: "group", + Response: new(resourceVersionResponse), + ReadOnly: false, }, }, }, - sqlResourceVersionInc: { + sqlResourceVersionUpdate: { { Name: "increment resource version", - Data: &sqlResourceVersionRequest{ - SQLTemplate: mocks.NewTestingSQLTemplate(), - resourceVersion: &resourceVersion{ - ResourceVersion: 123, - }, + Data: &sqlResourceVersionUpsertRequest{ + SQLTemplate: mocks.NewTestingSQLTemplate(), + Resource: "resource", + Group: "group", + ResourceVersion: int64(12354), }, }, }, @@ -188,8 +205,9 @@ func TestUnifiedStorageQueries(t *testing.T) { sqlResourceVersionInsert: { { Name: "single path", - Data: &sqlResourceVersionRequest{ - SQLTemplate: mocks.NewTestingSQLTemplate(), + Data: &sqlResourceVersionUpsertRequest{ + SQLTemplate: mocks.NewTestingSQLTemplate(), + ResourceVersion: int64(12354), }, }, }, diff --git a/pkg/storage/unified/sql/sqltemplate/dialect.go b/pkg/storage/unified/sql/sqltemplate/dialect.go index 5806c38b33e..918545fdb25 100644 --- a/pkg/storage/unified/sql/sqltemplate/dialect.go +++ b/pkg/storage/unified/sql/sqltemplate/dialect.go @@ -62,6 +62,9 @@ type Dialect interface { // WHERE id = ? // {{ .SelectFor "Update NoWait" }}; -- will be uppercased SelectFor(...string) (string, error) + + // CurrentEpoch returns the current epoch value for the database in microseconds. + CurrentEpoch() string } // RowLockingClause represents a row-locking clause in a SELECT statement. diff --git a/pkg/storage/unified/sql/sqltemplate/dialect_mysql.go b/pkg/storage/unified/sql/sqltemplate/dialect_mysql.go index 6101667fa51..14fd18e0456 100644 --- a/pkg/storage/unified/sql/sqltemplate/dialect_mysql.go +++ b/pkg/storage/unified/sql/sqltemplate/dialect_mysql.go @@ -33,3 +33,7 @@ func (backtickIdent) Ident(s string) (string, error) { return s }) } + +func (mysql) CurrentEpoch() string { + return "CAST(FLOOR(UNIX_TIMESTAMP(NOW(6)) * 1000000) AS SIGNED)" +} diff --git a/pkg/storage/unified/sql/sqltemplate/dialect_postgresql.go b/pkg/storage/unified/sql/sqltemplate/dialect_postgresql.go index 39e8603ae0b..a0dd76010eb 100644 --- a/pkg/storage/unified/sql/sqltemplate/dialect_postgresql.go +++ b/pkg/storage/unified/sql/sqltemplate/dialect_postgresql.go @@ -35,3 +35,7 @@ func (p postgresql) Ident(s string) (string, error) { return p.standardIdent.Ident(s) } + +func (postgresql) CurrentEpoch() string { + return "(EXTRACT(EPOCH FROM statement_timestamp()) * 1000000)::BIGINT" +} diff --git a/pkg/storage/unified/sql/sqltemplate/dialect_sqlite.go b/pkg/storage/unified/sql/sqltemplate/dialect_sqlite.go index 8a41a8f2c4d..f84ea78c477 100644 --- a/pkg/storage/unified/sql/sqltemplate/dialect_sqlite.go +++ b/pkg/storage/unified/sql/sqltemplate/dialect_sqlite.go @@ -16,3 +16,8 @@ type sqlite struct { argPlaceholderFunc name } + +func (sqlite) CurrentEpoch() string { + // Alternative approaches like `unixepoch('subsecond') * 1000000` returns millisecond precision. + return "CAST((julianday('now') - 2440587.5) * 86400000000.0 AS BIGINT)" +} diff --git a/pkg/storage/unified/sql/sqltemplate/mocks/SQLTemplateIface.go b/pkg/storage/unified/sql/sqltemplate/mocks/SQLTemplateIface.go index d714c54e46d..86cbb32fd5b 100644 --- a/pkg/storage/unified/sql/sqltemplate/mocks/SQLTemplateIface.go +++ b/pkg/storage/unified/sql/sqltemplate/mocks/SQLTemplateIface.go @@ -171,6 +171,51 @@ func (_c *SQLTemplate_ArgPlaceholder_Call) RunAndReturn(run func(int) string) *S return _c } +// CurrentEpoch provides a mock function with given fields: +func (_m *SQLTemplate) CurrentEpoch() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for CurrentEpoch") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// SQLTemplate_CurrentEpoch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CurrentEpoch' +type SQLTemplate_CurrentEpoch_Call struct { + *mock.Call +} + +// CurrentEpoch is a helper method to define mock.On call +func (_e *SQLTemplate_Expecter) CurrentEpoch() *SQLTemplate_CurrentEpoch_Call { + return &SQLTemplate_CurrentEpoch_Call{Call: _e.mock.On("CurrentEpoch")} +} + +func (_c *SQLTemplate_CurrentEpoch_Call) Run(run func()) *SQLTemplate_CurrentEpoch_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *SQLTemplate_CurrentEpoch_Call) Return(_a0 string) *SQLTemplate_CurrentEpoch_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *SQLTemplate_CurrentEpoch_Call) RunAndReturn(run func() string) *SQLTemplate_CurrentEpoch_Call { + _c.Call.Return(run) + return _c +} + // DialectName provides a mock function with given fields: func (_m *SQLTemplate) DialectName() string { ret := _m.Called() diff --git a/pkg/storage/unified/sql/sqltemplate/mocks/WithResults.go b/pkg/storage/unified/sql/sqltemplate/mocks/WithResults.go index ea9dd58baf2..50f71da1e28 100644 --- a/pkg/storage/unified/sql/sqltemplate/mocks/WithResults.go +++ b/pkg/storage/unified/sql/sqltemplate/mocks/WithResults.go @@ -171,6 +171,51 @@ func (_c *WithResults_ArgPlaceholder_Call[T]) RunAndReturn(run func(int) string) return _c } +// CurrentEpoch provides a mock function with given fields: +func (_m *WithResults[T]) CurrentEpoch() string { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for CurrentEpoch") + } + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// WithResults_CurrentEpoch_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CurrentEpoch' +type WithResults_CurrentEpoch_Call[T interface{}] struct { + *mock.Call +} + +// CurrentEpoch is a helper method to define mock.On call +func (_e *WithResults_Expecter[T]) CurrentEpoch() *WithResults_CurrentEpoch_Call[T] { + return &WithResults_CurrentEpoch_Call[T]{Call: _e.mock.On("CurrentEpoch")} +} + +func (_c *WithResults_CurrentEpoch_Call[T]) Run(run func()) *WithResults_CurrentEpoch_Call[T] { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *WithResults_CurrentEpoch_Call[T]) Return(_a0 string) *WithResults_CurrentEpoch_Call[T] { + _c.Call.Return(_a0) + return _c +} + +func (_c *WithResults_CurrentEpoch_Call[T]) RunAndReturn(run func() string) *WithResults_CurrentEpoch_Call[T] { + _c.Call.Return(run) + return _c +} + // DialectName provides a mock function with given fields: func (_m *WithResults[T]) DialectName() string { ret := _m.Called() diff --git a/pkg/storage/unified/sql/test/integration_test.go b/pkg/storage/unified/sql/test/integration_test.go index 6b54d131f95..d30210f583c 100644 --- a/pkg/storage/unified/sql/test/integration_test.go +++ b/pkg/storage/unified/sql/test/integration_test.go @@ -57,7 +57,9 @@ func newServer(t *testing.T) (sql.Backend, resource.ResourceServer) { } func TestIntegrationBackendHappyPath(t *testing.T) { - t.Skip("TODO: test blocking, skipping to unblock Enterprise until we fix this") + if infraDB.IsTestDbSQLite() { + t.Skip("TODO: test blocking, skipping to unblock Enterprise until we fix this") + } if testing.Short() { t.Skip("skipping integration test") } @@ -75,47 +77,48 @@ func TestIntegrationBackendHappyPath(t *testing.T) { stream, err := backend.WatchWriteEvents(context.Background()) // Using a different context to avoid canceling the stream after the DefaultContextTimeout require.NoError(t, err) + var rv1, rv2, rv3, rv4, rv5 int64 t.Run("Add 3 resources", func(t *testing.T) { - rv, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED) + rv1, err = writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED) require.NoError(t, err) - require.Equal(t, int64(1), rv) + require.Greater(t, rv1, int64(0)) - rv, err = writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED) + rv2, err = writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED) require.NoError(t, err) - require.Equal(t, int64(2), rv) + require.Greater(t, rv2, rv1) - rv, err = writeEvent(ctx, backend, "item3", resource.WatchEvent_ADDED) + rv3, err = writeEvent(ctx, backend, "item3", resource.WatchEvent_ADDED) require.NoError(t, err) - require.Equal(t, int64(3), rv) + require.Greater(t, rv3, rv2) }) t.Run("Update item2", func(t *testing.T) { - rv, err := writeEvent(ctx, backend, "item2", resource.WatchEvent_MODIFIED) + rv4, err = writeEvent(ctx, backend, "item2", resource.WatchEvent_MODIFIED) require.NoError(t, err) - require.Equal(t, int64(4), rv) + require.Greater(t, rv4, rv3) }) t.Run("Delete item1", func(t *testing.T) { - rv, err := writeEvent(ctx, backend, "item1", resource.WatchEvent_DELETED) + rv5, err = writeEvent(ctx, backend, "item1", resource.WatchEvent_DELETED) require.NoError(t, err) - require.Equal(t, int64(5), rv) + require.Greater(t, rv5, rv4) }) t.Run("Read latest item 2", func(t *testing.T) { resp := backend.ReadResource(ctx, &resource.ReadRequest{Key: resourceKey("item2")}) require.Nil(t, resp.Error) - require.Equal(t, int64(4), resp.ResourceVersion) + require.Equal(t, rv4, resp.ResourceVersion) require.Equal(t, "item2 MODIFIED", string(resp.Value)) }) t.Run("Read early version of item2", func(t *testing.T) { resp := backend.ReadResource(ctx, &resource.ReadRequest{ Key: resourceKey("item2"), - ResourceVersion: 3, // item2 was created at rv=2 and updated at rv=4 + ResourceVersion: rv3, // item2 was created at rv2 and updated at rv4 }) require.Nil(t, resp.Error) - require.Equal(t, int64(2), resp.ResourceVersion) + require.Equal(t, rv2, resp.ResourceVersion) require.Equal(t, "item2 ADDED", string(resp.Value)) }) @@ -134,38 +137,40 @@ func TestIntegrationBackendHappyPath(t *testing.T) { require.Len(t, resp.Items, 2) require.Equal(t, "item2 MODIFIED", string(resp.Items[0].Value)) require.Equal(t, "item3 ADDED", string(resp.Items[1].Value)) - require.Equal(t, int64(5), resp.ResourceVersion) + require.Equal(t, rv5, resp.ResourceVersion) }) t.Run("Watch events", func(t *testing.T) { event := <-stream require.Equal(t, "item1", event.Key.Name) - require.Equal(t, int64(1), event.ResourceVersion) + require.Equal(t, rv1, event.ResourceVersion) require.Equal(t, resource.WatchEvent_ADDED, event.Type) event = <-stream require.Equal(t, "item2", event.Key.Name) - require.Equal(t, int64(2), event.ResourceVersion) + require.Equal(t, rv2, event.ResourceVersion) require.Equal(t, resource.WatchEvent_ADDED, event.Type) event = <-stream require.Equal(t, "item3", event.Key.Name) - require.Equal(t, int64(3), event.ResourceVersion) + require.Equal(t, rv3, event.ResourceVersion) require.Equal(t, resource.WatchEvent_ADDED, event.Type) event = <-stream require.Equal(t, "item2", event.Key.Name) - require.Equal(t, int64(4), event.ResourceVersion) + require.Equal(t, rv4, event.ResourceVersion) require.Equal(t, resource.WatchEvent_MODIFIED, event.Type) event = <-stream require.Equal(t, "item1", event.Key.Name) - require.Equal(t, int64(5), event.ResourceVersion) + require.Equal(t, rv5, event.ResourceVersion) require.Equal(t, resource.WatchEvent_DELETED, event.Type) }) } func TestIntegrationBackendWatchWriteEventsFromLastest(t *testing.T) { - t.Skip("TODO: test blocking, skipping to unblock Enterprise until we fix this") + if infraDB.IsTestDbSQLite() { + t.Skip("TODO: test blocking, skipping to unblock Enterprise until we fix this") + } if testing.Short() { t.Skip("skipping integration test") } @@ -188,7 +193,9 @@ func TestIntegrationBackendWatchWriteEventsFromLastest(t *testing.T) { } func TestIntegrationBackendList(t *testing.T) { - t.Skip("TODO: test blocking, skipping to unblock Enterprise until we fix this") + if infraDB.IsTestDbSQLite() { + t.Skip("TODO: test blocking, skipping to unblock Enterprise until we fix this") + } if testing.Short() { t.Skip("skipping integration test") } @@ -197,14 +204,23 @@ func TestIntegrationBackendList(t *testing.T) { backend, server := newServer(t) // Create a few resources before starting the watch - _, _ = writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED) // rv=1 - _, _ = writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED) // rv=2 - will be modified at rv=6 - _, _ = writeEvent(ctx, backend, "item3", resource.WatchEvent_ADDED) // rv=3 - will be deleted at rv=7 - _, _ = writeEvent(ctx, backend, "item4", resource.WatchEvent_ADDED) // rv=4 - _, _ = writeEvent(ctx, backend, "item5", resource.WatchEvent_ADDED) // rv=5 - _, _ = writeEvent(ctx, backend, "item2", resource.WatchEvent_MODIFIED) // rv=6 - _, _ = writeEvent(ctx, backend, "item3", resource.WatchEvent_DELETED) // rv=7 - _, _ = writeEvent(ctx, backend, "item6", resource.WatchEvent_ADDED) // rv=8 + rv1, _ := writeEvent(ctx, backend, "item1", resource.WatchEvent_ADDED) + require.Greater(t, rv1, int64(0)) + rv2, _ := writeEvent(ctx, backend, "item2", resource.WatchEvent_ADDED) // rv=2 - will be modified at rv=6 + require.Greater(t, rv2, rv1) + rv3, _ := writeEvent(ctx, backend, "item3", resource.WatchEvent_ADDED) // rv=3 - will be deleted at rv=7 + require.Greater(t, rv3, rv2) + rv4, _ := writeEvent(ctx, backend, "item4", resource.WatchEvent_ADDED) + require.Greater(t, rv4, rv3) + rv5, _ := writeEvent(ctx, backend, "item5", resource.WatchEvent_ADDED) + require.Greater(t, rv5, rv4) + rv6, _ := writeEvent(ctx, backend, "item2", resource.WatchEvent_MODIFIED) + require.Greater(t, rv6, rv5) + rv7, _ := writeEvent(ctx, backend, "item3", resource.WatchEvent_DELETED) + require.Greater(t, rv7, rv6) + rv8, _ := writeEvent(ctx, backend, "item6", resource.WatchEvent_ADDED) + require.Greater(t, rv8, rv7) + t.Run("fetch all latest", func(t *testing.T) { res, err := server.List(ctx, &resource.ListRequest{ Options: &resource.ListOptions{ @@ -245,12 +261,12 @@ func TestIntegrationBackendList(t *testing.T) { require.Equal(t, "item1 ADDED", string(res.Items[0].Value)) require.Equal(t, "item2 MODIFIED", string(res.Items[1].Value)) require.Equal(t, "item4 ADDED", string(res.Items[2].Value)) - require.Equal(t, int64(8), continueToken.ResourceVersion) + require.Equal(t, rv8, continueToken.ResourceVersion) }) t.Run("list at revision", func(t *testing.T) { res, err := server.List(ctx, &resource.ListRequest{ - ResourceVersion: 4, + ResourceVersion: rv4, Options: &resource.ListOptions{ Key: &resource.ResourceKey{ Group: "group", @@ -271,7 +287,7 @@ func TestIntegrationBackendList(t *testing.T) { t.Run("fetch first page at revision with limit", func(t *testing.T) { res, err := server.List(ctx, &resource.ListRequest{ Limit: 3, - ResourceVersion: 7, + ResourceVersion: rv7, Options: &resource.ListOptions{ Key: &resource.ResourceKey{ Group: "group", @@ -290,12 +306,12 @@ func TestIntegrationBackendList(t *testing.T) { continueToken, err := sql.GetContinueToken(res.NextPageToken) require.NoError(t, err) - require.Equal(t, int64(7), continueToken.ResourceVersion) + require.Equal(t, rv7, continueToken.ResourceVersion) }) t.Run("fetch second page at revision", func(t *testing.T) { continueToken := &sql.ContinueToken{ - ResourceVersion: 8, + ResourceVersion: rv8, StartOffset: 2, } res, err := server.List(ctx, &resource.ListRequest{ @@ -317,12 +333,14 @@ func TestIntegrationBackendList(t *testing.T) { continueToken, err = sql.GetContinueToken(res.NextPageToken) require.NoError(t, err) - require.Equal(t, int64(8), continueToken.ResourceVersion) + require.Equal(t, rv8, continueToken.ResourceVersion) require.Equal(t, int64(4), continueToken.StartOffset) }) } func TestClientServer(t *testing.T) { - t.Skip("TODO: test blocking, skipping to unblock Enterprise until we fix this") + if infraDB.IsTestDbSQLite() { + t.Skip("TODO: test blocking, skipping to unblock Enterprise until we fix this") + } ctx := testutil.NewTestContext(t, time.Now().Add(5*time.Second)) dbstore := infraDB.InitTestDB(t) diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_history_insert-insert into resource_history.sql b/pkg/storage/unified/sql/testdata/mysql--resource_history_insert-insert into resource_history.sql index d76132ae625..27f85926301 100755 --- a/pkg/storage/unified/sql/testdata/mysql--resource_history_insert-insert into resource_history.sql +++ b/pkg/storage/unified/sql/testdata/mysql--resource_history_insert-insert into resource_history.sql @@ -11,10 +11,10 @@ INSERT INTO `resource_history` ) VALUES ( '', - '', - '', - '', - '', + 'gg', + 'rr', + 'nn', + 'name', 1234, '[]', 'UNKNOWN' diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_read-without_resource_version.sql b/pkg/storage/unified/sql/testdata/mysql--resource_read-without_resource_version.sql index 27530d7792d..10a19336dcd 100755 --- a/pkg/storage/unified/sql/testdata/mysql--resource_read-without_resource_version.sql +++ b/pkg/storage/unified/sql/testdata/mysql--resource_read-without_resource_version.sql @@ -3,8 +3,8 @@ SELECT `value` FROM `resource` WHERE 1 = 1 - AND `namespace` = '' - AND `group` = '' - AND `resource` = '' - AND `name` = '' + AND `namespace` = 'nn' + AND `group` = 'gg' + AND `resource` = 'rr' + AND `name` = 'name' ; diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_update-single path.sql b/pkg/storage/unified/sql/testdata/mysql--resource_update-single path.sql index f3a173f5a03..34582a97d87 100755 --- a/pkg/storage/unified/sql/testdata/mysql--resource_update-single path.sql +++ b/pkg/storage/unified/sql/testdata/mysql--resource_update-single path.sql @@ -4,8 +4,8 @@ UPDATE `resource` `value` = '[]', `action` = 'UNKNOWN' WHERE 1 = 1 - AND `group` = '' - AND `resource` = '' - AND `namespace` = '' - AND `name` = '' + AND `group` = 'gg' + AND `resource` = 'rr' + AND `namespace` = 'nn' + AND `name` = 'name' ; diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_version_get-single path.sql b/pkg/storage/unified/sql/testdata/mysql--resource_version_get-single path.sql index 15678ba3e7b..69d7075bd3d 100755 --- a/pkg/storage/unified/sql/testdata/mysql--resource_version_get-single path.sql +++ b/pkg/storage/unified/sql/testdata/mysql--resource_version_get-single path.sql @@ -1,8 +1,9 @@ SELECT - `resource_version` + `resource_version`, + CAST(FLOOR(UNIX_TIMESTAMP(NOW(6)) * 1000000) AS SIGNED) FROM `resource_version` WHERE 1 = 1 - AND `group` = '' - AND `resource` = '' + AND `group` = 'group' + AND `resource` = 'resource' FOR UPDATE ; diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_version_inc-increment resource version.sql b/pkg/storage/unified/sql/testdata/mysql--resource_version_inc-increment resource version.sql deleted file mode 100755 index 2a737b37014..00000000000 --- a/pkg/storage/unified/sql/testdata/mysql--resource_version_inc-increment resource version.sql +++ /dev/null @@ -1,7 +0,0 @@ -UPDATE `resource_version` -SET - `resource_version` = 123 -WHERE 1 = 1 - AND `group` = '' - AND `resource` = '' -; diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_version_insert-single path.sql b/pkg/storage/unified/sql/testdata/mysql--resource_version_insert-single path.sql index f99b2b00148..432520706ae 100755 --- a/pkg/storage/unified/sql/testdata/mysql--resource_version_insert-single path.sql +++ b/pkg/storage/unified/sql/testdata/mysql--resource_version_insert-single path.sql @@ -7,6 +7,6 @@ INSERT INTO `resource_version` VALUES ( '', '', - 2 + CAST(FLOOR(UNIX_TIMESTAMP(NOW(6)) * 1000000) AS SIGNED) ) ; diff --git a/pkg/storage/unified/sql/testdata/mysql--resource_version_update-increment resource version.sql b/pkg/storage/unified/sql/testdata/mysql--resource_version_update-increment resource version.sql new file mode 100755 index 00000000000..610283edb9f --- /dev/null +++ b/pkg/storage/unified/sql/testdata/mysql--resource_version_update-increment resource version.sql @@ -0,0 +1,7 @@ +UPDATE `resource_version` +SET + `resource_version` = 12354 +WHERE 1 = 1 + AND `group` = 'group' + AND `resource` = 'resource' +; diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_history_insert-insert into resource_history.sql b/pkg/storage/unified/sql/testdata/postgres--resource_history_insert-insert into resource_history.sql index a15a8db4b1e..c7db6c75739 100755 --- a/pkg/storage/unified/sql/testdata/postgres--resource_history_insert-insert into resource_history.sql +++ b/pkg/storage/unified/sql/testdata/postgres--resource_history_insert-insert into resource_history.sql @@ -11,10 +11,10 @@ INSERT INTO "resource_history" ) VALUES ( '', - '', - '', - '', - '', + 'gg', + 'rr', + 'nn', + 'name', 1234, '[]', 'UNKNOWN' diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_read-without_resource_version.sql b/pkg/storage/unified/sql/testdata/postgres--resource_read-without_resource_version.sql index e010d010dbc..5b8879b6c6a 100755 --- a/pkg/storage/unified/sql/testdata/postgres--resource_read-without_resource_version.sql +++ b/pkg/storage/unified/sql/testdata/postgres--resource_read-without_resource_version.sql @@ -3,8 +3,8 @@ SELECT "value" FROM "resource" WHERE 1 = 1 - AND "namespace" = '' - AND "group" = '' - AND "resource" = '' - AND "name" = '' + AND "namespace" = 'nn' + AND "group" = 'gg' + AND "resource" = 'rr' + AND "name" = 'name' ; diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_update-single path.sql b/pkg/storage/unified/sql/testdata/postgres--resource_update-single path.sql index c0b2f77ce6c..4febc55d530 100755 --- a/pkg/storage/unified/sql/testdata/postgres--resource_update-single path.sql +++ b/pkg/storage/unified/sql/testdata/postgres--resource_update-single path.sql @@ -4,8 +4,8 @@ UPDATE "resource" "value" = '[]', "action" = 'UNKNOWN' WHERE 1 = 1 - AND "group" = '' - AND "resource" = '' - AND "namespace" = '' - AND "name" = '' + AND "group" = 'gg' + AND "resource" = 'rr' + AND "namespace" = 'nn' + AND "name" = 'name' ; diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_version_get-single path.sql b/pkg/storage/unified/sql/testdata/postgres--resource_version_get-single path.sql index d6e8f041e5a..bec77d7b02e 100755 --- a/pkg/storage/unified/sql/testdata/postgres--resource_version_get-single path.sql +++ b/pkg/storage/unified/sql/testdata/postgres--resource_version_get-single path.sql @@ -1,8 +1,9 @@ SELECT - "resource_version" + "resource_version", + (EXTRACT(EPOCH FROM statement_timestamp()) * 1000000)::BIGINT FROM "resource_version" WHERE 1 = 1 - AND "group" = '' - AND "resource" = '' + AND "group" = 'group' + AND "resource" = 'resource' FOR UPDATE ; diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_version_inc-increment resource version.sql b/pkg/storage/unified/sql/testdata/postgres--resource_version_inc-increment resource version.sql deleted file mode 100755 index a6f7c024e25..00000000000 --- a/pkg/storage/unified/sql/testdata/postgres--resource_version_inc-increment resource version.sql +++ /dev/null @@ -1,7 +0,0 @@ -UPDATE "resource_version" -SET - "resource_version" = 123 -WHERE 1 = 1 - AND "group" = '' - AND "resource" = '' -; diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_version_insert-single path.sql b/pkg/storage/unified/sql/testdata/postgres--resource_version_insert-single path.sql index 14b25955585..42392fee7d0 100755 --- a/pkg/storage/unified/sql/testdata/postgres--resource_version_insert-single path.sql +++ b/pkg/storage/unified/sql/testdata/postgres--resource_version_insert-single path.sql @@ -7,6 +7,6 @@ INSERT INTO "resource_version" VALUES ( '', '', - 2 + (EXTRACT(EPOCH FROM statement_timestamp()) * 1000000)::BIGINT ) ; diff --git a/pkg/storage/unified/sql/testdata/postgres--resource_version_update-increment resource version.sql b/pkg/storage/unified/sql/testdata/postgres--resource_version_update-increment resource version.sql new file mode 100755 index 00000000000..9077af0ba47 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/postgres--resource_version_update-increment resource version.sql @@ -0,0 +1,7 @@ +UPDATE "resource_version" +SET + "resource_version" = 12354 +WHERE 1 = 1 + AND "group" = 'group' + AND "resource" = 'resource' +; diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_history_insert-insert into resource_history.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_history_insert-insert into resource_history.sql index a15a8db4b1e..c7db6c75739 100755 --- a/pkg/storage/unified/sql/testdata/sqlite--resource_history_insert-insert into resource_history.sql +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_history_insert-insert into resource_history.sql @@ -11,10 +11,10 @@ INSERT INTO "resource_history" ) VALUES ( '', - '', - '', - '', - '', + 'gg', + 'rr', + 'nn', + 'name', 1234, '[]', 'UNKNOWN' diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_read-without_resource_version.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_read-without_resource_version.sql index e010d010dbc..5b8879b6c6a 100755 --- a/pkg/storage/unified/sql/testdata/sqlite--resource_read-without_resource_version.sql +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_read-without_resource_version.sql @@ -3,8 +3,8 @@ SELECT "value" FROM "resource" WHERE 1 = 1 - AND "namespace" = '' - AND "group" = '' - AND "resource" = '' - AND "name" = '' + AND "namespace" = 'nn' + AND "group" = 'gg' + AND "resource" = 'rr' + AND "name" = 'name' ; diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_update-single path.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_update-single path.sql index c0b2f77ce6c..4febc55d530 100755 --- a/pkg/storage/unified/sql/testdata/sqlite--resource_update-single path.sql +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_update-single path.sql @@ -4,8 +4,8 @@ UPDATE "resource" "value" = '[]', "action" = 'UNKNOWN' WHERE 1 = 1 - AND "group" = '' - AND "resource" = '' - AND "namespace" = '' - AND "name" = '' + AND "group" = 'gg' + AND "resource" = 'rr' + AND "namespace" = 'nn' + AND "name" = 'name' ; diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_version_get-single path.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_version_get-single path.sql index 071a12f8677..9443791d642 100755 --- a/pkg/storage/unified/sql/testdata/sqlite--resource_version_get-single path.sql +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_version_get-single path.sql @@ -1,7 +1,8 @@ SELECT - "resource_version" + "resource_version", + CAST((julianday('now') - 2440587.5) * 86400000000.0 AS BIGINT) FROM "resource_version" WHERE 1 = 1 - AND "group" = '' - AND "resource" = '' + AND "group" = 'group' + AND "resource" = 'resource' ; diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_version_inc-increment resource version.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_version_inc-increment resource version.sql deleted file mode 100755 index a6f7c024e25..00000000000 --- a/pkg/storage/unified/sql/testdata/sqlite--resource_version_inc-increment resource version.sql +++ /dev/null @@ -1,7 +0,0 @@ -UPDATE "resource_version" -SET - "resource_version" = 123 -WHERE 1 = 1 - AND "group" = '' - AND "resource" = '' -; diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_version_insert-single path.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_version_insert-single path.sql index 14b25955585..e58e7094a3d 100755 --- a/pkg/storage/unified/sql/testdata/sqlite--resource_version_insert-single path.sql +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_version_insert-single path.sql @@ -7,6 +7,6 @@ INSERT INTO "resource_version" VALUES ( '', '', - 2 + CAST((julianday('now') - 2440587.5) * 86400000000.0 AS BIGINT) ) ; diff --git a/pkg/storage/unified/sql/testdata/sqlite--resource_version_update-increment resource version.sql b/pkg/storage/unified/sql/testdata/sqlite--resource_version_update-increment resource version.sql new file mode 100755 index 00000000000..9077af0ba47 --- /dev/null +++ b/pkg/storage/unified/sql/testdata/sqlite--resource_version_update-increment resource version.sql @@ -0,0 +1,7 @@ +UPDATE "resource_version" +SET + "resource_version" = 12354 +WHERE 1 = 1 + AND "group" = 'group' + AND "resource" = 'resource' +; From 773030f15cb758966282712cfcf568b2931525a8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:47:01 +0100 Subject: [PATCH 070/110] Update dependency @types/eslint to v9 (#94564) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- packages/grafana-plugin-configs/package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- yarn.lock | 18 ++++++++++++++---- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 6bf41f692ec..154584a3f1a 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "@types/d3-scale-chromatic": "3.0.3", "@types/debounce-promise": "3.1.9", "@types/diff": "^5", - "@types/eslint": "8.56.10", + "@types/eslint": "9.6.1", "@types/eslint-scope": "^3.7.7", "@types/file-saver": "2.0.7", "@types/glob": "^8.0.0", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index fa204ae3c75..003e7c9fbf9 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -9,7 +9,7 @@ "devDependencies": { "@grafana/tsconfig": "^2.0.0", "@swc/core": "1.4.2", - "@types/eslint": "8.56.10", + "@types/eslint": "9.6.1", "copy-webpack-plugin": "12.0.2", "eslint": "8.57.0", "eslint-webpack-plugin": "4.2.0", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index f0492ea2fe1..72c6fab3ba4 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -88,7 +88,7 @@ "@testing-library/user-event": "14.5.2", "@types/d3": "7.4.3", "@types/debounce-promise": "3.1.9", - "@types/eslint": "8.56.10", + "@types/eslint": "9.6.1", "@types/jest": "29.5.13", "@types/jquery": "3.5.31", "@types/lodash": "4.17.10", diff --git a/yarn.lock b/yarn.lock index 22f8b8cc7db..87a58674870 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3889,7 +3889,7 @@ __metadata: dependencies: "@grafana/tsconfig": "npm:^2.0.0" "@swc/core": "npm:1.4.2" - "@types/eslint": "npm:8.56.10" + "@types/eslint": "npm:9.6.1" copy-webpack-plugin: "npm:12.0.2" eslint: "npm:8.57.0" eslint-webpack-plugin: "npm:4.2.0" @@ -3948,7 +3948,7 @@ __metadata: "@testing-library/user-event": "npm:14.5.2" "@types/d3": "npm:7.4.3" "@types/debounce-promise": "npm:3.1.9" - "@types/eslint": "npm:8.56.10" + "@types/eslint": "npm:9.6.1" "@types/jest": "npm:29.5.13" "@types/jquery": "npm:3.5.31" "@types/lodash": "npm:4.17.10" @@ -9897,7 +9897,17 @@ __metadata: languageName: node linkType: hard -"@types/eslint@npm:*, @types/eslint@npm:8.56.10, @types/eslint@npm:^8.56.10": +"@types/eslint@npm:*, @types/eslint@npm:9.6.1": + version: 9.6.1 + resolution: "@types/eslint@npm:9.6.1" + dependencies: + "@types/estree": "npm:*" + "@types/json-schema": "npm:*" + checksum: 10/719fcd255760168a43d0e306ef87548e1e15bffe361d5f4022b0f266575637acc0ecb85604ac97879ee8ae83c6a6d0613b0ed31d0209ddf22a0fe6d608fc56fe + languageName: node + linkType: hard + +"@types/eslint@npm:^8.56.10": version: 8.56.10 resolution: "@types/eslint@npm:8.56.10" dependencies: @@ -18980,7 +18990,7 @@ __metadata: "@types/d3-scale-chromatic": "npm:3.0.3" "@types/debounce-promise": "npm:3.1.9" "@types/diff": "npm:^5" - "@types/eslint": "npm:8.56.10" + "@types/eslint": "npm:9.6.1" "@types/eslint-scope": "npm:^3.7.7" "@types/file-saver": "npm:2.0.7" "@types/glob": "npm:^8.0.0" From f08de95630f8d68ee1b5f1b7c8c0459dc9dc8434 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 11 Oct 2024 10:54:15 +0100 Subject: [PATCH 071/110] Alerting: Change styling so query results with no labels are clearer (#94404) --- .betterer.results | 5 +-- .../components/expressions/Expression.tsx | 44 +++++++++++-------- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/.betterer.results b/.betterer.results index 5aa32246134..7dff19d1230 100644 --- a/.betterer.results +++ b/.betterer.results @@ -1692,10 +1692,7 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], [0, 0, 0, "No untranslated strings. Wrap text with ", "6"], [0, 0, 0, "No untranslated strings. Wrap text with ", "7"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "8"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "9"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "10"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "11"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "8"] ], "public/app/features/alerting/unified/components/expressions/ExpressionStatusIndicator.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] diff --git a/public/app/features/alerting/unified/components/expressions/Expression.tsx b/public/app/features/alerting/unified/components/expressions/Expression.tsx index 20a0a9561bb..863ca7bd9db 100644 --- a/public/app/features/alerting/unified/components/expressions/Expression.tsx +++ b/public/app/features/alerting/unified/components/expressions/Expression.tsx @@ -4,7 +4,7 @@ import { FC, useCallback, useState } from 'react'; import { useFormContext } from 'react-hook-form'; import { DataFrame, dateTimeFormat, GrafanaTheme2, isTimeSeriesFrames, LoadingState, PanelData } from '@grafana/data'; -import { Alert, AutoSizeInput, Button, clearButtonStyles, IconButton, Stack, useStyles2 } from '@grafana/ui'; +import { Alert, AutoSizeInput, Button, clearButtonStyles, IconButton, Stack, Text, useStyles2 } from '@grafana/ui'; import { ClassicConditions } from 'app/features/expressions/components/ClassicConditions'; import { Math } from 'app/features/expressions/components/Math'; import { Reduce } from 'app/features/expressions/components/Reduce'; @@ -359,6 +359,11 @@ interface FrameProps extends Pick { index: number; } +const OpeningBracket = () => {'{'}; +const ClosingBracket = () => {'}'}; +const Quote = () => {'"'}; +const Equals = () => {'='}; + const FrameRow: FC = ({ frame, index, isAlertCondition }) => { const styles = useStyles2(getStyles); @@ -377,23 +382,26 @@ const FrameRow: FC = ({ frame, index, isAlertCondition }) => {
- {hasLabels ? '' : name} - {hasLabels && ( - <> - {'{'} - {labels.map(([key, value], index) => ( - - {key} - = - " - {value} - " - {index < labels.length - 1 && , } - - ))} - {'}'} - - )} + + {hasLabels ? ( + <> + + {labels.map(([key, value], index) => ( + + {key} + + + {value} + + {index < labels.length - 1 && , } + + ))} + + + ) : ( + {title} + )} +
{value}
{showFiring && } From 029b5bc2d043308ce52db23277e6751e53f7c6db Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Fri, 11 Oct 2024 12:07:43 +0200 Subject: [PATCH 072/110] Alerting: Default to right instant or range when filling alert rule from query params (#94272) * default to right instant or range when filling alert rule from query params * address pr review comment --- .../alert-rule-form/AlertRuleForm.tsx | 55 ++++++++++++++----- public/app/types/unified-alerting-dto.ts | 2 + 2 files changed, 44 insertions(+), 13 deletions(-) diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 57c385d856e..a22616ff227 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -1,6 +1,6 @@ import { css } from '@emotion/css'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { FormProvider, SubmitErrorHandler, UseFormWatch, useForm } from 'react-hook-form'; +import { FormProvider, SubmitErrorHandler, useForm, UseFormWatch } from 'react-hook-form'; import { useParams } from 'react-router-dom-v5-compat'; import { GrafanaTheme2 } from '@grafana/data'; @@ -20,12 +20,13 @@ import { isGrafanaRulerRulePaused, isRecordingRuleByType, } from 'app/features/alerting/unified/utils/rules'; +import { isExpressionQuery } from 'app/features/expressions/guards'; import { RuleGroupIdentifier, RuleIdentifier, RuleWithLocation } from 'app/types/unified-alerting'; import { PostableRuleGrafanaRuleDTO, RulerRuleDTO } from 'app/types/unified-alerting-dto'; import { - LogMessages, logInfo, + LogMessages, trackAlertRuleFormCancelled, trackAlertRuleFormError, trackAlertRuleFormSaved, @@ -35,20 +36,21 @@ import { useDeleteRuleFromGroup } from '../../../hooks/ruleGroup/useDeleteRuleFr import { useAddRuleToRuleGroup, useUpdateRuleInRuleGroup } from '../../../hooks/ruleGroup/useUpsertRuleFromRuleGroup'; import { useURLSearchParams } from '../../../hooks/useURLSearchParams'; import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; +import { DataSourceType } from '../../../utils/datasource'; import { DEFAULT_GROUP_EVALUATION_INTERVAL, - MANUAL_ROUTING_KEY, - SIMPLIFIED_QUERY_EDITOR_KEY, formValuesFromExistingRule, formValuesToRulerGrafanaRuleDTO, formValuesToRulerRuleDTO, getDefaultFormValues, getDefaultQueries, ignoreHiddenQueries, + MANUAL_ROUTING_KEY, normalizeDefaultAnnotations, + SIMPLIFIED_QUERY_EDITOR_KEY, } from '../../../utils/rule-form'; -import { fromRulerRule, fromRulerRuleAndRuleGroupIdentifier, stringifyIdentifier } from '../../../utils/rule-id'; import * as ruleId from '../../../utils/rule-id'; +import { fromRulerRule, fromRulerRuleAndRuleGroupIdentifier, stringifyIdentifier } from '../../../utils/rule-id'; import { createRelativeUrl } from '../../../utils/url'; import { GrafanaRuleExporter } from '../../export/GrafanaRuleExporter'; import { AlertRuleNameAndMetric } from '../AlertRuleNameInput'; @@ -384,14 +386,16 @@ function formValuesFromQueryParams(ruleDefinition: string, type: RuleFormType): }; } - return ignoreHiddenQueries({ - ...getDefaultFormValues(), - ...ruleFromQueryParams, - annotations: normalizeDefaultAnnotations(ruleFromQueryParams.annotations ?? []), - queries: ruleFromQueryParams.queries ?? getDefaultQueries(), - type: type || RuleFormType.grafana, - evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL, - }); + return setInstantOrRange( + ignoreHiddenQueries({ + ...getDefaultFormValues(), + ...ruleFromQueryParams, + annotations: normalizeDefaultAnnotations(ruleFromQueryParams.annotations ?? []), + queries: ruleFromQueryParams.queries ?? getDefaultQueries(), + type: type || RuleFormType.grafana, + evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL, + }) + ); } function formValuesFromPrefill(rule: Partial): RuleFormValues { @@ -401,6 +405,31 @@ function formValuesFromPrefill(rule: Partial): RuleFormValues { }); } +function setInstantOrRange(values: RuleFormValues): RuleFormValues { + return { + ...values, + queries: values.queries?.map((query) => { + if (isExpressionQuery(query.model)) { + return query; + } + // data query + const defaultToInstant = + query.model.datasource?.type === DataSourceType.Loki || + query.model.datasource?.type === DataSourceType.Prometheus; + const isInstant = + 'instant' in query.model && query.model.instant !== undefined ? query.model.instant : defaultToInstant; + return { + ...query, + model: { + ...query.model, + instant: isInstant, + range: !isInstant, // we cannot have both instant and range queries in alerting + }, + }; + }), + }; +} + function storeInLocalStorageValues(values: RuleFormValues) { if (values.manualRouting) { localStorage.setItem(MANUAL_ROUTING_KEY, 'true'); diff --git a/public/app/types/unified-alerting-dto.ts b/public/app/types/unified-alerting-dto.ts index 0c379f929b1..bf149896c12 100644 --- a/public/app/types/unified-alerting-dto.ts +++ b/public/app/types/unified-alerting-dto.ts @@ -201,6 +201,8 @@ export interface AlertDataQuery extends DataQuery { maxDataPoints?: number; intervalMs?: number; expression?: string; + instant?: boolean; + range?: boolean; } export interface AlertQuery { From b91b9a1e38878f7c408129ff2c9c70e6d6bb70d5 Mon Sep 17 00:00:00 2001 From: Tom Ratcliffe Date: Fri, 11 Oct 2024 11:08:50 +0100 Subject: [PATCH 073/110] Alerting: Use `jest/expect-expect` rule to check for assertions (#93112) --- public/app/features/alerting/.eslintrc | 1 + .../alerting/unified/MuteTimings.test.tsx | 16 ++++++++-------- .../features/alerting/unified/Receivers.test.tsx | 6 +++--- .../unified/RuleEditorGrafanaRules.test.tsx | 3 ++- .../alerting/unified/hooks/useAbilities.test.tsx | 4 ---- public/test/helpers/alertingRuleEditor.tsx | 12 ++++++++---- 6 files changed, 22 insertions(+), 20 deletions(-) diff --git a/public/app/features/alerting/.eslintrc b/public/app/features/alerting/.eslintrc index 00287bc0bb8..c34b924496a 100644 --- a/public/app/features/alerting/.eslintrc +++ b/public/app/features/alerting/.eslintrc @@ -12,6 +12,7 @@ "extends": ["plugin:testing-library/react"], "rules": { "testing-library/prefer-user-event": "error", + "jest/expect-expect": ["error", { "assertFunctionNames": ["expect*", "reducerTester"] }], }, }, ], diff --git a/public/app/features/alerting/unified/MuteTimings.test.tsx b/public/app/features/alerting/unified/MuteTimings.test.tsx index c329ddcecab..c583d45599a 100644 --- a/public/app/features/alerting/unified/MuteTimings.test.tsx +++ b/public/app/features/alerting/unified/MuteTimings.test.tsx @@ -158,7 +158,7 @@ const defaultConfigWithBothTimeIntervalsField: AlertManagerCortexConfig = { template_files: {}, }; -const expectedToHaveRedirectedToRoutesRoute = async () => +const expectToHaveRedirectedToRoutesRoute = async () => expect(await screen.findByText(indexPageText)).toBeInTheDocument(); const fillOutForm = async ({ @@ -226,7 +226,7 @@ describe('Mute timings', () => { await saveMuteTiming(); - await expectedToHaveRedirectedToRoutesRoute(); + await expectToHaveRedirectedToRoutesRoute(); const requests = await capture; const alertmanagerUpdate = await getAlertmanagerConfigUpdate(requests); @@ -252,7 +252,7 @@ describe('Mute timings', () => { }); await saveMuteTiming(); - await expectedToHaveRedirectedToRoutesRoute(); + await expectToHaveRedirectedToRoutesRoute(); const requests = await capture; const alertmanagerUpdate = await getAlertmanagerConfigUpdate(requests); @@ -278,7 +278,7 @@ describe('Mute timings', () => { }); await saveMuteTiming(); - await expectedToHaveRedirectedToRoutesRoute(); + await expectToHaveRedirectedToRoutesRoute(); }); it('prepopulates the form when editing a mute timing', async () => { @@ -310,7 +310,7 @@ describe('Mute timings', () => { await fillOutForm(formValues); await saveMuteTiming(); - await expectedToHaveRedirectedToRoutesRoute(); + await expectToHaveRedirectedToRoutesRoute(); const requests = await capture; const alertmanagerUpdate = await getAlertmanagerConfigUpdate(requests); @@ -345,7 +345,7 @@ describe('Mute timings', () => { await fillOutForm({ name: 'Lunch breaks' }); await saveMuteTiming(); - await expectedToHaveRedirectedToRoutesRoute(); + await expectToHaveRedirectedToRoutesRoute(); }); it('shows error when mute timing does not exist', async () => { @@ -367,7 +367,7 @@ describe('Mute timings', () => { await fillOutForm({ name: 'a new mute timing' }); await saveMuteTiming(); - await expectedToHaveRedirectedToRoutesRoute(); + await expectToHaveRedirectedToRoutesRoute(); }); it('shows error when mute timing does not exist', async () => { @@ -384,7 +384,7 @@ describe('Mute timings', () => { }); await saveMuteTiming(); - await expectedToHaveRedirectedToRoutesRoute(); + await expectToHaveRedirectedToRoutesRoute(); }); it('loads view form for provisioned interval', async () => { diff --git a/public/app/features/alerting/unified/Receivers.test.tsx b/public/app/features/alerting/unified/Receivers.test.tsx index 6a4d4227e6f..36b18b52ee3 100644 --- a/public/app/features/alerting/unified/Receivers.test.tsx +++ b/public/app/features/alerting/unified/Receivers.test.tsx @@ -19,7 +19,7 @@ import NewReceiverView from './components/receivers/NewReceiverView'; const server = setupMswServer(); -const assertSaveWasSuccessful = async () => { +const expectSaveWasSuccessful = async () => { // TODO: Have a better way to assert that the contact point was saved. This is instead asserting on some // text that's present on the list page, as there's a lot of overlap in text between the form and the list page return waitFor(() => expect(screen.getByText(/search by name or type/i)).toBeInTheDocument(), { timeout: 2000 }); @@ -77,7 +77,7 @@ it('can save a contact point with a select dropdown', async () => { await saveContactPoint(); - await assertSaveWasSuccessful(); + await expectSaveWasSuccessful(); }); it('can save existing Telegram contact point', async () => { @@ -89,5 +89,5 @@ it('can save existing Telegram contact point', async () => { // trigger this error if it regresses await saveContactPoint(); - await assertSaveWasSuccessful(); + await expectSaveWasSuccessful(); }); diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx index be6cb47b1a7..40fd64998b4 100644 --- a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx +++ b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx @@ -118,7 +118,8 @@ describe('RuleEditor grafana managed rules', () => { await clickSelectOption(groupInput, grafanaRulerGroup.name); await userEvent.type(ui.inputs.annotationValue(1).get(), 'some description'); - // save and check what was sent to backend await userEvent.click(ui.buttons.saveAndExit.get()); + + expect(await screen.findByRole('status')).toHaveTextContent('Rule added successfully'); }); }); diff --git a/public/app/features/alerting/unified/hooks/useAbilities.test.tsx b/public/app/features/alerting/unified/hooks/useAbilities.test.tsx index 510e5390379..e58a09f6666 100644 --- a/public/app/features/alerting/unified/hooks/useAbilities.test.tsx +++ b/public/app/features/alerting/unified/hooks/useAbilities.test.tsx @@ -207,10 +207,6 @@ describe('AlertRule abilities', () => { expect(result.current).toMatchSnapshot(); }); - - it('should not allow certain actions for provisioned rules', () => {}); - - it('should not allow certain actions for federated rules', () => {}); }); function createAlertmanagerWrapper(alertmanagerSourceName: string) { diff --git a/public/test/helpers/alertingRuleEditor.tsx b/public/test/helpers/alertingRuleEditor.tsx index e07a335fad9..465b2ef0dc3 100644 --- a/public/test/helpers/alertingRuleEditor.tsx +++ b/public/test/helpers/alertingRuleEditor.tsx @@ -3,6 +3,7 @@ import { render } from 'test/test-utils'; import { byRole, byTestId, byText } from 'testing-library-selector'; import { selectors } from '@grafana/e2e-selectors'; +import { AppNotificationList } from 'app/core/components/AppNotifications/AppNotificationList'; import RuleEditor from 'app/features/alerting/unified/RuleEditor'; export const ui = { @@ -36,10 +37,13 @@ export const ui = { export function renderRuleEditor(identifier?: string, recording = false) { return render( - - } /> - } /> - , + <> + + + } /> + } /> + + , { historyOptions: { initialEntries: [ From 64a00aff6e24b129224419fa9c5119260f1eb668 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 09:51:40 +0000 Subject: [PATCH 074/110] Update dependency msw to v2.4.10 --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 154584a3f1a..fb0fca45245 100644 --- a/package.json +++ b/package.json @@ -205,7 +205,7 @@ "knip": "^5.10.0", "lerna": "8.1.8", "mini-css-extract-plugin": "2.9.1", - "msw": "2.4.9", + "msw": "2.4.10", "mutationobserver-shim": "0.3.7", "ngtemplate-loader": "2.1.0", "node-notifier": "10.0.1", diff --git a/yarn.lock b/yarn.lock index 87a58674870..43bfe408068 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19142,7 +19142,7 @@ __metadata: moment-timezone: "npm:0.5.46" monaco-editor: "npm:0.34.1" moveable: "npm:0.53.0" - msw: "npm:2.4.9" + msw: "npm:2.4.10" mutationobserver-shim: "npm:0.3.7" nanoid: "npm:^5.0.4" ngtemplate-loader: "npm:2.1.0" @@ -24179,9 +24179,9 @@ __metadata: languageName: node linkType: hard -"msw@npm:2.4.9": - version: 2.4.9 - resolution: "msw@npm:2.4.9" +"msw@npm:2.4.10": + version: 2.4.10 + resolution: "msw@npm:2.4.10" dependencies: "@bundled-es-modules/cookie": "npm:^2.0.0" "@bundled-es-modules/statuses": "npm:^1.0.1" @@ -24207,7 +24207,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 10/fe00b2d2934993cfb26661ab919944a677d68f097d1e5990a0a4245334741412855abe499dd77822212c586dd8dd002ef8062f0f4f451a6955bd5dccab1905a8 + checksum: 10/f8fb491f91f24a5f17369f4263e99b5609871854422eb6d7c959f782d568fd6229f2dee84e6db72a19e8583073513d9f651517b8cb9a57a1b6849aff1499cba2 languageName: node linkType: hard From 67ee40ce121cb032423e73d52ac119c443c3e678 Mon Sep 17 00:00:00 2001 From: "grafana-pr-automation[bot]" <140550294+grafana-pr-automation[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:24:47 +0100 Subject: [PATCH 075/110] I18n: Download translations from Crowdin (#94599) --- public/locales/de-DE/grafana.json | 3 +++ public/locales/es-ES/grafana.json | 3 +++ public/locales/fr-FR/grafana.json | 3 +++ public/locales/pt-BR/grafana.json | 3 +++ public/locales/zh-Hans/grafana.json | 3 +++ 5 files changed, 15 insertions(+) diff --git a/public/locales/de-DE/grafana.json b/public/locales/de-DE/grafana.json index ad79affc6b5..5696514fbb2 100644 --- a/public/locales/de-DE/grafana.json +++ b/public/locales/de-DE/grafana.json @@ -1225,6 +1225,7 @@ "documentation": "", "host": { "description": "", + "error": "", "label": "", "placeholder": "" }, @@ -1234,11 +1235,13 @@ }, "search_filter": { "description": "", + "error": "", "label": "", "placeholder": "" }, "search-base-dns": { "description": "", + "error": "", "label": "", "placeholder": "" }, diff --git a/public/locales/es-ES/grafana.json b/public/locales/es-ES/grafana.json index 9ab396e02e5..de435a0f299 100644 --- a/public/locales/es-ES/grafana.json +++ b/public/locales/es-ES/grafana.json @@ -1225,6 +1225,7 @@ "documentation": "", "host": { "description": "", + "error": "", "label": "", "placeholder": "" }, @@ -1234,11 +1235,13 @@ }, "search_filter": { "description": "", + "error": "", "label": "", "placeholder": "" }, "search-base-dns": { "description": "", + "error": "", "label": "", "placeholder": "" }, diff --git a/public/locales/fr-FR/grafana.json b/public/locales/fr-FR/grafana.json index da3b6c03325..5026b24b35a 100644 --- a/public/locales/fr-FR/grafana.json +++ b/public/locales/fr-FR/grafana.json @@ -1225,6 +1225,7 @@ "documentation": "", "host": { "description": "", + "error": "", "label": "", "placeholder": "" }, @@ -1234,11 +1235,13 @@ }, "search_filter": { "description": "", + "error": "", "label": "", "placeholder": "" }, "search-base-dns": { "description": "", + "error": "", "label": "", "placeholder": "" }, diff --git a/public/locales/pt-BR/grafana.json b/public/locales/pt-BR/grafana.json index edb0a1a470e..e0041a149dc 100644 --- a/public/locales/pt-BR/grafana.json +++ b/public/locales/pt-BR/grafana.json @@ -1225,6 +1225,7 @@ "documentation": "", "host": { "description": "", + "error": "", "label": "", "placeholder": "" }, @@ -1234,11 +1235,13 @@ }, "search_filter": { "description": "", + "error": "", "label": "", "placeholder": "" }, "search-base-dns": { "description": "", + "error": "", "label": "", "placeholder": "" }, diff --git a/public/locales/zh-Hans/grafana.json b/public/locales/zh-Hans/grafana.json index 20bc710c828..62188766e99 100644 --- a/public/locales/zh-Hans/grafana.json +++ b/public/locales/zh-Hans/grafana.json @@ -1216,6 +1216,7 @@ "documentation": "", "host": { "description": "", + "error": "", "label": "", "placeholder": "" }, @@ -1225,11 +1226,13 @@ }, "search_filter": { "description": "", + "error": "", "label": "", "placeholder": "" }, "search-base-dns": { "description": "", + "error": "", "label": "", "placeholder": "" }, From 032d0669cd03c60946a44ed46f08bd20a30955e0 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 11:31:04 +0100 Subject: [PATCH 076/110] Release: Bump version to 11.4.0-pre (#94569) bump version 11.4.0-pre Co-authored-by: github-actions[bot] --- .../grafana-extensionstest-app/package.json | 4 +- lerna.json | 2 +- package.json | 2 +- packages/grafana-data/package.json | 4 +- packages/grafana-e2e-selectors/package.json | 2 +- packages/grafana-eslint-rules/package.json | 2 +- packages/grafana-flamegraph/package.json | 6 +- packages/grafana-icons/package.json | 2 +- .../grafana-o11y-ds-frontend/package.json | 12 +- packages/grafana-plugin-configs/package.json | 2 +- packages/grafana-prometheus/package.json | 12 +- packages/grafana-runtime/package.json | 10 +- packages/grafana-schema/package.json | 2 +- .../x/AnnotationsListPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/BarChartPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/BarGaugePanelCfg_types.gen.ts | 2 +- .../x/CandlestickPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/CanvasPanelCfg_types.gen.ts | 2 +- .../x/CloudWatchDataQuery_types.gen.ts | 2 +- .../x/DashboardListPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/DatagridPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/DebugPanelCfg_types.gen.ts | 2 +- .../x/ElasticsearchDataQuery_types.gen.ts | 2 +- .../panelcfg/x/GaugePanelCfg_types.gen.ts | 2 +- .../panelcfg/x/GeomapPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/HeatmapPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/HistogramPanelCfg_types.gen.ts | 2 +- .../logs/panelcfg/x/LogsPanelCfg_types.gen.ts | 2 +- .../dataquery/x/LokiDataQuery_types.gen.ts | 2 +- .../news/panelcfg/x/NewsPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/NodeGraphPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/PieChartPanelCfg_types.gen.ts | 2 +- .../stat/panelcfg/x/StatPanelCfg_types.gen.ts | 2 +- .../x/StateTimelinePanelCfg_types.gen.ts | 2 +- .../x/StatusHistoryPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/TablePanelCfg_types.gen.ts | 2 +- .../text/panelcfg/x/TextPanelCfg_types.gen.ts | 2 +- .../x/TimeSeriesPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/TrendPanelCfg_types.gen.ts | 2 +- .../panelcfg/x/XYChartPanelCfg_types.gen.ts | 2 +- packages/grafana-sql/package.json | 10 +- packages/grafana-ui/package.json | 8 +- .../datasource/azuremonitor/package.json | 14 +- .../datasource/cloud-monitoring/package.json | 14 +- .../package.json | 14 +- .../grafana-pyroscope-datasource/package.json | 12 +- .../grafana-testdata-datasource/package.json | 14 +- .../plugins/datasource/jaeger/package.json | 2 +- .../app/plugins/datasource/mssql/package.json | 14 +- .../app/plugins/datasource/mysql/package.json | 14 +- .../app/plugins/datasource/parca/package.json | 12 +- .../app/plugins/datasource/tempo/package.json | 4 +- .../plugins/datasource/zipkin/package.json | 2 +- yarn.lock | 158 +++++++++--------- 54 files changed, 204 insertions(+), 204 deletions(-) diff --git a/e2e/test-plugins/grafana-extensionstest-app/package.json b/e2e/test-plugins/grafana-extensionstest-app/package.json index f7577e54dd5..fc38d4f3e24 100644 --- a/e2e/test-plugins/grafana-extensionstest-app/package.json +++ b/e2e/test-plugins/grafana-extensionstest-app/package.json @@ -1,6 +1,6 @@ { "name": "@test-plugins/extensions-test-app", - "version": "1.0.0", + "version": "11.4.0-pre", "private": true, "scripts": { "build": "webpack -c ./webpack.config.ts --env production", @@ -12,7 +12,7 @@ "license": "Apache-2.0", "devDependencies": { "@grafana/eslint-config": "7.0.0", - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@types/lodash": "4.17.7", "@types/node": "20.14.14", "@types/prismjs": "1.26.4", diff --git a/lerna.json b/lerna.json index 88526882713..e8129a1ad90 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "npmClient": "yarn", - "version": "11.3.0-pre" + "version": "11.4.0-pre" } diff --git a/package.json b/package.json index fb0fca45245..097af3e1ffc 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "grafana", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "repository": "github:grafana/grafana", "scripts": { "build": "NODE_ENV=production nx exec --verbose -- webpack --config scripts/webpack/webpack.prod.js", diff --git a/packages/grafana-data/package.json b/packages/grafana-data/package.json index ce45ca9e694..46d2c1d304d 100644 --- a/packages/grafana-data/package.json +++ b/packages/grafana-data/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/data", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "description": "Grafana Data Library", "keywords": [ "typescript" @@ -36,7 +36,7 @@ }, "dependencies": { "@braintree/sanitize-url": "7.0.1", - "@grafana/schema": "11.3.0-pre", + "@grafana/schema": "11.4.0-pre", "@types/d3-interpolate": "^3.0.0", "@types/string-hash": "1.1.3", "d3-interpolate": "3.0.1", diff --git a/packages/grafana-e2e-selectors/package.json b/packages/grafana-e2e-selectors/package.json index ace51d6eee0..3fb5804948d 100644 --- a/packages/grafana-e2e-selectors/package.json +++ b/packages/grafana-e2e-selectors/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/e2e-selectors", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "description": "Grafana End-to-End Test Selectors Library", "keywords": [ "cli", diff --git a/packages/grafana-eslint-rules/package.json b/packages/grafana-eslint-rules/package.json index 5ac20942895..596158c9a68 100644 --- a/packages/grafana-eslint-rules/package.json +++ b/packages/grafana-eslint-rules/package.json @@ -1,7 +1,7 @@ { "name": "@grafana/eslint-plugin", "description": "ESLint rules for use within the Grafana repo. Not suitable (or supported) for external use.", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "main": "./index.cjs", "author": "Grafana Labs", "license": "Apache-2.0", diff --git a/packages/grafana-flamegraph/package.json b/packages/grafana-flamegraph/package.json index 4767950153e..2c817fbaa04 100644 --- a/packages/grafana-flamegraph/package.json +++ b/packages/grafana-flamegraph/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/flamegraph", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "description": "Grafana flamegraph visualization component", "keywords": [ "grafana", @@ -44,8 +44,8 @@ ], "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "@leeoniya/ufuzzy": "1.0.14", "d3": "^7.8.5", "lodash": "4.17.21", diff --git a/packages/grafana-icons/package.json b/packages/grafana-icons/package.json index fd2741ca1b1..f72eff65875 100644 --- a/packages/grafana-icons/package.json +++ b/packages/grafana-icons/package.json @@ -1,6 +1,6 @@ { "name": "@grafana/saga-icons", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "private": true, "description": "Icons for Grafana", "author": "Grafana Labs", diff --git a/packages/grafana-o11y-ds-frontend/package.json b/packages/grafana-o11y-ds-frontend/package.json index 90f13ecb692..eed1b9c1d25 100644 --- a/packages/grafana-o11y-ds-frontend/package.json +++ b/packages/grafana-o11y-ds-frontend/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "name": "@grafana/o11y-ds-frontend", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "description": "Library to manage traces in Grafana.", "sideEffects": false, "repository": { @@ -18,12 +18,12 @@ }, "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", - "@grafana/e2e-selectors": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", "@grafana/experimental": "2.1.2", - "@grafana/runtime": "11.3.0-pre", - "@grafana/schema": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/schema": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "react-select": "5.8.1", "react-use": "17.5.1", "rxjs": "7.8.1", diff --git a/packages/grafana-plugin-configs/package.json b/packages/grafana-plugin-configs/package.json index 003e7c9fbf9..186a3896971 100644 --- a/packages/grafana-plugin-configs/package.json +++ b/packages/grafana-plugin-configs/package.json @@ -2,7 +2,7 @@ "name": "@grafana/plugin-configs", "description": "Shared dependencies and files for core plugins", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "tslib": "2.7.0" }, diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 72c6fab3ba4..0501e20e08d 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "AGPL-3.0-only", "name": "@grafana/prometheus", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "description": "Grafana Prometheus Library", "keywords": [ "typescript" @@ -38,12 +38,12 @@ "dependencies": { "@emotion/css": "11.13.4", "@floating-ui/react": "0.26.24", - "@grafana/data": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", "@grafana/experimental": "2.1.2", "@grafana/faro-web-sdk": "1.10.2", - "@grafana/runtime": "11.3.0-pre", - "@grafana/schema": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/schema": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.14", "@lezer/common": "1.2.2", @@ -76,7 +76,7 @@ }, "devDependencies": { "@emotion/eslint-plugin": "11.12.0", - "@grafana/e2e-selectors": "11.3.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", "@grafana/tsconfig": "^2.0.0", "@rollup/plugin-image": "3.0.3", "@rollup/plugin-node-resolve": "15.3.0", diff --git a/packages/grafana-runtime/package.json b/packages/grafana-runtime/package.json index 9aefe49a823..95a00944584 100644 --- a/packages/grafana-runtime/package.json +++ b/packages/grafana-runtime/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/runtime", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "description": "Grafana Runtime Library", "keywords": [ "grafana", @@ -37,11 +37,11 @@ "postpack": "mv package.json.bak package.json" }, "dependencies": { - "@grafana/data": "11.3.0-pre", - "@grafana/e2e-selectors": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", "@grafana/faro-web-sdk": "^1.3.6", - "@grafana/schema": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/schema": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "history": "4.10.1", "lodash": "4.17.21", "rxjs": "7.8.1", diff --git a/packages/grafana-schema/package.json b/packages/grafana-schema/package.json index 6b0ca630ad3..05e4fbb8dd8 100644 --- a/packages/grafana-schema/package.json +++ b/packages/grafana-schema/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/schema", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "description": "Grafana Schema Library", "keywords": [ "typescript" diff --git a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts index fd19d4267ca..3fb87719ff6 100644 --- a/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/annotationslist/panelcfg/x/AnnotationsListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options { limit: number; diff --git a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts index a6b78e671c4..371c56e5dff 100644 --- a/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/barchart/panelcfg/x/BarChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip, common.OptionsWithTextFormatting { /** diff --git a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts index fbff065bcdd..9c5dcb7854b 100644 --- a/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/bargauge/panelcfg/x/BarGaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options extends common.OptionsWithLegend, common.SingleStatBaseOptions { displayMode: common.BarGaugeDisplayMode; diff --git a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts index 0dea20acc4b..328c4309cb2 100644 --- a/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/candlestick/panelcfg/x/CandlestickPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export enum VizDisplayMode { Candles = 'candles', diff --git a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts index b40116598e7..3271a491db0 100644 --- a/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/canvas/panelcfg/x/CanvasPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export enum HorizontalConstraint { Center = 'center', diff --git a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts index f59d93e60d2..691e556e70f 100644 --- a/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/cloudwatch/dataquery/x/CloudWatchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface MetricStat { /** diff --git a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts index d64925b0958..bccefe7dd16 100644 --- a/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/dashboardlist/panelcfg/x/DashboardListPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts index bdc84b74fc5..8ade131500b 100644 --- a/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/datagrid/panelcfg/x/DatagridPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options { selectedSeries: number; diff --git a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts index d7d41860dad..9c2d289a95c 100644 --- a/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/debug/panelcfg/x/DebugPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export type UpdateConfig = { render: boolean, diff --git a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts index d21b25534ac..74a47041e89 100644 --- a/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/elasticsearch/dataquery/x/ElasticsearchDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested); diff --git a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts index b9f809b5ea2..236e7f8be7e 100644 --- a/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/gauge/panelcfg/x/GaugePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options extends common.SingleStatBaseOptions { minVizHeight: number; diff --git a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts index 8f3bb011101..20206475413 100644 --- a/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/geomap/panelcfg/x/GeomapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options { basemap: ui.MapLayerOptions; diff --git a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts index b0764d00b3b..e27d99fdc8e 100644 --- a/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/heatmap/panelcfg/x/HeatmapPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; /** * Controls the color mode of the heatmap diff --git a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts index ff2aea48588..7837b9c9d19 100644 --- a/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/histogram/panelcfg/x/HistogramPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options extends common.OptionsWithLegend, common.OptionsWithTooltip { /** diff --git a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts index 5af91fec64c..5e31328c325 100644 --- a/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/logs/panelcfg/x/LogsPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options { dedupStrategy: common.LogsDedupStrategy; diff --git a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts index 6298dd986e2..b8d2b5ffc49 100644 --- a/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/loki/dataquery/x/LokiDataQuery_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export enum QueryEditorMode { Builder = 'builder', diff --git a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts index e011dddf763..47c39842d79 100644 --- a/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/news/panelcfg/x/NewsPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts index 9405bd13003..357969cb116 100644 --- a/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/nodegraph/panelcfg/x/NodeGraphPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface ArcOption { /** diff --git a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts index 48aa1661936..4dda50ccb00 100644 --- a/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/piechart/panelcfg/x/PieChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; /** * Select the pie chart display style. diff --git a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts index c720c206ee0..5917c2b8d39 100644 --- a/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/stat/panelcfg/x/StatPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options extends common.SingleStatBaseOptions { colorMode: common.BigValueColorMode; diff --git a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts index c65eb67fd0c..d74a2f04623 100644 --- a/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statetimeline/panelcfg/x/StateTimelinePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts index 77e8c9cd68a..f51dc21c98b 100644 --- a/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/statushistory/panelcfg/x/StatusHistoryPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options extends ui.OptionsWithLegend, ui.OptionsWithTooltip, ui.OptionsWithTimezones { /** diff --git a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts index 435c36726ee..4f434b0fc1e 100644 --- a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as ui from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options { /** diff --git a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts index 4e3daebded8..b9620c89905 100644 --- a/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/text/panelcfg/x/TextPanelCfg_types.gen.ts @@ -8,7 +8,7 @@ // // Run 'make gen-cue' from repository root to regenerate. -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export enum TextMode { Code = 'code', diff --git a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts index f8b95bf3f7d..cf26d19549c 100644 --- a/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/timeseries/panelcfg/x/TimeSeriesPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; export interface Options extends common.OptionsWithTimezones { legend: common.VizLegendOptions; diff --git a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts index bd84e783a06..cd1bb27fa3d 100644 --- a/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/trend/panelcfg/x/TrendPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; /** * Identical to timeseries... except it does not have timezone settings diff --git a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts index 6ca02d98289..8e98dfaa449 100644 --- a/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/xychart/panelcfg/x/XYChartPanelCfg_types.gen.ts @@ -10,7 +10,7 @@ import * as common from '@grafana/schema'; -export const pluginVersion = "11.3.0-pre"; +export const pluginVersion = "11.4.0-pre"; /** * Auto is "table" in the UI diff --git a/packages/grafana-sql/package.json b/packages/grafana-sql/package.json index 806a2c99540..1ade6f7d0ba 100644 --- a/packages/grafana-sql/package.json +++ b/packages/grafana-sql/package.json @@ -3,7 +3,7 @@ "license": "AGPL-3.0-only", "private": true, "name": "@grafana/sql", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "repository": { "type": "git", "url": "http://github.com/grafana/grafana.git", @@ -15,11 +15,11 @@ }, "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", - "@grafana/e2e-selectors": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", "@grafana/experimental": "2.1.2", - "@grafana/runtime": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "@react-awesome-query-builder/ui": "6.6.3", "immutable": "4.3.7", "lodash": "4.17.21", diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index 1ce22d07abd..71ab9c12f01 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -2,7 +2,7 @@ "author": "Grafana Labs", "license": "Apache-2.0", "name": "@grafana/ui", - "version": "11.3.0-pre", + "version": "11.4.0-pre", "description": "Grafana Components Library", "keywords": [ "grafana", @@ -51,10 +51,10 @@ "@emotion/react": "11.13.3", "@emotion/serialize": "1.3.2", "@floating-ui/react": "0.26.24", - "@grafana/data": "11.3.0-pre", - "@grafana/e2e-selectors": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", "@grafana/faro-web-sdk": "^1.3.6", - "@grafana/schema": "11.3.0-pre", + "@grafana/schema": "11.4.0-pre", "@hello-pangea/dnd": "17.0.0", "@leeoniya/ufuzzy": "1.0.14", "@monaco-editor/react": "4.6.0", diff --git a/public/app/plugins/datasource/azuremonitor/package.json b/public/app/plugins/datasource/azuremonitor/package.json index b7e2c1fc43a..a38212075c9 100644 --- a/public/app/plugins/datasource/azuremonitor/package.json +++ b/public/app/plugins/datasource/azuremonitor/package.json @@ -2,14 +2,14 @@ "name": "@grafana-plugins/grafana-azure-monitor-datasource", "description": "Grafana data source for Azure Monitor", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", "@grafana/experimental": "2.1.2", - "@grafana/runtime": "11.3.0-pre", - "@grafana/schema": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/schema": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "@kusto/monaco-kusto": "^10.0.0", "fast-deep-equal": "^3.1.3", "i18next": "^23.0.0", @@ -25,8 +25,8 @@ "tslib": "2.7.0" }, "devDependencies": { - "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@testing-library/dom": "10.0.0", "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", diff --git a/public/app/plugins/datasource/cloud-monitoring/package.json b/public/app/plugins/datasource/cloud-monitoring/package.json index 22c4d50f4da..e66c407f9f7 100644 --- a/public/app/plugins/datasource/cloud-monitoring/package.json +++ b/public/app/plugins/datasource/cloud-monitoring/package.json @@ -2,15 +2,15 @@ "name": "@grafana-plugins/stackdriver", "description": "Grafana data source for Google Cloud Monitoring", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", "@grafana/experimental": "2.1.2", "@grafana/google-sdk": "0.1.2", - "@grafana/runtime": "11.3.0-pre", - "@grafana/schema": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/schema": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "debounce-promise": "3.1.2", "fast-deep-equal": "^3.1.3", "i18next": "^23.0.0", @@ -26,8 +26,8 @@ "tslib": "2.7.0" }, "devDependencies": { - "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@testing-library/dom": "10.0.0", "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", diff --git a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json index 8c7a04589cc..45916784d1e 100644 --- a/public/app/plugins/datasource/grafana-postgresql-datasource/package.json +++ b/public/app/plugins/datasource/grafana-postgresql-datasource/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/grafana-postgresql-datasource", "description": "PostgreSQL data source plugin", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", "@grafana/experimental": "2.1.2", - "@grafana/runtime": "11.3.0-pre", - "@grafana/sql": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/sql": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "lodash": "4.17.21", "react": "18.2.0", "rxjs": "7.8.1", "tslib": "2.7.0" }, "devDependencies": { - "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.13", diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json index 92fd9f67172..32df6b873bc 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/grafana-pyroscope-datasource", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", - "@grafana/runtime": "11.3.0-pre", - "@grafana/schema": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/schema": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "fast-deep-equal": "^3.1.3", "lodash": "4.17.21", "monaco-editor": "0.34.1", @@ -20,7 +20,7 @@ "tslib": "2.7.0" }, "devDependencies": { - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "15.0.2", diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/package.json b/public/app/plugins/datasource/grafana-testdata-datasource/package.json index 2d3a0165f48..2786227e30e 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/package.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/package.json @@ -2,14 +2,14 @@ "name": "@grafana-plugins/grafana-testdata-datasource", "description": "Generates test data in different forms", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", "@grafana/experimental": "2.1.2", - "@grafana/runtime": "11.3.0-pre", - "@grafana/schema": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/schema": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "d3-random": "^3.0.1", "lodash": "4.17.21", "micro-memoize": "^4.1.2", @@ -22,8 +22,8 @@ "uuid": "9.0.1" }, "devDependencies": { - "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@testing-library/dom": "10.0.0", "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", diff --git a/public/app/plugins/datasource/jaeger/package.json b/public/app/plugins/datasource/jaeger/package.json index 9811041480a..c9138f24875 100644 --- a/public/app/plugins/datasource/jaeger/package.json +++ b/public/app/plugins/datasource/jaeger/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/jaeger", "description": "Jaeger plugin for Grafana", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "workspace:*", diff --git a/public/app/plugins/datasource/mssql/package.json b/public/app/plugins/datasource/mssql/package.json index 1f0a2e0b5e6..4842f47373e 100644 --- a/public/app/plugins/datasource/mssql/package.json +++ b/public/app/plugins/datasource/mssql/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/mssql", "description": "MSSQL data source plugin", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", "@grafana/experimental": "2.1.2", - "@grafana/runtime": "11.3.0-pre", - "@grafana/sql": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/sql": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "lodash": "4.17.21", "react": "18.2.0", "rxjs": "7.8.1", "tslib": "2.7.0" }, "devDependencies": { - "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.13", diff --git a/public/app/plugins/datasource/mysql/package.json b/public/app/plugins/datasource/mysql/package.json index 1531f4f935d..ccd3f4fd5c7 100644 --- a/public/app/plugins/datasource/mysql/package.json +++ b/public/app/plugins/datasource/mysql/package.json @@ -2,22 +2,22 @@ "name": "@grafana-plugins/mysql", "description": "MySQL data source plugin", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", "@grafana/experimental": "2.1.2", - "@grafana/runtime": "11.3.0-pre", - "@grafana/sql": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/sql": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "lodash": "4.17.21", "react": "18.2.0", "rxjs": "7.8.1", "tslib": "2.7.0" }, "devDependencies": { - "@grafana/e2e-selectors": "11.3.0-pre", - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/e2e-selectors": "11.4.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", "@types/jest": "29.5.13", diff --git a/public/app/plugins/datasource/parca/package.json b/public/app/plugins/datasource/parca/package.json index bb79f49d23f..64c436983b9 100644 --- a/public/app/plugins/datasource/parca/package.json +++ b/public/app/plugins/datasource/parca/package.json @@ -2,13 +2,13 @@ "name": "@grafana-plugins/parca", "description": "Continuous profiling for analysis of CPU and memory usage, down to the line number and throughout time. Saving infrastructure cost, improving performance, and increasing reliability.", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", - "@grafana/data": "11.3.0-pre", - "@grafana/runtime": "11.3.0-pre", - "@grafana/schema": "11.3.0-pre", - "@grafana/ui": "11.3.0-pre", + "@grafana/data": "11.4.0-pre", + "@grafana/runtime": "11.4.0-pre", + "@grafana/schema": "11.4.0-pre", + "@grafana/ui": "11.4.0-pre", "lodash": "4.17.21", "monaco-editor": "0.34.1", "react": "18.2.0", @@ -18,7 +18,7 @@ "tslib": "2.7.0" }, "devDependencies": { - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@testing-library/dom": "10.0.0", "@testing-library/react": "15.0.2", "@testing-library/user-event": "14.5.2", diff --git a/public/app/plugins/datasource/tempo/package.json b/public/app/plugins/datasource/tempo/package.json index 63d2b5c785f..dc5c71c3c28 100644 --- a/public/app/plugins/datasource/tempo/package.json +++ b/public/app/plugins/datasource/tempo/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/tempo", "description": "Grafana plugin for the Tempo data source.", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "workspace:*", @@ -39,7 +39,7 @@ "uuid": "9.0.1" }, "devDependencies": { - "@grafana/plugin-configs": "11.3.0-pre", + "@grafana/plugin-configs": "11.4.0-pre", "@testing-library/dom": "10.0.0", "@testing-library/jest-dom": "6.4.2", "@testing-library/react": "15.0.2", diff --git a/public/app/plugins/datasource/zipkin/package.json b/public/app/plugins/datasource/zipkin/package.json index 0d91729726b..24f05d282a7 100644 --- a/public/app/plugins/datasource/zipkin/package.json +++ b/public/app/plugins/datasource/zipkin/package.json @@ -2,7 +2,7 @@ "name": "@grafana-plugins/zipkin", "description": "Zipkin plugin for Grafana", "private": true, - "version": "11.3.0-pre", + "version": "11.4.0-pre", "dependencies": { "@emotion/css": "11.13.4", "@grafana/data": "workspace:*", diff --git a/yarn.lock b/yarn.lock index 43bfe408068..7e1167b8978 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3094,13 +3094,13 @@ __metadata: resolution: "@grafana-plugins/grafana-azure-monitor-datasource@workspace:public/app/plugins/datasource/azuremonitor" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/experimental": "npm:2.1.2" - "@grafana/plugin-configs": "npm:11.3.0-pre" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/schema": "npm:11.3.0-pre" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/schema": "npm:11.4.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@kusto/monaco-kusto": "npm:^10.0.0" "@testing-library/dom": "npm:10.0.0" "@testing-library/react": "npm:15.0.2" @@ -3138,13 +3138,13 @@ __metadata: resolution: "@grafana-plugins/grafana-postgresql-datasource@workspace:public/app/plugins/datasource/grafana-postgresql-datasource" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/experimental": "npm:2.1.2" - "@grafana/plugin-configs": "npm:11.3.0-pre" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/sql": "npm:11.3.0-pre" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/sql": "npm:11.4.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@testing-library/react": "npm:15.0.2" "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:29.5.13" @@ -3169,11 +3169,11 @@ __metadata: resolution: "@grafana-plugins/grafana-pyroscope-datasource@workspace:public/app/plugins/datasource/grafana-pyroscope-datasource" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/plugin-configs": "npm:11.3.0-pre" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/schema": "npm:11.3.0-pre" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/schema": "npm:11.4.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@testing-library/dom": "npm:10.0.0" "@testing-library/jest-dom": "npm:6.4.2" "@testing-library/react": "npm:15.0.2" @@ -3210,13 +3210,13 @@ __metadata: resolution: "@grafana-plugins/grafana-testdata-datasource@workspace:public/app/plugins/datasource/grafana-testdata-datasource" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/experimental": "npm:2.1.2" - "@grafana/plugin-configs": "npm:11.3.0-pre" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/schema": "npm:11.3.0-pre" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/schema": "npm:11.4.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@testing-library/dom": "npm:10.0.0" "@testing-library/react": "npm:15.0.2" "@testing-library/user-event": "npm:14.5.2" @@ -3293,13 +3293,13 @@ __metadata: resolution: "@grafana-plugins/mssql@workspace:public/app/plugins/datasource/mssql" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/experimental": "npm:2.1.2" - "@grafana/plugin-configs": "npm:11.3.0-pre" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/sql": "npm:11.3.0-pre" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/sql": "npm:11.4.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@testing-library/react": "npm:15.0.2" "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:29.5.13" @@ -3324,13 +3324,13 @@ __metadata: resolution: "@grafana-plugins/mysql@workspace:public/app/plugins/datasource/mysql" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/experimental": "npm:2.1.2" - "@grafana/plugin-configs": "npm:11.3.0-pre" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/sql": "npm:11.3.0-pre" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/sql": "npm:11.4.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@testing-library/react": "npm:15.0.2" "@testing-library/user-event": "npm:14.5.2" "@types/jest": "npm:29.5.13" @@ -3355,11 +3355,11 @@ __metadata: resolution: "@grafana-plugins/parca@workspace:public/app/plugins/datasource/parca" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/plugin-configs": "npm:11.3.0-pre" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/schema": "npm:11.3.0-pre" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/schema": "npm:11.4.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@testing-library/dom": "npm:10.0.0" "@testing-library/react": "npm:15.0.2" "@testing-library/user-event": "npm:14.5.2" @@ -3387,14 +3387,14 @@ __metadata: resolution: "@grafana-plugins/stackdriver@workspace:public/app/plugins/datasource/cloud-monitoring" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/experimental": "npm:2.1.2" "@grafana/google-sdk": "npm:0.1.2" - "@grafana/plugin-configs": "npm:11.3.0-pre" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/schema": "npm:11.3.0-pre" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/schema": "npm:11.4.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@testing-library/dom": "npm:10.0.0" "@testing-library/react": "npm:15.0.2" "@testing-library/user-event": "npm:14.5.2" @@ -3442,7 +3442,7 @@ __metadata: "@grafana/lezer-traceql": "npm:0.0.19" "@grafana/monaco-logql": "npm:^0.0.7" "@grafana/o11y-ds-frontend": "workspace:*" - "@grafana/plugin-configs": "npm:11.3.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" "@grafana/runtime": "workspace:*" "@grafana/schema": "workspace:*" "@grafana/ui": "workspace:*" @@ -3552,12 +3552,12 @@ __metadata: languageName: node linkType: hard -"@grafana/data@npm:11.3.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": +"@grafana/data@npm:11.4.0-pre, @grafana/data@workspace:*, @grafana/data@workspace:packages/grafana-data": version: 0.0.0-use.local resolution: "@grafana/data@workspace:packages/grafana-data" dependencies: "@braintree/sanitize-url": "npm:7.0.1" - "@grafana/schema": "npm:11.3.0-pre" + "@grafana/schema": "npm:11.4.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@rollup/plugin-node-resolve": "npm:15.3.0" "@types/d3-interpolate": "npm:^3.0.0" @@ -3605,7 +3605,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/e2e-selectors@npm:11.3.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": +"@grafana/e2e-selectors@npm:11.4.0-pre, @grafana/e2e-selectors@workspace:*, @grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors": version: 0.0.0-use.local resolution: "@grafana/e2e-selectors@workspace:packages/grafana-e2e-selectors" dependencies: @@ -3768,9 +3768,9 @@ __metadata: "@babel/preset-env": "npm:7.25.8" "@babel/preset-react": "npm:7.25.7" "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@leeoniya/ufuzzy": "npm:1.0.14" "@rollup/plugin-node-resolve": "npm:15.3.0" "@testing-library/dom": "npm:10.0.0" @@ -3852,13 +3852,13 @@ __metadata: resolution: "@grafana/o11y-ds-frontend@workspace:packages/grafana-o11y-ds-frontend" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/experimental": "npm:2.1.2" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/schema": "npm:11.3.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/schema": "npm:11.4.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@testing-library/dom": "npm:10.0.0" "@testing-library/jest-dom": "npm:^6.1.2" "@testing-library/react": "npm:15.0.2" @@ -3883,7 +3883,7 @@ __metadata: languageName: unknown linkType: soft -"@grafana/plugin-configs@npm:11.3.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs": +"@grafana/plugin-configs@npm:11.4.0-pre, @grafana/plugin-configs@workspace:*, @grafana/plugin-configs@workspace:packages/grafana-plugin-configs": version: 0.0.0-use.local resolution: "@grafana/plugin-configs@workspace:packages/grafana-plugin-configs" dependencies: @@ -3923,14 +3923,14 @@ __metadata: "@emotion/css": "npm:11.13.4" "@emotion/eslint-plugin": "npm:11.12.0" "@floating-ui/react": "npm:0.26.24" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/experimental": "npm:2.1.2" "@grafana/faro-web-sdk": "npm:1.10.2" - "@grafana/runtime": "npm:11.3.0-pre" - "@grafana/schema": "npm:11.3.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" + "@grafana/schema": "npm:11.4.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.14" "@lezer/common": "npm:1.2.2" @@ -4027,16 +4027,16 @@ __metadata: languageName: unknown linkType: soft -"@grafana/runtime@npm:11.3.0-pre, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime": +"@grafana/runtime@npm:11.4.0-pre, @grafana/runtime@workspace:*, @grafana/runtime@workspace:packages/grafana-runtime": version: 0.0.0-use.local resolution: "@grafana/runtime@workspace:packages/grafana-runtime" dependencies: - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/faro-web-sdk": "npm:^1.3.6" - "@grafana/schema": "npm:11.3.0-pre" + "@grafana/schema": "npm:11.4.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@rollup/plugin-node-resolve": "npm:15.3.0" "@rollup/plugin-terser": "npm:0.4.4" "@testing-library/dom": "npm:10.0.0" @@ -4145,7 +4145,7 @@ __metadata: languageName: node linkType: hard -"@grafana/schema@npm:11.3.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": +"@grafana/schema@npm:11.4.0-pre, @grafana/schema@workspace:*, @grafana/schema@workspace:packages/grafana-schema": version: 0.0.0-use.local resolution: "@grafana/schema@workspace:packages/grafana-schema" dependencies: @@ -4163,17 +4163,17 @@ __metadata: languageName: unknown linkType: soft -"@grafana/sql@npm:11.3.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql": +"@grafana/sql@npm:11.4.0-pre, @grafana/sql@workspace:*, @grafana/sql@workspace:packages/grafana-sql": version: 0.0.0-use.local resolution: "@grafana/sql@workspace:packages/grafana-sql" dependencies: "@emotion/css": "npm:11.13.4" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/experimental": "npm:2.1.2" - "@grafana/runtime": "npm:11.3.0-pre" + "@grafana/runtime": "npm:11.4.0-pre" "@grafana/tsconfig": "npm:^2.0.0" - "@grafana/ui": "npm:11.3.0-pre" + "@grafana/ui": "npm:11.4.0-pre" "@react-awesome-query-builder/ui": "npm:6.6.3" "@testing-library/dom": "npm:10.0.0" "@testing-library/jest-dom": "npm:^6.1.2" @@ -4223,7 +4223,7 @@ __metadata: languageName: node linkType: hard -"@grafana/ui@npm:11.3.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": +"@grafana/ui@npm:11.4.0-pre, @grafana/ui@workspace:*, @grafana/ui@workspace:packages/grafana-ui": version: 0.0.0-use.local resolution: "@grafana/ui@workspace:packages/grafana-ui" dependencies: @@ -4233,10 +4233,10 @@ __metadata: "@emotion/serialize": "npm:1.3.2" "@faker-js/faker": "npm:^9.0.0" "@floating-ui/react": "npm:0.26.24" - "@grafana/data": "npm:11.3.0-pre" - "@grafana/e2e-selectors": "npm:11.3.0-pre" + "@grafana/data": "npm:11.4.0-pre" + "@grafana/e2e-selectors": "npm:11.4.0-pre" "@grafana/faro-web-sdk": "npm:^1.3.6" - "@grafana/schema": "npm:11.3.0-pre" + "@grafana/schema": "npm:11.4.0-pre" "@grafana/tsconfig": "npm:^2.0.0" "@hello-pangea/dnd": "npm:17.0.0" "@leeoniya/ufuzzy": "npm:1.0.14" @@ -9175,7 +9175,7 @@ __metadata: "@emotion/css": "npm:11.11.2" "@grafana/data": "workspace:*" "@grafana/eslint-config": "npm:7.0.0" - "@grafana/plugin-configs": "npm:11.3.0-pre" + "@grafana/plugin-configs": "npm:11.4.0-pre" "@grafana/runtime": "workspace:*" "@grafana/schema": "workspace:*" "@grafana/ui": "workspace:*" From 7c79f8f7a5cc53e24ee19182087221f43eac96d9 Mon Sep 17 00:00:00 2001 From: Andreas Christou Date: Fri, 11 Oct 2024 11:31:37 +0100 Subject: [PATCH 077/110] GCM: Time field naming (#94548) * Name time field correctly * Update tests * Lint --- .../cloud-monitoring/time_series_filter.go | 3 ++ .../time_series_filter_test.go | 32 ++++++++++++++----- .../cloud-monitoring/time_series_query.go | 3 ++ .../time_series_query_test.go | 22 +++++++++++++ 4 files changed, 52 insertions(+), 8 deletions(-) diff --git a/pkg/tsdb/cloud-monitoring/time_series_filter.go b/pkg/tsdb/cloud-monitoring/time_series_filter.go index 5928840f599..baade93989a 100644 --- a/pkg/tsdb/cloud-monitoring/time_series_filter.go +++ b/pkg/tsdb/cloud-monitoring/time_series_filter.go @@ -37,6 +37,9 @@ func parseTimeSeriesResponse(queryRes *backend.DataResponse, "groupBys": groupBys, }, } + // Ensure the time field is named correctly + timeField := frame.Fields[0] + timeField.Name = data.TimeSeriesTimeFieldName var err error frames, err = appendFrames(frames, series, 0, defaultMetricName, seriesLabels, frame, query) diff --git a/pkg/tsdb/cloud-monitoring/time_series_filter_test.go b/pkg/tsdb/cloud-monitoring/time_series_filter_test.go index 031d40a0347..c44949e6c51 100644 --- a/pkg/tsdb/cloud-monitoring/time_series_filter_test.go +++ b/pkg/tsdb/cloud-monitoring/time_series_filter_test.go @@ -11,7 +11,7 @@ import ( "time" "github.com/grafana/grafana-plugin-sdk-go/backend" - sdkdata "github.com/grafana/grafana-plugin-sdk-go/data" + gdata "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/tsdb/cloud-monitoring/kinds/dataquery" "github.com/stretchr/testify/assert" @@ -429,7 +429,7 @@ func TestTimeSeriesFilter(t *testing.T) { frames := res.Frames custom, ok := frames[0].Meta.Custom.(map[string]any) require.True(t, ok) - labels, ok := custom["labels"].(sdkdata.Labels) + labels, ok := custom["labels"].(gdata.Labels) require.True(t, ok) assert.Equal(t, "114250375703598695", labels["resource.label.instance_id"]) }) @@ -459,12 +459,12 @@ func TestTimeSeriesFilter(t *testing.T) { require.NoError(t, (&cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{GroupBys: []string{"test_group_by"}}}).parseResponse(res, data, "test_query", service.logger)) require.NotNil(t, res.Frames[0].Meta) - assert.Equal(t, sdkdata.FrameMeta{ + assert.Equal(t, gdata.FrameMeta{ ExecutedQueryString: "test_query", Custom: map[string]any{ "groupBys": []string{"test_group_by"}, "alignmentPeriod": "", - "labels": sdkdata.Labels{ + "labels": gdata.Labels{ "resource.label.project_id": "grafana-prod", "resource.type": "https_lb_rule", }, @@ -482,12 +482,12 @@ func TestTimeSeriesFilter(t *testing.T) { require.NoError(t, (&cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{GroupBys: []string{"test_group_by"}}}).parseResponse(res, data, "test_query", service.logger)) require.NotNil(t, res.Frames[0].Meta) - assert.Equal(t, sdkdata.FrameMeta{ + assert.Equal(t, gdata.FrameMeta{ ExecutedQueryString: "test_query", Custom: map[string]any{ "groupBys": []string{"test_group_by"}, "alignmentPeriod": "", - "labels": sdkdata.Labels{ + "labels": gdata.Labels{ "resource.label.project_id": "grafana-demo", "resource.type": "global", }, @@ -505,12 +505,12 @@ func TestTimeSeriesFilter(t *testing.T) { require.NoError(t, (&cloudMonitoringTimeSeriesList{parameters: &dataquery.TimeSeriesList{GroupBys: []string{"test_group_by"}}}).parseResponse(res, data, "test_query", service.logger)) require.NotNil(t, res.Frames[0].Meta) - assert.Equal(t, sdkdata.FrameMeta{ + assert.Equal(t, gdata.FrameMeta{ ExecutedQueryString: "test_query", Custom: map[string]any{ "groupBys": []string{"test_group_by"}, "alignmentPeriod": "", - "labels": sdkdata.Labels{ + "labels": gdata.Labels{ "resource.label.project_id": "grafana-prod", "resource.type": "https_lb_rule", }, @@ -544,6 +544,22 @@ func TestTimeSeriesFilter(t *testing.T) { assert.Contains(t, value, `zone=monitoring.regex.full_match("us-central1-a~")`) }) }) + + t.Run("time field is appropriately named", func(t *testing.T) { + res := &backend.DataResponse{} + data, err := loadTestFile("./test-data/4-series-response-distribution-explicit.json") + require.NoError(t, err) + query := &cloudMonitoringTimeSeriesList{ + parameters: &dataquery.TimeSeriesList{ + ProjectName: "test-proj", + }, + aliasBy: "", + } + err = query.parseResponse(res, data, "", service.logger) + require.NoError(t, err) + frames := res.Frames + assert.Equal(t, gdata.TimeSeriesTimeFieldName, frames[0].Fields[0].Name) + }) } func loadTestFile(path string) (cloudMonitoringResponse, error) { diff --git a/pkg/tsdb/cloud-monitoring/time_series_query.go b/pkg/tsdb/cloud-monitoring/time_series_query.go index 13bd03d7d6e..169c151bc24 100644 --- a/pkg/tsdb/cloud-monitoring/time_series_query.go +++ b/pkg/tsdb/cloud-monitoring/time_series_query.go @@ -75,6 +75,9 @@ func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *b return err } } + // Ensure the time field is named correctly + timeField := frame.Fields[0] + timeField.Name = data.TimeSeriesTimeFieldName } if len(response.TimeSeriesData) > 0 { dl := timeSeriesQuery.buildDeepLink() diff --git a/pkg/tsdb/cloud-monitoring/time_series_query_test.go b/pkg/tsdb/cloud-monitoring/time_series_query_test.go index 8ddfc183366..4097630f43d 100644 --- a/pkg/tsdb/cloud-monitoring/time_series_query_test.go +++ b/pkg/tsdb/cloud-monitoring/time_series_query_test.go @@ -148,4 +148,26 @@ func TestTimeSeriesQuery(t *testing.T) { query := &cloudMonitoringTimeSeriesQuery{parameters: &dataquery.TimeSeriesQuery{GraphPeriod: strPtr("disabled")}} assert.Equal(t, query.appendGraphPeriod(&backend.QueryDataRequest{Queries: []backend.DataQuery{{}}}), "") }) + + t.Run("time field is appropriately named", func(t *testing.T) { + res := &backend.DataResponse{} + data, err := loadTestFile("./test-data/7-series-response-mql.json") + require.NoError(t, err) + fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local) + query := &cloudMonitoringTimeSeriesQuery{ + parameters: &dataquery.TimeSeriesQuery{ + ProjectName: "test-proj", + Query: "test-query", + }, + aliasBy: "", + timeRange: backend.TimeRange{ + From: fromStart, + To: fromStart.Add(34 * time.Minute), + }, + } + err = query.parseResponse(res, data, "", service.logger) + require.NoError(t, err) + frames := res.Frames + assert.Equal(t, gdata.TimeSeriesTimeFieldName, frames[0].Fields[0].Name) + }) } From 7ef38bd6c116fd1f4cf4735a1f1dd09cb7607bc6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:11:53 +0000 Subject: [PATCH 078/110] Update dependency sass to v1.79.5 --- package.json | 2 +- packages/grafana-prometheus/package.json | 2 +- yarn.lock | 165 ++++++++++++++++++++++- 3 files changed, 161 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 097af3e1ffc..7ed2390c6fb 100644 --- a/package.json +++ b/package.json @@ -222,7 +222,7 @@ "redux-mock-store": "1.5.4", "rimraf": "6.0.1", "rudder-sdk-js": "2.48.19", - "sass": "1.79.4", + "sass": "1.79.5", "sass-loader": "16.0.2", "smtp-tester": "^2.1.0", "style-loader": "4.0.0", diff --git a/packages/grafana-prometheus/package.json b/packages/grafana-prometheus/package.json index 0501e20e08d..df531ae0ccc 100644 --- a/packages/grafana-prometheus/package.json +++ b/packages/grafana-prometheus/package.json @@ -131,7 +131,7 @@ "rollup-plugin-dts": "^6.1.1", "rollup-plugin-esbuild": "6.1.1", "rollup-plugin-node-externals": "^7.1.3", - "sass": "1.79.4", + "sass": "1.79.5", "sass-loader": "16.0.2", "style-loader": "4.0.0", "testing-library-selector": "0.3.1", diff --git a/yarn.lock b/yarn.lock index 7e1167b8978..5176e1ed856 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4009,7 +4009,7 @@ __metadata: rollup-plugin-esbuild: "npm:6.1.1" rollup-plugin-node-externals: "npm:^7.1.3" rxjs: "npm:7.8.1" - sass: "npm:1.79.4" + sass: "npm:1.79.5" sass-loader: "npm:16.0.2" semver: "npm:7.6.3" style-loader: "npm:4.0.0" @@ -6143,6 +6143,140 @@ __metadata: languageName: node linkType: hard +"@parcel/watcher-android-arm64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-android-arm64@npm:2.4.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-darwin-arm64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-darwin-arm64@npm:2.4.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-darwin-x64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-darwin-x64@npm:2.4.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher-freebsd-x64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-freebsd-x64@npm:2.4.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm-glibc@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-arm-glibc@npm:2.4.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm64-glibc@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-arm64-glibc@npm:2.4.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-arm64-musl@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-arm64-musl@npm:2.4.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-linux-x64-glibc@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-x64-glibc@npm:2.4.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + +"@parcel/watcher-linux-x64-musl@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-linux-x64-musl@npm:2.4.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@parcel/watcher-win32-arm64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-win32-arm64@npm:2.4.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + +"@parcel/watcher-win32-ia32@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-win32-ia32@npm:2.4.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@parcel/watcher-win32-x64@npm:2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher-win32-x64@npm:2.4.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + +"@parcel/watcher@npm:^2.4.1": + version: 2.4.1 + resolution: "@parcel/watcher@npm:2.4.1" + dependencies: + "@parcel/watcher-android-arm64": "npm:2.4.1" + "@parcel/watcher-darwin-arm64": "npm:2.4.1" + "@parcel/watcher-darwin-x64": "npm:2.4.1" + "@parcel/watcher-freebsd-x64": "npm:2.4.1" + "@parcel/watcher-linux-arm-glibc": "npm:2.4.1" + "@parcel/watcher-linux-arm64-glibc": "npm:2.4.1" + "@parcel/watcher-linux-arm64-musl": "npm:2.4.1" + "@parcel/watcher-linux-x64-glibc": "npm:2.4.1" + "@parcel/watcher-linux-x64-musl": "npm:2.4.1" + "@parcel/watcher-win32-arm64": "npm:2.4.1" + "@parcel/watcher-win32-ia32": "npm:2.4.1" + "@parcel/watcher-win32-x64": "npm:2.4.1" + detect-libc: "npm:^1.0.3" + is-glob: "npm:^4.0.3" + micromatch: "npm:^4.0.5" + node-addon-api: "npm:^7.0.0" + node-gyp: "npm:latest" + dependenciesMeta: + "@parcel/watcher-android-arm64": + optional: true + "@parcel/watcher-darwin-arm64": + optional: true + "@parcel/watcher-darwin-x64": + optional: true + "@parcel/watcher-freebsd-x64": + optional: true + "@parcel/watcher-linux-arm-glibc": + optional: true + "@parcel/watcher-linux-arm64-glibc": + optional: true + "@parcel/watcher-linux-arm64-musl": + optional: true + "@parcel/watcher-linux-x64-glibc": + optional: true + "@parcel/watcher-linux-x64-musl": + optional: true + "@parcel/watcher-win32-arm64": + optional: true + "@parcel/watcher-win32-ia32": + optional: true + "@parcel/watcher-win32-x64": + optional: true + checksum: 10/c163dff1828fa249c00f24931332dea5a8f2fcd1bfdd0e304ccdf7619c58bff044526fa39241fd2121d2a2141f71775ce3117450d78c4df3070d152282017644 + languageName: node + linkType: hard + "@petamoriken/float16@npm:^3.4.7": version: 3.5.0 resolution: "@petamoriken/float16@npm:3.5.0" @@ -15665,6 +15799,15 @@ __metadata: languageName: node linkType: hard +"detect-libc@npm:^1.0.3": + version: 1.0.3 + resolution: "detect-libc@npm:1.0.3" + bin: + detect-libc: ./bin/detect-libc.js + checksum: 10/3849fe7720feb153e4ac9407086956e073f1ce1704488290ef0ca8aab9430a8d48c8a9f8351889e7cdc64e5b1128589501e4fef48f3a4a49ba92cd6d112d0757 + languageName: node + linkType: hard + "detect-libc@npm:^2.0.0": version: 2.0.3 resolution: "detect-libc@npm:2.0.3" @@ -19203,7 +19346,7 @@ __metadata: rimraf: "npm:6.0.1" rudder-sdk-js: "npm:2.48.19" rxjs: "npm:7.8.1" - sass: "npm:1.79.4" + sass: "npm:1.79.5" sass-loader: "npm:16.0.2" selecto: "npm:1.26.3" semver: "npm:7.6.3" @@ -24374,6 +24517,15 @@ __metadata: languageName: node linkType: hard +"node-addon-api@npm:^7.0.0": + version: 7.1.1 + resolution: "node-addon-api@npm:7.1.1" + dependencies: + node-gyp: "npm:latest" + checksum: 10/ee1e1ed6284a2f8cd1d59ac6175ecbabf8978dcf570345e9a8095a9d0a2b9ced591074ae77f9009287b00c402352b38aa9322a34f2199cdc9f567b842a636b94 + languageName: node + linkType: hard + "node-dir@npm:^0.1.17": version: 0.1.17 resolution: "node-dir@npm:0.1.17" @@ -29197,16 +29349,17 @@ __metadata: languageName: node linkType: hard -"sass@npm:1.79.4": - version: 1.79.4 - resolution: "sass@npm:1.79.4" +"sass@npm:1.79.5": + version: 1.79.5 + resolution: "sass@npm:1.79.5" dependencies: + "@parcel/watcher": "npm:^2.4.1" chokidar: "npm:^4.0.0" immutable: "npm:^4.0.0" source-map-js: "npm:>=0.6.2 <2.0.0" bin: sass: sass.js - checksum: 10/82e2ee5c2e46c96818454c7d97bcfb5b36c1c27de3b1e705adad7a49a8b32226c5254cc4c8804f45db2b6aa018848973177274c2b1137d4caf7abb5cb7bbf8b9 + checksum: 10/86f6ebe9c3c8d86cf8fff2e12f93109697140090604fb6180c8792c0e4fc98377c433dbd4c86a0272c47ba4c307edf24435a06d7e5995956048a23f9e187fedc languageName: node linkType: hard From 95afb3a1125db785721f56135179a25d7507c999 Mon Sep 17 00:00:00 2001 From: Giuseppe Guerra Date: Fri, 11 Oct 2024 12:33:18 +0200 Subject: [PATCH 079/110] Plugins: Track SystemJS load errors with Faro (#94465) * Test SRI check errors with Faro * Plugins: Send SystemJS import errors to Faro * unbork * track loadingStrategy * Revert "unbork" This reverts commit 02a61f4046772032c3391b497668147dd0039b0a. * Reapply "unbork" This reverts commit eaee8fbb39ae0a34766d4390f8bb1c53ad85ba3c. --- public/app/features/plugins/plugin_loader.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 3fa1f3bf98d..6d7ace4e8ca 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -23,6 +23,7 @@ import { SystemJSWithLoaderHooks } from './loader/types'; import { buildImportMap, resolveModulePath } from './loader/utils'; import { importPluginModuleInSandbox } from './sandbox/sandbox_plugin_loader'; import { isFrontendSandboxSupported } from './sandbox/utils'; +import { pluginsLogger } from './utils'; const imports = buildImportMap(sharedDependenciesMap); @@ -118,7 +119,19 @@ export async function importPluginModule({ return importPluginModuleInSandbox({ pluginId }); } - return SystemJS.import(modulePath); + return SystemJS.import(modulePath).catch((e) => { + let error = new Error('Could not load plugin: ' + e); + console.error(error); + pluginsLogger.logError(error, { + path, + pluginId, + pluginVersion: version ?? '', + expectedHash: moduleHash ?? '', + loadingStrategy: loadingStrategy.toString(), + sriChecksEnabled: (config.featureToggles.pluginsSriChecks ?? false).toString(), + }); + throw error; + }); } export function importDataSourcePlugin(meta: DataSourcePluginMeta): Promise { From e2672021bc828c3ef914ca2bcd0413754d5b686d Mon Sep 17 00:00:00 2001 From: Prem Saraswat Date: Fri, 11 Oct 2024 17:15:43 +0530 Subject: [PATCH 080/110] [unified-storage/apistore] Fix GuranteedUpdate skipping updates when `tryUpdate` is passed (#94557) `GuranteedUpdate` method of `apistore.Storage` had a bug, where it would errorneously conclude that the object is unchanged, in case a `tryUpdate` function is passed that modifies the existing object itself (as it is the case in many core types in K8s upstream). The modified `existingObj` was compared with `updatedObj`, which would essentially be same and this lead to the update being skipped. This patch fixes this by always passing a copy of the `existingObj`. Signed-off-by: Prem Kumar --- pkg/storage/unified/apistore/store.go | 2 +- pkg/storage/unified/apistore/store_test.go | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/storage/unified/apistore/store.go b/pkg/storage/unified/apistore/store.go index d4911da167c..11a5cc3e5ea 100644 --- a/pkg/storage/unified/apistore/store.go +++ b/pkg/storage/unified/apistore/store.go @@ -465,7 +465,7 @@ func (s *Storage) GuaranteedUpdate( return apierrors.NewNotFound(s.gr, req.Key.Name) } - updatedObj, _, err = tryUpdate(existingObj, res) + updatedObj, _, err = tryUpdate(existingObj.DeepCopyObject(), res) if err != nil { if attempt >= MaxUpdateAttempts { return err diff --git a/pkg/storage/unified/apistore/store_test.go b/pkg/storage/unified/apistore/store_test.go index 287aeea5c41..f0d648b9b5e 100644 --- a/pkg/storage/unified/apistore/store_test.go +++ b/pkg/storage/unified/apistore/store_test.go @@ -135,12 +135,12 @@ func TestDeleteWithSuggestion(t *testing.T) { storagetesting.RunTestDeleteWithSuggestion(ctx, t, store) } -//func TestDeleteWithSuggestionAndConflict(t *testing.T) { -// ctx, store, destroyFunc, err := testSetup(t) -// defer destroyFunc() -// assert.NoError(t, err) -// storagetesting.RunTestDeleteWithSuggestionAndConflict(ctx, t, store) -//} +func TestDeleteWithSuggestionAndConflict(t *testing.T) { + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestDeleteWithSuggestionAndConflict(ctx, t, store) +} // TODO: this test relies on update //func TestDeleteWithSuggestionOfDeletedObject(t *testing.T) { From 0a4e6ff86b24e4e2718dd64a69e5184d20a8357a Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Fri, 11 Oct 2024 13:47:44 +0200 Subject: [PATCH 081/110] Alerting: Add SaveAlertInstancesForRule instance store method (#94505) Alerting: Add SaveAlertInstancesForRule method to the InstanceStore interface --- pkg/services/ngalert/models/alert_rule.go | 10 ++++++++ .../ngalert/models/alert_rule_test.go | 24 +++++++++++++++++++ pkg/services/ngalert/models/testing.go | 8 +++++++ pkg/services/ngalert/schedule/alert_rule.go | 22 ++++++++--------- .../ngalert/schedule/alert_rule_test.go | 24 +++++++++---------- pkg/services/ngalert/state/manager.go | 4 ++-- pkg/services/ngalert/state/manager_test.go | 2 +- pkg/services/ngalert/state/persist.go | 4 +++- pkg/services/ngalert/state/testing.go | 6 ++++- .../ngalert/store/instance_database.go | 10 +++++++- 10 files changed, 85 insertions(+), 29 deletions(-) diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index 84cf3f4d4b3..6ce118341d0 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -464,6 +464,11 @@ type AlertRuleKeyWithVersion struct { AlertRuleKey `xorm:"extends"` } +type AlertRuleKeyWithGroup struct { + RuleGroup string + AlertRuleKey `xorm:"extends"` +} + type AlertRuleKeyWithId struct { AlertRuleKey ID int64 @@ -517,6 +522,11 @@ func (alertRule *AlertRule) GetKey() AlertRuleKey { return AlertRuleKey{OrgID: alertRule.OrgID, UID: alertRule.UID} } +// GetKeyWithGroup returns the alert definitions identifier +func (alertRule *AlertRule) GetKeyWithGroup() AlertRuleKeyWithGroup { + return AlertRuleKeyWithGroup{AlertRuleKey: alertRule.GetKey(), RuleGroup: alertRule.RuleGroup} +} + // GetGroupKey returns the identifier of a group the rule belongs to func (alertRule *AlertRule) GetGroupKey() AlertRuleGroupKey { return AlertRuleGroupKey{OrgID: alertRule.OrgID, NamespaceUID: alertRule.NamespaceUID, RuleGroup: alertRule.RuleGroup} diff --git a/pkg/services/ngalert/models/alert_rule_test.go b/pkg/services/ngalert/models/alert_rule_test.go index 652e0c23d41..5aa76c513d1 100644 --- a/pkg/services/ngalert/models/alert_rule_test.go +++ b/pkg/services/ngalert/models/alert_rule_test.go @@ -884,3 +884,27 @@ func TestTimeRangeYAML(t *testing.T) { require.NoError(t, err) require.Equal(t, yamlRaw, string(serialized)) } + +func TestAlertRuleGetKey(t *testing.T) { + t.Run("should return correct key", func(t *testing.T) { + rule := RuleGen.GenerateRef() + expected := AlertRuleKey{ + OrgID: rule.OrgID, + UID: rule.UID, + } + require.Equal(t, expected, rule.GetKey()) + }) +} + +func TestAlertRuleGetKeyWithGroup(t *testing.T) { + t.Run("should return correct key", func(t *testing.T) { + rule := RuleGen.With( + RuleMuts.WithUniqueGroupIndex(), + ).GenerateRef() + expected := AlertRuleKeyWithGroup{ + AlertRuleKey: rule.GetKey(), + RuleGroup: rule.RuleGroup, + } + require.Equal(t, expected, rule.GetKeyWithGroup()) + }) +} diff --git a/pkg/services/ngalert/models/testing.go b/pkg/services/ngalert/models/testing.go index af53eca7e82..b8b2b5ceed2 100644 --- a/pkg/services/ngalert/models/testing.go +++ b/pkg/services/ngalert/models/testing.go @@ -587,6 +587,14 @@ func GenerateRuleKey(orgID int64) AlertRuleKey { } } +// GenerateRuleKeyWithGroup generates a random alert rule key with group +func GenerateRuleKeyWithGroup(orgID int64) AlertRuleKeyWithGroup { + return AlertRuleKeyWithGroup{ + AlertRuleKey: GenerateRuleKey(orgID), + RuleGroup: util.GenerateShortUID(), + } +} + // GenerateGroupKey generates a random group key func GenerateGroupKey(orgID int64) AlertRuleGroupKey { return AlertRuleGroupKey{ diff --git a/pkg/services/ngalert/schedule/alert_rule.go b/pkg/services/ngalert/schedule/alert_rule.go index 25ceaf47783..9fe802d3dd1 100644 --- a/pkg/services/ngalert/schedule/alert_rule.go +++ b/pkg/services/ngalert/schedule/alert_rule.go @@ -86,7 +86,7 @@ func newRuleFactory( } return newAlertRule( ctx, - rule.GetKey(), + rule.GetKeyWithGroup(), appURL, disableGrafanaFolder, maxAttempts, @@ -112,7 +112,7 @@ type ruleProvider interface { } type alertRule struct { - key ngmodels.AlertRuleKey + key ngmodels.AlertRuleKeyWithGroup evalCh chan *Evaluation updateCh chan RuleVersionAndPauseStatus @@ -140,7 +140,7 @@ type alertRule struct { func newAlertRule( parent context.Context, - key ngmodels.AlertRuleKey, + key ngmodels.AlertRuleKeyWithGroup, appURL *url.URL, disableGrafanaFolder bool, maxAttempts int64, @@ -155,7 +155,7 @@ func newAlertRule( evalAppliedHook func(ngmodels.AlertRuleKey, time.Time), stopAppliedHook func(ngmodels.AlertRuleKey), ) *alertRule { - ctx, stop := util.WithCancelCause(ngmodels.WithRuleKey(parent, key)) + ctx, stop := util.WithCancelCause(ngmodels.WithRuleKey(parent, key.AlertRuleKey)) return &alertRule{ key: key, evalCh: make(chan *Evaluation), @@ -194,7 +194,7 @@ func (a *alertRule) Status() ngmodels.RuleStatus { // // the second element contains a dropped message that was sent by a concurrent sender. func (a *alertRule) Eval(eval *Evaluation) (bool, *Evaluation) { - if a.key != eval.rule.GetKey() { + if a.key != eval.rule.GetKeyWithGroup() { // Make sure that rule has the same key. This should not happen a.logger.Error("Invalid rule sent for evaluating. Skipping", "ruleKeyToEvaluate", eval.rule.GetKey().String()) return false, eval @@ -352,7 +352,7 @@ func (a *alertRule) Run() error { // cases. ctx, cancelFunc := context.WithTimeout(context.Background(), time.Minute) defer cancelFunc() - states := a.stateManager.DeleteStateByRuleUID(ngmodels.WithRuleKey(ctx, a.key), a.key, ngmodels.StateReasonRuleDeleted) + states := a.stateManager.DeleteStateByRuleUID(ngmodels.WithRuleKey(ctx, a.key.AlertRuleKey), a.key, ngmodels.StateReasonRuleDeleted) a.expireAndSend(grafanaCtx, states) } a.logger.Debug("Stopping alert rule routine") @@ -467,7 +467,7 @@ func (a *alertRule) send(ctx context.Context, logger log.Logger, states state.St if len(alerts.PostableAlerts) > 0 { logger.Debug("Sending transitions to notifier", "transitions", len(alerts.PostableAlerts)) - a.sender.Send(ctx, a.key, alerts) + a.sender.Send(ctx, a.key.AlertRuleKey, alerts) } return alerts } @@ -476,12 +476,12 @@ func (a *alertRule) send(ctx context.Context, logger log.Logger, states state.St func (a *alertRule) expireAndSend(ctx context.Context, states []state.StateTransition) { expiredAlerts := state.FromAlertsStateToStoppedAlert(states, a.appURL, a.clock) if len(expiredAlerts.PostableAlerts) > 0 { - a.sender.Send(ctx, a.key, expiredAlerts) + a.sender.Send(ctx, a.key.AlertRuleKey, expiredAlerts) } } func (a *alertRule) resetState(ctx context.Context, isPaused bool) { - rule := a.ruleProvider.get(a.key) + rule := a.ruleProvider.get(a.key.AlertRuleKey) reason := ngmodels.StateReasonUpdated if isPaused { reason = ngmodels.StateReasonPaused @@ -496,7 +496,7 @@ func (a *alertRule) evalApplied(now time.Time) { return } - a.evalAppliedHook(a.key, now) + a.evalAppliedHook(a.key.AlertRuleKey, now) } // stopApplied is only used on tests. @@ -505,7 +505,7 @@ func (a *alertRule) stopApplied() { return } - a.stopAppliedHook(a.key) + a.stopAppliedHook(a.key.AlertRuleKey) } func SchedulerUserFor(orgID int64) *user.SignedInUser { diff --git a/pkg/services/ngalert/schedule/alert_rule_test.go b/pkg/services/ngalert/schedule/alert_rule_test.go index 0621204481d..7433d62ec2f 100644 --- a/pkg/services/ngalert/schedule/alert_rule_test.go +++ b/pkg/services/ngalert/schedule/alert_rule_test.go @@ -39,7 +39,7 @@ func TestAlertRule(t *testing.T) { t.Run("when rule evaluation is not stopped", func(t *testing.T) { t.Run("update should send to updateCh", func(t *testing.T) { - r := blankRuleForTests(context.Background(), models.GenerateRuleKey(1)) + r := blankRuleForTests(context.Background(), models.GenerateRuleKeyWithGroup(1)) resultCh := make(chan bool) go func() { resultCh <- r.Update(RuleVersionAndPauseStatus{fingerprint(rand.Uint64()), false}) @@ -52,7 +52,7 @@ func TestAlertRule(t *testing.T) { } }) t.Run("update should drop any concurrent sending to updateCh", func(t *testing.T) { - r := blankRuleForTests(context.Background(), models.GenerateRuleKey(1)) + r := blankRuleForTests(context.Background(), models.GenerateRuleKeyWithGroup(1)) version1 := RuleVersionAndPauseStatus{fingerprint(rand.Uint64()), false} version2 := RuleVersionAndPauseStatus{fingerprint(rand.Uint64()), false} @@ -79,7 +79,7 @@ func TestAlertRule(t *testing.T) { }) t.Run("eval should send to evalCh", func(t *testing.T) { ruleSpec := gen.GenerateRef() - r := blankRuleForTests(context.Background(), ruleSpec.GetKey()) + r := blankRuleForTests(context.Background(), ruleSpec.GetKeyWithGroup()) expected := time.Now() resultCh := make(chan evalResponse) data := &Evaluation{ @@ -103,7 +103,7 @@ func TestAlertRule(t *testing.T) { }) t.Run("eval should drop any concurrent sending to evalCh", func(t *testing.T) { ruleSpec := gen.GenerateRef() - r := blankRuleForTests(context.Background(), ruleSpec.GetKey()) + r := blankRuleForTests(context.Background(), ruleSpec.GetKeyWithGroup()) time1 := time.UnixMilli(rand.Int63n(math.MaxInt64)) time2 := time.UnixMilli(rand.Int63n(math.MaxInt64)) resultCh1 := make(chan evalResponse) @@ -150,7 +150,7 @@ func TestAlertRule(t *testing.T) { }) t.Run("eval should exit when context is cancelled", func(t *testing.T) { ruleSpec := gen.GenerateRef() - r := blankRuleForTests(context.Background(), ruleSpec.GetKey()) + r := blankRuleForTests(context.Background(), ruleSpec.GetKeyWithGroup()) resultCh := make(chan evalResponse) data := &Evaluation{ scheduledAt: time.Now(), @@ -174,14 +174,14 @@ func TestAlertRule(t *testing.T) { }) t.Run("when rule evaluation is stopped", func(t *testing.T) { t.Run("Update should do nothing", func(t *testing.T) { - r := blankRuleForTests(context.Background(), models.GenerateRuleKey(1)) + r := blankRuleForTests(context.Background(), models.GenerateRuleKeyWithGroup(1)) r.Stop(errRuleDeleted) require.ErrorIs(t, r.ctx.Err(), errRuleDeleted) require.False(t, r.Update(RuleVersionAndPauseStatus{fingerprint(rand.Uint64()), false})) }) t.Run("eval should do nothing", func(t *testing.T) { ruleSpec := gen.GenerateRef() - r := blankRuleForTests(context.Background(), ruleSpec.GetKey()) + r := blankRuleForTests(context.Background(), ruleSpec.GetKeyWithGroup()) r.Stop(nil) data := &Evaluation{ scheduledAt: time.Now(), @@ -193,19 +193,19 @@ func TestAlertRule(t *testing.T) { require.Nilf(t, dropped, "expected no dropped evaluations but got one") }) t.Run("calling stop multiple times should not panic", func(t *testing.T) { - r := blankRuleForTests(context.Background(), models.GenerateRuleKey(1)) + r := blankRuleForTests(context.Background(), models.GenerateRuleKeyWithGroup(1)) r.Stop(nil) r.Stop(nil) }) t.Run("stop should not panic if parent context stopped", func(t *testing.T) { ctx, cancelFn := context.WithCancel(context.Background()) - r := blankRuleForTests(ctx, models.GenerateRuleKey(1)) + r := blankRuleForTests(ctx, models.GenerateRuleKeyWithGroup(1)) cancelFn() r.Stop(nil) }) }) t.Run("should be thread-safe", func(t *testing.T) { - r := blankRuleForTests(context.Background(), models.GenerateRuleKey(1)) + r := blankRuleForTests(context.Background(), models.GenerateRuleKeyWithGroup(1)) wg := sync.WaitGroup{} go func() { for { @@ -249,7 +249,7 @@ func TestAlertRule(t *testing.T) { }) t.Run("Run should exit if idle when Stop is called", func(t *testing.T) { - rule := blankRuleForTests(context.Background(), models.GenerateRuleKey(1)) + rule := blankRuleForTests(context.Background(), models.GenerateRuleKeyWithGroup(1)) runResult := make(chan error) go func() { runResult <- rule.Run() @@ -266,7 +266,7 @@ func TestAlertRule(t *testing.T) { }) } -func blankRuleForTests(ctx context.Context, key models.AlertRuleKey) *alertRule { +func blankRuleForTests(ctx context.Context, key models.AlertRuleKeyWithGroup) *alertRule { return newAlertRule(ctx, key, nil, false, 0, nil, nil, nil, nil, nil, nil, log.NewNopLogger(), nil, nil, nil) } diff --git a/pkg/services/ngalert/state/manager.go b/pkg/services/ngalert/state/manager.go index fc23cd85998..94c5371e308 100644 --- a/pkg/services/ngalert/state/manager.go +++ b/pkg/services/ngalert/state/manager.go @@ -240,7 +240,7 @@ func (st *Manager) Get(orgID int64, alertRuleUID string, stateId data.Fingerprin // DeleteStateByRuleUID removes the rule instances from cache and instanceStore. A closed channel is returned to be able // to gracefully handle the clear state step in scheduler in case we do not need to use the historian to save state // history. -func (st *Manager) DeleteStateByRuleUID(ctx context.Context, ruleKey ngModels.AlertRuleKey, reason string) []StateTransition { +func (st *Manager) DeleteStateByRuleUID(ctx context.Context, ruleKey ngModels.AlertRuleKeyWithGroup, reason string) []StateTransition { logger := st.log.FromContext(ctx) logger.Debug("Resetting state of the rule") @@ -290,7 +290,7 @@ func (st *Manager) DeleteStateByRuleUID(ctx context.Context, ruleKey ngModels.Al // ResetStateByRuleUID removes the rule instances from cache and instanceStore and saves state history. If the state // history has to be saved, rule must not be nil. func (st *Manager) ResetStateByRuleUID(ctx context.Context, rule *ngModels.AlertRule, reason string) []StateTransition { - ruleKey := rule.GetKey() + ruleKey := rule.GetKeyWithGroup() transitions := st.DeleteStateByRuleUID(ctx, ruleKey, reason) if rule == nil || st.historian == nil || len(transitions) == 0 { diff --git a/pkg/services/ngalert/state/manager_test.go b/pkg/services/ngalert/state/manager_test.go index 75a25aeeb4b..a46e5357fe5 100644 --- a/pkg/services/ngalert/state/manager_test.go +++ b/pkg/services/ngalert/state/manager_test.go @@ -2083,7 +2083,7 @@ func TestDeleteStateByRuleUID(t *testing.T) { assert.Equal(t, tc.startingInstanceDBCount, len(alerts)) expectedReason := util.GenerateShortUID() - transitions := st.DeleteStateByRuleUID(ctx, rule.GetKey(), expectedReason) + transitions := st.DeleteStateByRuleUID(ctx, rule.GetKeyWithGroup(), expectedReason) // Check that the deleted states are the same as the ones that were in cache assert.Equal(t, tc.startingStateCacheCount, len(transitions)) diff --git a/pkg/services/ngalert/state/persist.go b/pkg/services/ngalert/state/persist.go index b3e237a4fb7..a6d9a74d500 100644 --- a/pkg/services/ngalert/state/persist.go +++ b/pkg/services/ngalert/state/persist.go @@ -13,7 +13,9 @@ type InstanceStore interface { ListAlertInstances(ctx context.Context, cmd *models.ListAlertInstancesQuery) ([]*models.AlertInstance, error) SaveAlertInstance(ctx context.Context, instance models.AlertInstance) error DeleteAlertInstances(ctx context.Context, keys ...models.AlertInstanceKey) error - DeleteAlertInstancesByRule(ctx context.Context, key models.AlertRuleKey) error + // SaveAlertInstancesForRule overwrites the state for the given rule. + SaveAlertInstancesForRule(ctx context.Context, key models.AlertRuleKeyWithGroup, instances []models.AlertInstance) error + DeleteAlertInstancesByRule(ctx context.Context, key models.AlertRuleKeyWithGroup) error FullSync(ctx context.Context, instances []models.AlertInstance) error } diff --git a/pkg/services/ngalert/state/testing.go b/pkg/services/ngalert/state/testing.go index 6ec84d0df8f..ebbc1bb3fc9 100644 --- a/pkg/services/ngalert/state/testing.go +++ b/pkg/services/ngalert/state/testing.go @@ -56,7 +56,11 @@ func (f *FakeInstanceStore) DeleteAlertInstances(ctx context.Context, q ...model return nil } -func (f *FakeInstanceStore) DeleteAlertInstancesByRule(ctx context.Context, key models.AlertRuleKey) error { +func (f *FakeInstanceStore) SaveAlertInstancesForRule(ctx context.Context, key models.AlertRuleKeyWithGroup, instances []models.AlertInstance) error { + return nil +} + +func (f *FakeInstanceStore) DeleteAlertInstancesByRule(ctx context.Context, key models.AlertRuleKeyWithGroup) error { return nil } diff --git a/pkg/services/ngalert/store/instance_database.go b/pkg/services/ngalert/store/instance_database.go index c5f1b654bc4..4ede66e2a4a 100644 --- a/pkg/services/ngalert/store/instance_database.go +++ b/pkg/services/ngalert/store/instance_database.go @@ -2,6 +2,7 @@ package store import ( "context" + "errors" "fmt" "sort" "strings" @@ -32,6 +33,7 @@ func (st DBstore) ListAlertInstances(ctx context.Context, cmd *models.ListAlertI if cmd.RuleUID != "" { addToQuery(` AND rule_uid = ?`, cmd.RuleUID) } + if st.FeatureToggles.IsEnabled(ctx, featuremgmt.FlagAlertingNoNormalState) { s.WriteString(fmt.Sprintf(" AND NOT (current_state = '%s' AND current_reason = '')", models.InstanceStateNormal)) } @@ -206,7 +208,13 @@ func (st DBstore) DeleteAlertInstances(ctx context.Context, keys ...models.Alert return err } -func (st DBstore) DeleteAlertInstancesByRule(ctx context.Context, key models.AlertRuleKey) error { +// SaveAlertInstancesForRule is not implemented for instance database store. +func (st DBstore) SaveAlertInstancesForRule(ctx context.Context, key models.AlertRuleKeyWithGroup, instances []models.AlertInstance) error { + st.Logger.Error("SaveAlertInstancesForRule is not implemented for instance database store.") + return errors.New("method SaveAlertInstancesForRule is not implemented for instance database store") +} + +func (st DBstore) DeleteAlertInstancesByRule(ctx context.Context, key models.AlertRuleKeyWithGroup) error { return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *db.Session) error { _, err := sess.Exec("DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid = ?", key.OrgID, key.UID) return err From 1596acbd3b53f59f9498426121acd3665f548dd9 Mon Sep 17 00:00:00 2001 From: Sonia Aguilar <33540275+soniaAguilarPeiron@users.noreply.github.com> Date: Fri, 11 Oct 2024 13:55:01 +0200 Subject: [PATCH 082/110] Alerting: Add data-testids for Fullstory (#94555) * add data-testids for Fullstory * add more data-testids --- .../unified/components/rule-editor/NotificationsStep.tsx | 1 + .../unified/components/rule-editor/RuleEditorSection.tsx | 6 +++++- .../rule-editor/alert-rule-form/AlertRuleForm.tsx | 2 ++ .../components/rule-editor/labels/LabelsFieldInForm.tsx | 9 ++++++++- .../QueryAndExpressionsStep.tsx | 2 +- .../query-and-alert-condition/SmartAlertTypeDetector.tsx | 3 ++- 6 files changed, 19 insertions(+), 4 deletions(-) diff --git a/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx index 3a780463010..c7694e69519 100644 --- a/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/NotificationsStep.tsx @@ -143,6 +143,7 @@ function ManualAndAutomaticRouting({ alertUid }: { alertUid?: string }) { { switchMode.setAdvancedMode(event.currentTarget.checked); diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index a22616ff227..046e1387429 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -236,6 +236,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { {existing && ( )} diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx index 535f13d37f4..085df8a30e5 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/QueryAndExpressionsStep.tsx @@ -718,7 +718,7 @@ function TypeSelectorButton({ onClickType }: { onClickType: (type: ExpressionQue return ( - diff --git a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SmartAlertTypeDetector.tsx b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SmartAlertTypeDetector.tsx index b1f83b217aa..cc9bd6a8241 100644 --- a/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SmartAlertTypeDetector.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/query-and-alert-condition/SmartAlertTypeDetector.tsx @@ -2,7 +2,7 @@ import { useFormContext } from 'react-hook-form'; import { DataSourceInstanceSettings } from '@grafana/data'; import { DataSourceJsonData } from '@grafana/schema'; -import { RadioButtonGroup, Text, Stack } from '@grafana/ui'; +import { RadioButtonGroup, Stack, Text } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { ExpressionDatasourceUID } from 'app/features/expressions/types'; import { AccessControlAction } from 'app/types'; @@ -133,6 +133,7 @@ export function SmartAlertTypeDetector({ disabledOptions={disabledOptions} value={ruleFormType} onChange={onClickSwitch} + data-testid="rule-type-radio-group" /> {/* editing an existing rule, we just show "cannot be changed" */} {editingExistingRule && ( From 01897edccd1145c5fcc24affc539a32871a5b5aa Mon Sep 17 00:00:00 2001 From: haelekuin <86392447+haelekuin@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:25:49 +0200 Subject: [PATCH 083/110] Docs: Fix incorrect authentication token field name (#94610) --- docs/sources/setup-grafana/configure-security/audit-grafana.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/setup-grafana/configure-security/audit-grafana.md b/docs/sources/setup-grafana/configure-security/audit-grafana.md index 981eddbbd08..9f421becc5f 100644 --- a/docs/sources/setup-grafana/configure-security/audit-grafana.md +++ b/docs/sources/setup-grafana/configure-security/audit-grafana.md @@ -48,7 +48,7 @@ Audit logs contain the following fields. The fields followed by **\*** are alway | `user.orgId`\* | number | Current organization of the user that made the request. | | `user.orgRole` | string | Current role of the user that made the request. | | `user.name` | string | Name of the Grafana user that made the request. | -| `user.tokenId` | number | ID of the user authentication token. | +| `user.authTokenId` | number | ID of the user authentication token. | | `user.apiKeyId` | number | ID of the Grafana API key used to make the request. | | `user.isAnonymous`\* | boolean | If an anonymous user made the request, `true`. Otherwise, `false`. | | `action`\* | string | The request action. For example, `create`, `update`, or `manage-permissions`. | From 62738dd5c8203e1c06dc7764cbf554eb68690e41 Mon Sep 17 00:00:00 2001 From: Misi Date: Fri, 11 Oct 2024 14:42:32 +0200 Subject: [PATCH 084/110] Auth: Fix missing authLabels from user list page (#94612) Fix slice initialization --- pkg/services/searchusers/searchusers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/searchusers/searchusers.go b/pkg/services/searchusers/searchusers.go index 036ac87aee7..683a318e4d4 100644 --- a/pkg/services/searchusers/searchusers.go +++ b/pkg/services/searchusers/searchusers.go @@ -112,7 +112,7 @@ func (s *OSSService) SearchUser(c *contextmodel.ReqContext) (*user.SearchUserQue for _, user := range res.Users { user.AvatarURL = dtos.GetGravatarUrl(s.cfg, user.Email) - user.AuthLabels = make([]string, len(user.AuthModule)) + user.AuthLabels = make([]string, 0, len(user.AuthModule)) for _, authModule := range user.AuthModule { user.AuthLabels = append(user.AuthLabels, login.GetAuthProviderLabel(authModule)) } From 8a3508a547c1f6407518e492a1847fbc400a5382 Mon Sep 17 00:00:00 2001 From: Kim Nylander <104772500+knylander-grafana@users.noreply.github.com> Date: Fri, 11 Oct 2024 08:57:05 -0400 Subject: [PATCH 085/110] [DOC] Remove traceQLStreaming feature flag from docs (#94581) * Remove traceQLStreaming feature flag from docs * Apply suggestions from code review Co-authored-by: Jack Baldry * Updates from prettier --------- Co-authored-by: Jack Baldry --- .../tempo/configure-tempo-data-source.md | 21 +++++++------------ .../feature-toggles/index.md | 1 - .../datasources/tempo-editor-traceql.md | 6 ++---- .../datasources/tempo-search-traceql.md | 9 +++----- 4 files changed, 13 insertions(+), 24 deletions(-) diff --git a/docs/sources/datasources/tempo/configure-tempo-data-source.md b/docs/sources/datasources/tempo/configure-tempo-data-source.md index 1a85b2357af..9505f33bd41 100644 --- a/docs/sources/datasources/tempo/configure-tempo-data-source.md +++ b/docs/sources/datasources/tempo/configure-tempo-data-source.md @@ -123,30 +123,25 @@ For additional information on setting up TLS encryption with Tempo, refer to [Co ## Streaming - - Streaming enables TraceQL query results to be displayed as they become available. Without streaming, no results are displayed until all results have returned. -{{< docs/public-preview product="TraceQL streaming results" >}} - To use streaming, you need to: -- Run Tempo version 2.2 or newer, or Grafana Enterprise Traces (GET) version 2.2 or newer, or use Grafana Cloud Traces. -- For self-managed Tempo or GET instances: If your Tempo or GET instance is behind a load balancer or proxy that doesn't supporting gRPC or HTTP2, streaming may not work and should be disabled. +- Run Tempo version 2.2 or later, or Grafana Enterprise Traces (GET) version 2.2 or later, or use Grafana Cloud Traces. +- Tempo must have `stream_over_http_enabled: true` for streaming to work. + + For more information, refer to [Tempo GRPC API](https://grafana.com/docs/tempo//api_docs/#tempo-grpc-api). + +- For self-managed Tempo or GET instances: If your Tempo or GET instance is behind a load balancer or proxy that doesn't supporting gRPC or HTTP2, streaming may not work and should be deactivated. ### Activate streaming -You can activate streaming by either setting the `traceQLStreaming` [feature toggle](https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/feature-toggles/) to true or by activating the **Streaming** toggle in the Tempo data source. +Streaming is available in Grafana and Grafana Cloud. +You can activate streaming by turning the **Streaming** toggle to on in the Tempo data source. ![Streaming section in Tempo data source](/media/docs/grafana/data-sources/tempo-data-source-streaming-v11.2.png) -If you are using Grafana Cloud, the `traceQLStreaming` feature toggle is already set to `true` by default. - -If the Tempo data source is set to allow streaming but the `traceQLStreaming` feature toggle is set to `false` in Grafana, streaming occurs. - -If the data source has streaming disabled and `traceQLStreaming` is set to `true`, streaming happens for that data source. - When streaming is active, it's shows as **Enabled** in **Explore**. To check the status, select Explore in the menu, select your Tempo data source, and expand the **Options** section. diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 46c13858bb3..a76696808bf 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -41,7 +41,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `recordedQueriesMulti` | Enables writing multiple items from a single query within Recorded Queries | Yes | | `logsExploreTableVisualisation` | A table visualisation for logs in Explore | Yes | | `transformationsRedesign` | Enables the transformations redesign | Yes | -| `traceQLStreaming` | Enables response streaming of TraceQL queries of the Tempo data source | | | `awsAsyncQueryCaching` | Enable caching for async queries for Redshift and Athena. Requires that the datasource has caching and async query support enabled | Yes | | `prometheusConfigOverhaulAuth` | Update the Prometheus configuration page with the new auth component | Yes | | `alertingNoDataErrorExecution` | Changes how Alerting state manager handles execution of NoData/Error | Yes | diff --git a/docs/sources/shared/datasources/tempo-editor-traceql.md b/docs/sources/shared/datasources/tempo-editor-traceql.md index 8b47093f616..4a63ecb651b 100644 --- a/docs/sources/shared/datasources/tempo-editor-traceql.md +++ b/docs/sources/shared/datasources/tempo-editor-traceql.md @@ -129,10 +129,8 @@ Using the **Spans** option makes it easier access the spans to apply transformat The Tempo data source supports streaming responses to TraceQL queries so you can see partial query results as they come in without waiting for the whole query to finish. -{{% admonition type="note" %}} -To use this feature in Grafana OSS v10.1 and later, enable the `traceQLStreaming` feature toggle. This capability is enabled by default in Grafana Cloud. -{{% /admonition %}} - Streaming is available for both the **Search** and **TraceQL** query types, and you'll get immediate visibility of incoming traces on the results table. +To learn how to activate streaming, refer to [Streaming](https://grafana.com/docs/grafana//datasources/tempo/configure-tempo-data-source/#streaming) in the Tempo data source documentation. + {{< video-embed src="/media/docs/grafana/data-sources/tempo-streaming-v2.mp4" >}} diff --git a/docs/sources/shared/datasources/tempo-search-traceql.md b/docs/sources/shared/datasources/tempo-search-traceql.md index 5f5f19b034e..e51a25860f5 100644 --- a/docs/sources/shared/datasources/tempo-search-traceql.md +++ b/docs/sources/shared/datasources/tempo-search-traceql.md @@ -167,12 +167,9 @@ Selecting a Trace ID (2 in the screenshot) displays more detailed information (3 The Tempo data source supports streaming responses to TraceQL queries so you can see partial query results as they come in without waiting for the whole query to finish. -{{% admonition type="note" %}} -To use this public preview feature, enable the `traceQLStreaming` feature toggle. -When active, all configured Tempo data sources will attempt to use streaming. -You can control which Tempo data sources do and don't attempt to stream results at the per-data source level using the **Streaming** section of the Tempo data source configuration. - -{{% /admonition %}} +When active, all configured Tempo data sources attempt to use streaming. +You can activate and control which Tempo data sources do and don't attempt to stream results at the per-data source level using the **Streaming** section of the Tempo data source configuration. +For more information, refer to the [Tempo data source](https://grafana.com/docs/grafana//datasources/tempo/configure-tempo-data-source/#streaming) documentation. Streaming is available for both the **Search** and **TraceQL** query types. You'll get immediate visibility of incoming traces on the results table. From 992186c88f34df11a8aa582dedd1b8d8393ad41f Mon Sep 17 00:00:00 2001 From: "Arati R." <33031346+suntala@users.noreply.github.com> Date: Fri, 11 Oct 2024 15:13:56 +0200 Subject: [PATCH 086/110] K8s/Folders: Require create permissions when creating folder (#94514) * Require create permissions when creating folder * Test folder create permissions * Add test for nested folder permissions on creation * Replace hardcoded verbs --- pkg/registry/apis/folders/legacy_storage.go | 1 + pkg/registry/apis/folders/register.go | 29 ++-- pkg/tests/apis/folder/folders_test.go | 175 +++++++++++++++----- 3 files changed, 155 insertions(+), 50 deletions(-) diff --git a/pkg/registry/apis/folders/legacy_storage.go b/pkg/registry/apis/folders/legacy_storage.go index 283c5d47887..858d2fdaef4 100644 --- a/pkg/registry/apis/folders/legacy_storage.go +++ b/pkg/registry/apis/folders/legacy_storage.go @@ -174,6 +174,7 @@ func (s *legacyStorage) Create(ctx context.Context, if err != nil { return nil, err } + parent := accessor.GetFolder() out, err := s.service.Create(ctx, &folder.CreateFolderCommand{ diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index 470d4baf287..a891d5f249d 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -15,6 +15,7 @@ import ( "k8s.io/kube-openapi/pkg/spec3" "github.com/grafana/grafana/pkg/apimachinery/identity" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" @@ -154,7 +155,9 @@ func (b *FolderAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAP func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer { return authorizer.AuthorizerFunc( func(ctx context.Context, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) { - if !attr.IsResourceRequest() || attr.GetName() == "" { + verb := attr.GetVerb() + name := attr.GetName() + if (!attr.IsResourceRequest()) || (name == "" && verb != utils.VerbCreate) { return authorizer.DecisionNoOpinion, "", nil } @@ -164,24 +167,24 @@ func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer { return authorizer.DecisionDeny, "valid user is required", err } - action := dashboards.ActionFoldersRead - scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(attr.GetName()) + scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(name) + eval := accesscontrol.EvalPermission(dashboards.ActionFoldersRead, scope) // "get" is used for sub-resources with GET http (parents, access, count) - switch attr.GetVerb() { - case "patch": + switch verb { + case utils.VerbCreate: + eval = accesscontrol.EvalPermission(dashboards.ActionFoldersCreate) + case utils.VerbPatch: fallthrough - case "create": + case utils.VerbUpdate: + eval = accesscontrol.EvalPermission(dashboards.ActionFoldersWrite, scope) + case utils.VerbDeleteCollection: fallthrough - case "update": - action = dashboards.ActionFoldersWrite - case "deletecollection": - fallthrough - case "delete": - action = dashboards.ActionFoldersDelete + case utils.VerbDelete: + eval = accesscontrol.EvalPermission(dashboards.ActionFoldersDelete, scope) } - ok, err := b.accessControl.Evaluate(ctx, user, accesscontrol.EvalPermission(action, scope)) + ok, err := b.accessControl.Evaluate(ctx, user, eval) if ok { return authorizer.DecisionAllow, "", nil } diff --git a/pkg/tests/apis/folder/folders_test.go b/pkg/tests/apis/folder/folders_test.go index 9683eb03cbb..6c7afa8e2cc 100644 --- a/pkg/tests/apis/folder/folders_test.go +++ b/pkg/tests/apis/folder/folders_test.go @@ -17,8 +17,10 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" folderv0alpha1 "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" + "github.com/grafana/grafana/pkg/services/org" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tests/apis" "github.com/grafana/grafana/pkg/tests/testinfra" @@ -289,8 +291,8 @@ func TestIntegrationFoldersApp(t *testing.T) { doFolderTests(t, helper) }) - t.Run("with dual write (unified storage, mode 1, nested folders)", func(t *testing.T) { - checkNestedCreate(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + t.Run("with dual write (unified storage, mode 1, create nested folders)", func(t *testing.T) { + doNestedCreateTest(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ AppModeProduction: true, DisableAnonymous: true, APIServerStorageType: "unified", @@ -388,7 +390,48 @@ func doFolderTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelper return helper } -func checkNestedCreate(t *testing.T, helper *apis.K8sTestHelper) { +// This does a get with both k8s and legacy API, and verifies the results are the same +func getFromBothAPIs(t *testing.T, + helper *apis.K8sTestHelper, + client *apis.K8sResourceClient, + uid string, + // Optionally match some expect some values + expect *folder.Folder, +) *unstructured.Unstructured { + t.Helper() + + found, err := client.Resource.Get(context.Background(), uid, metav1.GetOptions{}) + require.NoError(t, err) + require.Equal(t, uid, found.GetName()) + + dto := apis.DoRequest(helper, apis.RequestParams{ + User: client.Args.User, + Method: http.MethodGet, + Path: "/api/folders/" + uid, + }, &folder.Folder{}).Result + require.NotNil(t, dto) + require.Equal(t, uid, dto.UID) + + spec, ok := found.Object["spec"].(map[string]any) + require.True(t, ok) + require.Equal(t, dto.UID, found.GetName()) + require.Equal(t, dto.Title, spec["title"]) + // #TODO add checks for other fields + + if expect != nil { + if expect.Title != "" { + require.Equal(t, expect.Title, dto.Title) + require.Equal(t, expect.Title, spec["title"]) + } + if expect.UID != "" { + require.Equal(t, expect.UID, dto.UID) + require.Equal(t, expect.UID, found.GetName()) + } + } + return found +} + +func doNestedCreateTest(t *testing.T, helper *apis.K8sTestHelper) { client := helper.GetResourceClient(apis.ResourceClientArgs{ User: helper.Org1.Admin, GVR: gvr, @@ -431,43 +474,101 @@ func checkNestedCreate(t *testing.T, helper *apis.K8sTestHelper) { require.Equal(t, parentCreate.Result.URL, parent.URL) } -// This does a get with both k8s and legacy API, and verifies the results are the same -func getFromBothAPIs(t *testing.T, - helper *apis.K8sTestHelper, - client *apis.K8sResourceClient, - uid string, - // Optionally match some expect some values - expect *folder.Folder, -) *unstructured.Unstructured { - t.Helper() +func TestIntegrationFolderCreatePermissions(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } - found, err := client.Resource.Get(context.Background(), uid, metav1.GetOptions{}) - require.NoError(t, err) - require.Equal(t, uid, found.GetName()) + folderWithoutParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\"}" + folderWithParentInput := "{ \"uid\": \"uid\", \"title\": \"Folder\", \"parentUid\": \"parentuid\"}" - dto := apis.DoRequest(helper, apis.RequestParams{ - User: client.Args.User, - Method: http.MethodGet, - Path: "/api/folders/" + uid, - }, &folder.Folder{}).Result - require.NotNil(t, dto) - require.Equal(t, uid, dto.UID) + type testCase struct { + description string + input string + permissions []resourcepermissions.SetResourcePermissionCommand + expectedCode int + } + tcs := []testCase{ + { + description: "creation of folder without parent succeeds given the correct request for creating a folder", + input: folderWithoutParentInput, + expectedCode: http.StatusOK, + permissions: []resourcepermissions.SetResourcePermissionCommand{ + { + Actions: []string{"folders:create"}, + Resource: "folders", + ResourceAttribute: "uid", + ResourceID: "*", + }, + }, + }, + { + description: "creation of folder without parent fails without permissions to create a folder", + input: folderWithoutParentInput, + expectedCode: http.StatusForbidden, + permissions: []resourcepermissions.SetResourcePermissionCommand{}, + }, + { + description: "creation of folder with parent succeeds given the correct request for creating a folder", + input: folderWithParentInput, + expectedCode: http.StatusOK, + permissions: []resourcepermissions.SetResourcePermissionCommand{ + { + Actions: []string{"folders:create"}, + Resource: "folders", + ResourceAttribute: "uid", + ResourceID: "parentuid", + }, + }, + }, + } - spec, ok := found.Object["spec"].(map[string]any) - require.True(t, ok) - require.Equal(t, dto.UID, found.GetName()) - require.Equal(t, dto.Title, spec["title"]) - // #TODO add checks for other fields + for _, tc := range tcs { + t.Run(tc.description, func(t *testing.T) { + helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{ + AppModeProduction: true, + DisableAnonymous: true, + APIServerStorageType: "unified", + UnifiedStorageConfig: map[string]setting.UnifiedStorageConfig{ + folderv0alpha1.RESOURCEGROUP: { + DualWriterMode: grafanarest.Mode1, + }, + }, + EnableFeatureToggles: []string{ + featuremgmt.FlagGrafanaAPIServerTestingWithExperimentalAPIs, + featuremgmt.FlagNestedFolders, + featuremgmt.FlagKubernetesFolders, + }, + }) - if expect != nil { - if expect.Title != "" { - require.Equal(t, expect.Title, dto.Title) - require.Equal(t, expect.Title, spec["title"]) - } - if expect.UID != "" { - require.Equal(t, expect.UID, dto.UID) - require.Equal(t, expect.UID, found.GetName()) - } + user := helper.CreateUser("user", apis.Org1, org.RoleViewer, tc.permissions) + + parentPayload := `{ + "title": "Test/parent", + "uid": "parentuid" + }` + parentCreate := apis.DoRequest(helper, apis.RequestParams{ + User: helper.Org1.Admin, + Method: http.MethodPost, + Path: "/api/folders", + Body: []byte(parentPayload), + }, &folder.Folder{}) + require.NotNil(t, parentCreate.Result) + parentUID := parentCreate.Result.UID + require.NotEmpty(t, parentUID) + + resp := apis.DoRequest(helper, apis.RequestParams{ + User: user, + Method: http.MethodPost, + Path: "/api/folders", + Body: []byte(tc.input), + }, &dtos.Folder{}) + require.Equal(t, tc.expectedCode, resp.Response.StatusCode) + + if tc.expectedCode == http.StatusOK { + require.Equal(t, "uid", resp.Result.UID) + require.Equal(t, "Folder", resp.Result.Title) + } + }) } - return found } From 6787e2f1088a621a845fd356090539c571b8481f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Fri, 11 Oct 2024 15:24:45 +0200 Subject: [PATCH 087/110] AddToDashboard: Refactorings & changes to address a number of problems (#94458) * Extract shared add to dashboard flow * Change how we save and add the new panels * More cleanup * Began updating tests * Move and update tests * set time range * Fix lint / ts issues * Fix time history step issue * fix test * Update betterer * remove more tests that are now not needed * work around cyclic dep * Update * Fixes * fix lint * remove unused type --- .betterer.results | 10 +- .../AddToDashboardForm.test.tsx | 261 +++++++++++++ .../addToDashboard}/AddToDashboardForm.tsx | 121 ++---- .../addToDashboard/addPanelsOnLoadBehavior.ts | 35 ++ .../addToDashboard/addToDashboard.test.ts | 41 ++ .../addToDashboard/addToDashboard.ts | 85 +++++ .../pages/DashboardScenePage.test.tsx | 17 +- .../pages/DashboardScenePage.tsx | 23 +- .../DashboardScenePageStateManager.test.ts | 45 +-- .../pages/DashboardScenePageStateManager.ts | 42 +- .../PanelDataQueriesTab.test.tsx | 13 +- .../scene/DashboardScene.test.tsx | 41 -- .../dashboard-scene/scene/DashboardScene.tsx | 30 +- .../transformSaveModelToScene.ts | 2 + .../components/HelpWizard/HelpWizard.tsx | 7 - .../HelpWizard/SupportSnapshotService.ts | 10 - .../containers/DashboardPageProxy.tsx | 6 +- .../features/dashboard/state/initDashboard.ts | 38 +- .../app/features/dashboard/utils/dashboard.ts | 2 +- .../ExploreToDashboardPanel.tsx | 31 ++ .../AddToDashboard/addToDashboard.test.ts | 156 ++------ .../AddToDashboard/addToDashboard.ts | 47 +-- .../extensions/AddToDashboard/index.test.tsx | 359 +----------------- .../extensions/AddToDashboard/index.tsx | 4 +- .../extensions/getExploreExtensionConfigs.tsx | 4 +- public/app/features/explore/state/utils.ts | 2 +- public/app/types/dashboard.ts | 2 + public/locales/en-US/grafana.json | 1 - public/locales/pseudo-LOCALE/grafana.json | 1 - 29 files changed, 614 insertions(+), 822 deletions(-) create mode 100644 public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.test.tsx rename public/app/features/{explore/extensions/AddToDashboard => dashboard-scene/addToDashboard}/AddToDashboardForm.tsx (61%) create mode 100644 public/app/features/dashboard-scene/addToDashboard/addPanelsOnLoadBehavior.ts create mode 100644 public/app/features/dashboard-scene/addToDashboard/addToDashboard.test.ts create mode 100644 public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts create mode 100644 public/app/features/explore/extensions/AddToDashboard/ExploreToDashboardPanel.tsx diff --git a/.betterer.results b/.betterer.results index 7dff19d1230..31d1f23e1c8 100644 --- a/.betterer.results +++ b/.betterer.results @@ -2636,6 +2636,11 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"] ], + "public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx:5381": [ + [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] + ], "public/app/features/dashboard-scene/embedding/EmbeddedDashboardTestPage.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"] ], @@ -4000,11 +4005,6 @@ exports[`better eslint`] = { [0, 0, 0, "Do not use any type assertions.", "0"], [0, 0, 0, "Do not use any type assertions.", "1"] ], - "public/app/features/explore/extensions/AddToDashboard/AddToDashboardForm.tsx:5381": [ - [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "2"] - ], "public/app/features/explore/extensions/ConfirmNavigationModal.tsx:5381": [ [0, 0, 0, "No untranslated strings. Wrap text with ", "0"], [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], diff --git a/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.test.tsx b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.test.tsx new file mode 100644 index 00000000000..8aecfaa7ddf --- /dev/null +++ b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.test.tsx @@ -0,0 +1,261 @@ +import { act, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { render } from 'test/test-utils'; + +import { selectors } from '@grafana/e2e-selectors'; +import { locationService, setEchoSrv } from '@grafana/runtime'; +import { defaultDashboard } from '@grafana/schema'; +import { backendSrv } from 'app/core/services/backend_srv'; +import { contextSrv } from 'app/core/services/context_srv'; +import { Echo } from 'app/core/services/echo/Echo'; +import store from 'app/core/store'; +import { DashboardSearchItemType } from 'app/features/search/types'; + +import { AddToDashboardForm, Props } from './AddToDashboardForm'; + +async function setup(overrides: Partial = {}) { + const props: Props = { + buildPanel: () => ({ id: 1, type: 'table', options: { showHeader: false } }), + onClose: jest.fn(), + options: undefined, + ...overrides, + }; + + const res = render(); + await act(() => Promise.resolve()); + return res; +} + +jest.mock('app/core/services/context_srv'); + +const mocks = { + contextSrv: jest.mocked(contextSrv), +}; + +describe('AddToDashboardButton', () => { + beforeAll(() => { + setEchoSrv(new Echo()); + }); + + beforeEach(() => { + jest.clearAllMocks(); + jest.spyOn(backendSrv, 'search').mockResolvedValue([]); + mocks.contextSrv.hasPermission.mockImplementation(() => true); + locationService.push('/'); + }); + + describe('navigation', () => { + it('Navigates to dashboard when clicking on "Open"', async () => { + // @ts-expect-error global.open should return a Window, but is not implemented in js-dom. + const openSpy = jest.spyOn(global, 'open').mockReturnValue(true); + + await setup(); + + await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); + + expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument(); + + expect(locationService.getLocation().pathname).toBe('/dashboard/new'); + expect(openSpy).not.toHaveBeenCalled(); + }); + + it('Navigates to dashboard in a new tab when clicking on "Open in a new tab"', async () => { + // @ts-expect-error global.open should return a Window, but is not implemented in js-dom. + const openSpy = jest.spyOn(global, 'open').mockReturnValue(true); + + await setup(); + + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + + expect(openSpy).toHaveBeenCalledWith(expect.anything(), '_blank'); + expect(locationService.getLocation().pathname).toBe('/'); + }); + }); + + describe('Add to new dashboard', () => { + describe('Navigate to correct dashboard when saving', () => { + it('Navigates to the new dashboard', async () => { + await setup(); + + await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); + + expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument(); + expect(locationService.getLocation().pathname).toBe('/dashboard/new'); + }); + }); + }); + + describe('Add to existing dashboard', () => { + it('Renders the dashboard picker when switching to "Existing Dashboard"', async () => { + await setup(); + + expect(screen.queryByRole('combobox', { name: /dashboard/ })).not.toBeInTheDocument(); + + await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); + expect(screen.getByRole('combobox', { name: /dashboard/ })).toBeInTheDocument(); + }); + + it('Does not submit if no dashboard is selected', async () => { + locationService.push = jest.fn(); + + await setup(); + + await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); + await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); + + locationService.push = jest.fn(); + expect(locationService.push).not.toHaveBeenCalled(); + }); + + describe('Navigate to correct dashboard when saving', () => { + it('Opens the selected dashboard in a new tab', async () => { + // @ts-expect-error global.open should return a Window, but is not implemented in js-dom. + const openSpy = jest.spyOn(global, 'open').mockReturnValue(true); + + jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({ + dashboard: { ...defaultDashboard, templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' }, + meta: {}, + }); + + jest.spyOn(backendSrv, 'search').mockResolvedValue([ + { + uid: 'someUid', + isStarred: false, + title: 'Dashboard Title', + tags: [], + type: DashboardSearchItemType.DashDB, + uri: 'someUri', + url: 'someUrl', + }, + ]); + + await setup(); + + await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); + await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); + + await waitFor(async () => { + await screen.findByTestId(selectors.components.Select.option); + }); + + await userEvent.click(screen.getByTestId(selectors.components.Select.option)); + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + + await waitFor(async () => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + expect(openSpy).toBeCalledWith('d/someUid', '_blank'); + }); + + it('Navigates to the selected dashboard', async () => { + jest.spyOn(backendSrv, 'search').mockResolvedValue([ + { + uid: 'someUid', + isStarred: false, + title: 'Dashboard Title', + tags: [], + type: DashboardSearchItemType.DashDB, + uri: 'someUri', + url: 'someUrl', + }, + ]); + + await setup(); + + await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); + await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); + + await waitFor(async () => { + await screen.findByTestId(selectors.components.Select.option); + }); + + await userEvent.click(screen.getByTestId(selectors.components.Select.option)); + await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); + + await waitFor(async () => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); + }); + + expect(locationService.getLocation().pathname).toBe('/d/someUid'); + }); + }); + }); +}); + +describe('Permissions', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('Should only show existing dashboard option with no access to create', async () => { + mocks.contextSrv.hasPermission.mockImplementation((action) => { + if (action === 'dashboards:create') { + return false; + } else { + return true; + } + }); + + await setup(); + + expect(screen.queryByRole('radio')).not.toBeInTheDocument(); + }); + + it('Should only show new dashboard option with no access to write', async () => { + mocks.contextSrv.hasPermission.mockImplementation((action) => { + if (action === 'dashboards:write') { + return false; + } else { + return true; + } + }); + + await setup(); + + expect(screen.queryByRole('radio')).not.toBeInTheDocument(); + }); +}); + +describe('Error handling', () => { + beforeEach(() => { + mocks.contextSrv.hasPermission.mockImplementation(() => true); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('Shows an error if opening a new tab fails', async () => { + jest.spyOn(global, 'open').mockReturnValue(null); + const removeDashboardSpy = jest.spyOn(store, 'delete'); + + await setup(); + + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + + await waitFor(async () => { + expect(await screen.findByRole('alert')).toBeInTheDocument(); + }); + + expect(removeDashboardSpy).toHaveBeenCalled(); + }); + + it('Shows an error if saving to localStorage fails', async () => { + jest.spyOn(store, 'setObject').mockImplementation(() => { + throw 'SOME ERROR'; + }); + + await setup(); + + expect(screen.queryByRole('alert')).not.toBeInTheDocument(); + + await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); + + await waitFor(async () => { + expect(await screen.findByRole('alert')).toBeInTheDocument(); + }); + }); +}); diff --git a/public/app/features/explore/extensions/AddToDashboard/AddToDashboardForm.tsx b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx similarity index 61% rename from public/app/features/explore/extensions/AddToDashboard/AddToDashboardForm.tsx rename to public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx index 847a981a7bc..7c000604565 100644 --- a/public/app/features/explore/extensions/AddToDashboard/AddToDashboardForm.tsx +++ b/public/app/features/dashboard-scene/addToDashboard/AddToDashboardForm.tsx @@ -1,18 +1,16 @@ import { partial } from 'lodash'; -import { type ReactElement, useEffect, useState } from 'react'; -import { DeepMap, FieldError, FieldErrors, useForm, Controller } from 'react-hook-form'; +import { ReactElement, useEffect, useState } from 'react'; +import { Controller, DeepMap, FieldError, FieldErrors, useForm } from 'react-hook-form'; -import { locationUtil, SelectableValue } from '@grafana/data'; -import { config, locationService, reportInteraction } from '@grafana/runtime'; +import { SelectableValue, TimeRange } from '@grafana/data'; +import { reportInteraction } from '@grafana/runtime'; +import { Panel } from '@grafana/schema'; import { Alert, Button, Field, Modal, RadioButtonGroup } from '@grafana/ui'; import { DashboardPicker } from 'app/core/components/Select/DashboardPicker'; -import { contextSrv } from 'app/core/services/context_srv'; -import { removeDashboardToFetchFromLocalStorage } from 'app/features/dashboard/state/initDashboard'; -import { AccessControlAction, useSelector } from 'app/types'; +import { contextSrv } from 'app/core/core'; +import { AccessControlAction } from 'app/types'; -import { getExploreItemSelector } from '../../state/selectors'; - -import { setDashboardInLocalStorage, AddToDashboardError } from './addToDashboard'; +import { addToDashboard, SubmissionError } from './addToDashboard'; enum SaveTarget { NewDashboard = 'new-dashboard', @@ -22,6 +20,7 @@ enum SaveTarget { interface SaveTargetDTO { saveTarget: SaveTarget; } + interface SaveToNewDashboardDTO extends SaveTargetDTO { saveTarget: SaveTarget.NewDashboard; } @@ -33,36 +32,21 @@ interface SaveToExistingDashboard extends SaveTargetDTO { type FormDTO = SaveToNewDashboardDTO | SaveToExistingDashboard; -function assertIsSaveToExistingDashboardError( - errors: FieldErrors -): asserts errors is DeepMap { - // the shape of the errors object is always compatible with the type above, but we need to - // explicitly assert its type so that TS can narrow down FormDTO to SaveToExistingDashboard - // when we use it in the form. -} - -function getDashboardURL(dashboardUid?: string) { - return dashboardUid ? `d/${dashboardUid}` : 'dashboard/new'; -} - -enum GenericError { - UNKNOWN = 'unknown-error', - NAVIGATION = 'navigation-error', -} - -interface SubmissionError { - error: AddToDashboardError | GenericError; - message: string; -} - -interface Props { +export interface Props { onClose: () => void; - exploreId: string; + buildPanel: (options: TOptions) => Panel; + timeRange?: TimeRange; + options: TOptions; + children?: React.ReactNode; } -export function AddToDashboardForm(props: Props): ReactElement { - const { exploreId, onClose } = props; - const exploreItem = useSelector(getExploreItemSelector(exploreId))!; +export function AddToDashboardForm({ + onClose, + buildPanel, + timeRange, + options, + children, +}: Props): ReactElement { const [submissionError, setSubmissionError] = useState(); const { handleSubmit, @@ -77,12 +61,14 @@ export function AddToDashboardForm(props: Props): ReactElement { const canWriteDashboard = contextSrv.hasPermission(AccessControlAction.DashboardsWrite); const saveTargets: Array> = []; + if (canCreateDashboard) { saveTargets.push({ label: 'New dashboard', value: SaveTarget.NewDashboard, }); } + if (canWriteDashboard) { saveTargets.push({ label: 'Existing dashboard', @@ -92,60 +78,24 @@ export function AddToDashboardForm(props: Props): ReactElement { const saveTarget = saveTargets.length > 1 ? watch('saveTarget') : saveTargets[0].value; - const onSubmit = async (openInNewTab: boolean, data: FormDTO) => { + const onSubmit = (openInNewTab: boolean, data: FormDTO) => { setSubmissionError(undefined); + const dashboardUid = data.saveTarget === SaveTarget.ExistingDashboard ? data.dashboardUid : undefined; + const panel = buildPanel(options); reportInteraction('e_2_d_submit', { newTab: openInNewTab, saveTarget: data.saveTarget, - queries: exploreItem.queries.length, + queries: panel.targets, }); - const { from, to } = exploreItem.range.raw; - - try { - await setDashboardInLocalStorage({ - dashboardUid, - datasource: exploreItem.datasourceInstance?.getRef(), - queries: exploreItem.queries, - queryResponse: exploreItem.queryResponse, - panelState: exploreItem?.panelsState, - time: { - from: typeof from === 'string' ? from : from.toISOString(), - to: typeof to === 'string' ? to : to.toISOString(), - }, - }); - } catch (error) { - switch (error) { - case AddToDashboardError.FETCH_DASHBOARD: - setSubmissionError({ error, message: 'Could not fetch dashboard information. Please try again.' }); - break; - case AddToDashboardError.SET_DASHBOARD_LS: - setSubmissionError({ error, message: 'Could not add panel to dashboard. Please try again.' }); - break; - default: - setSubmissionError({ error: GenericError.UNKNOWN, message: 'Something went wrong. Please try again.' }); - } + const error = addToDashboard({ dashboardUid, panel, openInNewTab, timeRange }); + if (error) { + setSubmissionError(error); return; } - const dashboardURL = getDashboardURL(dashboardUid); - if (!openInNewTab) { - onClose(); - locationService.push(locationUtil.stripBaseFromUrl(dashboardURL)); - return; - } - - const didTabOpen = !!global.open(config.appUrl + dashboardURL, '_blank'); - if (!didTabOpen) { - setSubmissionError({ - error: GenericError.NAVIGATION, - message: 'Could not navigate to the selected dashboard. Please try again.', - }); - removeDashboardToFetchFromLocalStorage(); - return; - } onClose(); }; @@ -155,6 +105,9 @@ export function AddToDashboardForm(props: Props): ReactElement { return ( + {/* For custom form options */} + {children} + {saveTargets.length > 1 && ( ); } + +function assertIsSaveToExistingDashboardError( + errors: FieldErrors +): asserts errors is DeepMap { + // the shape of the errors object is always compatible with the type above, but we need to + // explicitly assert its type so that TS can narrow down FormDTO to SaveToExistingDashboard + // when we use it in the form. +} diff --git a/public/app/features/dashboard-scene/addToDashboard/addPanelsOnLoadBehavior.ts b/public/app/features/dashboard-scene/addToDashboard/addPanelsOnLoadBehavior.ts new file mode 100644 index 00000000000..3847a027e11 --- /dev/null +++ b/public/app/features/dashboard-scene/addToDashboard/addPanelsOnLoadBehavior.ts @@ -0,0 +1,35 @@ +import { SceneTimeRange } from '@grafana/scenes'; +import store from 'app/core/store'; +import { DashboardModel } from 'app/features/dashboard/state'; +import { DASHBOARD_FROM_LS_KEY, DashboardDTO } from 'app/types'; + +import { DashboardScene } from '../scene/DashboardScene'; +import { buildGridItemForPanel } from '../serialization/transformSaveModelToScene'; + +export function addPanelsOnLoadBehavior(scene: DashboardScene) { + const dto = store.getObject(DASHBOARD_FROM_LS_KEY); + + if (dto) { + console.log('asd', dto); + const model = new DashboardModel(dto.dashboard); + + for (const panel of model.panels) { + const gridItem = buildGridItemForPanel(panel); + scene.addPanel(gridItem.state.body); + } + + if (dto.dashboard.time) { + const newTimeRange = new SceneTimeRange({ from: dto.dashboard.time.from, to: dto.dashboard.time.to }); + const timeRange = scene.state.$timeRange; + if (timeRange) { + timeRange.setState({ + value: newTimeRange.state.value, + from: newTimeRange.state.from, + to: newTimeRange.state.to, + }); + } + } + } + + store.delete(DASHBOARD_FROM_LS_KEY); +} diff --git a/public/app/features/dashboard-scene/addToDashboard/addToDashboard.test.ts b/public/app/features/dashboard-scene/addToDashboard/addToDashboard.test.ts new file mode 100644 index 00000000000..3add777f7cd --- /dev/null +++ b/public/app/features/dashboard-scene/addToDashboard/addToDashboard.test.ts @@ -0,0 +1,41 @@ +import { dateTime } from '@grafana/data'; +import store from 'app/core/store'; + +import { addToDashboard } from './addToDashboard'; + +describe('addToDashboard', () => { + let spy: jest.SpyInstance; + + beforeAll(() => { + spy = jest.spyOn(store, 'setObject'); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('Should save dashboard with new panel in local storage', () => { + addToDashboard({ + panel: { + type: 'table', + gridPos: { x: 0, y: 0, w: 12, h: 12 }, + options: { showHeader: true }, + }, + }); + + const panel = spy.mock.calls[0][1].dashboard.panels[0]; + expect(panel.type).toEqual('table'); + expect(panel.options).toEqual({ showHeader: true }); + }); + + it('Correct time range is used', () => { + addToDashboard({ + panel: { type: 'table' }, + timeRange: { from: dateTime(), to: dateTime(), raw: { from: 'now-5m', to: 'now' } }, + }); + + const dashboard = spy.mock.calls[0][1].dashboard; + expect(dashboard.time.from).toEqual('now-5m'); + expect(dashboard.time.to).toEqual('now'); + }); +}); diff --git a/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts b/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts new file mode 100644 index 00000000000..8abd8dc59d0 --- /dev/null +++ b/public/app/features/dashboard-scene/addToDashboard/addToDashboard.ts @@ -0,0 +1,85 @@ +import { locationUtil, TimeRange } from '@grafana/data'; +import { config, locationService } from '@grafana/runtime'; +import { Panel } from '@grafana/schema'; +import store from 'app/core/store'; +import { DASHBOARD_SCHEMA_VERSION } from 'app/features/dashboard/state/DashboardMigrator'; +import { DASHBOARD_FROM_LS_KEY, DashboardDTO } from 'app/types'; + +export enum GenericError { + UNKNOWN = 'unknown-error', + NAVIGATION = 'navigation-error', +} + +export interface SubmissionError { + error: AddToDashboardError | GenericError; + message: string; +} + +export enum AddToDashboardError { + FETCH_DASHBOARD = 'fetch-dashboard', + SET_DASHBOARD_LS = 'set-dashboard-ls-error', +} + +interface AddPanelToDashboardOptions { + panel: Panel; + dashboardUid?: string; + openInNewTab?: boolean; + timeRange?: TimeRange; +} + +export function addToDashboard({ + panel, + dashboardUid, + openInNewTab, + timeRange, +}: AddPanelToDashboardOptions): SubmissionError | undefined { + let dto: DashboardDTO = { + meta: {}, + dashboard: { + title: '', + uid: '', + panels: [panel], + schemaVersion: DASHBOARD_SCHEMA_VERSION, + }, + }; + + if (timeRange) { + const raw = timeRange.raw; + dto.dashboard.time = { + from: typeof raw.from === 'string' ? raw.from : raw.from.toISOString(), + to: typeof raw.to === 'string' ? raw.to : raw.to.toISOString(), + }; + } + + try { + store.setObject(DASHBOARD_FROM_LS_KEY, dto); + } catch { + return { + error: AddToDashboardError.SET_DASHBOARD_LS, + message: 'Could not add panel to dashboard. Please try again.', + }; + } + + const dashboardURL = getDashboardURL(dashboardUid); + + if (openInNewTab) { + const didTabOpen = !!global.open(config.appUrl + dashboardURL, '_blank'); + + if (!didTabOpen) { + store.delete(DASHBOARD_FROM_LS_KEY); + return { + error: GenericError.NAVIGATION, + message: 'Could not navigate to the selected dashboard. Please try again.', + }; + } + + return; + } + + locationService.push(locationUtil.stripBaseFromUrl(dashboardURL)); + return; +} + +function getDashboardURL(dashboardUid?: string) { + return dashboardUid ? `d/${dashboardUid}` : 'dashboard/new'; +} diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx index 54aa8f75151..26e3c0eb6d0 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx +++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.test.tsx @@ -20,8 +20,7 @@ import { getRouteComponentProps } from 'app/core/navigation/__mocks__/routeProps import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import store from 'app/core/store'; import { DashboardLoaderSrv, setDashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; -import { DASHBOARD_FROM_LS_KEY } from 'app/features/dashboard/state/initDashboard'; -import { DashboardRoutes } from 'app/types'; +import { DASHBOARD_FROM_LS_KEY, DashboardRoutes } from 'app/types'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; @@ -267,20 +266,6 @@ describe('DashboardScenePage', () => { }); }); - it('is in edit mode when coming from explore to an existing dashboard', async () => { - store.setObject(DASHBOARD_FROM_LS_KEY, { dashboard: simpleDashboard, meta: { slug: '123' } }); - - setup(); - - await waitForDashboardToRender(); - - const panelAMenu = await screen.findByLabelText('Menu for panel with title Panel A'); - expect(panelAMenu).toBeInTheDocument(); - await userEvent.click(panelAMenu); - const editMenuItem = await screen.findAllByText('Edit'); - expect(editMenuItem).toHaveLength(1); - }); - describe('home page', () => { it('should render the dashboard when the route is home', async () => { (useParams as jest.Mock).mockReturnValue({}); diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx b/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx index 59b0f8bd8b5..be740e26c9b 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx +++ b/public/app/features/dashboard-scene/pages/DashboardScenePage.tsx @@ -1,5 +1,5 @@ // Libraries -import { useEffect, useMemo } from 'react'; +import { useEffect } from 'react'; import { useParams } from 'react-router-dom-v5-compat'; import { usePrevious } from 'react-use'; @@ -9,10 +9,8 @@ import { Alert, Box } from '@grafana/ui'; import { Page } from 'app/core/components/Page/Page'; import PageLoader from 'app/core/components/PageLoader/PageLoader'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; -import store from 'app/core/store'; import { DashboardPageRouteParams, DashboardPageRouteSearchParams } from 'app/features/dashboard/containers/types'; -import { DASHBOARD_FROM_LS_KEY } from 'app/features/dashboard/state/initDashboard'; -import { DashboardDTO, DashboardRoutes } from 'app/types'; +import { DashboardRoutes } from 'app/types'; import { DashboardPrompt } from '../saving/DashboardPrompt'; @@ -30,12 +28,6 @@ export function DashboardScenePage({ route, queryParams, history }: Props) { // After scene migration is complete and we get rid of old dashboard we should refactor dashboardWatcher so this route reload is not need const routeReloadCounter = (history.location.state as any)?.routeReloadCounter; - // Check if the user is coming from Explore, it's indicated byt the dashboard existence in local storage - const comingFromExplore = useMemo(() => { - return Boolean(store.getObject(DASHBOARD_FROM_LS_KEY)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [uid, slug, type]); - useEffect(() => { if (route.routeName === DashboardRoutes.Normal && type === 'snapshot') { stateManager.loadSnapshot(slug!); @@ -44,7 +36,6 @@ export function DashboardScenePage({ route, queryParams, history }: Props) { uid: uid ?? '', route: route.routeName as DashboardRoutes, urlFolderUid: queryParams.folderUid, - keepDashboardFromExploreInLocalStorage: false, }); } @@ -53,16 +44,6 @@ export function DashboardScenePage({ route, queryParams, history }: Props) { }; }, [stateManager, uid, route.routeName, queryParams.folderUid, routeReloadCounter, slug, type]); - // Effect that handles explore->dashboards workflow - useEffect(() => { - // When coming from explore and adding to an existing dashboard, we should enter edit mode - if (dashboard && comingFromExplore) { - if (route.routeName !== DashboardRoutes.New) { - dashboard.onEnterEditMode(comingFromExplore); - } - } - }, [dashboard, comingFromExplore, route.routeName]); - if (!dashboard) { return ( diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts index 25509df7fc3..124bc7b27e8 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.test.ts @@ -2,8 +2,7 @@ import { advanceBy } from 'jest-date-mock'; import { BackendSrv, setBackendSrv } from '@grafana/runtime'; import store from 'app/core/store'; -import { DASHBOARD_FROM_LS_KEY } from 'app/features/dashboard/state/initDashboard'; -import { DashboardRoutes } from 'app/types'; +import { DASHBOARD_FROM_LS_KEY, DashboardRoutes } from 'app/types'; import { DashboardScene } from '../scene/DashboardScene'; import { setupLoadDashboardMock } from '../utils/test-utils'; @@ -56,17 +55,6 @@ describe('DashboardScenePageStateManager', () => { expect(loader.state.dashboard).toBeUndefined(); }); - it('shoud fetch dashboard from local storage and remove it after if it exists', async () => { - const loader = new DashboardScenePageStateManager({}); - const localStorageDashboard = { uid: 'fake-dash' }; - store.setObject(DASHBOARD_FROM_LS_KEY, localStorageDashboard); - - const result = await loader.fetchDashboard({ uid: 'fake-dash', route: DashboardRoutes.Normal }); - - expect(result).toEqual(localStorageDashboard); - expect(store.getObject(DASHBOARD_FROM_LS_KEY)).toBeUndefined(); - }); - it('should initialize the dashboard scene with the loaded dashboard', async () => { setupLoadDashboardMock({ dashboard: { uid: 'fake-dash' }, meta: {} }); @@ -215,36 +203,5 @@ describe('DashboardScenePageStateManager', () => { expect(loadDashSpy).toHaveBeenCalledTimes(2); }); }); - - describe('When coming from explore', () => { - it('shoud fetch dashboard from local storage and keep it there after when asked', async () => { - const loader = new DashboardScenePageStateManager({}); - const localStorageDashboard = { uid: 'fake-dash' }; - store.setObject(DASHBOARD_FROM_LS_KEY, { dashboard: localStorageDashboard }); - - const result = await loader.fetchDashboard({ - uid: 'fake-dash', - route: DashboardRoutes.Normal, - keepDashboardFromExploreInLocalStorage: true, - }); - - expect(result).toEqual({ dashboard: localStorageDashboard }); - expect(store.getObject(DASHBOARD_FROM_LS_KEY)).toEqual({ dashboard: localStorageDashboard }); - }); - - it('shoud not store dashboard in cache when coming from Explore', async () => { - const loader = new DashboardScenePageStateManager({}); - const localStorageDashboard = { uid: 'fake-dash' }; - store.setObject(DASHBOARD_FROM_LS_KEY, { dashboard: localStorageDashboard }); - - await loader.loadDashboard({ - uid: 'fake-dash', - route: DashboardRoutes.Normal, - keepDashboardFromExploreInLocalStorage: false, - }); - - expect(loader.getDashboardFromCache('fake-dash')).toBeNull(); - }); - }); }); }); diff --git a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts index 1a04ca0c0d9..3f7ade11335 100644 --- a/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts +++ b/public/app/features/dashboard-scene/pages/DashboardScenePageStateManager.ts @@ -1,16 +1,11 @@ import { locationUtil } from '@grafana/data'; import { config, getBackendSrv, isFetchError, locationService } from '@grafana/runtime'; import { StateManagerBase } from 'app/core/services/StateManagerBase'; -import { default as localStorageStore } from 'app/core/store'; import { getMessageFromError } from 'app/core/utils/errors'; import { startMeasure, stopMeasure } from 'app/core/utils/metrics'; import { dashboardLoaderSrv } from 'app/features/dashboard/services/DashboardLoaderSrv'; import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; import { emitDashboardViewEvent } from 'app/features/dashboard/state/analyticsProcessor'; -import { - DASHBOARD_FROM_LS_KEY, - removeDashboardToFetchFromLocalStorage, -} from 'app/features/dashboard/state/initDashboard'; import { trackDashboardSceneLoaded } from 'app/features/dashboard/utils/tracking'; import { getSelectedScopesNames } from 'app/features/scopes'; import { DashboardDTO, DashboardRoutes } from 'app/types'; @@ -47,12 +42,6 @@ export interface LoadDashboardOptions { uid: string; route: DashboardRoutes; urlFolderUid?: string; - // A temporary approach not to clean the dashboard from local storage when navigating from Explore to Dashboard - // We currently need it as there are two flows of fetching dashboard. The legacy one (initDashboard), uses the new one(DashboardScenePageStateManager.fetch) where the - // removal of the dashboard from local storage is implemented. So in the old flow we wouldn't be able to early return dashboard from local storage, if we prematurely - // removed it when prefetching the dashboard in DashboardPageProxy. - // This property will be removed when the old flow (initDashboard) is removed. - keepDashboardFromExploreInLocalStorage?: boolean; } export class DashboardScenePageStateManager extends StateManagerBase { @@ -63,21 +52,7 @@ export class DashboardScenePageStateManager extends StateManagerBase { - const model = localStorageStore.getObject(DASHBOARD_FROM_LS_KEY); - - if (model) { - if (!keepDashboardFromExploreInLocalStorage) { - removeDashboardToFetchFromLocalStorage(); - } - return model; - } - + public async fetchDashboard({ uid, route, urlFolderUid }: LoadDashboardOptions): Promise { const cacheKey = route === DashboardRoutes.Home ? HOME_DASHBOARD_CACHE_KEY : uid; const cachedDashboard = this.getDashboardFromCache(cacheKey); @@ -203,29 +178,20 @@ export class DashboardScenePageStateManager extends StateManagerBase { - const comingFromExplore = Boolean( - localStorageStore.getObject(DASHBOARD_FROM_LS_KEY) && - options.keepDashboardFromExploreInLocalStorage === false - ); - this.setState({ dashboard: undefined, isLoading: true }); const rsp = await this.fetchDashboard(options); const fromCache = this.getSceneFromCache(options.uid); - - // When coming from Explore, skip returnning scene from cache - if (!comingFromExplore) { - if (fromCache && fromCache.state.version === rsp?.dashboard.version) { - return fromCache; - } + if (fromCache && fromCache.state.version === rsp?.dashboard.version) { + return fromCache; } if (rsp?.dashboard) { const scene = transformSaveModelToScene(rsp); // Cache scene only if not coming from Explore, we don't want to cache temporary dashboard - if (options.uid && !comingFromExplore) { + if (options.uid) { this.setSceneCache(options.uid, scene); } diff --git a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx index 5a406406282..b4a8fd20698 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataQueriesTab.test.tsx @@ -18,6 +18,7 @@ import { import { getPanelPlugin } from '@grafana/data/test/__mocks__/pluginMocks'; import { selectors } from '@grafana/e2e-selectors'; import { config, locationService, setPluginExtensionsHook } from '@grafana/runtime'; +import { PANEL_EDIT_LAST_USED_DATASOURCE } from 'app/features/dashboard/utils/dashboard'; import { InspectTab } from 'app/features/inspector/types'; import { SHARED_DASHBOARD_QUERY } from 'app/plugins/datasource/dashboard'; import { DASHBOARD_DATASOURCE_PLUGIN_ID } from 'app/plugins/datasource/dashboard/types'; @@ -257,6 +258,7 @@ jest.mock('app/core/store', () => ({ get: jest.fn(), getObject: jest.fn((_a, b) => b), setObject: jest.fn(), + delete: jest.fn(), })); const store = jest.requireMock('app/core/store'); @@ -665,9 +667,14 @@ describe('PanelDataQueriesTab', () => { it('should load last used data source if no data source specified for a panel', async () => { store.exists.mockReturnValue(true); - store.getObject.mockReturnValue({ - dashboardUid: 'ffbe00e2-803c-4d49-adb7-41aad336234f', - datasourceUid: 'gdev-testdata', + store.getObject.mockImplementation((key: string, def: unknown) => { + if (key === PANEL_EDIT_LAST_USED_DATASOURCE) { + return { + dashboardUid: 'ffbe00e2-803c-4d49-adb7-41aad336234f', + datasourceUid: 'gdev-testdata', + }; + } + return def; }); const { queriesTab } = await setupScene('panel-5'); diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx index 8c35c9d8618..423516fd528 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.test.tsx @@ -812,47 +812,6 @@ describe('DashboardScene', () => { }); }); - describe('When coming from explore', () => { - // When coming from Explore the first panel in a dashboard is a temporary panel - it('should remove first panel from the grid when discarding changes', () => { - const layout = DefaultGridLayoutManager.fromVizPanels([ - new VizPanel({ - title: 'Panel A', - key: 'panel-1', - pluginId: 'table', - $data: new SceneQueryRunner({ key: 'data-query-runner', queries: [{ refId: 'A' }] }), - }), - new VizPanel({ - title: 'Panel B', - key: 'panel-2', - pluginId: 'table', - }), - ]); - const scene = new DashboardScene({ - title: 'hello', - uid: 'dash-1', - description: 'hello description', - editable: true, - $timeRange: new SceneTimeRange({ - timeZone: 'browser', - }), - controls: new DashboardControls({}), - $behaviors: [new behaviors.CursorSync({})], - body: layout, - }); - - scene.onEnterEditMode(true); - expect(scene.state.isEditing).toBe(true); - expect(layout.state.grid.state.children.length).toBe(2); - - scene.exitEditMode({ skipConfirm: true }); - - const restoredGrid = scene.state.body as DefaultGridLayoutManager; - expect(scene.state.isEditing).toBe(false); - expect(restoredGrid.state.grid.state.children.length).toBe(1); - }); - }); - describe('When a dashboard contain angular panels', () => { it('should return true if the dashboard contains angular panels', () => { // create a scene with angular panels inside diff --git a/public/app/features/dashboard-scene/scene/DashboardScene.tsx b/public/app/features/dashboard-scene/scene/DashboardScene.tsx index aa3fee00f82..7d7fcc88e55 100644 --- a/public/app/features/dashboard-scene/scene/DashboardScene.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardScene.tsx @@ -158,11 +158,6 @@ export class DashboardScene extends SceneObjectBase { */ private _changeTracker: DashboardSceneChangeTracker; - /** - * Flag to indicate if the user came from Explore - */ - private _fromExplore = false; - /** * A reference to the scopes facade */ @@ -244,8 +239,7 @@ export class DashboardScene extends SceneObjectBase { } } - public onEnterEditMode = (fromExplore = false) => { - this._fromExplore = fromExplore; + public onEnterEditMode = () => { // Save this state this._initialState = sceneUtils.cloneSceneObjectState(this.state); this._initialUrlState = locationService.getLocation(); @@ -334,10 +328,6 @@ export class DashboardScene extends SceneObjectBase { locationService.replace(locationUtil.stripBaseFromUrl(url)); - if (this._fromExplore) { - this.cleanupStateFromExplore(); - } - if (restoreInitialState) { // Restore initial state and disable editing this.setState({ ...this._initialState, isEditing: false }); @@ -357,18 +347,6 @@ export class DashboardScene extends SceneObjectBase { this.state.body.editModeChanged(false); } - private cleanupStateFromExplore() { - this._fromExplore = false; - // When coming from explore but discarding changes, remove the panel that explore is potentially adding. - if (this._initialSaveModel?.panels) { - this._initialSaveModel.panels = this._initialSaveModel.panels.slice(1); - } - - if (this._initialState) { - this._initialState.body.cleanUpStateFromExplore?.(); - } - } - public canDiscard() { return this._initialState !== undefined; } @@ -482,6 +460,10 @@ export class DashboardScene extends SceneObjectBase { this.onEnterEditMode(); } + const panelId = dashboardSceneGraph.getNextPanelId(this); + vizPanel.setState({ key: getVizPanelKeyForPanelId(panelId) }); + vizPanel.clearParent(); + this.state.body.addPanel(vizPanel); } @@ -532,7 +514,7 @@ export class DashboardScene extends SceneObjectBase { panel.setState({ key: getVizPanelKeyForPanelId(panelId) }); panel.clearParent(); - this.state.body.addPanel(panel); + this.addPanel(panel); store.delete(LS_PANEL_COPY_KEY); } diff --git a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts index e23afc410d6..f16d3f3b6db 100644 --- a/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts +++ b/public/app/features/dashboard-scene/serialization/transformSaveModelToScene.ts @@ -23,6 +23,7 @@ import { import { DashboardModel, PanelModel } from 'app/features/dashboard/state'; import { DashboardDTO, DashboardDataDTO } from 'app/types'; +import { addPanelsOnLoadBehavior } from '../addToDashboard/addPanelsOnLoadBehavior'; import { AlertStatesDataLayer } from '../scene/AlertStatesDataLayer'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardControls } from '../scene/DashboardControls'; @@ -248,6 +249,7 @@ export function createDashboardSceneFromDashboardModel(oldModel: DashboardModel, registerPanelInteractionsReporter, new behaviors.LiveNowTimer({ enabled: oldModel.liveNow }), preserveDashboardSceneStateInLocalStorage, + addPanelsOnLoadBehavior, new DashboardScopesFacade({ reloadOnScopesChange: oldModel.meta.reloadOnScopesChange, uid: oldModel.uid, diff --git a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx index f6058b74d61..bc8987ff813 100644 --- a/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx +++ b/public/app/features/dashboard/components/HelpWizard/HelpWizard.tsx @@ -192,13 +192,6 @@ export function HelpWizard({ panel, plugin, onClose }: Props) { > Copy Github comment - diff --git a/public/app/features/dashboard/components/HelpWizard/SupportSnapshotService.ts b/public/app/features/dashboard/components/HelpWizard/SupportSnapshotService.ts index e9f68ad556d..145cec475ca 100644 --- a/public/app/features/dashboard/components/HelpWizard/SupportSnapshotService.ts +++ b/public/app/features/dashboard/components/HelpWizard/SupportSnapshotService.ts @@ -1,7 +1,6 @@ import saveAs from 'file-saver'; import { dateTimeFormat, formattedValueToString, getValueFormat, SelectableValue } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { SceneObject } from '@grafana/scenes'; import { StateManagerBase } from 'app/core/services/StateManagerBase'; import { Randomize } from 'app/features/dashboard-scene/inspect/HelpWizard/randomizer'; @@ -9,7 +8,6 @@ import { createDashboardSceneFromDashboardModel } from 'app/features/dashboard-s import { getTimeSrv } from '../../services/TimeSrv'; import { DashboardModel, PanelModel } from '../../state'; -import { setDashboardToFetchFromLocalStorage } from '../../state/initDashboard'; import { getDebugDashboard, getGithubMarkdown } from './utils'; @@ -136,12 +134,4 @@ export class SupportSnapshotService extends StateManagerBase { - const { snapshot } = this.state; - if (snapshot) { - setDashboardToFetchFromLocalStorage({ meta: {}, dashboard: snapshot }); - global.open(config.appUrl + 'dashboard/new', '_blank'); - } - }; } diff --git a/public/app/features/dashboard/containers/DashboardPageProxy.tsx b/public/app/features/dashboard/containers/DashboardPageProxy.tsx index e45bb442aee..e51a8ce9ad8 100644 --- a/public/app/features/dashboard/containers/DashboardPageProxy.tsx +++ b/public/app/features/dashboard/containers/DashboardPageProxy.tsx @@ -40,11 +40,7 @@ function DashboardPageProxy(props: DashboardPageProxyProps) { return null; } - return stateManager.fetchDashboard({ - route: props.route.routeName as DashboardRoutes, - uid: params.uid ?? '', - keepDashboardFromExploreInLocalStorage: true, - }); + return stateManager.fetchDashboard({ route: props.route.routeName as DashboardRoutes, uid: params.uid ?? '' }); }, [params.uid, props.route.routeName]); if (!config.featureToggles.dashboardSceneForViewers) { diff --git a/public/app/features/dashboard/state/initDashboard.ts b/public/app/features/dashboard/state/initDashboard.ts index 599c4ad2a8b..cf58c391fd3 100644 --- a/public/app/features/dashboard/state/initDashboard.ts +++ b/public/app/features/dashboard/state/initDashboard.ts @@ -19,7 +19,15 @@ import { getFolderByUid } from 'app/features/folders/state/actions'; import { dashboardWatcher } from 'app/features/live/dashboard/dashboardWatcher'; import { playlistSrv } from 'app/features/playlist/PlaylistSrv'; import { toStateKey } from 'app/features/variables/utils'; -import { DashboardDTO, DashboardInitPhase, DashboardRoutes, StoreState, ThunkDispatch, ThunkResult } from 'app/types'; +import { + DASHBOARD_FROM_LS_KEY, + DashboardDTO, + DashboardInitPhase, + DashboardRoutes, + StoreState, + ThunkDispatch, + ThunkResult, +} from 'app/types'; import { createDashboardQueryRunner } from '../../query/state/DashboardQueryRunner/DashboardQueryRunner'; import { initVariablesTransaction } from '../../variables/state/actions'; @@ -50,13 +58,6 @@ async function fetchDashboard( dispatch: ThunkDispatch, getState: () => StoreState ): Promise { - // When creating new or adding panels to a dashboard from explore we load it from local storage - const model = store.getObject(DASHBOARD_FROM_LS_KEY); - if (model) { - removeDashboardToFetchFromLocalStorage(); - return model; - } - try { switch (args.routeName) { case DashboardRoutes.Home: { @@ -184,7 +185,6 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult { // fetch dashboard data const dashDTO = await fetchDashboard(args, dispatch, getState); - const versionBeforeMigration = dashDTO?.dashboard?.version; // returns null if there was a redirect or error @@ -192,6 +192,8 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult { return; } + addPanelsFromLocalStorage(dashDTO); + // set initializing state dispatch(dashboardInitServices()); @@ -298,12 +300,18 @@ export function initDashboard(args: InitDashboardArgs): ThunkResult { }; } -export const DASHBOARD_FROM_LS_KEY = 'DASHBOARD_FROM_LS_KEY'; +function addPanelsFromLocalStorage(model: DashboardDTO) { + // When creating new or adding panels to a dashboard from explore we load it from local storage + const fromLS = store.getObject(DASHBOARD_FROM_LS_KEY); + if (fromLS) { + if (fromLS.dashboard.panels) { + model.dashboard.panels = fromLS.dashboard.panels.concat(model.dashboard.panels); + } -export function setDashboardToFetchFromLocalStorage(model: DashboardDTO) { - store.setObject(DASHBOARD_FROM_LS_KEY, model); -} + if (fromLS.dashboard.time) { + model.dashboard.time = fromLS.dashboard.time; + } -export function removeDashboardToFetchFromLocalStorage() { - store.delete(DASHBOARD_FROM_LS_KEY); + store.delete(DASHBOARD_FROM_LS_KEY); + } } diff --git a/public/app/features/dashboard/utils/dashboard.ts b/public/app/features/dashboard/utils/dashboard.ts index 5627bb9209c..d11a154feca 100644 --- a/public/app/features/dashboard/utils/dashboard.ts +++ b/public/app/features/dashboard/utils/dashboard.ts @@ -119,7 +119,7 @@ type LastUsedDatasource = } | undefined; -const PANEL_EDIT_LAST_USED_DATASOURCE = 'grafana.dashboards.panelEdit.lastUsedDatasource'; +export const PANEL_EDIT_LAST_USED_DATASOURCE = 'grafana.dashboards.panelEdit.lastUsedDatasource'; // Function that returns last used datasource from local storage export function getLastUsedDatasourceFromStorage(dashboardUid: string): LastUsedDatasource { diff --git a/public/app/features/explore/extensions/AddToDashboard/ExploreToDashboardPanel.tsx b/public/app/features/explore/extensions/AddToDashboard/ExploreToDashboardPanel.tsx new file mode 100644 index 00000000000..87dbc64f5ac --- /dev/null +++ b/public/app/features/explore/extensions/AddToDashboard/ExploreToDashboardPanel.tsx @@ -0,0 +1,31 @@ +import { type ReactElement } from 'react'; + +import { AddToDashboardForm } from 'app/features/dashboard-scene/addToDashboard/AddToDashboardForm'; +import { useSelector } from 'app/types'; + +import { getExploreItemSelector } from '../../state/selectors'; + +import { buildDashboardPanelFromExploreState } from './addToDashboard'; + +interface Props { + onClose: () => void; + exploreId: string; +} + +export function ExploreToDashboardPanel(props: Props): ReactElement { + const { exploreId, onClose } = props; + const exploreItem = useSelector(getExploreItemSelector(exploreId))!; + + const buildPanel = () => { + return buildDashboardPanelFromExploreState({ + datasource: exploreItem.datasourceInstance?.getRef(), + queries: exploreItem.queries, + queryResponse: exploreItem.queryResponse, + panelState: exploreItem?.panelsState, + }); + }; + + return ( + + ); +} diff --git a/public/app/features/explore/extensions/AddToDashboard/addToDashboard.test.ts b/public/app/features/explore/extensions/AddToDashboard/addToDashboard.test.ts index 5b6d9c91196..1c7c8912a17 100644 --- a/public/app/features/explore/extensions/AddToDashboard/addToDashboard.test.ts +++ b/public/app/features/explore/extensions/AddToDashboard/addToDashboard.test.ts @@ -1,115 +1,29 @@ -import { MutableDataFrame } from '@grafana/data'; -import { DataQuery, defaultDashboard } from '@grafana/schema'; -import * as api from 'app/features/dashboard/state/initDashboard'; +import { getDefaultTimeRange, MutableDataFrame } from '@grafana/data'; +import { DataQuery, LoadingState } from '@grafana/schema'; import { ExplorePanelData } from 'app/types'; -import { createEmptyQueryResponse } from '../../state/utils'; - -import { setDashboardInLocalStorage } from './addToDashboard'; - -let mockDashboard = {} as unknown; -jest.mock('app/features/dashboard/api/dashboard_api', () => ({ - getDashboardAPI: () => ({ - getDashboardDTO: () => { - return Promise.resolve(mockDashboard); - }, - }), -})); - -describe('addPanelToDashboard', () => { - let spy: jest.SpyInstance; - beforeAll(() => { - spy = jest.spyOn(api, 'setDashboardToFetchFromLocalStorage'); - }); +import { buildDashboardPanelFromExploreState } from './addToDashboard'; +describe('buildDashboardPanelFromExploreState', () => { afterEach(() => { jest.resetAllMocks(); }); - it('Correct datasource ref is used', async () => { - await setDashboardInLocalStorage({ + it('Correct datasource ref is used', () => { + const result = buildDashboardPanelFromExploreState({ queries: [], queryResponse: createEmptyQueryResponse(), datasource: { type: 'loki', uid: 'someUid' }, - time: { from: 'now-1h', to: 'now' }, - }); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - dashboard: expect.objectContaining({ - panels: expect.arrayContaining([expect.objectContaining({ datasource: { type: 'loki', uid: 'someUid' } })]), - }), - }) - ); - }); - - it('Correct time range is used', async () => { - await setDashboardInLocalStorage({ - queries: [], - queryResponse: createEmptyQueryResponse(), - datasource: { type: 'loki', uid: 'someUid' }, - time: { from: 'now-10h', to: 'now' }, }); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - dashboard: expect.objectContaining({ - time: expect.objectContaining({ from: 'now-10h', to: 'now' }), - }), - }) - ); + expect(result.datasource).toEqual({ type: 'loki', uid: 'someUid' }); }); - it('All queries are correctly passed through', async () => { + it('All queries are correctly passed through', () => { const queries: DataQuery[] = [{ refId: 'A' }, { refId: 'B', hide: true }]; - await setDashboardInLocalStorage({ - queries, - queryResponse: createEmptyQueryResponse(), - time: { from: 'now-1h', to: 'now' }, - }); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - dashboard: expect.objectContaining({ - panels: expect.arrayContaining([expect.objectContaining({ targets: expect.arrayContaining(queries) })]), - }), - }) - ); - }); - - it('Previous panels should not be removed', async () => { - const queries: DataQuery[] = [{ refId: 'A' }]; - const existingPanel = { prop: 'this should be kept' }; - - // Set the mocked dashboard - mockDashboard = { - dashboard: { - ...defaultDashboard, - templating: { list: [] }, - title: 'Previous panels should not be removed', - uid: 'someUid', - panels: [existingPanel], - }, - meta: {}, - }; - - await setDashboardInLocalStorage({ - queries, - queryResponse: createEmptyQueryResponse(), - dashboardUid: 'someUid', - datasource: { type: '' }, - time: { from: 'now-1h', to: 'now' }, - }); - - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - dashboard: expect.objectContaining({ - panels: expect.arrayContaining([ - expect.objectContaining({ targets: expect.arrayContaining(queries) }), - existingPanel, - ]), - }), - }) - ); + const result = buildDashboardPanelFromExploreState({ queries, queryResponse: createEmptyQueryResponse() }); + expect(result.targets).toEqual(queries); }); describe('Setting visualization type', () => { @@ -125,14 +39,8 @@ describe('addPanelToDashboard', () => { ]; it.each(cases)('%s', async (_, queries, queryResponse) => { - await setDashboardInLocalStorage({ queries, queryResponse, time: { from: 'now-1h', to: 'now' } }); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - dashboard: expect.objectContaining({ - panels: expect.arrayContaining([expect.objectContaining({ type: 'table' })]), - }), - }) - ); + const result = buildDashboardPanelFromExploreState({ queries, queryResponse }); + expect(result.type).toBe('table'); }); }); @@ -157,14 +65,8 @@ describe('addPanelToDashboard', () => { [framesType]: [new MutableDataFrame({ refId: 'A', fields: [] })], }; - await setDashboardInLocalStorage({ queries, queryResponse, time: { from: 'now-1h', to: 'now' } }); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - dashboard: expect.objectContaining({ - panels: expect.arrayContaining([expect.objectContaining({ type: expectedPanel })]), - }), - }) - ); + const result = buildDashboardPanelFromExploreState({ queries, queryResponse }); + expect(result.type).toBe(expectedPanel); } ); @@ -181,15 +83,29 @@ describe('addPanelToDashboard', () => { ], }; - await setDashboardInLocalStorage({ queries, queryResponse, time: { from: 'now-1h', to: 'now' } }); - expect(spy).toHaveBeenCalledWith( - expect.objectContaining({ - dashboard: expect.objectContaining({ - panels: expect.arrayContaining([expect.objectContaining({ type: 'someCustomPluginId' })]), - }), - }) - ); + const result = buildDashboardPanelFromExploreState({ queries, queryResponse }); + expect(result.type).toBe('someCustomPluginId'); }); }); }); }); + +function createEmptyQueryResponse(): ExplorePanelData { + return { + state: LoadingState.NotStarted, + series: [], + timeRange: getDefaultTimeRange(), + graphFrames: [], + logsFrames: [], + traceFrames: [], + nodeGraphFrames: [], + flameGraphFrames: [], + customFrames: [], + tableFrames: [], + rawPrometheusFrames: [], + rawPrometheusResult: null, + graphResult: null, + logsResult: null, + tableResult: null, + }; +} diff --git a/public/app/features/explore/extensions/AddToDashboard/addToDashboard.ts b/public/app/features/explore/extensions/AddToDashboard/addToDashboard.ts index 51c927801c7..db647d4b1eb 100644 --- a/public/app/features/explore/extensions/AddToDashboard/addToDashboard.ts +++ b/public/app/features/explore/extensions/AddToDashboard/addToDashboard.ts @@ -1,23 +1,14 @@ import { DataFrame, ExplorePanelsState } from '@grafana/data'; -import { Dashboard, DataQuery, DataSourceRef } from '@grafana/schema'; +import { DataQuery, DataSourceRef, Panel } from '@grafana/schema'; import { DataTransformerConfig } from '@grafana/schema/dist/esm/raw/dashboard/x/dashboard_types.gen'; -import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api'; -import { setDashboardToFetchFromLocalStorage } from 'app/features/dashboard/state/initDashboard'; -import { buildNewDashboardSaveModel } from 'app/features/dashboard-scene/serialization/buildNewDashboardSaveModel'; -import { DashboardDTO, ExplorePanelData } from 'app/types'; +import { ExplorePanelData } from 'app/types'; -export enum AddToDashboardError { - FETCH_DASHBOARD = 'fetch-dashboard', - SET_DASHBOARD_LS = 'set-dashboard-ls-error', -} - -interface AddPanelToDashboardOptions { +interface ExploreToDashboardPanelOptions { queries: DataQuery[]; queryResponse: ExplorePanelData; datasource?: DataSourceRef; dashboardUid?: string; panelState?: ExplorePanelsState; - time: Dashboard['time']; } /** @@ -27,7 +18,10 @@ interface AddPanelToDashboardOptions { * @param panelType * @param options */ -function getLogsTableTransformations(panelType: string, options: AddPanelToDashboardOptions): DataTransformerConfig[] { +function getLogsTableTransformations( + panelType: string, + options: ExploreToDashboardPanelOptions +): DataTransformerConfig[] { let transformations: DataTransformerConfig[] = []; if (panelType === 'table' && options.panelState?.logs?.columns) { // If we have a labels column, we need to extract the fields from it @@ -64,10 +58,11 @@ function getLogsTableTransformations(panelType: string, options: AddPanelToDashb return transformations; } -export async function setDashboardInLocalStorage(options: AddPanelToDashboardOptions) { +export function buildDashboardPanelFromExploreState(options: ExploreToDashboardPanelOptions): Panel { const panelType = getPanelType(options.queries, options.queryResponse, options?.panelState); - const panel = { + return { + //@ts-ignore targets: options.queries, type: panelType, title: 'New Panel', @@ -75,28 +70,6 @@ export async function setDashboardInLocalStorage(options: AddPanelToDashboardOpt datasource: options.datasource, transformations: getLogsTableTransformations(panelType, options), }; - - let dto: DashboardDTO; - - if (options.dashboardUid) { - try { - dto = await getDashboardAPI().getDashboardDTO(options.dashboardUid); - } catch (e) { - throw AddToDashboardError.FETCH_DASHBOARD; - } - } else { - dto = await buildNewDashboardSaveModel(); - } - - dto.dashboard.panels = [panel, ...(dto.dashboard.panels ?? [])]; - - dto.dashboard.time = options.time; - - try { - setDashboardToFetchFromLocalStorage(dto); - } catch { - throw AddToDashboardError.SET_DASHBOARD_LS; - } } const isVisible = (query: DataQuery) => !query.hide; diff --git a/public/app/features/explore/extensions/AddToDashboard/index.test.tsx b/public/app/features/explore/extensions/AddToDashboard/index.test.tsx index 6c38ba8b471..79a51c929cb 100644 --- a/public/app/features/explore/extensions/AddToDashboard/index.test.tsx +++ b/public/app/features/explore/extensions/AddToDashboard/index.test.tsx @@ -1,25 +1,25 @@ -import { act, render, screen, waitFor } from '@testing-library/react'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ReactNode } from 'react'; import { Provider } from 'react-redux'; -import { selectors } from '@grafana/e2e-selectors'; -import { locationService, setEchoSrv } from '@grafana/runtime'; -import { DataQuery, defaultDashboard } from '@grafana/schema'; -import { backendSrv } from 'app/core/services/backend_srv'; +import { setEchoSrv } from '@grafana/runtime'; +import { DataQuery } from '@grafana/schema'; import { contextSrv } from 'app/core/services/context_srv'; import { Echo } from 'app/core/services/echo/Echo'; -import * as initDashboard from 'app/features/dashboard/state/initDashboard'; -import { DashboardSearchItemType } from 'app/features/search/types'; import { configureStore } from 'app/store/configureStore'; import { ExploreState } from 'app/types'; import { createEmptyQueryResponse } from '../../state/utils'; -import * as api from './addToDashboard'; - import { AddToDashboard } from '.'; +jest.mock('app/core/services/context_srv'); + +const mocks = { + contextSrv: jest.mocked(contextSrv), +}; + const setup = (children: ReactNode, queries: DataQuery[] = [{ refId: 'A' }]) => { const store = configureStore({ explore: { @@ -40,15 +40,8 @@ const setup = (children: ReactNode, queries: DataQuery[] = [{ refId: 'A' }]) => return render({children}); }; -jest.mock('app/core/services/context_srv'); - -const mocks = { - contextSrv: jest.mocked(contextSrv), -}; - const openModal = async (nameOverride?: string) => { await userEvent.click(screen.getByRole('button', { name: /add to dashboard/i })); - expect(await screen.findByRole('dialog', { name: nameOverride || 'Add panel to dashboard' })).toBeInTheDocument(); }; @@ -57,12 +50,8 @@ describe('AddToDashboardButton', () => { setEchoSrv(new Echo()); }); - /* The Add to dashboard form brings in the DashboardPicker, which will call backendSrv.search as part of its instantiation - If we do not need a list of dashboards for the test, return an empty array. */ beforeEach(() => { - // Mock the search response so we don't get any refused connection errors - // from this test (as the fetch polyfill means this logic would actually try and call the API) - jest.spyOn(backendSrv, 'search').mockResolvedValue([]); + mocks.contextSrv.hasPermission.mockImplementation(() => true); }); afterEach(() => { @@ -81,21 +70,9 @@ describe('AddToDashboardButton', () => { }); describe('Success path', () => { - const addToDashboardResponse = Promise.resolve(); - - const waitForAddToDashboardResponse = async () => { - return act(async () => { - await addToDashboardResponse; - }); - }; - - beforeEach(() => { - jest.spyOn(api, 'setDashboardInLocalStorage').mockReturnValue(addToDashboardResponse); - mocks.contextSrv.hasPermission.mockImplementation(() => true); - }); - afterEach(() => { jest.restoreAllMocks(); + mocks.contextSrv.hasPermission.mockImplementation(() => true); }); it('Opens and closes the modal correctly', async () => { @@ -104,321 +81,7 @@ describe('AddToDashboardButton', () => { await openModal(); await userEvent.click(screen.getByRole('button', { name: /cancel/i })); - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); - - describe('navigation', () => { - it('Navigates to dashboard when clicking on "Open"', async () => { - // @ts-expect-error global.open should return a Window, but is not implemented in js-dom. - const openSpy = jest.spyOn(global, 'open').mockReturnValue(true); - const pushSpy = jest.spyOn(locationService, 'push'); - - setup(); - - await openModal(); - - await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); - - await waitForAddToDashboardResponse(); - - expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument(); - - expect(pushSpy).toHaveBeenCalled(); - expect(openSpy).not.toHaveBeenCalled(); - }); - - it('Navigates to dashboard in a new tab when clicking on "Open in a new tab"', async () => { - // @ts-expect-error global.open should return a Window, but is not implemented in js-dom. - const openSpy = jest.spyOn(global, 'open').mockReturnValue(true); - const pushSpy = jest.spyOn(locationService, 'push'); - - setup(); - - await openModal(); - - await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); - - await waitForAddToDashboardResponse(); - - expect(openSpy).toHaveBeenCalledWith(expect.anything(), '_blank'); - expect(pushSpy).not.toHaveBeenCalled(); - }); - }); - - describe('Save to new dashboard', () => { - describe('Navigate to correct dashboard when saving', () => { - it('Opens the new dashboard in a new tab', async () => { - // @ts-expect-error global.open should return a Window, but is not implemented in js-dom. - const openSpy = jest.spyOn(global, 'open').mockReturnValue(true); - - setup(); - - await openModal(); - - await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); - - await waitForAddToDashboardResponse(); - - expect(openSpy).toHaveBeenCalledWith('dashboard/new', '_blank'); - }); - - it('Navigates to the new dashboard', async () => { - const pushSpy = jest.spyOn(locationService, 'push'); - - setup(); - - await openModal(); - - await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); - - await waitForAddToDashboardResponse(); - - expect(screen.queryByRole('dialog', { name: 'Add panel to dashboard' })).not.toBeInTheDocument(); - - expect(pushSpy).toHaveBeenCalledWith('dashboard/new'); - }); - }); - }); - - describe('Save to existing dashboard', () => { - it('Renders the dashboard picker when switching to "Existing Dashboard"', async () => { - setup(); - - await openModal(); - - expect(screen.queryByRole('combobox', { name: /dashboard/ })).not.toBeInTheDocument(); - - await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); - expect(screen.getByRole('combobox', { name: /dashboard/ })).toBeInTheDocument(); - }); - - it('Does not submit if no dashboard is selected', async () => { - locationService.push = jest.fn(); - - setup(); - - await openModal(); - - await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); - - await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); - await waitForAddToDashboardResponse(); - - expect(locationService.push).not.toHaveBeenCalled(); - }); - - describe('Navigate to correct dashboard when saving', () => { - it('Opens the selected dashboard in a new tab', async () => { - // @ts-expect-error global.open should return a Window, but is not implemented in js-dom. - const openSpy = jest.spyOn(global, 'open').mockReturnValue(true); - - jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({ - dashboard: { ...defaultDashboard, templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' }, - meta: {}, - }); - jest.spyOn(backendSrv, 'search').mockResolvedValue([ - { - uid: 'someUid', - isStarred: false, - title: 'Dashboard Title', - tags: [], - type: DashboardSearchItemType.DashDB, - uri: 'someUri', - url: 'someUrl', - }, - ]); - - setup(); - - await openModal(); - - await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); - - await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); - - await waitFor(async () => { - await screen.findByTestId(selectors.components.Select.option); - }); - await userEvent.click(screen.getByTestId(selectors.components.Select.option)); - - await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); - - await waitFor(async () => { - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - }); - - expect(openSpy).toBeCalledWith('d/someUid', '_blank'); - }); - - it('Navigates to the selected dashboard', async () => { - const pushSpy = jest.spyOn(locationService, 'push'); - - jest.spyOn(backendSrv, 'getDashboardByUid').mockResolvedValue({ - dashboard: { ...defaultDashboard, templating: { list: [] }, title: 'Dashboard Title', uid: 'someUid' }, - meta: {}, - }); - jest.spyOn(backendSrv, 'search').mockResolvedValue([ - { - uid: 'someUid', - isStarred: false, - title: 'Dashboard Title', - tags: [], - type: DashboardSearchItemType.DashDB, - uri: 'someUri', - url: 'someUrl', - }, - ]); - - setup(); - - await openModal(); - - await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); - - await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); - - await waitFor(async () => { - await screen.findByTestId(selectors.components.Select.option); - }); - await userEvent.click(screen.getByTestId(selectors.components.Select.option)); - - await userEvent.click(screen.getByRole('button', { name: /open dashboard$/i })); - - await waitFor(async () => { - expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); - }); - - expect(pushSpy).toBeCalledWith('d/someUid'); - }); - }); - }); - }); - - describe('Permissions', () => { - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('Should only show existing dashboard option with no access to create', async () => { - mocks.contextSrv.hasPermission.mockImplementation((action) => { - if (action === 'dashboards:create') { - return false; - } else { - return true; - } - }); - setup(); - await openModal('Add panel to existing dashboard'); - expect(screen.queryByRole('radio')).not.toBeInTheDocument(); - }); - - it('Should only show new dashboard option with no access to write', async () => { - mocks.contextSrv.hasPermission.mockImplementation((action) => { - if (action === 'dashboards:write') { - return false; - } else { - return true; - } - }); - setup(); - await openModal('Add panel to new dashboard'); - expect(screen.queryByRole('radio')).not.toBeInTheDocument(); - }); - }); - - describe('Error handling', () => { - beforeEach(() => { - mocks.contextSrv.hasPermission.mockImplementation(() => true); - }); - - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('Shows an error if opening a new tab fails', async () => { - jest.spyOn(global, 'open').mockReturnValue(null); - const removeDashboardSpy = jest.spyOn(initDashboard, 'removeDashboardToFetchFromLocalStorage'); - - setup(); - - await openModal(); - expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - - await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); - - await waitFor(async () => { - expect(await screen.findByRole('alert')).toBeInTheDocument(); - }); - - expect(removeDashboardSpy).toHaveBeenCalled(); - }); - - it('Shows an error if saving to localStorage fails', async () => { - jest.spyOn(initDashboard, 'setDashboardToFetchFromLocalStorage').mockImplementation(() => { - throw 'SOME ERROR'; - }); - - setup(); - - await openModal(); - expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - - await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); - - await waitFor(async () => { - expect(await screen.findByRole('alert')).toBeInTheDocument(); - }); - }); - - it('Shows an error if fetching dashboard fails', async () => { - jest.spyOn(backendSrv, 'getDashboardByUid').mockRejectedValue('SOME ERROR'); - jest.spyOn(backendSrv, 'search').mockResolvedValue([ - { - uid: 'someUid', - isStarred: false, - title: 'Dashboard Title', - tags: [], - type: DashboardSearchItemType.DashDB, - uri: 'someUri', - url: 'someUrl', - }, - ]); - - setup(); - - await openModal(); - expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - - await userEvent.click(screen.getByRole('radio', { name: /existing dashboard/i })); - - await userEvent.click(screen.getByRole('combobox', { name: /dashboard/i })); - - await waitFor(async () => { - await screen.findByTestId(selectors.components.Select.option); - }); - await userEvent.click(screen.getByTestId(selectors.components.Select.option)); - - await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); - - await waitFor(async () => { - expect(await screen.findByRole('alert')).toBeInTheDocument(); - }); - }); - - it('Shows an error if an unknown error happens', async () => { - jest.spyOn(api, 'setDashboardInLocalStorage').mockRejectedValue('SOME ERROR'); - - setup(); - - await openModal(); - expect(screen.queryByRole('alert')).not.toBeInTheDocument(); - - await userEvent.click(screen.getByRole('button', { name: /open in new tab/i })); - - await waitFor(async () => { - expect(await screen.findByRole('alert')).toBeInTheDocument(); - }); - }); }); }); diff --git a/public/app/features/explore/extensions/AddToDashboard/index.tsx b/public/app/features/explore/extensions/AddToDashboard/index.tsx index 4b6b4d7f9ad..65326ca0d13 100644 --- a/public/app/features/explore/extensions/AddToDashboard/index.tsx +++ b/public/app/features/explore/extensions/AddToDashboard/index.tsx @@ -6,7 +6,7 @@ import { useSelector } from 'app/types'; import { getExploreItemSelector } from '../../state/selectors'; -import { AddToDashboardForm } from './AddToDashboardForm'; +import { ExploreToDashboardPanel } from './ExploreToDashboardPanel'; import { getAddToDashboardTitle } from './getAddToDashboardTitle'; interface Props { @@ -35,7 +35,7 @@ export const AddToDashboard = ({ exploreId }: Props) => { {isOpen && ( - + )} diff --git a/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx b/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx index 887e446c36d..4ad812d2e5a 100644 --- a/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx +++ b/public/app/features/explore/extensions/getExploreExtensionConfigs.tsx @@ -8,7 +8,7 @@ import { createAddedLinkConfig } from '../../plugins/extensions/utils'; import { changeCorrelationEditorDetails } from '../state/main'; import { runQueries } from '../state/query'; -import { AddToDashboardForm } from './AddToDashboard/AddToDashboardForm'; +import { ExploreToDashboardPanel } from './AddToDashboard/ExploreToDashboardPanel'; import { getAddToDashboardTitle } from './AddToDashboard/getAddToDashboardTitle'; import { type PluginExtensionExploreContext } from './ToolbarExtensionPoint'; @@ -36,7 +36,7 @@ export function getExploreExtensionConfigs(): PluginExtensionAddedLinkConfig[] { onClick: (_, { context, openModal }) => { openModal({ title: getAddToDashboardTitle(), - body: ({ onDismiss }) => , + body: ({ onDismiss }) => , }); }, }), diff --git a/public/app/features/explore/state/utils.ts b/public/app/features/explore/state/utils.ts index 464e4b0ecdf..8b2c6888c3f 100644 --- a/public/app/features/explore/state/utils.ts +++ b/public/app/features/explore/state/utils.ts @@ -23,7 +23,7 @@ import { import { config, getDataSourceSrv } from '@grafana/runtime'; import { DataQuery, DataSourceJsonData, DataSourceRef, TimeZone } from '@grafana/schema'; import { getLocalRichHistoryStorage } from 'app/core/history/richHistoryStorageProvider'; -import { SortOrder } from 'app/core/utils/richHistory'; +import { SortOrder } from 'app/core/utils/richHistoryTypes'; import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource'; import { ExplorePanelData, StoreState } from 'app/types'; import { ExploreItemState, RichHistoryQuery } from 'app/types/explore'; diff --git a/public/app/types/dashboard.ts b/public/app/types/dashboard.ts index 731ad1e9d49..c6713fa0865 100644 --- a/public/app/types/dashboard.ts +++ b/public/app/types/dashboard.ts @@ -139,3 +139,5 @@ export interface DashboardState { initialDatasource?: DataSourceRef['uid']; initError: DashboardInitError | null; } + +export const DASHBOARD_FROM_LS_KEY = 'DASHBOARD_FROM_LS_KEY'; diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 5519b8dc79d..0088bb72839 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1096,7 +1096,6 @@ "help-wizard": { "download-snapshot": "Download snapshot", "github-comment": "Copy Github comment", - "preview-snapshot": "Preview snapshot", "support-bundle": "You can also retrieve a support bundle containing information concerning your Grafana instance and configured datasources in the <1>support bundles section.", "troubleshooting-help": "To request troubleshooting help, send a snapshot of this panel to Grafana Labs Technical Support. The snapshot contains query response data and panel settings." }, diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index ee7d5df34d9..813115daeeb 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1096,7 +1096,6 @@ "help-wizard": { "download-snapshot": "Đőŵʼnľőäđ şʼnäpşĥőŧ", "github-comment": "Cőpy Ğįŧĥūþ čőmmęʼnŧ", - "preview-snapshot": "Přęvįęŵ şʼnäpşĥőŧ", "support-bundle": "Ÿőū čäʼn äľşő řęŧřįęvę ä şūppőřŧ þūʼnđľę čőʼnŧäįʼnįʼnģ įʼnƒőřmäŧįőʼn čőʼnčęřʼnįʼnģ yőūř Ğřäƒäʼnä įʼnşŧäʼnčę äʼnđ čőʼnƒįģūřęđ đäŧäşőūřčęş įʼn ŧĥę <1>şūppőřŧ þūʼnđľęş şęčŧįőʼn.", "troubleshooting-help": "Ŧő řęqūęşŧ ŧřőūþľęşĥőőŧįʼnģ ĥęľp, şęʼnđ ä şʼnäpşĥőŧ őƒ ŧĥįş päʼnęľ ŧő Ğřäƒäʼnä Ŀäþş Ŧęčĥʼnįčäľ Ŝūppőřŧ. Ŧĥę şʼnäpşĥőŧ čőʼnŧäįʼnş qūęřy řęşpőʼnşę đäŧä äʼnđ päʼnęľ şęŧŧįʼnģş." }, From 9d182986f19c20aa700fc0577d79bb738adb14c2 Mon Sep 17 00:00:00 2001 From: Alexander Akhmetov Date: Fri, 11 Oct 2024 15:25:29 +0200 Subject: [PATCH 088/110] Alerting: make StatePersister more configurable to support custom rule-level state persisters (#94590) --- pkg/services/ngalert/state/cache.go | 4 ++-- pkg/services/ngalert/state/manager.go | 6 +++--- pkg/services/ngalert/state/persister_async.go | 17 +++++++++++------ pkg/services/ngalert/state/persister_noop.go | 7 +++++-- pkg/services/ngalert/state/persister_sync.go | 4 ++-- .../ngalert/state/persister_sync_test.go | 7 ++++--- 6 files changed, 27 insertions(+), 18 deletions(-) diff --git a/pkg/services/ngalert/state/cache.go b/pkg/services/ngalert/state/cache.go index 28a5391f55d..0da09784d38 100644 --- a/pkg/services/ngalert/state/cache.go +++ b/pkg/services/ngalert/state/cache.go @@ -346,8 +346,8 @@ func (c *cache) removeByRuleUID(orgID int64, uid string) []*State { return states } -// asInstances returns the whole content of the cache as a slice of AlertInstance. -func (c *cache) asInstances(skipNormalState bool) []ngModels.AlertInstance { +// GetAlertInstances returns the whole content of the cache as a slice of AlertInstance. +func (c *cache) GetAlertInstances(skipNormalState bool) []ngModels.AlertInstance { var states []ngModels.AlertInstance c.mtxStates.RLock() defer c.mtxStates.RUnlock() diff --git a/pkg/services/ngalert/state/manager.go b/pkg/services/ngalert/state/manager.go index 94c5371e308..b351f6c274f 100644 --- a/pkg/services/ngalert/state/manager.go +++ b/pkg/services/ngalert/state/manager.go @@ -31,8 +31,8 @@ type AlertInstanceManager interface { } type StatePersister interface { - Async(ctx context.Context, cache *cache) - Sync(ctx context.Context, span trace.Span, states StateTransitions) + Async(ctx context.Context, instancesProvider AlertInstancesProvider) + Sync(ctx context.Context, span trace.Span, ruleKey ngModels.AlertRuleKeyWithGroup, states StateTransitions) } // Sender is an optional callback intended for sending the states to an alertmanager. @@ -347,7 +347,7 @@ func (st *Manager) ProcessEvalResults( statesToSend = st.updateLastSentAt(allChanges, evaluatedAt) } - st.persister.Sync(ctx, span, allChanges) + st.persister.Sync(ctx, span, alertRule.GetKeyWithGroup(), allChanges) if st.historian != nil { st.historian.Record(ctx, history_model.NewRuleMeta(alertRule, logger), allChanges) } diff --git a/pkg/services/ngalert/state/persister_async.go b/pkg/services/ngalert/state/persister_async.go index 91807f26921..cc617866202 100644 --- a/pkg/services/ngalert/state/persister_async.go +++ b/pkg/services/ngalert/state/persister_async.go @@ -9,8 +9,13 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/ngalert/metrics" + "github.com/grafana/grafana/pkg/services/ngalert/models" ) +type AlertInstancesProvider interface { + GetAlertInstances(skipNormalState bool) []models.AlertInstance +} + type AsyncStatePersister struct { log log.Logger // doNotSaveNormalState controls whether eval.Normal state is persisted to the database and returned by get methods. @@ -30,16 +35,16 @@ func NewAsyncStatePersister(log log.Logger, ticker *clock.Ticker, cfg ManagerCfg } } -func (a *AsyncStatePersister) Async(ctx context.Context, cache *cache) { +func (a *AsyncStatePersister) Async(ctx context.Context, instancesProvider AlertInstancesProvider) { for { select { case <-a.ticker.C: - if err := a.fullSync(ctx, cache); err != nil { + if err := a.fullSync(ctx, instancesProvider); err != nil { a.log.Error("Failed to do a full state sync to database", "err", err) } case <-ctx.Done(): a.log.Info("Scheduler is shutting down, doing a final state sync.") - if err := a.fullSync(context.Background(), cache); err != nil { + if err := a.fullSync(context.Background(), instancesProvider); err != nil { a.log.Error("Failed to do a full state sync to database", "err", err) } a.ticker.Stop() @@ -49,10 +54,10 @@ func (a *AsyncStatePersister) Async(ctx context.Context, cache *cache) { } } -func (a *AsyncStatePersister) fullSync(ctx context.Context, cache *cache) error { +func (a *AsyncStatePersister) fullSync(ctx context.Context, instancesProvider AlertInstancesProvider) error { startTime := time.Now() a.log.Debug("Full state sync start") - instances := cache.asInstances(a.doNotSaveNormalState) + instances := instancesProvider.GetAlertInstances(a.doNotSaveNormalState) if err := a.store.FullSync(ctx, instances); err != nil { a.log.Error("Full state sync failed", "duration", time.Since(startTime), "instances", len(instances)) return err @@ -64,6 +69,6 @@ func (a *AsyncStatePersister) fullSync(ctx context.Context, cache *cache) error return nil } -func (a *AsyncStatePersister) Sync(_ context.Context, _ trace.Span, _ StateTransitions) { +func (a *AsyncStatePersister) Sync(_ context.Context, _ trace.Span, _ models.AlertRuleKeyWithGroup, _ StateTransitions) { a.log.Debug("Sync: No-Op") } diff --git a/pkg/services/ngalert/state/persister_noop.go b/pkg/services/ngalert/state/persister_noop.go index 0275bc5f351..0e770bdc1c4 100644 --- a/pkg/services/ngalert/state/persister_noop.go +++ b/pkg/services/ngalert/state/persister_noop.go @@ -4,12 +4,15 @@ import ( "context" "go.opentelemetry.io/otel/trace" + + "github.com/grafana/grafana/pkg/services/ngalert/models" ) type NoopPersister struct{} -func (n *NoopPersister) Async(_ context.Context, _ *cache) {} -func (n *NoopPersister) Sync(_ context.Context, _ trace.Span, _ StateTransitions) {} +func (n *NoopPersister) Async(_ context.Context, _ AlertInstancesProvider) {} +func (n *NoopPersister) Sync(_ context.Context, _ trace.Span, _ models.AlertRuleKeyWithGroup, _ StateTransitions) { +} func NewNoopPersister() StatePersister { return &NoopPersister{} diff --git a/pkg/services/ngalert/state/persister_sync.go b/pkg/services/ngalert/state/persister_sync.go index 1aee0bed09e..5e0e653547b 100644 --- a/pkg/services/ngalert/state/persister_sync.go +++ b/pkg/services/ngalert/state/persister_sync.go @@ -30,12 +30,12 @@ func NewSyncStatePersisiter(log log.Logger, cfg ManagerCfg) StatePersister { } } -func (a *SyncStatePersister) Async(_ context.Context, _ *cache) { +func (a *SyncStatePersister) Async(_ context.Context, _ AlertInstancesProvider) { a.log.Debug("Async: No-Op") } // Sync persists the state transitions to the database. It deletes stale states and saves the current states. -func (a *SyncStatePersister) Sync(ctx context.Context, span trace.Span, allStates StateTransitions) { +func (a *SyncStatePersister) Sync(ctx context.Context, span trace.Span, _ ngModels.AlertRuleKeyWithGroup, allStates StateTransitions) { staleStates := allStates.StaleStates() if len(staleStates) > 0 { a.deleteAlertStates(ctx, staleStates) diff --git a/pkg/services/ngalert/state/persister_sync_test.go b/pkg/services/ngalert/state/persister_sync_test.go index 390e97a4137..e4e98b91401 100644 --- a/pkg/services/ngalert/state/persister_sync_test.go +++ b/pkg/services/ngalert/state/persister_sync_test.go @@ -40,6 +40,7 @@ func TestSyncPersister_saveAlertStates(t *testing.T) { create(eval.NoData, ""), create(eval.Error, ""), } + ruleKey := ngmodels.AlertRuleKeyWithGroup{} transitionToKey := map[ngmodels.AlertInstanceKey]StateTransition{} transitions := make([]StateTransition, 0) @@ -69,7 +70,7 @@ func TestSyncPersister_saveAlertStates(t *testing.T) { InstanceStore: st, MaxStateSaveConcurrency: 1, }) - syncStatePersister.Sync(context.Background(), span, transitions) + syncStatePersister.Sync(context.Background(), span, ruleKey, transitions) savedKeys := map[ngmodels.AlertInstanceKey]ngmodels.AlertInstance{} for _, op := range st.RecordedOps() { saved := op.(ngmodels.AlertInstance) @@ -90,7 +91,7 @@ func TestSyncPersister_saveAlertStates(t *testing.T) { InstanceStore: st, MaxStateSaveConcurrency: 1, }) - syncStatePersister.Sync(context.Background(), span, transitions) + syncStatePersister.Sync(context.Background(), span, ruleKey, transitions) savedKeys := map[ngmodels.AlertInstanceKey]ngmodels.AlertInstance{} for _, op := range st.RecordedOps() { @@ -160,7 +161,7 @@ func TestSyncPersister_saveAlertStates(t *testing.T) { PreviousStateReason: util.GenerateShortUID(), } - syncStatePersister.Sync(context.Background(), span, []StateTransition{transition}) + syncStatePersister.Sync(context.Background(), span, ruleKey, []StateTransition{transition}) require.Len(t, st.RecordedOps(), 1) saved := st.RecordedOps()[0].(ngmodels.AlertInstance) From a5d72e264d56b58a4bdd15f80ed70e26363d5294 Mon Sep 17 00:00:00 2001 From: Alex Khomenko Date: Fri, 11 Oct 2024 16:33:01 +0300 Subject: [PATCH 089/110] DataTrailsApp: Update to react-router v6 (#94447) * DataTrailsApp: Update router * Update route --- public/app/features/trails/DataTrailsApp.tsx | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/public/app/features/trails/DataTrailsApp.tsx b/public/app/features/trails/DataTrailsApp.tsx index a3b7c8ec46a..a3057fa4b66 100644 --- a/public/app/features/trails/DataTrailsApp.tsx +++ b/public/app/features/trails/DataTrailsApp.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { Routes, Route } from 'react-router-dom-v5-compat'; import { PageLayoutType } from '@grafana/data'; import { locationService } from '@grafana/runtime'; @@ -32,11 +32,11 @@ export class DataTrailsApp extends SceneObjectBase { const { trail, home } = model.useState(); return ( - + + {/* The routes are relative to the HOME_ROUTE */} ( + path={'/'} + element={ { > - )} + } /> - } /> - + } /> + ); }; } From 4d08f446675c1cce135663494b390eb5cce74e57 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 11 Oct 2024 15:07:01 +0100 Subject: [PATCH 090/110] SingleTopNav: Revert to using `AppChromeUpdate` so banners are correct (#94540) * revert to using AppChromeUpdate * fix dashboard settings in old arch * remove empty interface * fix AlertRuleForm --- .../core/components/AppChrome/AppChrome.tsx | 22 +- .../components/AppChrome/AppChromeUpdate.tsx | 2 +- .../AppChrome/MegaMenu/MegaMenu.tsx | 75 ++++--- .../AppChrome/TopBar/SingleTopBarActions.tsx | 33 +++ public/app/core/components/Page/Page.tsx | 120 ++++------- .../components/Page/PageToolbarActions.tsx | 47 ---- public/app/core/components/Page/types.ts | 2 - public/app/core/context/GrafanaContext.ts | 25 ++- .../alerting/unified/CloneRuleEditor.test.tsx | 5 +- .../components/receivers/TemplateForm.tsx | 44 ++-- .../alert-rule-form/AlertRuleForm.tsx | 202 ++++++++---------- .../alert-rule-form/ModifyExportRuleForm.tsx | 46 ++-- .../scene/DashboardSceneRenderer.tsx | 17 +- .../settings/AnnotationsEditView.tsx | 24 +-- .../settings/DashboardLinksEditView.tsx | 23 +- .../settings/GeneralSettingsEditView.tsx | 12 +- .../settings/JsonModelEditView.tsx | 13 +- .../settings/PermissionsEditView.tsx | 13 +- .../settings/VariablesEditView.tsx | 23 +- .../settings/VersionsEditView.tsx | 13 +- .../dashboard/components/DashNav/DashNav.tsx | 16 +- .../AccessControlDashboardPermissions.tsx | 4 +- .../DashboardSettings/AnnotationsSettings.tsx | 4 +- .../DashboardSettings/DashboardSettings.tsx | 18 +- .../DashboardSettings/GeneralSettings.tsx | 3 +- .../DashboardSettings/JsonEditorSettings.tsx | 4 +- .../DashboardSettings/LinksSettings.tsx | 4 +- .../DashboardSettings/VersionsSettings.tsx | 4 +- .../components/DashboardSettings/types.ts | 3 +- .../components/PanelEditor/PanelEditor.tsx | 16 +- .../dashboard/containers/DashboardPage.tsx | 24 +-- .../editor/VariableEditorContainer.tsx | 4 +- 32 files changed, 338 insertions(+), 527 deletions(-) create mode 100644 public/app/core/components/AppChrome/TopBar/SingleTopBarActions.tsx delete mode 100644 public/app/core/components/Page/PageToolbarActions.tsx diff --git a/public/app/core/components/AppChrome/AppChrome.tsx b/public/app/core/components/AppChrome/AppChrome.tsx index 9c323a7ba42..e85295e4660 100644 --- a/public/app/core/components/AppChrome/AppChrome.tsx +++ b/public/app/core/components/AppChrome/AppChrome.tsx @@ -18,6 +18,7 @@ import { MegaMenu, MENU_WIDTH } from './MegaMenu/MegaMenu'; import { NavToolbar } from './NavToolbar/NavToolbar'; import { ReturnToPrevious } from './ReturnToPrevious/ReturnToPrevious'; import { SingleTopBar } from './TopBar/SingleTopBar'; +import { SingleTopBarActions } from './TopBar/SingleTopBarActions'; import { TopSearchBar } from './TopBar/TopSearchBar'; import { TOP_BAR_LEVEL_HEIGHT } from './types'; @@ -28,7 +29,7 @@ export function AppChrome({ children }: Props) { const state = chrome.useState(); const searchBarHidden = state.searchBarHidden || state.kioskMode === KioskMode.TV; const theme = useTheme2(); - const styles = useStyles2(getStyles, searchBarHidden); + const styles = useStyles2(getStyles, searchBarHidden, Boolean(state.actions)); const dockedMenuBreakpoint = theme.breakpoints.values.xl; const dockedMenuLocalStorageState = store.getBool(DOCKED_LOCAL_STORAGE_KEY, true); @@ -99,12 +100,15 @@ export function AppChrome({ children }: Props) { )}
{isSingleTopNav ? ( - + <> + + {state.actions && {state.actions}} + ) : ( <> {!searchBarHidden && } @@ -156,13 +160,13 @@ export function AppChrome({ children }: Props) { ); } -const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean) => { +const getStyles = (theme: GrafanaTheme2, searchBarHidden: boolean, hasActions: boolean) => { const isSingleTopNav = config.featureToggles.singleTopNav; return { content: css({ display: 'flex', flexDirection: 'column', - paddingTop: isSingleTopNav ? TOP_BAR_LEVEL_HEIGHT : TOP_BAR_LEVEL_HEIGHT * 2, + paddingTop: !isSingleTopNav || hasActions ? TOP_BAR_LEVEL_HEIGHT * 2 : TOP_BAR_LEVEL_HEIGHT, flexGrow: 1, height: 'auto', }), diff --git a/public/app/core/components/AppChrome/AppChromeUpdate.tsx b/public/app/core/components/AppChrome/AppChromeUpdate.tsx index ef92b81903a..8daf42dba22 100644 --- a/public/app/core/components/AppChrome/AppChromeUpdate.tsx +++ b/public/app/core/components/AppChrome/AppChromeUpdate.tsx @@ -7,7 +7,7 @@ export interface AppChromeUpdateProps { actions?: React.ReactNode; } /** - * @deprecated This component is deprecated and will be removed in a future release. + * This is the way core pages add actions to the second chrome toolbar */ export const AppChromeUpdate = React.memo(({ actions }: AppChromeUpdateProps) => { const { chrome } = useGrafana(); diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx index b2be2f189b1..23aa2b0b65a 100644 --- a/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx +++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenu.tsx @@ -13,6 +13,8 @@ import { setBookmark } from 'app/core/reducers/navBarTree'; import { usePatchUserPreferencesMutation } from 'app/features/preferences/api/index'; import { useDispatch, useSelector } from 'app/types'; +import { TOP_BAR_LEVEL_HEIGHT } from '../types'; + import { MegaMenuHeader } from './MegaMenuHeader'; import { MegaMenuItem } from './MegaMenuItem'; import { usePinnedItems } from './hooks'; @@ -166,41 +168,44 @@ export const MegaMenu = memo( MegaMenu.displayName = 'MegaMenu'; -const getStyles = (theme: GrafanaTheme2) => ({ - content: css({ - display: 'flex', - flexDirection: 'column', - height: '100%', - minHeight: 0, - position: 'relative', - }), - mobileHeader: css({ - display: 'flex', - justifyContent: 'space-between', - padding: theme.spacing(1, 1, 1, 2), - borderBottom: `1px solid ${theme.colors.border.weak}`, +const getStyles = (theme: GrafanaTheme2) => { + const isSingleTopNav = config.featureToggles.singleTopNav; + return { + content: css({ + display: 'flex', + flexDirection: 'column', + height: isSingleTopNav ? `calc(100% - ${TOP_BAR_LEVEL_HEIGHT}px)` : '100%', + minHeight: 0, + position: 'relative', + }), + mobileHeader: css({ + display: 'flex', + justifyContent: 'space-between', + padding: theme.spacing(1, 1, 1, 2), + borderBottom: `1px solid ${theme.colors.border.weak}`, - [theme.breakpoints.up('md')]: { + [theme.breakpoints.up('md')]: { + display: 'none', + }, + }), + itemList: css({ + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + listStyleType: 'none', + padding: theme.spacing(1, 1, 2, 1), + [theme.breakpoints.up('md')]: { + width: MENU_WIDTH, + }, + }), + dockMenuButton: css({ display: 'none', - }, - }), - itemList: css({ - boxSizing: 'border-box', - display: 'flex', - flexDirection: 'column', - listStyleType: 'none', - padding: theme.spacing(1, 1, 2, 1), - [theme.breakpoints.up('md')]: { - width: MENU_WIDTH, - }, - }), - dockMenuButton: css({ - display: 'none', - position: 'relative', - top: theme.spacing(1), + position: 'relative', + top: theme.spacing(1), - [theme.breakpoints.up('xl')]: { - display: 'inline-flex', - }, - }), -}); + [theme.breakpoints.up('xl')]: { + display: 'inline-flex', + }, + }), + }; +}; diff --git a/public/app/core/components/AppChrome/TopBar/SingleTopBarActions.tsx b/public/app/core/components/AppChrome/TopBar/SingleTopBarActions.tsx new file mode 100644 index 00000000000..51e5bfad6d3 --- /dev/null +++ b/public/app/core/components/AppChrome/TopBar/SingleTopBarActions.tsx @@ -0,0 +1,33 @@ +import { css } from '@emotion/css'; +import { PropsWithChildren } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Components } from '@grafana/e2e-selectors'; +import { Stack, useStyles2 } from '@grafana/ui'; + +import { TOP_BAR_LEVEL_HEIGHT } from '../types'; + +export function SingleTopBarActions({ children }: PropsWithChildren) { + const styles = useStyles2(getStyles); + + return ( +
+ + {children} + +
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + actionsBar: css({ + alignItems: 'center', + backgroundColor: theme.colors.background.primary, + borderBottom: `1px solid ${theme.colors.border.weak}`, + display: 'flex', + height: TOP_BAR_LEVEL_HEIGHT, + padding: theme.spacing(0, 1, 0, 2), + }), + }; +}; diff --git a/public/app/core/components/Page/Page.tsx b/public/app/core/components/Page/Page.tsx index 0c5aab757b5..cd29bbfb556 100644 --- a/public/app/core/components/Page/Page.tsx +++ b/public/app/core/components/Page/Page.tsx @@ -1,58 +1,19 @@ import { css, cx } from '@emotion/css'; -import { - createContext, - Dispatch, - ReactNode, - SetStateAction, - useContext, - useEffect, - useLayoutEffect, - useState, -} from 'react'; +import { useLayoutEffect } from 'react'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { useStyles2 } from '@grafana/ui'; import { useGrafana } from 'app/core/context/GrafanaContext'; -import { TOP_BAR_LEVEL_HEIGHT } from '../AppChrome/types'; import NativeScrollbar from '../NativeScrollbar'; import { PageContents } from './PageContents'; import { PageHeader } from './PageHeader'; import { PageTabs } from './PageTabs'; -import { PageToolbarActions } from './PageToolbarActions'; import { PageType } from './types'; import { usePageNav } from './usePageNav'; import { usePageTitle } from './usePageTitle'; -export interface PageContextType { - setToolbar: Dispatch>; -} - -export const PageContext = createContext(undefined); - -function usePageContext(): PageContextType { - const context = useContext(PageContext); - if (!context) { - throw new Error('No PageContext found'); - } - return context; -} - -/** - * Hook to dynamically set the toolbar of a Page from a child component. - * Prefer setting the toolbar directly as a prop to Page. - * @param toolbar a ReactNode that will be rendered in a second toolbar - */ -export function usePageToolbar(toolbar?: ReactNode) { - const { setToolbar } = usePageContext(); - useEffect(() => { - setToolbar(toolbar); - return () => setToolbar(undefined); - }, [setToolbar, toolbar]); -} - export const Page: PageType = ({ navId, navModel: oldNavProp, @@ -63,15 +24,12 @@ export const Page: PageType = ({ subTitle, children, className, - toolbar: toolbarProp, info, layout = PageLayoutType.Standard, onSetScrollRef, ...otherProps }) => { - const isSingleTopNav = config.featureToggles.singleTopNav; - const [toolbar, setToolbar] = useState(toolbarProp); - const styles = useStyles2(getStyles, Boolean(isSingleTopNav && toolbar)); + const styles = useStyles2(getStyles); const navModel = usePageNav(navId, oldNavProp); const { chrome } = useGrafana(); @@ -92,58 +50,54 @@ export const Page: PageType = ({ }, [navModel, pageNav, chrome, layout]); return ( - -
- {isSingleTopNav && toolbar && {toolbar}} - {layout === PageLayoutType.Standard && ( - -
- {pageHeaderNav && ( - - )} - {pageNav && pageNav.children && } -
{children}
-
-
- )} +
+ {layout === PageLayoutType.Standard && ( + +
+ {pageHeaderNav && ( + + )} + {pageNav && pageNav.children && } +
{children}
+
+
+ )} - {layout === PageLayoutType.Canvas && ( - -
{children}
-
- )} + {layout === PageLayoutType.Canvas && ( + +
{children}
+
+ )} - {layout === PageLayoutType.Custom && children} -
- + {layout === PageLayoutType.Custom && children} +
); }; Page.Contents = PageContents; -const getStyles = (theme: GrafanaTheme2, hasToolbar: boolean) => { +const getStyles = (theme: GrafanaTheme2) => { return { wrapper: css({ label: 'page-wrapper', display: 'flex', flex: '1 1 0', flexDirection: 'column', - marginTop: hasToolbar ? TOP_BAR_LEVEL_HEIGHT : 0, position: 'relative', }), pageContent: css({ diff --git a/public/app/core/components/Page/PageToolbarActions.tsx b/public/app/core/components/Page/PageToolbarActions.tsx deleted file mode 100644 index 20c5589333f..00000000000 --- a/public/app/core/components/Page/PageToolbarActions.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { css } from '@emotion/css'; -import { PropsWithChildren } from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Components } from '@grafana/e2e-selectors'; -import { useChromeHeaderHeight } from '@grafana/runtime'; -import { Stack, useStyles2 } from '@grafana/ui'; -import { useGrafana } from 'app/core/context/GrafanaContext'; - -import { MENU_WIDTH } from '../AppChrome/MegaMenu/MegaMenu'; -import { TOP_BAR_LEVEL_HEIGHT } from '../AppChrome/types'; - -export interface Props {} - -export function PageToolbarActions({ children }: PropsWithChildren) { - const chromeHeaderHeight = useChromeHeaderHeight(); - const { chrome } = useGrafana(); - const state = chrome.useState(); - const menuDockedAndOpen = !state.chromeless && state.megaMenuDocked && state.megaMenuOpen; - const styles = useStyles2(getStyles, chromeHeaderHeight ?? 0, menuDockedAndOpen); - - return ( -
- - {children} - -
- ); -} - -const getStyles = (theme: GrafanaTheme2, chromeHeaderHeight: number, menuDockedAndOpen: boolean) => { - return { - pageToolbar: css({ - alignItems: 'center', - backgroundColor: theme.colors.background.primary, - borderBottom: `1px solid ${theme.colors.border.weak}`, - display: 'flex', - height: TOP_BAR_LEVEL_HEIGHT, - left: menuDockedAndOpen ? MENU_WIDTH : 0, - padding: theme.spacing(0, 1, 0, 2), - position: 'fixed', - top: chromeHeaderHeight, - right: 0, - zIndex: theme.zIndex.navbarFixed, - }), - }; -}; diff --git a/public/app/core/components/Page/types.ts b/public/app/core/components/Page/types.ts index eb6bd7dd4d3..f83f00e77f3 100644 --- a/public/app/core/components/Page/types.ts +++ b/public/app/core/components/Page/types.ts @@ -25,8 +25,6 @@ export interface PageProps extends HTMLAttributes { layout?: PageLayoutType; /** Can be used to get the scroll container element to access scroll position */ onSetScrollRef?: (ref: ScrollRefElement) => void; - /** Set a page-level toolbar */ - toolbar?: React.ReactNode; } export interface PageInfoItem { diff --git a/public/app/core/context/GrafanaContext.ts b/public/app/core/context/GrafanaContext.ts index 424bd71ee68..cf3ed6f1f1c 100644 --- a/public/app/core/context/GrafanaContext.ts +++ b/public/app/core/context/GrafanaContext.ts @@ -4,6 +4,7 @@ import { GrafanaConfig } from '@grafana/data'; import { LocationService, locationService, BackendSrv, config } from '@grafana/runtime'; import { AppChromeService } from '../components/AppChrome/AppChromeService'; +import { TOP_BAR_LEVEL_HEIGHT } from '../components/AppChrome/types'; import { NewFrontendAssetsChecker } from '../services/NewFrontendAssetsChecker'; import { KeybindingSrv } from '../services/keybindingSrv'; @@ -42,17 +43,25 @@ export function useReturnToPreviousInternal() { ); } -const SINGLE_HEADER_BAR_HEIGHT = 40; - export function useChromeHeaderHeight() { const { chrome } = useGrafana(); - const { kioskMode, searchBarHidden, chromeless } = chrome.useState(); + const { actions, kioskMode, searchBarHidden, chromeless } = chrome.useState(); - if (kioskMode || chromeless) { - return 0; - } else if (searchBarHidden || config.featureToggles.singleTopNav) { - return SINGLE_HEADER_BAR_HEIGHT; + if (config.featureToggles.singleTopNav) { + if (kioskMode || chromeless) { + return 0; + } else if (actions) { + return TOP_BAR_LEVEL_HEIGHT * 2; + } else { + return TOP_BAR_LEVEL_HEIGHT; + } } else { - return SINGLE_HEADER_BAR_HEIGHT * 2; + if (kioskMode || chromeless) { + return 0; + } else if (searchBarHidden) { + return TOP_BAR_LEVEL_HEIGHT; + } else { + return TOP_BAR_LEVEL_HEIGHT * 2; + } } } diff --git a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx b/public/app/features/alerting/unified/CloneRuleEditor.test.tsx index fec861fa12c..38155aa917a 100644 --- a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/CloneRuleEditor.test.tsx @@ -5,7 +5,6 @@ import { byRole, byTestId, byText } from 'testing-library-selector'; import { selectors } from '@grafana/e2e-selectors/src'; import { setDataSourceSrv } from '@grafana/runtime'; -import { PageContext } from 'app/core/components/Page/Page'; import { DashboardSearchItem, DashboardSearchItemType } from 'app/features/search/types'; import { RuleWithLocation } from 'app/types/unified-alerting'; @@ -73,9 +72,7 @@ function Wrapper({ children }: React.PropsWithChildren<{}>) { const formApi = useForm({ defaultValues: getDefaultFormValues() }); return ( - - {children} - + {children} ); } diff --git a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx index c306701172b..eeb6890e744 100644 --- a/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx +++ b/public/app/features/alerting/unified/components/receivers/TemplateForm.tsx @@ -1,13 +1,13 @@ import { css, cx } from '@emotion/css'; import { addMinutes, subDays, subHours } from 'date-fns'; import { Location } from 'history'; -import { useMemo, useRef, useState } from 'react'; +import { useRef, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useToggle } from 'react-use'; import AutoSizer from 'react-virtualized-auto-sizer'; import { GrafanaTheme2 } from '@grafana/data'; -import { config as runtimeConfig, isFetchError, locationService } from '@grafana/runtime'; +import { isFetchError, locationService } from '@grafana/runtime'; import { Alert, Button, @@ -21,7 +21,6 @@ import { InlineField, Box, } from '@grafana/ui'; -import { usePageToolbar } from 'app/core/components/Page/Page'; import { useAppNotification } from 'app/core/copy/appNotification'; import { useCleanup } from 'app/core/hooks/useCleanup'; import { ActiveTab as ContactPointsActiveTabs } from 'app/features/alerting/unified/components/contact-points/ContactPoints'; @@ -158,33 +157,28 @@ export const TemplateForm = ({ originalTemplate, prefill, alertmanager }: Props) } }; - const actionButtons = useMemo( - () => ( - - - - Cancel - - - ), - [alertmanager, isSubmitting] + const actionButtons = ( + + + + Cancel + + ); - usePageToolbar(actionButtons); - return ( <> - {!runtimeConfig.featureToggles.singleTopNav && } + {/* error message */} {error && ( diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx index 046e1387429..6a72bdcab21 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx @@ -1,5 +1,5 @@ import { css } from '@emotion/css'; -import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useState } from 'react'; import { FormProvider, SubmitErrorHandler, useForm, UseFormWatch } from 'react-hook-form'; import { useParams } from 'react-router-dom-v5-compat'; @@ -7,7 +7,6 @@ import { GrafanaTheme2 } from '@grafana/data'; import { config, locationService } from '@grafana/runtime'; import { Button, ConfirmModal, CustomScrollbar, Spinner, Stack, useStyles2 } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; -import { usePageToolbar } from 'app/core/components/Page/Page'; import { useAppNotification } from 'app/core/copy/appNotification'; import { contextSrv } from 'app/core/core'; import InfoPausedRule from 'app/features/alerting/unified/components/InfoPausedRule'; @@ -136,66 +135,52 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { }; // @todo why is error not propagated to form? - const submit = useCallback( - async (values: RuleFormValues, exitOnSave: boolean) => { - if (conditionErrorMsg !== '') { - notifyApp.error(conditionErrorMsg); - return; - } + const submit = async (values: RuleFormValues, exitOnSave: boolean) => { + if (conditionErrorMsg !== '') { + notifyApp.error(conditionErrorMsg); + return; + } - trackAlertRuleFormSaved({ formAction: existing ? 'update' : 'create', ruleType: values.type }); + trackAlertRuleFormSaved({ formAction: existing ? 'update' : 'create', ruleType: values.type }); - const ruleDefinition = grafanaTypeRule - ? formValuesToRulerGrafanaRuleDTO(values) - : formValuesToRulerRuleDTO(values); + const ruleDefinition = grafanaTypeRule ? formValuesToRulerGrafanaRuleDTO(values) : formValuesToRulerRuleDTO(values); - const ruleGroupIdentifier = existing - ? getRuleGroupLocationFromRuleWithLocation(existing) - : getRuleGroupLocationFromFormValues(values); + const ruleGroupIdentifier = existing + ? getRuleGroupLocationFromRuleWithLocation(existing) + : getRuleGroupLocationFromFormValues(values); - // @TODO move this to a hook too to make sure the logic here is tested for regressions? - if (!existing) { - // when creating a new rule, we save the manual routing setting , and editorSettings.simplifiedQueryEditor to the local storage - storeInLocalStorageValues(values); - await addRuleToRuleGroup.execute(ruleGroupIdentifier, ruleDefinition, evaluateEvery); - } else { - const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, existing.rule); - const targetRuleGroupIdentifier = getRuleGroupLocationFromFormValues(values); - await updateRuleInRuleGroup.execute( - ruleGroupIdentifier, - ruleIdentifier, - ruleDefinition, - targetRuleGroupIdentifier, - evaluateEvery - ); - } + // @TODO move this to a hook too to make sure the logic here is tested for regressions? + if (!existing) { + // when creating a new rule, we save the manual routing setting , and editorSettings.simplifiedQueryEditor to the local storage + storeInLocalStorageValues(values); + await addRuleToRuleGroup.execute(ruleGroupIdentifier, ruleDefinition, evaluateEvery); + } else { + const ruleIdentifier = fromRulerRuleAndRuleGroupIdentifier(ruleGroupIdentifier, existing.rule); + const targetRuleGroupIdentifier = getRuleGroupLocationFromFormValues(values); + await updateRuleInRuleGroup.execute( + ruleGroupIdentifier, + ruleIdentifier, + ruleDefinition, + targetRuleGroupIdentifier, + evaluateEvery + ); + } - const { dataSourceName, namespaceName, groupName } = ruleGroupIdentifier; - if (exitOnSave) { - const returnTo = queryParams.get('returnTo') || getReturnToUrl(ruleGroupIdentifier, ruleDefinition); + const { dataSourceName, namespaceName, groupName } = ruleGroupIdentifier; + if (exitOnSave) { + const returnTo = queryParams.get('returnTo') || getReturnToUrl(ruleGroupIdentifier, ruleDefinition); - locationService.push(returnTo); - return; - } + locationService.push(returnTo); + return; + } - // Cloud Ruler rules identifier changes on update due to containing rule name and hash components - // After successful update we need to update the URL to avoid displaying 404 errors - if (isCloudRulerRule(ruleDefinition)) { - const updatedRuleIdentifier = fromRulerRule(dataSourceName, namespaceName, groupName, ruleDefinition); - locationService.replace(`/alerting/${encodeURIComponent(stringifyIdentifier(updatedRuleIdentifier))}/edit`); - } - }, - [ - addRuleToRuleGroup, - conditionErrorMsg, - evaluateEvery, - existing, - grafanaTypeRule, - notifyApp, - queryParams, - updateRuleInRuleGroup, - ] - ); + // Cloud Ruler rules identifier changes on update due to containing rule name and hash components + // After successful update we need to update the URL to avoid displaying 404 errors + if (isCloudRulerRule(ruleDefinition)) { + const updatedRuleIdentifier = fromRulerRule(dataSourceName, namespaceName, groupName, ruleDefinition); + locationService.replace(`/alerting/${encodeURIComponent(stringifyIdentifier(updatedRuleIdentifier))}/edit`); + } + }; const deleteRule = async () => { if (existing) { @@ -208,80 +193,73 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { } }; - const onInvalid: SubmitErrorHandler = useCallback( - (errors): void => { - trackAlertRuleFormError({ - grafana_version: config.buildInfo.version, - org_id: contextSrv.user.orgId, - user_id: contextSrv.user.id, - error: Object.keys(errors).toString(), - formAction: existing ? 'update' : 'create', - }); - notifyApp.error('There are errors in the form. Please correct them and try again!'); - }, - [existing, notifyApp] - ); + const onInvalid: SubmitErrorHandler = (errors): void => { + trackAlertRuleFormError({ + grafana_version: config.buildInfo.version, + org_id: contextSrv.user.orgId, + user_id: contextSrv.user.id, + error: Object.keys(errors).toString(), + formAction: existing ? 'update' : 'create', + }); + notifyApp.error('There are errors in the form. Please correct them and try again!'); + }; - const cancelRuleCreation = useCallback(() => { + const cancelRuleCreation = () => { logInfo(LogMessages.cancelSavingAlertRule); trackAlertRuleFormCancelled({ formAction: existing ? 'update' : 'create' }); locationService.getHistory().goBack(); - }, [existing]); + }; const evaluateEveryInForm = watch('evaluateEvery'); useEffect(() => setEvaluateEvery(evaluateEveryInForm), [evaluateEveryInForm]); - const actionButtons = useMemo( - () => ( - - {existing && ( - - )} + const actionButtons = ( + + {existing && ( - + + {existing ? ( + - {existing ? ( - - ) : null} - {existing && isCortexLokiOrRecordingRule(watch) && ( - - )} - - ), - [cancelRuleCreation, existing, handleSubmit, isSubmitting, onInvalid, styles.buttonSpinner, submit, watch] + ) : null} + {existing && isCortexLokiOrRecordingRule(watch) && ( + + )} + ); - usePageToolbar(actionButtons); const isPaused = existing && isGrafanaRulerRule(existing.rule) && isGrafanaRulerRulePaused(existing.rule); if (!type) { @@ -289,7 +267,7 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { } return ( - {!config.featureToggles.singleTopNav && } + e.preventDefault()} className={styles.form}>
{isPaused && } diff --git a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx index ee890b033bc..79eef183128 100644 --- a/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/alert-rule-form/ModifyExportRuleForm.tsx @@ -2,9 +2,7 @@ import { memo, useCallback, useEffect, useMemo, useState } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useAsync } from 'react-use'; -import { config } from '@grafana/runtime'; import { Button, CustomScrollbar, LinkButton, LoadingPlaceholder, Stack } from '@grafana/ui'; -import { usePageToolbar } from 'app/core/components/Page/Page'; import { useAppNotification } from 'app/core/copy/appNotification'; import { useQueryParams } from 'app/core/hooks/useQueryParams'; @@ -52,47 +50,39 @@ export function ModifyExportRuleForm({ ruleForm, alertUid }: ModifyExportRuleFor const [conditionErrorMsg, setConditionErrorMsg] = useState(''); const [evaluateEvery, setEvaluateEvery] = useState(ruleForm?.evaluateEvery ?? DEFAULT_GROUP_EVALUATION_INTERVAL); - const onInvalid = useCallback((): void => { + const onInvalid = (): void => { notifyApp.error('There are errors in the form. Please correct them and try again!'); - }, [notifyApp]); + }; const checkAlertCondition = (msg = '') => { setConditionErrorMsg(msg); }; - const submit = useCallback( - (exportData: RuleFormValues | undefined) => { - if (conditionErrorMsg !== '') { - notifyApp.error(conditionErrorMsg); - return; - } - setExportData(exportData); - }, - [conditionErrorMsg, notifyApp] - ); + const submit = (exportData: RuleFormValues | undefined) => { + if (conditionErrorMsg !== '') { + notifyApp.error(conditionErrorMsg); + return; + } + setExportData(exportData); + }; const onClose = useCallback(() => { setExportData(undefined); }, [setExportData]); - const actionButtons = useMemo( - () => [ - submit(undefined)}> - Cancel - , - , - ], - [formAPI, onInvalid, returnTo, submit] - ); - - usePageToolbar(actionButtons); + const actionButtons = [ + submit(undefined)}> + Cancel + , + , + ]; return ( <> - {!config.featureToggles.singleTopNav && } + e.preventDefault()}>
diff --git a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx index d338a64be03..88f2bdc68ed 100644 --- a/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx +++ b/public/app/features/dashboard-scene/scene/DashboardSceneRenderer.tsx @@ -3,10 +3,9 @@ import { useEffect, useMemo } from 'react'; import { useLocation } from 'react-router-dom-v5-compat'; import { GrafanaTheme2, PageLayoutType } from '@grafana/data'; -import { config, useChromeHeaderHeight } from '@grafana/runtime'; +import { useChromeHeaderHeight } from '@grafana/runtime'; import { SceneComponentProps } from '@grafana/scenes'; import { useStyles2 } from '@grafana/ui'; -import { TOP_BAR_LEVEL_HEIGHT } from 'app/core/components/AppChrome/types'; import NativeScrollbar from 'app/core/components/NativeScrollbar'; import { Page } from 'app/core/components/Page/Page'; import { EntityNotFound } from 'app/core/components/PageNotFound/EntityNotFound'; @@ -15,7 +14,7 @@ import DashboardEmpty from 'app/features/dashboard/dashgrid/DashboardEmpty'; import { useSelector } from 'app/types'; import { DashboardScene } from './DashboardScene'; -import { NavToolbarActions, ToolbarActions } from './NavToolbarActions'; +import { NavToolbarActions } from './NavToolbarActions'; import { PanelSearchLayout } from './PanelSearchLayout'; import { DashboardAngularDeprecationBanner } from './angular/DashboardAngularDeprecationBanner'; @@ -31,7 +30,6 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps { @@ -81,17 +79,12 @@ export function DashboardSceneRenderer({ model }: SceneComponentProps : undefined} - > + {editPanel && } {!editPanel && (
- {!isSingleTopNav && } + {controls && (
@@ -140,7 +133,7 @@ function getStyles(theme: GrafanaTheme2, headerHeight: number) { position: 'sticky', zIndex: theme.zIndex.activePanel, background: theme.colors.background.canvas, - top: config.featureToggles.singleTopNav ? headerHeight + TOP_BAR_LEVEL_HEIGHT : headerHeight, + top: headerHeight, }, }), canvasContent: css({ diff --git a/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx b/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx index 0e0b0976a2a..72510de5130 100644 --- a/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/AnnotationsEditView.tsx @@ -1,11 +1,11 @@ import { AnnotationQuery, getDataSourceRef, NavModel, NavModelItem, PageLayoutType } from '@grafana/data'; -import { config, getDataSourceSrv } from '@grafana/runtime'; +import { getDataSourceSrv } from '@grafana/runtime'; import { SceneComponentProps, SceneObjectBase, VizPanel, dataLayers } from '@grafana/scenes'; import { Page } from 'app/core/components/Page/Page'; import { DashboardAnnotationsDataLayer } from '../scene/DashboardAnnotationsDataLayer'; import { DashboardScene } from '../scene/DashboardScene'; -import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions'; +import { NavToolbarActions } from '../scene/NavToolbarActions'; import { dataLayersToAnnotations } from '../serialization/dataLayersToAnnotations'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getDashboardSceneFor } from '../utils/utils'; @@ -133,7 +133,6 @@ function AnnotationsSettingsView({ model }: SceneComponentProps : undefined} - > - {!isSingleTopNav && } + + : undefined} - > - {!isSingleTopNav && } + + : undefined} - > - {!isSingleTopNav && } + + : undefined} - > - {!isSingleTopNav && } + + ); diff --git a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx index 1540c2fbbd0..73b1d8cdceb 100644 --- a/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/GeneralSettingsEditView.tsx @@ -25,7 +25,7 @@ import { GenAIDashTitleButton } from 'app/features/dashboard/components/GenAI/Ge import { updateNavModel } from '../pages/utils'; import { DashboardScene } from '../scene/DashboardScene'; -import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions'; +import { NavToolbarActions } from '../scene/NavToolbarActions'; import { dashboardSceneGraph } from '../utils/dashboardSceneGraph'; import { getDashboardSceneFor } from '../utils/utils'; @@ -177,16 +177,10 @@ export class GeneralSettingsEditView const { intervals } = model.getRefreshPicker().useState(); const { hideTimeControls } = model.getDashboardControls().useState(); const { enabled: liveNow } = model.getLiveNowTimer().useState(); - const isSingleTopNav = config.featureToggles.singleTopNav; return ( - : undefined} - > - {!isSingleTopNav && } + +
i const { navModel, pageNav } = useDashboardEditPageNav(dashboard, model.getUrlKey()); const canSave = dashboard.useState().meta.canSave; const { jsonText } = model.useState(); - const isSingleTopNav = config.featureToggles.singleTopNav; const onSave = async (overwrite: boolean) => { const result = await onSaveDashboard(dashboard, JSON.parse(model.state.jsonText), { @@ -176,13 +174,8 @@ export class JsonModelEditView extends SceneObjectBase i ); } return ( - : undefined} - > - {!isSingleTopNav && } + +
The JSON model below is the data structure that defines the dashboard. This includes dashboard settings, diff --git a/public/app/features/dashboard-scene/settings/PermissionsEditView.tsx b/public/app/features/dashboard-scene/settings/PermissionsEditView.tsx index 727122e9ecb..5dfa1a49659 100644 --- a/public/app/features/dashboard-scene/settings/PermissionsEditView.tsx +++ b/public/app/features/dashboard-scene/settings/PermissionsEditView.tsx @@ -1,5 +1,4 @@ import { PageLayoutType } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { SceneComponentProps, SceneObjectBase } from '@grafana/scenes'; import { Permissions } from 'app/core/components/AccessControl'; import { Page } from 'app/core/components/Page/Page'; @@ -7,7 +6,7 @@ import { contextSrv } from 'app/core/core'; import { AccessControlAction } from 'app/types'; import { DashboardScene } from '../scene/DashboardScene'; -import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions'; +import { NavToolbarActions } from '../scene/NavToolbarActions'; import { getDashboardSceneFor } from '../utils/utils'; import { DashboardEditView, DashboardEditViewState, useDashboardEditPageNav } from './utils'; @@ -35,16 +34,10 @@ function PermissionsEditorSettings({ model }: SceneComponentProps : undefined} - > - {!isSingleTopNav && } + + ); diff --git a/public/app/features/dashboard-scene/settings/VariablesEditView.tsx b/public/app/features/dashboard-scene/settings/VariablesEditView.tsx index 993bcd0fb10..ffdfe4e7856 100644 --- a/public/app/features/dashboard-scene/settings/VariablesEditView.tsx +++ b/public/app/features/dashboard-scene/settings/VariablesEditView.tsx @@ -1,10 +1,9 @@ import { NavModel, NavModelItem, PageLayoutType } from '@grafana/data'; -import { config } from '@grafana/runtime'; import { SceneComponentProps, SceneObjectBase, SceneVariable, SceneVariables, sceneGraph } from '@grafana/scenes'; import { Page } from 'app/core/components/Page/Page'; import { DashboardScene } from '../scene/DashboardScene'; -import { NavToolbarActions, ToolbarActions } from '../scene/NavToolbarActions'; +import { NavToolbarActions } from '../scene/NavToolbarActions'; import { getDashboardSceneFor } from '../utils/utils'; import { EditListViewSceneUrlSync } from './EditListViewSceneUrlSync'; @@ -207,7 +206,6 @@ function VariableEditorSettingsListView({ model }: SceneComponentProps : undefined} - > - {!isSingleTopNav && } + + : undefined} - > - {!isSingleTopNav && } + + 1; const hasMore = model.versions.length >= model.limit; const isLastPage = model.versions.find((rev) => rev.version === 1); - const isSingleTopNav = config.featureToggles.singleTopNav; const viewModeCompare = ( <> @@ -239,13 +237,8 @@ function VersionsEditorSettingsListView({ model }: SceneComponentProps : undefined} - > - {!isSingleTopNav && } + + {viewMode === 'compare' ? viewModeCompare : viewModeList} ); diff --git a/public/app/features/dashboard/components/DashNav/DashNav.tsx b/public/app/features/dashboard/components/DashNav/DashNav.tsx index 3d92e3731c3..86a3986be31 100644 --- a/public/app/features/dashboard/components/DashNav/DashNav.tsx +++ b/public/app/features/dashboard/components/DashNav/DashNav.tsx @@ -16,6 +16,7 @@ import { Badge, } from '@grafana/ui'; import { updateNavIndex } from 'app/core/actions'; +import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { NavToolbarSeparator } from 'app/core/components/AppChrome/NavToolbar/NavToolbarSeparator'; import config from 'app/core/config'; import { useAppNotification } from 'app/core/copy/appNotification'; @@ -82,7 +83,6 @@ export const DashNav = memo((props) => { // this ensures the component rerenders when the location changes useLocation(); const forceUpdate = useForceUpdate(); - const isSingleTopNav = config.featureToggles.singleTopNav; // We don't really care about the event payload here only that it triggeres a re-render of this component useBusEvent(props.dashboard.events, DashboardMetaChangedEvent); @@ -357,11 +357,15 @@ export const DashNav = memo((props) => { }; return ( - <> - {renderLeftActions()} - {!isSingleTopNav && } - {renderRightActions()} - + + {renderLeftActions()} + + {renderRightActions()} + + } + /> ); }); diff --git a/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx b/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx index 303cae3ac2b..6b31090b447 100644 --- a/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx +++ b/public/app/features/dashboard/components/DashboardPermissions/AccessControlDashboardPermissions.tsx @@ -5,12 +5,12 @@ import { AccessControlAction } from 'app/types'; import { SettingsPageProps } from '../DashboardSettings/types'; -export const AccessControlDashboardPermissions = ({ dashboard, sectionNav, toolbar }: SettingsPageProps) => { +export const AccessControlDashboardPermissions = ({ dashboard, sectionNav }: SettingsPageProps) => { const canSetPermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPermissionsWrite); const pageNav = sectionNav.node.parentItem; return ( - + ); diff --git a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx index e345cf1533d..158d80b9dcc 100644 --- a/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/AnnotationsSettings.tsx @@ -7,7 +7,7 @@ import { AnnotationSettingsEdit, AnnotationSettingsList, newAnnotationName } fro import { SettingsPageProps } from './types'; -export function AnnotationsSettings({ dashboard, editIndex, sectionNav, toolbar }: SettingsPageProps) { +export function AnnotationsSettings({ dashboard, editIndex, sectionNav }: SettingsPageProps) { const onNew = () => { const newAnnotation: AnnotationQuery = { name: newAnnotationName, @@ -27,7 +27,7 @@ export function AnnotationsSettings({ dashboard, editIndex, sectionNav, toolbar const isEditing = editIndex != null && editIndex < dashboard.annotations.list.length; return ( - + {!isEditing && } {isEditing && } diff --git a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx index ab2fd4d6a21..adb6af01c37 100644 --- a/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx +++ b/public/app/features/dashboard/components/DashboardSettings/DashboardSettings.tsx @@ -4,7 +4,7 @@ import { useLocation } from 'react-router-dom-v5-compat'; import { locationUtil, NavModel, NavModelItem } from '@grafana/data'; import { selectors } from '@grafana/e2e-selectors'; -import { config, locationService } from '@grafana/runtime'; +import { locationService } from '@grafana/runtime'; import { Button, Stack, Text, ToolbarButtonRow } from '@grafana/ui'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { Page } from 'app/core/components/Page/Page'; @@ -36,7 +36,6 @@ const onClose = () => locationService.partial({ editview: null, editIndex: null export function DashboardSettings({ dashboard, editview, pageNav, sectionNav }: Props) { const [updateId, setUpdateId] = useState(0); - const isSingleTopNav = config.featureToggles.singleTopNav; useEffect(() => { dashboard.events.subscribe(DashboardMetaChangedEvent, () => setUpdateId((v) => v + 1)); }, [dashboard]); @@ -82,15 +81,8 @@ export function DashboardSettings({ dashboard, editview, pageNav, sectionNav }: return ( <> - {!isSingleTopNav && ( - {actions}} /> - )} - {actions} : undefined} - sectionNav={subSectionNav} - dashboard={dashboard} - editIndex={editIndex} - /> + {actions}} /> + ); } @@ -217,9 +209,9 @@ function getSectionNav( }; } -function MakeEditable({ dashboard, sectionNav, toolbar }: SettingsPageProps) { +function MakeEditable({ dashboard, sectionNav }: SettingsPageProps) { return ( - + Dashboard not editable + + ); + })} +
+ )} + + ); +} + +function onHintButtonClick(hint: QueryHint, props: PromQueryEditorProps) { + reportInteraction('grafana_query_builder_hints_clicked', { + hint: hint.type, + datasourceType: props.datasource.type, + }); + + if (hint.fix?.action) { + const newQuery = props.datasource.modifyQuery(props.query, hint.fix.action); + return props.onChange(newQuery); + } +} + +const getStyles = (theme: GrafanaTheme2) => { + return { + container: css({ + display: 'flex', + alignItems: 'start', + }), + hint: css({ + marginRight: theme.spacing(1), + padEnd: theme.spacing(2), + }), + }; +}; From a69ee676babc7644da41781efc5ae2c301f06de6 Mon Sep 17 00:00:00 2001 From: Claudiu Dragalina-Paraipan Date: Mon, 14 Oct 2024 13:47:18 +0300 Subject: [PATCH 109/110] [authn] adding `appPlatformGrpcClientAuth` featureflag (#94640) * introduce appPlatformGrpcClientAuth (renamed appPlatformAccessTokens which is not used) * re-run toggles gen --------- Co-authored-by: gamab --- .../src/types/featureToggles.gen.ts | 2 +- pkg/services/featuremgmt/registry.go | 4 ++-- pkg/services/featuremgmt/toggles_gen.csv | 2 +- pkg/services/featuremgmt/toggles_gen.go | 6 +++--- pkg/services/featuremgmt/toggles_gen.json | 17 ++++++++++++++++- 5 files changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 6283847aa1a..83738ec5cf1 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -211,7 +211,7 @@ export interface FeatureToggles { exploreLogsAggregatedMetrics?: boolean; exploreLogsLimitedTimeRange?: boolean; homeSetupGuide?: boolean; - appPlatformAccessTokens?: boolean; + appPlatformGrpcClientAuth?: boolean; appSidecar?: boolean; groupAttributeSync?: boolean; alertingQueryAndExpressionsStepMode?: boolean; diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 1d289b98596..9034ce2fb89 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -1457,8 +1457,8 @@ var ( Owner: growthAndOnboarding, }, { - Name: "appPlatformAccessTokens", - Description: "Enables the use of access tokens for the App Platform", + Name: "appPlatformGrpcClientAuth", + Description: "Enables the gRPC client to authenticate with the App Platform by using ID & access tokens", Stage: FeatureStageExperimental, Owner: identityAccessTeam, HideFromDocs: true, diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 3dd5ee565ce..e65a21dd9b5 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -192,7 +192,7 @@ exploreLogsShardSplitting,experimental,@grafana/observability-logs,false,false,t exploreLogsAggregatedMetrics,experimental,@grafana/observability-logs,false,false,true exploreLogsLimitedTimeRange,experimental,@grafana/observability-logs,false,false,true homeSetupGuide,experimental,@grafana/growth-and-onboarding,false,false,true -appPlatformAccessTokens,experimental,@grafana/identity-access-team,false,false,false +appPlatformGrpcClientAuth,experimental,@grafana/identity-access-team,false,false,false appSidecar,experimental,@grafana/explore-squad,false,false,false groupAttributeSync,experimental,@grafana/identity-access-team,false,false,false alertingQueryAndExpressionsStepMode,experimental,@grafana/alerting-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 1aee2335aee..31fe284f57f 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -779,9 +779,9 @@ const ( // Used in Home for users who want to return to the onboarding flow or quickly find popular config pages FlagHomeSetupGuide = "homeSetupGuide" - // FlagAppPlatformAccessTokens - // Enables the use of access tokens for the App Platform - FlagAppPlatformAccessTokens = "appPlatformAccessTokens" + // FlagAppPlatformGrpcClientAuth + // Enables the gRPC client to authenticate with the App Platform by using ID & access tokens + FlagAppPlatformGrpcClientAuth = "appPlatformGrpcClientAuth" // FlagAppSidecar // Enable the app sidecar feature that allows rendering 2 apps at the same time diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 1a1e67ca5fe..40e0388ab28 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -386,7 +386,8 @@ "metadata": { "name": "appPlatformAccessTokens", "resourceVersion": "1725549369316", - "creationTimestamp": "2024-09-05T15:16:09Z" + "creationTimestamp": "2024-09-05T15:16:09Z", + "deletionTimestamp": "2024-10-11T15:54:21Z" }, "spec": { "description": "Enables the use of access tokens for the App Platform", @@ -396,6 +397,20 @@ "hideFromDocs": true } }, + { + "metadata": { + "name": "appPlatformGrpcClientAuth", + "resourceVersion": "1728662061076", + "creationTimestamp": "2024-10-11T15:54:21Z" + }, + "spec": { + "description": "Enables the gRPC client to authenticate with the App Platform by using ID \u0026 access tokens", + "stage": "experimental", + "codeowner": "@grafana/identity-access-team", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "appSidecar", From 2f3c539d9b559e87582736cc613fb741af8d08b9 Mon Sep 17 00:00:00 2001 From: Jack Baldry Date: Mon, 14 Oct 2024 11:47:26 +0100 Subject: [PATCH 110/110] Remove doc-validator requirement to run on all pull requests (#94673) --- .github/workflows/doc-validator.yml | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/.github/workflows/doc-validator.yml b/.github/workflows/doc-validator.yml index 75b721904cc..0e6f613da9b 100644 --- a/.github/workflows/doc-validator.yml +++ b/.github/workflows/doc-validator.yml @@ -1,13 +1,18 @@ name: "doc-validator" on: - pull_request: - paths: ["docs/sources/**"] workflow_dispatch: + inputs: + include: + description: | + Regular expression that matches paths to include in linting. + + For example: docs/sources/(?:alerting|fundamentals)/.+\.md + required: true jobs: doc-validator: runs-on: "ubuntu-latest" container: - image: "grafana/doc-validator:v5.0.0" + image: "grafana/doc-validator:v5.2.0" steps: - name: "Checkout code" uses: "actions/checkout@v4" @@ -15,15 +20,7 @@ jobs: # Only run doc-validator on specific directories. run: > doc-validator - '--include=^docs/sources/(?:alerting|fundamentals|getting-started|introduction|setup-grafana|upgrade-guide|whatsnew/whats-new-in-v(?:9|10))/.+\.md$' + '--include=${{ inputs.include }}' '--skip-checks=^(?:image.+|canonical-does-not-match-pretty-URL)$' ./docs/sources /docs/grafana/latest - | reviewdog - -f=rdjsonl - --fail-on-error - --filter-mode=nofilter - --name=doc-validator - --reporter=github-pr-review - env: - REVIEWDOG_GITHUB_API_TOKEN: "${{ secrets.GITHUB_TOKEN }}"