From 6f31a69bfd9cd3480fcd156da41a0e87f265a138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 14 Apr 2022 12:57:56 +0200 Subject: [PATCH 01/40] QueryEditorRow: Show query errors next to query in a consistent way across Grafana (#47613) * Show query errors under each query row * Testing a more plain error box * Font size * Make it green * Nit UI * Slight simplification of condition * New design * Update Co-authored-by: Dominik Prokop --- packages/grafana-data/src/types/datasource.ts | 2 +- packages/grafana-ui/src/types/icon.ts | 1 + .../features/explore/ErrorContainer.test.tsx | 4 +- .../app/features/explore/ErrorContainer.tsx | 9 +--- .../explore/ResponseErrorContainer.test.tsx | 7 ++-- .../explore/ResponseErrorContainer.tsx | 6 ++- .../inspector/InspectErrorTab.test.tsx | 4 +- .../query/components/QueryEditorRow.test.ts | 15 +++++++ .../query/components/QueryEditorRow.tsx | 20 ++++----- .../query/components/QueryErrorAlert.tsx | 41 +++++++++++++++++++ .../app/plugins/datasource/loki/datasource.ts | 7 +++- 11 files changed, 86 insertions(+), 30 deletions(-) create mode 100644 public/app/features/query/components/QueryErrorAlert.tsx diff --git a/packages/grafana-data/src/types/datasource.ts b/packages/grafana-data/src/types/datasource.ts index 69717854e2b..1396140c41e 100644 --- a/packages/grafana-data/src/types/datasource.ts +++ b/packages/grafana-data/src/types/datasource.ts @@ -468,7 +468,7 @@ export interface DataQueryError { error?: string; }; message?: string; - status?: string; + status?: number; statusText?: string; refId?: string; type?: DataQueryErrorType; diff --git a/packages/grafana-ui/src/types/icon.ts b/packages/grafana-ui/src/types/icon.ts index de315cb978e..870cf8b2d82 100644 --- a/packages/grafana-ui/src/types/icon.ts +++ b/packages/grafana-ui/src/types/icon.ts @@ -69,6 +69,7 @@ export const getAvailableIcons = () => 'envelope', 'exchange-alt', 'exclamation-triangle', + 'exclamation-circle', 'external-link-alt', 'eye', 'eye-slash', diff --git a/public/app/features/explore/ErrorContainer.test.tsx b/public/app/features/explore/ErrorContainer.test.tsx index 589cdba349c..accc19c1877 100644 --- a/public/app/features/explore/ErrorContainer.test.tsx +++ b/public/app/features/explore/ErrorContainer.test.tsx @@ -11,7 +11,7 @@ describe('ErrorContainer', () => { error: 'Error data content', }, message: 'Error message', - status: 'Error status', + status: 500, statusText: 'Error status text', refId: 'A', }, @@ -30,7 +30,7 @@ describe('ErrorContainer', () => { message: 'Error data message', error: 'Error data content', }, - status: 'Error status', + status: 500, statusText: 'Error status text', refId: 'A', }, diff --git a/public/app/features/explore/ErrorContainer.tsx b/public/app/features/explore/ErrorContainer.tsx index 675b2dcf6f7..fffa9c10313 100644 --- a/public/app/features/explore/ErrorContainer.tsx +++ b/public/app/features/explore/ErrorContainer.tsx @@ -1,8 +1,7 @@ import React, { FunctionComponent } from 'react'; import { DataQueryError } from '@grafana/data'; -import { Alert, useTheme2 } from '@grafana/ui'; +import { Alert } from '@grafana/ui'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; -import { css } from '@emotion/css'; export interface ErrorContainerProps { queryError?: DataQueryError; @@ -10,18 +9,14 @@ export interface ErrorContainerProps { export const ErrorContainer: FunctionComponent = (props) => { const { queryError } = props; - const theme = useTheme2(); const showError = queryError ? true : false; const duration = showError ? 100 : 10; const title = queryError ? 'Query error' : 'Unknown error'; const message = queryError?.message || queryError?.data?.message || null; - const alertWithTopMargin = css` - margin-top: ${theme.spacing(2)}; - `; return ( - + {message} diff --git a/public/app/features/explore/ResponseErrorContainer.test.tsx b/public/app/features/explore/ResponseErrorContainer.test.tsx index 75982c68a2f..387dc9ee937 100644 --- a/public/app/features/explore/ResponseErrorContainer.test.tsx +++ b/public/app/features/explore/ResponseErrorContainer.test.tsx @@ -19,15 +19,14 @@ describe('ResponseErrorContainer', () => { expect(errorEl).toHaveTextContent(errorMessage); }); - it('shows error if there is refID', async () => { + it('do not show error if there is a refId', async () => { const errorMessage = 'test error'; setup({ refId: 'someId', message: errorMessage, }); - const errorEl = screen.getByTestId(selectors.components.Alert.alertV2('error')); - expect(errorEl).toBeInTheDocument(); - expect(errorEl).toHaveTextContent(errorMessage); + const errorEl = screen.queryByTestId(selectors.components.Alert.alertV2('error')); + expect(errorEl).not.toBeInTheDocument(); }); it('shows error.data.message if error.message does not exist', async () => { diff --git a/public/app/features/explore/ResponseErrorContainer.tsx b/public/app/features/explore/ResponseErrorContainer.tsx index a23ad7fa2f4..5dd5f59f154 100644 --- a/public/app/features/explore/ResponseErrorContainer.tsx +++ b/public/app/features/explore/ResponseErrorContainer.tsx @@ -9,8 +9,12 @@ interface Props { } export function ResponseErrorContainer(props: Props) { const queryResponse = useSelector((state: StoreState) => state.explore[props.exploreId]?.queryResponse); - const queryError = queryResponse?.state === LoadingState.Error ? queryResponse?.error : undefined; + // Errors with ref ids are shown below the corresponding query + if (queryError?.refId) { + return null; + } + return ; } diff --git a/public/app/features/inspector/InspectErrorTab.test.tsx b/public/app/features/inspector/InspectErrorTab.test.tsx index b04e1283d35..07531dbb3e3 100644 --- a/public/app/features/inspector/InspectErrorTab.test.tsx +++ b/public/app/features/inspector/InspectErrorTab.test.tsx @@ -62,11 +62,11 @@ describe('InspectErrorTab', () => { it('should return a jsonFormatter object of error if it has no .data and no .message', () => { const error = { - status: '400', + status: 400, }; const { container } = render(); expect(container.childElementCount).toEqual(1); expect(screen.getByText('status:')).toBeInTheDocument(); - expect(screen.getByText('"400"')).toBeInTheDocument(); + expect(screen.getByText('400')).toBeInTheDocument(); }); }); diff --git a/public/app/features/query/components/QueryEditorRow.test.ts b/public/app/features/query/components/QueryEditorRow.test.ts index 6117923d66c..34953929ce2 100644 --- a/public/app/features/query/components/QueryEditorRow.test.ts +++ b/public/app/features/query/components/QueryEditorRow.test.ts @@ -75,6 +75,21 @@ describe('filterPanelDataToQuery', () => { expect(panelDataA?.state).toBe(LoadingState.Done); }); + it('should return error for query that returns no data, but another query does return data', () => { + const withError = { + ...data, + state: LoadingState.Error, + error: { + message: 'Sad', + refId: 'Q', + }, + }; + + const panelDataB = filterPanelDataToQuery(withError, 'Q'); + expect(panelDataB?.series.length).toBe(0); + expect(panelDataB?.error?.refId).toBe('Q'); + }); + it('should not set the state to done if the frame is loading and has no errors', () => { const loadingData: PanelData = { state: LoadingState.Loading, diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx index fb7a32444c3..d7a6f156d2e 100644 --- a/public/app/features/query/components/QueryEditorRow.tsx +++ b/public/app/features/query/components/QueryEditorRow.tsx @@ -32,6 +32,7 @@ import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { DashboardModel } from 'app/features/dashboard/state/DashboardModel'; import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp'; import { RowActionComponents } from './QueryActionComponent'; +import { QueryErrorAlert } from './QueryErrorAlert'; interface Props { data: PanelData; @@ -382,7 +383,7 @@ export class QueryEditorRow extends PureComponent extends PureComponent + {data?.error && data.error.refId === query.refId && } {visualization} @@ -463,16 +465,12 @@ export interface AngularQueryComponentScope { export function filterPanelDataToQuery(data: PanelData, refId: string): PanelData | undefined { const series = data.series.filter((series) => series.refId === refId); - // No matching series - if (!series.length) { - // If there was an error with no data, pass it to the QueryEditors - if (data.error && !data.series.length) { - return { - ...data, - state: LoadingState.Error, - }; - } - return undefined; + // If there was an error with no data, pass it to the QueryEditors + if (data.error && !data.series.length) { + return { + ...data, + state: LoadingState.Error, + }; } // Only say this is an error if the error links to the query diff --git a/public/app/features/query/components/QueryErrorAlert.tsx b/public/app/features/query/components/QueryErrorAlert.tsx new file mode 100644 index 00000000000..c7cfd73909b --- /dev/null +++ b/public/app/features/query/components/QueryErrorAlert.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { DataQueryError, GrafanaTheme2 } from '@grafana/data'; +import { Icon, useStyles2 } from '@grafana/ui'; +import { css } from '@emotion/css'; + +export interface Props { + error: DataQueryError; +} + +export function QueryErrorAlert({ error }: Props) { + const styles = useStyles2(getStyles); + + const message = error?.message ?? error?.data?.message ?? 'Query error'; + + return ( +
+
+ +
+
{message}
+
+ ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + wrapper: css({ + marginTop: theme.spacing(0.5), + background: theme.colors.background.secondary, + display: 'flex', + }), + icon: css({ + background: theme.colors.error.main, + color: theme.colors.error.contrastText, + padding: theme.spacing(1), + }), + message: css({ + fontSize: theme.typography.bodySmall.fontSize, + fontFamily: theme.typography.fontFamilyMonospace, + padding: theme.spacing(1), + }), +}); diff --git a/public/app/plugins/datasource/loki/datasource.ts b/public/app/plugins/datasource/loki/datasource.ts index 41a71d3709f..9de4a813dbc 100644 --- a/public/app/plugins/datasource/loki/datasource.ts +++ b/public/app/plugins/datasource/loki/datasource.ts @@ -735,10 +735,13 @@ export class LokiDatasource } processError(err: FetchError, target: LokiQuery) { - let error = cloneDeep(err); - if (err.data.message.includes('escape') && target.expr.includes('\\')) { + let error: DataQueryError = cloneDeep(err); + error.refId = target.refId; + + if (error.data && err.data.message.includes('escape') && target.expr.includes('\\')) { error.data.message = `Error: ${err.data.message}. Make sure that all special characters are escaped with \\. For more information on escaping of special characters visit LogQL documentation at https://grafana.com/docs/loki/latest/logql/.`; } + return error; } From e50bd5cac8e714f0ff6694ca9473c3c3b60e5291 Mon Sep 17 00:00:00 2001 From: Ieva Date: Thu, 14 Apr 2022 12:40:15 +0100 Subject: [PATCH 02/40] Access control: expose SA frontend to users with the right permissions (#47727) * expose frontend to users with permissions * cover the ui endpoints * fix permissions --- pkg/api/accesscontrol.go | 7 ++++ pkg/api/api.go | 7 ++-- pkg/api/index.go | 9 +++-- .../ServiceAccountsListItem.tsx | 37 +++++++++++-------- .../features/serviceaccounts/state/actions.ts | 18 ++++++--- public/app/routes/routes.tsx | 4 +- public/app/types/accessControl.ts | 3 ++ 7 files changed, 57 insertions(+), 28 deletions(-) diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index 6f08cfd6382..dffe67e03a8 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -5,6 +5,7 @@ import ( ac "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" + "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/setting" ) @@ -462,6 +463,12 @@ var teamsEditAccessEvaluator = ac.EvalAll( ), ) +// apiKeyAccessEvaluator is used to protect the "Configuration > API keys" page access +var apiKeyAccessEvaluator = ac.EvalPermission(ac.ActionAPIKeyRead) + +// serviceAccountAccessEvaluator is used to protect the "Configuration > Service accounts" page access +var serviceAccountAccessEvaluator = ac.EvalPermission(serviceaccounts.ActionRead) + // Metadata helpers // getAccessControlMetadata returns the accesscontrol metadata associated with a given resource func (hs *HTTPServer) getAccessControlMetadata(c *models.ReqContext, diff --git a/pkg/api/api.go b/pkg/api/api.go index 4836363dfaf..a7de6111fab 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -14,6 +14,7 @@ import ( "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/serviceaccounts" "github.com/grafana/grafana/pkg/web" ) @@ -62,9 +63,9 @@ func (hs *HTTPServer) registerRoutes() { r.Get("/org/teams", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsRead)), hs.Index) r.Get("/org/teams/edit/*", authorize(reqCanAccessTeams, teamsEditAccessEvaluator), hs.Index) r.Get("/org/teams/new", authorize(reqCanAccessTeams, ac.EvalPermission(ac.ActionTeamsCreate)), hs.Index) - r.Get("/org/serviceaccounts", middleware.ReqOrgAdmin, hs.Index) - r.Get("/org/serviceaccounts/:serviceAccountId", middleware.ReqOrgAdmin, hs.Index) - r.Get("/org/apikeys/", reqOrgAdmin, hs.Index) + r.Get("/org/serviceaccounts", authorize(reqOrgAdmin, ac.EvalPermission(serviceaccounts.ActionRead)), hs.Index) + r.Get("/org/serviceaccounts/:serviceAccountId", authorize(reqOrgAdmin, ac.EvalPermission(serviceaccounts.ActionRead)), hs.Index) + r.Get("/org/apikeys/", authorize(reqOrgAdmin, ac.EvalPermission(ac.ActionAPIKeyRead)), hs.Index) r.Get("/dashboard/import/", reqSignedIn, hs.Index) r.Get("/configuration", reqGrafanaAdmin, hs.Index) r.Get("/admin", reqGrafanaAdmin, hs.Index) diff --git a/pkg/api/index.go b/pkg/api/index.go index 3167a3012e0..6f1bf97ee54 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -149,8 +149,11 @@ func (hs *HTTPServer) getAppLinks(c *models.ReqContext) ([]*dtos.NavLink, error) } func enableServiceAccount(hs *HTTPServer, c *models.ReqContext) bool { - return (c.OrgRole == models.ROLE_ADMIN || (hs.Cfg.EditorsCanAdmin && c.OrgRole == models.ROLE_EDITOR)) && - hs.Features.IsEnabled(featuremgmt.FlagServiceAccounts) + if !hs.Features.IsEnabled(featuremgmt.FlagServiceAccounts) { + return false + } + hasAccess := ac.HasAccess(hs.AccessControl, c) + return hasAccess(ac.ReqOrgAdmin, serviceAccountAccessEvaluator) } func (hs *HTTPServer) ReqCanAdminTeams(c *models.ReqContext) bool { @@ -291,7 +294,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto }) } - if c.OrgRole == models.ROLE_ADMIN { + if hasAccess(ac.ReqOrgAdmin, apiKeyAccessEvaluator) { configNodes = append(configNodes, &dtos.NavLink{ Text: "API keys", Id: "apikeys", diff --git a/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx b/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx index e5d875a9be6..d0de3b2e9e9 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx @@ -28,7 +28,10 @@ const ServiceAccountListItem = memo( const editUrl = `org/serviceaccounts/${serviceAccount.id}`; const styles = useStyles2(getStyles); const canUpdateRole = contextSrv.hasPermissionInMetadata(AccessControlAction.ServiceAccountsWrite, serviceAccount); - const rolePickerDisabled = !canUpdateRole; + const displayRolePicker = + contextSrv.hasPermission(AccessControlAction.ActionRolesList) && + contextSrv.hasPermission(AccessControlAction.ActionUserRolesList); + const enableRolePicker = contextSrv.hasPermission(AccessControlAction.OrgUsersRoleUpdate) && canUpdateRole; return ( @@ -61,26 +64,30 @@ const ServiceAccountListItem = memo( {serviceAccount.login} - - {contextSrv.licensedAccessControlEnabled() ? ( - onRoleChange(newRole, serviceAccount)} - roleOptions={roleOptions} - builtInRoles={builtInRoles} - disabled={rolePickerDisabled} - /> - ) : ( + {contextSrv.licensedAccessControlEnabled() ? ( + displayRolePicker && ( + + onRoleChange(newRole, serviceAccount)} + roleOptions={roleOptions} + builtInRoles={builtInRoles} + disabled={!enableRolePicker} + /> + + ) + ) : ( + onRoleChange(newRole, serviceAccount)} /> - )} - + + )} { return async (dispatch) => { try { - const options = await fetchRoleOptions(); - dispatch(acOptionsLoaded(options)); - const builtInRoles = await fetchBuiltinRoles(); - dispatch(builtInRolesLoaded(builtInRoles)); + if (contextSrv.licensedAccessControlEnabled() && contextSrv.hasPermission(AccessControlAction.ActionRolesList)) { + const options = await fetchRoleOptions(); + dispatch(acOptionsLoaded(options)); + } + if ( + contextSrv.licensedAccessControlEnabled() && + contextSrv.hasPermission(AccessControlAction.ActionBuiltinRolesList) + ) { + const builtInRoles = await fetchBuiltinRoles(); + dispatch(builtInRolesLoaded(builtInRoles)); + } } catch (error) { console.error(error); } diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx index bb8964345ca..b749467036d 100644 --- a/public/app/routes/routes.tsx +++ b/public/app/routes/routes.tsx @@ -195,14 +195,14 @@ export function getAppRoutes(): RouteDescriptor[] { }, { path: '/org/apikeys', - roles: () => ['Editor', 'Admin'], + roles: () => contextSrv.evaluatePermission(() => ['Admin'], [AccessControlAction.ActionAPIKeysRead]), component: SafeDynamicImport( () => import(/* webpackChunkName: "ApiKeysPage" */ 'app/features/api-keys/ApiKeysPage') ), }, { path: '/org/serviceaccounts', - roles: () => ['Editor', 'Admin'], + roles: () => contextSrv.evaluatePermission(() => ['Admin'], [AccessControlAction.ServiceAccountsRead]), component: SafeDynamicImport( () => import(/* webpackChunkName: "ServiceAccountsPage" */ 'app/features/serviceaccounts/ServiceAccountsListPage') diff --git a/public/app/types/accessControl.ts b/public/app/types/accessControl.ts index b2fd9b0ca2f..969985ba21a 100644 --- a/public/app/types/accessControl.ts +++ b/public/app/types/accessControl.ts @@ -23,6 +23,7 @@ export enum AccessControlAction { UsersQuotasList = 'users.quotas:list', UsersQuotasUpdate = 'users.quotas:update', + ServiceAccountsRead = 'serviceaccounts:read', ServiceAccountsCreate = 'serviceaccounts:create', ServiceAccountsWrite = 'serviceaccounts:write', ServiceAccountsDelete = 'serviceaccounts:delete', @@ -107,6 +108,8 @@ export enum AccessControlAction { // External alerting notifications actions. AlertingNotificationsExternalWrite = 'alert.notifications.external:write', AlertingNotificationsExternalRead = 'alert.notifications.external:read', + + ActionAPIKeysRead = 'apikeys:read', } export interface Role { From 90892050d094dcaef81211ae54b1eebb2b6bc15d Mon Sep 17 00:00:00 2001 From: Dimitris Sotirakis Date: Thu, 14 Apr 2022 14:56:14 +0300 Subject: [PATCH 03/40] CI: Remove `initialize` step (#47622) * Remove initialize step from pr.star * Remove initialize step from main.star * Remove initialize step from release.star * Reorder dependencies * Re-add identify runner step * Add enterprise steps * Remove is_downstream variable from starlark logic (#47715) * Make init steps depend on init-enterprise for enterprise pipelines * Exclude shellcheck from enterprise pipelines * Add init steps to enterprise integration tests pipeline * Add dependency for windows enterprise step * Update grabpl version --- .drone.yml | 699 ++++++++++++++++----------- scripts/drone/pipelines/docs.star | 17 +- scripts/drone/pipelines/main.star | 52 +- scripts/drone/pipelines/pr.star | 20 +- scripts/drone/pipelines/release.star | 73 ++- scripts/drone/steps/lib.star | 363 +++++++------- 6 files changed, 680 insertions(+), 544 deletions(-) diff --git a/.drone.yml b/.drone.yml index 4fc307c5c50..5ef3316da98 100644 --- a/.drone.yml +++ b/.drone.yml @@ -9,22 +9,32 @@ platform: os: linux services: [] steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl - commands: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner - commands: - - make gen-go + - mkdir -p bin + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - chmod +x bin/grabpl + image: byrnedo/alpine-curl:0.1.8 + name: grabpl +- commands: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - make gen-go + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install - commands: - ./bin/grabpl verify-drone depends_on: @@ -41,20 +51,18 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - depends_on: - - initialize image: grafana/build-container:1.5.3 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - - initialize + - grabpl image: grafana/build-container:1.5.3 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss depends_on: - - initialize + - wire-install environment: CGO_ENABLED: "1" image: grafana/build-container:1.5.3 @@ -65,7 +73,7 @@ steps: - yarn run i18n:compile - yarn run typecheck depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -73,19 +81,19 @@ steps: - commands: - ./bin/grabpl test-backend --edition oss depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -112,34 +120,46 @@ platform: os: linux services: [] steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl - commands: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner - commands: - - make gen-go + - mkdir -p bin + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - chmod +x bin/grabpl + image: byrnedo/alpine-curl:0.1.8 + name: grabpl +- commands: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - make gen-go + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} --variants linux-amd64,linux-amd64-musl,darwin-amd64,windows-amd64,armv6 depends_on: - - initialize + - gen-version + - wire-install environment: {} image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -147,7 +167,8 @@ steps: - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -155,7 +176,8 @@ steps: - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss depends_on: - - initialize + - gen-version + - yarn-install environment: null image: grafana/build-container:1.5.3 name: build-plugins @@ -356,10 +378,14 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl +- commands: + - echo $DRONE_RUNNER_NAME + image: alpine:3.15 + name: identify-runner - commands: - apt-get update - apt-get install -yq postgresql-client @@ -420,7 +446,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -429,11 +455,17 @@ steps: image: alpine:3.15 name: identify-runner - commands: - - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install - commands: - |- echo -e "unknwon @@ -444,14 +476,12 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - depends_on: - - initialize image: grafana/build-container:1.5.3 name: codespell - commands: - yarn run prettier:checkDocs depends_on: - - initialize + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -459,7 +489,8 @@ steps: - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -503,7 +534,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -512,11 +543,17 @@ steps: image: alpine:3.15 name: identify-runner - commands: - - make gen-go - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install - commands: - |- echo -e "unknwon @@ -527,14 +564,12 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - depends_on: - - initialize image: grafana/build-container:1.5.3 name: codespell - commands: - yarn run prettier:checkDocs depends_on: - - initialize + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -542,7 +577,8 @@ steps: - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -581,22 +617,32 @@ platform: os: linux services: [] steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl - commands: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner - commands: - - make gen-go + - mkdir -p bin + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - chmod +x bin/grabpl + image: byrnedo/alpine-curl:0.1.8 + name: grabpl +- commands: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - make gen-go + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install - commands: - ./bin/grabpl verify-drone depends_on: @@ -613,20 +659,18 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - depends_on: - - initialize image: grafana/build-container:1.5.3 name: codespell - commands: - ./bin/grabpl shellcheck depends_on: - - initialize + - grabpl image: grafana/build-container:1.5.3 name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss depends_on: - - initialize + - wire-install environment: CGO_ENABLED: "1" image: grafana/build-container:1.5.3 @@ -637,7 +681,7 @@ steps: - yarn run i18n:compile - yarn run typecheck depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -645,19 +689,19 @@ steps: - commands: - ./bin/grabpl test-backend --edition oss depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -682,22 +726,32 @@ platform: os: linux services: [] steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl - commands: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner - commands: - - make gen-go + - mkdir -p bin + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - chmod +x bin/grabpl + image: byrnedo/alpine-curl:0.1.8 + name: grabpl +- commands: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - make gen-go + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install - commands: - git clone "https://$${GITHUB_TOKEN}@github.com/grafana/grafana-enterprise.git" --depth=1 @@ -739,14 +793,16 @@ steps: - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - wire-install environment: {} image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -754,7 +810,8 @@ steps: - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -762,7 +819,8 @@ steps: - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --sign --signing-admin depends_on: - - initialize + - gen-version + - yarn-install environment: GRAFANA_API_KEY: from_secret: grafana_api_key @@ -1109,10 +1167,14 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl +- commands: + - echo $DRONE_RUNNER_NAME + image: alpine:3.15 + name: identify-runner - commands: - apt-get update - apt-get install -yq postgresql-client @@ -1177,10 +1239,10 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 - name: initialize + name: windows-init - commands: - $$gcpKey = $$env:GCP_KEY - '[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) @@ -1196,7 +1258,7 @@ steps: - gsutil cp $$fname gs://grafana-downloads/oss/main/ - gsutil cp "$$fname.sha256" gs://grafana-downloads/oss/main/ depends_on: - - initialize + - windows-init environment: GCP_KEY: from_secret: gcp_key @@ -1264,7 +1326,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1272,10 +1334,6 @@ steps: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner -- commands: - - make gen-go - image: grafana/build-container:1.5.3 - name: initialize - commands: - ./bin/grabpl store-packages --edition oss --gcp-key /tmp/gcpkey.json --build-id ${DRONE_BUILD_NUMBER} @@ -1346,28 +1404,38 @@ platform: os: linux services: [] steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl - commands: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner - commands: - - make gen-go - - ./bin/grabpl verify-version ${DRONE_TAG} + - mkdir -p bin + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - chmod +x bin/grabpl + image: byrnedo/alpine-curl:0.1.8 + name: grabpl +- commands: - ./bin/grabpl gen-version ${DRONE_TAG} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - make gen-go + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} ${DRONE_TAG} depends_on: - - initialize + - gen-version + - wire-install environment: GITHUB_TOKEN: from_secret: github_token @@ -1377,7 +1445,8 @@ steps: - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --edition oss ${DRONE_TAG} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -1386,7 +1455,8 @@ steps: - ./bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN} --edition oss ${DRONE_TAG} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -1394,7 +1464,8 @@ steps: - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --sign --signing-admin depends_on: - - initialize + - gen-version + - yarn-install environment: GRAFANA_API_KEY: from_secret: grafana_api_key @@ -1648,23 +1719,38 @@ platform: os: linux services: [] steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl - commands: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner - commands: - - make gen-go - - ./bin/grabpl verify-version ${DRONE_TAG} + - mkdir -p bin + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - chmod +x bin/grabpl + image: byrnedo/alpine-curl:0.1.8 + name: grabpl +- commands: - ./bin/grabpl gen-version ${DRONE_TAG} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - make gen-go + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install +- commands: + - ./bin/grabpl shellcheck + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: shellcheck - commands: - |- echo -e "unknwon @@ -1675,20 +1761,12 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - depends_on: - - initialize image: grafana/build-container:1.5.3 name: codespell -- commands: - - ./bin/grabpl shellcheck - depends_on: - - initialize - image: grafana/build-container:1.5.3 - name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss depends_on: - - initialize + - wire-install environment: CGO_ENABLED: "1" image: grafana/build-container:1.5.3 @@ -1699,7 +1777,7 @@ steps: - yarn run i18n:compile - yarn run typecheck depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -1707,19 +1785,19 @@ steps: - commands: - ./bin/grabpl test-backend --edition oss depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -1771,7 +1849,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1779,13 +1857,6 @@ steps: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner -- commands: - - make gen-go - - ./bin/grabpl verify-version ${DRONE_TAG} - - ./bin/grabpl gen-version ${DRONE_TAG} - - yarn install --immutable - image: grafana/build-container:1.5.3 - name: initialize - commands: - apt-get update - apt-get install -yq postgresql-client @@ -1855,10 +1926,10 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 - name: initialize + name: windows-init - commands: - $$gcpKey = $$env:GCP_KEY - '[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) @@ -1873,7 +1944,7 @@ steps: - gsutil cp $$fname gs://%PRERELEASE_BUCKET%/artifacts/downloads/${DRONE_TAG}/oss/release/ - gsutil cp "$$fname.sha256" gs://%PRERELEASE_BUCKET%/artifacts/downloads/${DRONE_TAG}/oss/release/ depends_on: - - initialize + - windows-init environment: GCP_KEY: from_secret: gcp_key @@ -1914,7 +1985,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1940,22 +2011,37 @@ steps: - mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json - mkdir bin - mv /tmp/grabpl bin/ - - make gen-go - - ./bin/grabpl verify-version ${DRONE_TAG} - - ./bin/grabpl gen-version ${DRONE_TAG} - - yarn install --immutable depends_on: - clone-enterprise environment: GITHUB_TOKEN: from_secret: github_token image: grafana/build-container:1.5.3 - name: initialize + name: init-enterprise +- commands: + - make gen-go + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: yarn-install +- commands: + - ./bin/grabpl gen-version ${DRONE_TAG} + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: gen-version - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} ${DRONE_TAG} depends_on: - - initialize + - gen-version + - wire-install environment: GITHUB_TOKEN: from_secret: github_token @@ -1965,7 +2051,8 @@ steps: - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --edition enterprise ${DRONE_TAG} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -1974,7 +2061,8 @@ steps: - ./bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN} --edition enterprise ${DRONE_TAG} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -1982,7 +2070,8 @@ steps: - commands: - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --sign --signing-admin depends_on: - - initialize + - gen-version + - yarn-install environment: GRAFANA_API_KEY: from_secret: grafana_api_key @@ -2011,7 +2100,8 @@ steps: - ./bin/grabpl build-backend --jobs 8 --edition enterprise2 --github-token $${GITHUB_TOKEN} ${DRONE_TAG} depends_on: - - initialize + - gen-version + - wire-install environment: GITHUB_TOKEN: from_secret: github_token @@ -2268,7 +2358,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2294,17 +2384,31 @@ steps: - mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json - mkdir bin - mv /tmp/grabpl bin/ - - make gen-go - - ./bin/grabpl verify-version ${DRONE_TAG} - - ./bin/grabpl gen-version ${DRONE_TAG} - - yarn install --immutable depends_on: - clone-enterprise environment: GITHUB_TOKEN: from_secret: github_token image: grafana/build-container:1.5.3 - name: initialize + name: init-enterprise +- commands: + - make gen-go + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: yarn-install +- commands: + - ./bin/grabpl gen-version ${DRONE_TAG} + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: gen-version - commands: - |- echo -e "unknwon @@ -2315,20 +2419,12 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - depends_on: - - initialize image: grafana/build-container:1.5.3 name: codespell -- commands: - - ./bin/grabpl shellcheck - depends_on: - - initialize - image: grafana/build-container:1.5.3 - name: shellcheck - commands: - ./bin/grabpl lint-backend --edition enterprise depends_on: - - initialize + - wire-install environment: CGO_ENABLED: "1" image: grafana/build-container:1.5.3 @@ -2339,7 +2435,7 @@ steps: - yarn run i18n:compile - yarn run typecheck depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -2347,19 +2443,19 @@ steps: - commands: - ./bin/grabpl test-backend --edition enterprise depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition enterprise depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -2367,7 +2463,7 @@ steps: - commands: - ./bin/grabpl lint-backend --edition enterprise2 depends_on: - - initialize + - wire-install environment: CGO_ENABLED: "1" image: grafana/build-container:1.5.3 @@ -2375,13 +2471,13 @@ steps: - commands: - ./bin/grabpl test-backend --edition enterprise2 depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-enterprise2 - commands: - ./bin/grabpl integration-tests --edition enterprise2 depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-integration-enterprise2 trigger: @@ -2441,7 +2537,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2467,17 +2563,13 @@ steps: - mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json - mkdir bin - mv /tmp/grabpl bin/ - - make gen-go - - ./bin/grabpl verify-version ${DRONE_TAG} - - ./bin/grabpl gen-version ${DRONE_TAG} - - yarn install --immutable depends_on: - clone-enterprise environment: GITHUB_TOKEN: from_secret: github_token image: grafana/build-container:1.5.3 - name: initialize + name: init-enterprise - commands: - apt-get update - apt-get install -yq postgresql-client @@ -2486,7 +2578,7 @@ steps: - go clean -testcache - ./bin/grabpl integration-tests --database postgres depends_on: - - initialize + - init-enterprise environment: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest @@ -2502,7 +2594,7 @@ steps: - go clean -testcache - ./bin/grabpl integration-tests --database mysql depends_on: - - initialize + - init-enterprise environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql @@ -2512,7 +2604,7 @@ steps: - dockerize -wait tcp://redis:6379/0 -timeout 120s - ./bin/grabpl integration-tests depends_on: - - initialize + - init-enterprise environment: REDIS_URL: redis://redis:6379/0 image: grafana/build-container:1.5.3 @@ -2521,7 +2613,7 @@ steps: - dockerize -wait tcp://memcached:11211 -timeout 120s - ./bin/grabpl integration-tests depends_on: - - initialize + - init-enterprise environment: MEMCACHED_HOSTS: memcached:11211 image: grafana/build-container:1.5.3 @@ -2569,7 +2661,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -2592,7 +2684,7 @@ steps: GITHUB_TOKEN: from_secret: github_token image: grafana/ci-wix:0.1.1 - name: initialize + name: windows-init - commands: - $$gcpKey = $$env:GCP_KEY - '[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) @@ -2607,7 +2699,7 @@ steps: - gsutil cp $$fname gs://%PRERELEASE_BUCKET%/artifacts/downloads/${DRONE_TAG}/enterprise/release/ - gsutil cp "$$fname.sha256" gs://%PRERELEASE_BUCKET%/artifacts/downloads/${DRONE_TAG}/enterprise/release/ depends_on: - - initialize + - windows-init environment: GCP_KEY: from_secret: gcp_key @@ -2644,7 +2736,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2722,7 +2814,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2783,7 +2875,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2862,7 +2954,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2924,7 +3016,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2962,7 +3054,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2998,27 +3090,16 @@ platform: os: linux services: [] steps: -- commands: - - echo $DRONE_RUNNER_NAME - image: alpine:3.15 - name: identify-runner -- commands: - - make gen-go - - ./bin/grabpl verify-version ${DRONE_TAG} - - ./bin/grabpl gen-version ${DRONE_TAG} - - yarn install --immutable - image: grafana/build-container:1.5.3 - name: initialize - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl - commands: - ./bin/grabpl artifacts npm retrieve --tag v${TAG} depends_on: - - initialize + - grabpl environment: GCP_KEY: from_secret: gcp_key @@ -3061,7 +3142,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3122,33 +3203,45 @@ platform: os: linux services: [] steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl - commands: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner - commands: - - make gen-go + - mkdir -p bin + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - chmod +x bin/grabpl + image: byrnedo/alpine-curl:0.1.8 + name: grabpl +- commands: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - make gen-go + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install - commands: - ./bin/grabpl build-backend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - wire-install environment: {} image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -3156,7 +3249,8 @@ steps: - commands: - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -3164,7 +3258,8 @@ steps: - commands: - ./bin/grabpl build-plugins --jobs 8 --edition oss --sign --signing-admin depends_on: - - initialize + - gen-version + - yarn-install environment: GRAFANA_API_KEY: from_secret: grafana_api_key @@ -3387,22 +3482,38 @@ platform: os: linux services: [] steps: -- commands: - - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl - - chmod +x bin/grabpl - image: byrnedo/alpine-curl:0.1.8 - name: grabpl - commands: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner - commands: - - make gen-go + - mkdir -p bin + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - chmod +x bin/grabpl + image: byrnedo/alpine-curl:0.1.8 + name: grabpl +- commands: - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable + depends_on: + - grabpl image: grafana/build-container:1.5.3 - name: initialize + name: gen-version +- commands: + - make gen-go + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: yarn-install +- commands: + - ./bin/grabpl shellcheck + depends_on: + - grabpl + image: grafana/build-container:1.5.3 + name: shellcheck - commands: - |- echo -e "unknwon @@ -3413,20 +3524,12 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - depends_on: - - initialize image: grafana/build-container:1.5.3 name: codespell -- commands: - - ./bin/grabpl shellcheck - depends_on: - - initialize - image: grafana/build-container:1.5.3 - name: shellcheck - commands: - ./bin/grabpl lint-backend --edition oss depends_on: - - initialize + - wire-install environment: CGO_ENABLED: "1" image: grafana/build-container:1.5.3 @@ -3437,7 +3540,7 @@ steps: - yarn run i18n:compile - yarn run typecheck depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -3445,19 +3548,19 @@ steps: - commands: - ./bin/grabpl test-backend --edition oss depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition oss depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -3503,7 +3606,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3511,12 +3614,6 @@ steps: - echo $DRONE_RUNNER_NAME image: alpine:3.15 name: identify-runner -- commands: - - make gen-go - - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable - image: grafana/build-container:1.5.3 - name: initialize - commands: - apt-get update - apt-get install -yq postgresql-client @@ -3580,10 +3677,10 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 - name: initialize + name: windows-init - commands: - $$gcpKey = $$env:GCP_KEY - '[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) @@ -3593,7 +3690,7 @@ steps: - rm gcpkey.json - cp C:\App\nssm-2.24.zip . depends_on: - - initialize + - windows-init environment: GCP_KEY: from_secret: gcp_key @@ -3628,7 +3725,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3653,25 +3750,42 @@ steps: - mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json - mkdir bin - mv /tmp/grabpl bin/ - - make gen-go - - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable depends_on: - clone-enterprise environment: {} image: grafana/build-container:1.5.3 - name: initialize + name: init-enterprise +- commands: + - make gen-go + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: yarn-install +- commands: + - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: gen-version - commands: - ./bin/grabpl build-backend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - wire-install environment: {} image: grafana/build-container:1.5.3 name: build-backend - commands: - ./bin/grabpl build-frontend --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -3680,7 +3794,8 @@ steps: - ./bin/grabpl build-frontend-packages --jobs 8 --edition enterprise --build-id ${DRONE_BUILD_NUMBER} depends_on: - - initialize + - gen-version + - yarn-install environment: NODE_OPTIONS: --max_old_space_size=8192 image: grafana/build-container:1.5.3 @@ -3688,7 +3803,8 @@ steps: - commands: - ./bin/grabpl build-plugins --jobs 8 --edition enterprise --sign --signing-admin depends_on: - - initialize + - gen-version + - yarn-install environment: GRAFANA_API_KEY: from_secret: grafana_api_key @@ -3717,7 +3833,8 @@ steps: - ./bin/grabpl build-backend --jobs 8 --edition enterprise2 --build-id ${DRONE_BUILD_NUMBER} --variants linux-amd64 depends_on: - - initialize + - gen-version + - wire-install environment: {} image: grafana/build-container:1.5.3 name: build-backend-enterprise2 @@ -3969,7 +4086,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3994,14 +4111,29 @@ steps: - mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json - mkdir bin - mv /tmp/grabpl bin/ - - make gen-go - - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable depends_on: - clone-enterprise environment: {} image: grafana/build-container:1.5.3 - name: initialize + name: init-enterprise +- commands: + - make gen-go + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: wire-install +- commands: + - yarn install --immutable + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: yarn-install +- commands: + - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} + depends_on: + - init-enterprise + image: grafana/build-container:1.5.3 + name: gen-version - commands: - |- echo -e "unknwon @@ -4012,20 +4144,12 @@ steps: wan" > words_to_ignore.txt - codespell -I words_to_ignore.txt docs/ - rm words_to_ignore.txt - depends_on: - - initialize image: grafana/build-container:1.5.3 name: codespell -- commands: - - ./bin/grabpl shellcheck - depends_on: - - initialize - image: grafana/build-container:1.5.3 - name: shellcheck - commands: - ./bin/grabpl lint-backend --edition enterprise depends_on: - - initialize + - wire-install environment: CGO_ENABLED: "1" image: grafana/build-container:1.5.3 @@ -4036,7 +4160,7 @@ steps: - yarn run i18n:compile - yarn run typecheck depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -4044,19 +4168,19 @@ steps: - commands: - ./bin/grabpl test-backend --edition enterprise depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend - commands: - ./bin/grabpl integration-tests --edition enterprise depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-integration - commands: - yarn run ci:test-frontend depends_on: - - initialize + - yarn-install environment: TEST_MAX_WORKERS: 50% image: grafana/build-container:1.5.3 @@ -4064,7 +4188,7 @@ steps: - commands: - ./bin/grabpl lint-backend --edition enterprise2 depends_on: - - initialize + - wire-install environment: CGO_ENABLED: "1" image: grafana/build-container:1.5.3 @@ -4072,13 +4196,13 @@ steps: - commands: - ./bin/grabpl test-backend --edition enterprise2 depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-enterprise2 - commands: - ./bin/grabpl integration-tests --edition enterprise2 depends_on: - - initialize + - wire-install image: grafana/build-container:1.5.3 name: test-backend-integration-enterprise2 trigger: @@ -4132,7 +4256,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4157,14 +4281,11 @@ steps: - mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json - mkdir bin - mv /tmp/grabpl bin/ - - make gen-go - - ./bin/grabpl gen-version --build-id ${DRONE_BUILD_NUMBER} - - yarn install --immutable depends_on: - clone-enterprise environment: {} image: grafana/build-container:1.5.3 - name: initialize + name: init-enterprise - commands: - apt-get update - apt-get install -yq postgresql-client @@ -4173,7 +4294,7 @@ steps: - go clean -testcache - ./bin/grabpl integration-tests --database postgres depends_on: - - initialize + - init-enterprise environment: GRAFANA_TEST_DB: postgres PGPASSWORD: grafanatest @@ -4189,7 +4310,7 @@ steps: - go clean -testcache - ./bin/grabpl integration-tests --database mysql depends_on: - - initialize + - init-enterprise environment: GRAFANA_TEST_DB: mysql MYSQL_HOST: mysql @@ -4199,7 +4320,7 @@ steps: - dockerize -wait tcp://redis:6379/0 -timeout 120s - ./bin/grabpl integration-tests depends_on: - - initialize + - init-enterprise environment: REDIS_URL: redis://redis:6379/0 image: grafana/build-container:1.5.3 @@ -4208,7 +4329,7 @@ steps: - dockerize -wait tcp://memcached:11211 -timeout 120s - ./bin/grabpl integration-tests depends_on: - - initialize + - init-enterprise environment: MEMCACHED_HOSTS: memcached:11211 image: grafana/build-container:1.5.3 @@ -4250,7 +4371,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.34/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -4273,7 +4394,7 @@ steps: GITHUB_TOKEN: from_secret: github_token image: grafana/ci-wix:0.1.1 - name: initialize + name: windows-init - commands: - $$gcpKey = $$env:GCP_KEY - '[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($$gcpKey)) @@ -4283,7 +4404,7 @@ steps: - rm gcpkey.json - cp C:\App\nssm-2.24.zip . depends_on: - - initialize + - windows-init environment: GCP_KEY: from_secret: gcp_key @@ -4447,6 +4568,6 @@ kind: secret name: gcp_upload_artifacts_key --- kind: signature -hmac: 48fc25b65552d02f3d75f3ad1e24a9854c0485bebac8d4e062cfa8605e0a75ba +hmac: 6b2cdb67f637bdd527ba527741fd7d0af7e61b0700487be912821b93d8d16ca1 ... diff --git a/scripts/drone/pipelines/docs.star b/scripts/drone/pipelines/docs.star index fc6ce20cfa7..ababa390740 100644 --- a/scripts/drone/pipelines/docs.star +++ b/scripts/drone/pipelines/docs.star @@ -1,7 +1,9 @@ load( 'scripts/drone/steps/lib.star', 'build_image', - 'initialize_step', + 'yarn_install_step', + 'identify_runner_step', + 'gen_version_step', 'download_grabpl_step', 'lint_frontend_step', 'codespell_step', @@ -25,16 +27,17 @@ load( def docs_pipelines(edition, ver_mode, trigger): - steps = [download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) - - # Insert remaining steps - steps.extend([ + steps = [ + download_grabpl_step(), + identify_runner_step(), + gen_version_step(ver_mode), + yarn_install_step(), codespell_step(), lint_docs(), build_frontend_package_step(edition=edition, ver_mode=ver_mode), build_frontend_docs_step(edition=edition), build_docs_website_step(), - ]) + ] return pipeline( name='{}-docs'.format(ver_mode), edition=edition, trigger=trigger, services=[], steps=steps, @@ -45,7 +48,7 @@ def lint_docs(): 'name': 'lint-docs', 'image': build_image, 'depends_on': [ - 'initialize', + 'yarn-install', ], 'environment': { 'NODE_OPTIONS': '--max_old_space_size=8192', diff --git a/scripts/drone/pipelines/main.star b/scripts/drone/pipelines/main.star index fb3f671cdcb..e86e337593f 100644 --- a/scripts/drone/pipelines/main.star +++ b/scripts/drone/pipelines/main.star @@ -2,7 +2,10 @@ load( 'scripts/drone/steps/lib.star', 'download_grabpl_step', 'build_image', - 'initialize_step', + 'identify_runner_step', + 'gen_version_step', + 'wire_install_step', + 'yarn_install_step', 'lint_drone_step', 'lint_backend_step', 'lint_frontend_step', @@ -67,10 +70,16 @@ load('scripts/drone/vault.star', 'from_secret') ver_mode = 'main' -def get_steps(edition, is_downstream=False): +def get_steps(edition): services = integration_test_services(edition) - publish = edition != 'enterprise' or is_downstream include_enterprise2 = edition == 'enterprise' + init_steps = [ + identify_runner_step(), + download_grabpl_step(), + gen_version_step(ver_mode), + wire_install_step(), + yarn_install_step(), + ] test_steps = [ lint_drone_step(), codespell_step(), @@ -84,9 +93,9 @@ def get_steps(edition, is_downstream=False): build_steps = [ trigger_test_release(), enterprise_downstream_step(edition=edition), - build_backend_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream), - build_frontend_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream), - build_frontend_package_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream), + build_backend_step(edition=edition, ver_mode=ver_mode), + build_frontend_step(edition=edition, ver_mode=ver_mode), + build_frontend_package_step(edition=edition, ver_mode=ver_mode), build_plugins_step(edition=edition, sign=True), validate_scuemata_step(), ensure_cuetsified_step(), @@ -106,12 +115,12 @@ def get_steps(edition, is_downstream=False): test_backend_integration_step(edition=edition2), ]) build_steps.extend([ - build_backend_step(edition=edition2, ver_mode=ver_mode, variants=['linux-amd64'], is_downstream=is_downstream), + build_backend_step(edition=edition2, ver_mode=ver_mode, variants=['linux-amd64']), ]) # Insert remaining steps build_steps.extend([ - package_step(edition=edition, ver_mode=ver_mode, include_enterprise2=include_enterprise2, is_downstream=is_downstream), + package_step(edition=edition, ver_mode=ver_mode, include_enterprise2=include_enterprise2), grafana_server_step(edition=edition), e2e_tests_step('dashboards-suite', edition=edition), e2e_tests_step('smoke-tests-suite', edition=edition), @@ -134,27 +143,27 @@ def get_steps(edition, is_downstream=False): build_steps.extend([ release_canary_npm_packages_step(edition, trigger=trigger_oss), - upload_packages_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream, trigger=trigger_oss), + upload_packages_step(edition=edition, ver_mode=ver_mode, trigger=trigger_oss), upload_cdn_step(edition=edition, ver_mode=ver_mode, trigger=trigger_oss) ]) if include_enterprise2: edition2 = 'enterprise2' build_steps.extend([ - package_step(edition=edition2, ver_mode=ver_mode, include_enterprise2=include_enterprise2, variants=['linux-amd64'], is_downstream=is_downstream), - upload_packages_step(edition=edition2, ver_mode=ver_mode, is_downstream=is_downstream), + package_step(edition=edition2, ver_mode=ver_mode, include_enterprise2=include_enterprise2, variants=['linux-amd64']), + upload_packages_step(edition=edition2, ver_mode=ver_mode), upload_cdn_step(edition=edition2, ver_mode=ver_mode) ]) - windows_steps = get_windows_steps(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream) - if edition == 'enterprise' and not is_downstream: + windows_steps = get_windows_steps(edition=edition, ver_mode=ver_mode) + if edition == 'enterprise': store_steps = [] else: store_steps = [ - store_packages_step(edition=edition, ver_mode=ver_mode, is_downstream=is_downstream), + store_packages_step(edition=edition, ver_mode=ver_mode), ] - return test_steps, build_steps, integration_test_steps, windows_steps, store_steps + return init_steps, test_steps, build_steps, integration_test_steps, windows_steps, store_steps def trigger_test_release(): return { @@ -210,7 +219,7 @@ def main_pipelines(edition): ], }, } - test_steps, build_steps, integration_test_steps, windows_steps, store_steps = get_steps(edition=edition) + init_steps, test_steps, build_steps, integration_test_steps, windows_steps, store_steps = get_steps(edition=edition) if edition == 'enterprise': services.append(ldap_service()) @@ -218,27 +227,26 @@ def main_pipelines(edition): pipelines = [docs_pipelines(edition, ver_mode, trigger), pipeline( name='main-test', edition=edition, trigger=trigger, services=[], - steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) + test_steps, + steps=init_steps + test_steps, volumes=[], ), pipeline( name='main-build-e2e-publish', edition=edition, trigger=trigger, services=[], - steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) + build_steps, + steps=init_steps + build_steps, volumes=volumes, ), pipeline( name='main-integration-tests', edition=edition, trigger=trigger, services=services, - steps=[download_grabpl_step()] + integration_test_steps, + steps=[download_grabpl_step(), identify_runner_step(),] + integration_test_steps, volumes=volumes, ), pipeline( name='windows-main', edition=edition, trigger=dict(trigger, repo=['grafana/grafana']), - steps=initialize_step(edition, platform='windows', ver_mode=ver_mode) + windows_steps, + steps=[identify_runner_step('windows')] + windows_steps, depends_on=['main-test', 'main-build-e2e-publish', 'main-integration-tests'], platform='windows', ), notify_pipeline( name='notify-drone-changes', slack_channel='slack-webhooks-test', trigger=drone_change_trigger, template=drone_change_template, secret='drone-changes-webhook', ), pipeline( name='publish-main', edition=edition, trigger=dict(trigger, repo=['grafana/grafana']), - steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode, - install_deps=False) + store_steps, + steps=[download_grabpl_step(), identify_runner_step(),] + store_steps, depends_on=['main-test', 'main-build-e2e-publish', 'main-integration-tests', 'windows-main', ], ), notify_pipeline( name='notify-main', slack_channel='grafana-ci-notifications', trigger=dict(trigger, status=['failure']), diff --git a/scripts/drone/pipelines/pr.star b/scripts/drone/pipelines/pr.star index 1f768f1f5a2..d14425cc84e 100644 --- a/scripts/drone/pipelines/pr.star +++ b/scripts/drone/pipelines/pr.star @@ -1,7 +1,10 @@ load( 'scripts/drone/steps/lib.star', 'download_grabpl_step', - 'initialize_step', + 'gen_version_step', + 'yarn_install_step', + 'wire_install_step', + 'identify_runner_step', 'lint_drone_step', 'lint_backend_step', 'lint_frontend_step', @@ -59,6 +62,13 @@ def pr_pipelines(edition): volumes = integration_test_services_volumes() variants = ['linux-amd64', 'linux-amd64-musl', 'darwin-amd64', 'windows-amd64', 'armv6',] include_enterprise2 = edition == 'enterprise' + init_steps = [ + identify_runner_step(), + download_grabpl_step(), + gen_version_step(ver_mode), + wire_install_step(), + yarn_install_step(), + ] test_steps = [ lint_drone_step(), codespell_step(), @@ -132,14 +142,12 @@ def pr_pipelines(edition): return [ pipeline( - name='pr-test', edition=edition, trigger=trigger, services=[], steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) - + test_steps, + name='pr-test', edition=edition, trigger=trigger, services=[], steps=init_steps + test_steps, ), pipeline( - name='pr-build-e2e', edition=edition, trigger=trigger, services=[], steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) - + build_steps, + name='pr-build-e2e', edition=edition, trigger=trigger, services=[], steps=init_steps + build_steps, ), pipeline( name='pr-integration-tests', edition=edition, trigger=trigger, services=services, - steps=[download_grabpl_step()] + integration_test_steps, + steps=[download_grabpl_step(), identify_runner_step(),] + integration_test_steps, volumes=volumes, ), docs_pipelines(edition, ver_mode, trigger_docs()) ] diff --git a/scripts/drone/pipelines/release.star b/scripts/drone/pipelines/release.star index 0e10c9892fa..cb7a8e9cb6b 100644 --- a/scripts/drone/pipelines/release.star +++ b/scripts/drone/pipelines/release.star @@ -1,10 +1,15 @@ load( 'scripts/drone/steps/lib.star', 'disable_tests', + 'clone_enterprise_step', 'download_grabpl_step', - 'initialize_step', + 'gen_version_step', + 'yarn_install_step', + 'wire_install_step', + 'init_enterprise_step', 'lint_drone_step', 'build_image', + 'identify_runner_step', 'publish_image', 'lint_backend_step', 'lint_frontend_step', @@ -77,7 +82,7 @@ def retrieve_npm_packages_step(): 'name': 'retrieve-npm-packages', 'image': publish_image, 'depends_on': [ - 'initialize', + 'grabpl', ], 'environment': { 'GCP_KEY': from_secret('gcp_key'), @@ -150,16 +155,26 @@ def get_steps(edition, ver_mode): should_upload = should_publish or ver_mode in ('release-branch',) include_enterprise2 = edition == 'enterprise' edition2 = 'enterprise2' + init_steps = [ + identify_runner_step(), + download_grabpl_step(), + gen_version_step(ver_mode), + wire_install_step(), + yarn_install_step(), + ] - test_steps = [ + test_steps = [] + if edition != 'enterprise': + test_steps.extend([shellcheck_step()]) + + test_steps.extend([ codespell_step(), - shellcheck_step(), lint_backend_step(edition=edition), lint_frontend_step(), test_backend_step(edition=edition), test_backend_integration_step(edition=edition), test_frontend_step(), - ] + ]) build_steps = [ build_backend_step(edition=edition, ver_mode=ver_mode), @@ -233,16 +248,16 @@ def get_steps(edition, ver_mode): if step: publish_steps.append(step) - return test_steps, build_steps, integration_test_steps, package_steps, windows_package_steps, publish_steps + return init_steps, test_steps, build_steps, integration_test_steps, package_steps, windows_package_steps, publish_steps def get_oss_pipelines(trigger, ver_mode): edition = 'oss' services = integration_test_services(edition=edition) volumes = integration_test_services_volumes() - test_steps, build_steps, integration_test_steps, package_steps, windows_package_steps, publish_steps = get_steps(edition=edition, ver_mode=ver_mode) + init_steps, test_steps, build_steps, integration_test_steps, package_steps, windows_package_steps, publish_steps = get_steps(edition=edition, ver_mode=ver_mode) windows_pipeline = pipeline( name='oss-windows-{}'.format(ver_mode), edition=edition, trigger=trigger, - steps=initialize_step(edition, platform='windows', ver_mode=ver_mode) + windows_package_steps, + steps=[identify_runner_step('windows')] + windows_package_steps, platform='windows', depends_on=[ 'oss-build{}-publish-{}'.format(get_e2e_suffix(), ver_mode), ], @@ -250,8 +265,7 @@ def get_oss_pipelines(trigger, ver_mode): pipelines = [ pipeline( name='oss-build{}-publish-{}'.format(get_e2e_suffix(), ver_mode), edition=edition, trigger=trigger, services=[], - steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) + - build_steps + package_steps + publish_steps, + steps=init_steps + build_steps + package_steps + publish_steps, volumes=volumes, ), ] @@ -259,14 +273,12 @@ def get_oss_pipelines(trigger, ver_mode): pipelines.extend([ pipeline( name='oss-test-{}'.format(ver_mode), edition=edition, trigger=trigger, services=[], - steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) + - test_steps, + steps=init_steps + test_steps, volumes=[], ), pipeline( name='oss-integration-tests-{}'.format(ver_mode), edition=edition, trigger=trigger, services=services, - steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) + - integration_test_steps, + steps=[download_grabpl_step(), identify_runner_step(),] + integration_test_steps, volumes=volumes, ) ]) @@ -286,10 +298,28 @@ def get_enterprise_pipelines(trigger, ver_mode): edition = 'enterprise' services = integration_test_services(edition=edition) volumes = integration_test_services_volumes() - test_steps, build_steps, integration_test_steps, package_steps, windows_package_steps, publish_steps = get_steps(edition=edition, ver_mode=ver_mode) + deps_on_clone_enterprise_step = { + 'depends_on': [ + 'init-enterprise', + ] + } + _, test_steps, build_steps, integration_test_steps, package_steps, windows_package_steps, publish_steps = get_steps(edition=edition, ver_mode=ver_mode) + init_steps = [ + download_grabpl_step(), + identify_runner_step(), + clone_enterprise_step(ver_mode), + init_enterprise_step(ver_mode) + ] + for step in [wire_install_step(), yarn_install_step(), gen_version_step(ver_mode)]: + step.update(deps_on_clone_enterprise_step) + init_steps.extend([step]) + + for step in integration_test_steps: + step.update(deps_on_clone_enterprise_step) + windows_pipeline = pipeline( name='enterprise-windows-{}'.format(ver_mode), edition=edition, trigger=trigger, - steps=initialize_step(edition, platform='windows', ver_mode=ver_mode) + windows_package_steps, + steps=[identify_runner_step('windows')] + windows_package_steps, platform='windows', depends_on=[ 'enterprise-build{}-publish-{}'.format(get_e2e_suffix(), ver_mode), ], @@ -297,8 +327,7 @@ def get_enterprise_pipelines(trigger, ver_mode): pipelines = [ pipeline( name='enterprise-build{}-publish-{}'.format(get_e2e_suffix(), ver_mode), edition=edition, trigger=trigger, services=[], - steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) + - build_steps + package_steps + publish_steps, + steps=init_steps + build_steps + package_steps + publish_steps, volumes=volumes, ), ] @@ -306,14 +335,12 @@ def get_enterprise_pipelines(trigger, ver_mode): pipelines.extend([ pipeline( name='enterprise-test-{}'.format(ver_mode), edition=edition, trigger=trigger, services=[], - steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) + - test_steps, + steps=init_steps + test_steps, volumes=[], ), pipeline( name='enterprise-integration-tests-{}'.format(ver_mode), edition=edition, trigger=trigger, services=services, - steps=[download_grabpl_step()] + initialize_step(edition, platform='linux', ver_mode=ver_mode) + - integration_test_steps, + steps=[download_grabpl_step(), identify_runner_step(), clone_enterprise_step(ver_mode), init_enterprise_step(ver_mode),] + integration_test_steps, volumes=volumes, ), ]) @@ -401,7 +428,7 @@ def publish_npm_pipelines(mode): ] return [pipeline( - name='publish-npm-packages-{}'.format(mode), trigger=trigger, steps = initialize_step(edition='oss', platform='linux', ver_mode='release') + steps, edition="all" + name='publish-npm-packages-{}'.format(mode), trigger=trigger, steps = steps, edition="all" )] def release_pipelines(ver_mode='release', trigger=None, environment=None): diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star index 3ba6b73a9d2..44e4c6ae5b7 100644 --- a/scripts/drone/steps/lib.star +++ b/scripts/drone/steps/lib.star @@ -1,6 +1,6 @@ load('scripts/drone/vault.star', 'from_secret', 'github_token', 'pull_secret', 'drone_token', 'prerelease_bucket') -grabpl_version = 'v2.9.34' +grabpl_version = 'v2.9.35' build_image = 'grafana/build-container:1.5.3' publish_image = 'grafana/grafana-ci-deploy:1.3.1' deploy_docker_image = 'us.gcr.io/kubernetes-dev/drone/plugins/deploy-image' @@ -18,6 +18,7 @@ trigger_oss = { } } + def slack_step(channel, template, secret): return { 'name': 'slack', @@ -30,106 +31,49 @@ def slack_step(channel, template, secret): } -def initialize_step(edition, platform, ver_mode, is_downstream=False, install_deps=True): - if platform == 'windows': - return [ - { - 'name': 'identify-runner', - 'image': windows_image, - 'commands': [ - 'echo $env:DRONE_RUNNER_NAME', - ], - }, - ] - - common_cmds = [ - # Generate Go code, will install Wire - # TODO: Install Wire in Docker image instead - 'make gen-go', - ] - +def gen_version_step(ver_mode): if ver_mode == 'release': args = '${DRONE_TAG}' - common_cmds.append('./bin/grabpl verify-version ${DRONE_TAG}') else: - if not is_downstream: - build_no = '${DRONE_BUILD_NUMBER}' - else: - build_no = '$${SOURCE_BUILD_NUMBER}' + build_no = '${DRONE_BUILD_NUMBER}' args = '--build-id {}'.format(build_no) - - identify_runner = identify_runner_step(platform) - - if install_deps: - common_cmds.extend([ + return { + 'name': 'gen-version', + 'image': build_image, + 'depends_on': [ + 'grabpl', + ], + 'commands': [ './bin/grabpl gen-version {}'.format(args), + ], + } + + +def yarn_install_step(): + return { + 'name': 'yarn-install', + 'image': build_image, + 'commands': [ 'yarn install --immutable', - ]) - if edition in ('enterprise', 'enterprise2'): - source_commit = '' - if ver_mode == 'release': - committish = '${DRONE_TAG}' - source_commit = ' ${DRONE_TAG}' - environment = { - 'GITHUB_TOKEN': from_secret(github_token), - } - token = "--github-token $${GITHUB_TOKEN}" - elif ver_mode == 'release-branch': - committish = '${DRONE_BRANCH}' - environment = {} - token = "" - else: - environment = {} - if is_downstream: - source_commit = ' $${SOURCE_COMMIT}' - committish = '${DRONE_COMMIT}' - token = "" - steps = [ - identify_runner, - clone_enterprise(committish), - { - 'name': 'initialize', - 'image': build_image, - 'depends_on': [ - 'clone-enterprise', - ], - 'environment': environment, - 'commands': [ - 'mv bin/grabpl /tmp/', - 'rmdir bin', - 'mv grafana-enterprise /tmp/', - '/tmp/grabpl init-enterprise {} /tmp/grafana-enterprise{}'.format(token, source_commit), - 'mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json', - 'mkdir bin', - 'mv /tmp/grabpl bin/' - ] + common_cmds, - }, - ] - - return steps - - steps = [ - identify_runner, - { - 'name': 'initialize', - 'image': build_image, - 'commands': common_cmds, - }, - ] - - return steps + ], + 'depends_on': [ + 'grabpl', + ], + } -def identify_runner_step(platform): - if platform == 'linux': - return { - 'name': 'identify-runner', - 'image': alpine_image, - 'commands': [ - 'echo $DRONE_RUNNER_NAME', - ], - } - else: +def wire_install_step(): + return { + 'name': 'wire-install', + 'image': build_image, + 'commands': [ + 'make gen-go', + ], + } + + +def identify_runner_step(platform='linux'): + if platform == 'windows': return { 'name': 'identify-runner', 'image': windows_image, @@ -137,9 +81,23 @@ def identify_runner_step(platform): 'echo $env:DRONE_RUNNER_NAME', ], } + else: + return { + 'name': 'identify-runner', + 'image': alpine_image, + 'commands': [ + 'echo $DRONE_RUNNER_NAME', + ], + } -def clone_enterprise(committish): +def clone_enterprise_step(ver_mode): + if ver_mode == 'release': + committish = '${DRONE_TAG}' + elif ver_mode == 'release-branch': + committish = '${DRONE_BRANCH}' + else: + committish = '${DRONE_COMMIT}' return { 'name': 'clone-enterprise', 'image': build_image, @@ -153,8 +111,51 @@ def clone_enterprise(committish): ], } +def init_enterprise_step(ver_mode): + source_commit = '' + if ver_mode == 'release': + source_commit = ' ${DRONE_TAG}' + environment = { + 'GITHUB_TOKEN': from_secret(github_token), + } + token = "--github-token $${GITHUB_TOKEN}" + elif ver_mode == 'release-branch': + environment = {} + token = "" + else: + environment = {} + token = "" + return { + 'name': 'init-enterprise', + 'image': build_image, + 'depends_on': [ + 'clone-enterprise', + ], + 'environment': environment, + 'commands': [ + 'mv bin/grabpl /tmp/', + 'rmdir bin', + 'mv grafana-enterprise /tmp/', + '/tmp/grabpl init-enterprise {} /tmp/grafana-enterprise{}'.format(token, source_commit), + 'mv /tmp/grafana-enterprise/deployment_tools_config.json deployment_tools_config.json', + 'mkdir bin', + 'mv /tmp/grabpl bin/' + ], + } + + +def download_grabpl_step(platform="linux"): + if platform == 'windows': + return { + 'name': 'grabpl', + 'image': wix_image, + 'commands': [ + '$$ProgressPreference = "SilentlyContinue"', + 'Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/{}/windows/grabpl.exe -OutFile grabpl.exe'.format( + grabpl_version), + ] + } -def download_grabpl_step(): return { 'name': 'grabpl', 'image': curl_image, @@ -211,7 +212,7 @@ def lint_backend_step(edition): 'CGO_ENABLED': '1', }, 'depends_on': [ - 'initialize', + 'wire-install', ], 'commands': [ # Don't use Make since it will re-download the linters @@ -224,9 +225,6 @@ def benchmark_ldap_step(): return { 'name': 'benchmark-ldap', 'image': build_image, - 'depends_on': [ - 'initialize', - ], 'environment': { 'LDAP_HOSTNAME': 'ldap', }, @@ -263,7 +261,6 @@ def store_storybook_step(edition, ver_mode, trigger=None): if edition in ('enterprise', 'enterprise2'): return None - commands = [] if ver_mode == 'release': commands.extend([ @@ -273,12 +270,12 @@ def store_storybook_step(edition, ver_mode, trigger=None): else: # main pipelines should deploy storybook to grafana-storybook/canary public bucket - commands = ['./bin/grabpl store-storybook --deployment canary --src-bucket grafana-storybook',] + commands = ['./bin/grabpl store-storybook --deployment canary --src-bucket grafana-storybook', ] step = { 'name': 'store-storybook', 'image': publish_image, - 'depends_on': ['build-storybook',] + end_to_end_tests_deps(edition), + 'depends_on': ['build-storybook', ] + end_to_end_tests_deps(edition), 'environment': { 'GCP_KEY': from_secret('gcp_key'), 'PRERELEASE_BUCKET': from_secret(prerelease_bucket) @@ -289,6 +286,7 @@ def store_storybook_step(edition, ver_mode, trigger=None): step.update(trigger) return step + def e2e_tests_artifacts(edition): return { 'name': 'e2e-tests-artifacts-upload' + enterprise2_suffix(edition), @@ -363,7 +361,7 @@ def upload_cdn_step(edition, ver_mode, trigger=None): return step -def build_backend_step(edition, ver_mode, variants=None, is_downstream=False): +def build_backend_step(edition, ver_mode, variants=None): variants_str = '' if variants: variants_str = ' --variants {}'.format(','.join(variants)) @@ -379,10 +377,7 @@ def build_backend_step(edition, ver_mode, variants=None, is_downstream=False): ), ] else: - if not is_downstream: - build_no = '${DRONE_BUILD_NUMBER}' - else: - build_no = '$${SOURCE_BUILD_NUMBER}' + build_no = '${DRONE_BUILD_NUMBER}' env = {} cmds = [ './bin/grabpl build-backend --jobs 8 --edition {} --build-id {}{}'.format( @@ -393,19 +388,17 @@ def build_backend_step(edition, ver_mode, variants=None, is_downstream=False): return { 'name': 'build-backend' + enterprise2_suffix(edition), 'image': build_image, - 'depends_on': [ - 'initialize', - ], 'environment': env, + 'depends_on': [ + 'gen-version', + 'wire-install', + ], 'commands': cmds, } -def build_frontend_step(edition, ver_mode, is_downstream=False): - if not is_downstream: - build_no = '${DRONE_BUILD_NUMBER}' - else: - build_no = '$${SOURCE_BUILD_NUMBER}' +def build_frontend_step(edition, ver_mode): + build_no = '${DRONE_BUILD_NUMBER}' # TODO: Use percentage for num jobs if ver_mode == 'release': @@ -422,42 +415,42 @@ def build_frontend_step(edition, ver_mode, is_downstream=False): return { 'name': 'build-frontend', 'image': build_image, - 'depends_on': [ - 'initialize', - ], 'environment': { 'NODE_OPTIONS': '--max_old_space_size=8192', }, + 'depends_on': [ + 'gen-version', + 'yarn-install', + ], 'commands': cmds, } -def build_frontend_package_step(edition, ver_mode, is_downstream=False): - if not is_downstream: - build_no = '${DRONE_BUILD_NUMBER}' - else: - build_no = '$${SOURCE_BUILD_NUMBER}' + +def build_frontend_package_step(edition, ver_mode): + build_no = '${DRONE_BUILD_NUMBER}' # TODO: Use percentage for num jobs if ver_mode == 'release': cmds = [ './bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN} ' + \ '--edition {} ${{DRONE_TAG}}'.format(edition), - ] + ] else: cmds = [ './bin/grabpl build-frontend-packages --jobs 8 --edition {} '.format(edition) + \ '--build-id {}'.format(build_no), - ] + ] return { 'name': 'build-frontend-packages', 'image': build_image, - 'depends_on': [ - 'initialize', - ], 'environment': { 'NODE_OPTIONS': '--max_old_space_size=8192', }, + 'depends_on': [ + 'gen-version', + 'yarn-install', + ], 'commands': cmds, } @@ -487,10 +480,11 @@ def build_plugins_step(edition, sign=False): return { 'name': 'build-plugins', 'image': build_image, - 'depends_on': [ - 'initialize', - ], 'environment': env, + 'depends_on': [ + 'gen-version', + 'yarn-install', + ], 'commands': [ # TODO: Use percentage for num jobs './bin/grabpl build-plugins --jobs 8 --edition {}{}'.format(edition, sign_args), @@ -503,7 +497,7 @@ def test_backend_step(edition): 'name': 'test-backend' + enterprise2_suffix(edition), 'image': build_image, 'depends_on': [ - 'initialize', + 'wire-install', ], 'commands': [ './bin/grabpl test-backend --edition {}'.format(edition), @@ -516,7 +510,7 @@ def test_backend_integration_step(edition): 'name': 'test-backend-integration' + enterprise2_suffix(edition), 'image': build_image, 'depends_on': [ - 'initialize', + 'wire-install', ], 'commands': [ './bin/grabpl integration-tests --edition {}'.format(edition), @@ -528,12 +522,12 @@ def test_frontend_step(): return { 'name': 'test-frontend', 'image': build_image, - 'depends_on': [ - 'initialize', - ], 'environment': { 'TEST_MAX_WORKERS': '50%', }, + 'depends_on': [ + 'yarn-install', + ], 'commands': [ 'yarn run ci:test-frontend', ], @@ -544,16 +538,16 @@ def lint_frontend_step(): return { 'name': 'lint-frontend', 'image': build_image, - 'depends_on': [ - 'initialize', - ], 'environment': { 'TEST_MAX_WORKERS': '50%', }, + 'depends_on': [ + 'yarn-install', + ], 'commands': [ 'yarn run prettier:check', 'yarn run lint', - 'yarn run i18n:compile', # TODO: right place for this? + 'yarn run i18n:compile', # TODO: right place for this? 'yarn run typecheck', ], } @@ -617,9 +611,6 @@ def codespell_step(): return { 'name': 'codespell', 'image': build_image, - 'depends_on': [ - 'initialize', - ], 'commands': [ # Important: all words have to be in lowercase, and separated by "\n". 'echo -e "unknwon\nreferer\nerrorstring\neror\niam\nwan" > words_to_ignore.txt', @@ -634,7 +625,7 @@ def shellcheck_step(): 'name': 'shellcheck', 'image': build_image, 'depends_on': [ - 'initialize', + 'grabpl', ], 'commands': [ './bin/grabpl shellcheck', @@ -642,7 +633,7 @@ def shellcheck_step(): } -def package_step(edition, ver_mode, include_enterprise2=False, variants=None, is_downstream=False): +def package_step(edition, ver_mode, include_enterprise2=False, variants=None): deps = [ 'build-plugins', 'build-backend', @@ -683,10 +674,7 @@ def package_step(edition, ver_mode, include_enterprise2=False, variants=None, is ), ] else: - if not is_downstream: - build_no = '${DRONE_BUILD_NUMBER}' - else: - build_no = '$${SOURCE_BUILD_NUMBER}' + build_no = '${DRONE_BUILD_NUMBER}' cmds = [ '{}./bin/grabpl package --jobs 8 --edition {} '.format(test_args, edition) + \ '--build-id {}{}{}'.format(build_no, variants_str, sign_args), @@ -731,6 +719,7 @@ def grafana_server_step(edition, port=3001): ], } + def e2e_tests_step(suite, edition, port=3001, tries=None): cmd = './bin/grabpl e2e-tests --port {} --suite {}'.format(port, suite) if tries: @@ -810,13 +799,15 @@ def build_docker_images_step(edition, ver_mode, archs=None, ubuntu=False, publis }, } + def publish_images_step(edition, ver_mode, mode, docker_repo, trigger=None): if mode == 'security': mode = '--{} '.format(mode) else: mode = '' - cmd = './bin/grabpl artifacts docker publish {}--dockerhub-repo {} --base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7'.format(mode, docker_repo) + cmd = './bin/grabpl artifacts docker publish {}--dockerhub-repo {} --base alpine --base ubuntu --arch amd64 --arch arm64 --arch armv7'.format( + mode, docker_repo) if ver_mode == 'release': deps = ['fetch-images-{}'.format(edition)] @@ -847,10 +838,7 @@ def publish_images_step(edition, ver_mode, mode, docker_repo, trigger=None): def postgres_integration_tests_step(edition, ver_mode): deps = [] - if edition in ('enterprise', 'enterprise2') and ver_mode in ('release-branch', 'release'): - deps.extend(['initialize']) - else: - deps.extend(['grabpl']) + deps.extend(['grabpl']) return { 'name': 'postgres-integration-tests', 'image': build_image, @@ -875,10 +863,7 @@ def postgres_integration_tests_step(edition, ver_mode): def mysql_integration_tests_step(edition, ver_mode): deps = [] - if edition in ('enterprise', 'enterprise2') and ver_mode in ('release-branch', 'release'): - deps.extend(['initialize']) - else: - deps.extend(['grabpl']) + deps.extend(['grabpl']) return { 'name': 'mysql-integration-tests', 'image': build_image, @@ -901,10 +886,7 @@ def mysql_integration_tests_step(edition, ver_mode): def redis_integration_tests_step(edition, ver_mode): deps = [] - if edition in ('enterprise', 'enterprise2') and ver_mode in ('release-branch', 'release'): - deps.extend(['initialize']) - else: - deps.extend(['grabpl']) + deps.extend(['grabpl']) return { 'name': 'redis-integration-tests', 'image': build_image, @@ -921,10 +903,7 @@ def redis_integration_tests_step(edition, ver_mode): def memcached_integration_tests_step(edition, ver_mode): deps = [] - if edition in ('enterprise', 'enterprise2') and ver_mode in ('release-branch', 'release'): - deps.extend(['initialize']) - else: - deps.extend(['grabpl']) + deps.extend(['grabpl']) return { 'name': 'memcached-integration-tests', 'image': build_image, @@ -965,15 +944,16 @@ def enterprise2_suffix(edition): return '' -def upload_packages_step(edition, ver_mode, is_downstream=False, trigger=None): - if ver_mode == 'main' and edition in ('enterprise', 'enterprise2') and not is_downstream: +def upload_packages_step(edition, ver_mode, trigger=None): + if ver_mode == 'main' and edition in ('enterprise', 'enterprise2'): return None if ver_mode == 'release': packages_bucket = '$${{PRERELEASE_BUCKET}}/artifacts/downloads{}'.format(enterprise2_suffix(edition)) cmd = './bin/grabpl upload-packages --edition {} --packages-bucket {}'.format(edition, packages_bucket) elif edition == 'enterprise2': - cmd = './bin/grabpl upload-packages --edition {} --packages-bucket grafana-downloads-enterprise2'.format(edition) + cmd = './bin/grabpl upload-packages --edition {} --packages-bucket grafana-downloads-enterprise2'.format( + edition) else: cmd = './bin/grabpl upload-packages --edition {} --packages-bucket grafana-downloads'.format(edition) @@ -981,7 +961,7 @@ def upload_packages_step(edition, ver_mode, is_downstream=False, trigger=None): if edition in 'enterprise2' or not end_to_end_tests_deps(edition): deps.extend([ 'package' + enterprise2_suffix(edition), - ]) + ]) else: deps.extend(end_to_end_tests_deps(edition)) @@ -1000,16 +980,13 @@ def upload_packages_step(edition, ver_mode, is_downstream=False, trigger=None): return step -def store_packages_step(edition, ver_mode, is_downstream=False): +def store_packages_step(edition, ver_mode): if ver_mode == 'release': cmd = './bin/grabpl store-packages --edition {} --packages-bucket grafana-downloads --gcp-key /tmp/gcpkey.json ${{DRONE_TAG}}'.format( edition, ) elif ver_mode == 'main': - if not is_downstream: - build_no = '${DRONE_BUILD_NUMBER}' - else: - build_no = '$${SOURCE_BUILD_NUMBER}' + build_no = '${DRONE_BUILD_NUMBER}' cmd = './bin/grabpl store-packages --edition {} --gcp-key /tmp/gcpkey.json --build-id {}'.format( edition, build_no, ) @@ -1035,12 +1012,7 @@ def store_packages_step(edition, ver_mode, is_downstream=False): } -def get_windows_steps(edition, ver_mode, is_downstream=False): - if not is_downstream: - source_commit = '' - else: - source_commit = ' $$env:SOURCE_COMMIT' - +def get_windows_steps(edition, ver_mode): init_cmds = [] sfx = '' if edition in ('enterprise', 'enterprise2'): @@ -1053,12 +1025,12 @@ def get_windows_steps(edition, ver_mode, is_downstream=False): ]) steps = [ { - 'name': 'initialize', + 'name': 'windows-init', 'image': wix_image, 'commands': init_cmds, }, ] - if (ver_mode == 'main' and (edition not in ('enterprise', 'enterprise2') or is_downstream)) or ver_mode in ( + if (ver_mode == 'main' and (edition not in ('enterprise', 'enterprise2'))) or ver_mode in ( 'release', 'release-branch', ): bucket_part = '' @@ -1070,10 +1042,7 @@ def get_windows_steps(edition, ver_mode, is_downstream=False): dir = 'main' bucket = 'grafana-downloads' bucket_part = ' --packages-bucket {}'.format(bucket) - if not is_downstream: - build_no = 'DRONE_BUILD_NUMBER' - else: - build_no = 'SOURCE_BUILD_NUMBER' + build_no = 'DRONE_BUILD_NUMBER' ver_part = '--build-id $$env:{}'.format(build_no) installer_commands = [ '$$gcpKey = $$env:GCP_KEY', @@ -1084,7 +1053,7 @@ def get_windows_steps(edition, ver_mode, is_downstream=False): 'rm gcpkey.json', 'cp C:\\App\\nssm-2.24.zip .', ] - if (ver_mode == 'main' and (edition not in ('enterprise', 'enterprise2') or is_downstream)) or ver_mode in ( + if (ver_mode == 'main' and (edition not in ('enterprise', 'enterprise2'))) or ver_mode in ( 'release', ): installer_commands.extend([ @@ -1105,15 +1074,15 @@ def get_windows_steps(edition, ver_mode, is_downstream=False): steps.append({ 'name': 'build-windows-installer', 'image': wix_image, + 'depends_on': [ + 'windows-init', + ], 'environment': { 'GCP_KEY': from_secret('gcp_key'), 'PRERELEASE_BUCKET': from_secret(prerelease_bucket), 'GITHUB_TOKEN': from_secret('github_token') }, 'commands': installer_commands, - 'depends_on': [ - 'initialize', - ], }) if edition in ('enterprise', 'enterprise2'): @@ -1132,11 +1101,10 @@ def get_windows_steps(edition, ver_mode, is_downstream=False): clone_cmds = [ 'git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git"', ] - if not is_downstream: - clone_cmds.extend([ - 'cd grafana-enterprise', - 'git checkout {}'.format(committish), - ]) + clone_cmds.extend([ + 'cd grafana-enterprise', + 'git checkout {}'.format(committish), + ]) steps.insert(0, { 'name': 'clone', 'image': wix_image, @@ -1154,7 +1122,7 @@ def get_windows_steps(edition, ver_mode, is_downstream=False): 'rm -r -force grafana-enterprise', 'cp grabpl.exe C:\\App\\grabpl.exe', 'rm -force grabpl.exe', - 'C:\\App\\grabpl.exe init-enterprise --github-token $$env:GITHUB_TOKEN C:\\App\\grafana-enterprise{}'.format(source_commit), + 'C:\\App\\grabpl.exe init-enterprise --github-token $$env:GITHUB_TOKEN C:\\App\\grafana-enterprise', 'cp C:\\App\\grabpl.exe grabpl.exe', ]) if 'environment' in steps[1]: @@ -1194,6 +1162,7 @@ def ensure_cuetsified_step(): ], } + def end_to_end_tests_deps(edition): if disable_tests: return [] From c63086822d3be2bd1b8d97ffa7de80a058e979e8 Mon Sep 17 00:00:00 2001 From: Nathan Rodman Date: Thu, 14 Apr 2022 05:19:15 -0700 Subject: [PATCH 04/40] Alerting: Add FGAC to alerts tab in panel editor (#47732) * add FGAC for alert panel tab * add test for panel tabs * Refactor condition Co-authored-by: Konrad Lalik --- .../alerting/unified/PanelAlertTabContent.tsx | 8 +++- .../PanelEditor/state/selectors.test.ts | 39 +++++++++++++++++++ .../components/PanelEditor/state/selectors.ts | 15 +++++-- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/public/app/features/alerting/unified/PanelAlertTabContent.tsx b/public/app/features/alerting/unified/PanelAlertTabContent.tsx index 7460af80219..6832a62b200 100644 --- a/public/app/features/alerting/unified/PanelAlertTabContent.tsx +++ b/public/app/features/alerting/unified/PanelAlertTabContent.tsx @@ -7,6 +7,8 @@ import { NewRuleFromPanelButton } from './components/panel-alerts-tab/NewRuleFro import { RulesTable } from './components/rules/RulesTable'; import { usePanelCombinedRules } from './hooks/usePanelCombinedRules'; import { selectors } from '@grafana/e2e-selectors'; +import { getRulesPermissions } from './utils/access-control'; +import { contextSrv } from 'app/core/services/context_srv'; interface Props { dashboard: DashboardModel; @@ -20,6 +22,8 @@ export const PanelAlertTabContent: FC = ({ dashboard, panel }) => { panel, poll: true, }); + const permissions = getRulesPermissions('grafana'); + const canCreateRules = contextSrv.hasPermission(permissions.create); const alert = errors.length ? ( @@ -44,7 +48,7 @@ export const PanelAlertTabContent: FC = ({ dashboard, panel }) => {
{alert} - {!!dashboard.meta.canSave && ( + {!!dashboard.meta.canSave && canCreateRules && ( )}
@@ -58,7 +62,7 @@ export const PanelAlertTabContent: FC = ({ dashboard, panel }) => { {!!dashboard.uid && ( <>

There are no alert rules linked to this panel.

- {!!dashboard.meta.canSave && } + {!!dashboard.meta.canSave && canCreateRules && } )} {!dashboard.uid && !!dashboard.meta.canSave && ( diff --git a/public/app/features/dashboard/components/PanelEditor/state/selectors.test.ts b/public/app/features/dashboard/components/PanelEditor/state/selectors.test.ts index 5768728b784..0bab17d32f9 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/selectors.test.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/selectors.test.ts @@ -2,6 +2,13 @@ import { getPanelEditorTabs } from './selectors'; import { PanelPlugin } from '@grafana/data'; import { PanelEditorTabId } from '../types'; import { updateConfig } from '../../../../../core/config'; +import { contextSrv } from 'app/core/services/context_srv'; + +jest.mock('app/core/services/context_srv'); + +const mocks = { + contextSrv: jest.mocked(contextSrv), +}; describe('getPanelEditorTabs selector', () => { it('return no tabs when no plugin provided', () => { @@ -79,5 +86,37 @@ describe('getPanelEditorTabs selector', () => { expect(tabs[1].id).toEqual(PanelEditorTabId.Transform); }); }); + + describe('with unified alerting enabled', () => { + beforeAll(() => { + updateConfig({ unifiedAlertingEnabled: true }); + }); + + it('shows the alert tab for users with read permission', () => { + mocks.contextSrv.hasPermission.mockReturnValue(true); + + const tabs = getPanelEditorTabs(undefined, { + meta: { + id: 'graph', + }, + } as PanelPlugin); + + expect(tabs.length).toEqual(3); + expect(tabs[2].id).toEqual(PanelEditorTabId.Alert); + }); + + it('hides the alert tab for users with read permission', () => { + mocks.contextSrv.hasPermission.mockReturnValue(false); + + const tabs = getPanelEditorTabs(undefined, { + meta: { + id: 'graph', + }, + } as PanelPlugin); + + expect(tabs.length).toEqual(2); + expect(tabs[1].id).toEqual(PanelEditorTabId.Transform); + }); + }); }); }); diff --git a/public/app/features/dashboard/components/PanelEditor/state/selectors.ts b/public/app/features/dashboard/components/PanelEditor/state/selectors.ts index 64ce72c531f..007945700da 100644 --- a/public/app/features/dashboard/components/PanelEditor/state/selectors.ts +++ b/public/app/features/dashboard/components/PanelEditor/state/selectors.ts @@ -2,6 +2,9 @@ import memoizeOne from 'memoize-one'; import { PanelPlugin } from '@grafana/data'; import { PanelEditorTab, PanelEditorTabId } from '../types'; import { getConfig } from 'app/core/config'; +import { getRulesPermissions } from 'app/features/alerting/unified/utils/access-control'; +import { contextSrv } from 'app/core/services/context_srv'; +import { GRAFANA_RULES_SOURCE_NAME } from 'app/features/alerting/unified/utils/datasource'; export const getPanelEditorTabs = memoizeOne((tab?: string, plugin?: PanelPlugin) => { const tabs: PanelEditorTab[] = []; @@ -34,10 +37,14 @@ export const getPanelEditorTabs = memoizeOne((tab?: string, plugin?: PanelPlugin }); } - if ( - ((getConfig().alertingEnabled || getConfig().unifiedAlertingEnabled) && plugin.meta.id === 'graph') || - plugin.meta.id === 'timeseries' - ) { + const { alertingEnabled, unifiedAlertingEnabled } = getConfig(); + const hasRuleReadPermissions = contextSrv.hasPermission(getRulesPermissions(GRAFANA_RULES_SOURCE_NAME).read); + const isAlertingAvailable = alertingEnabled || (unifiedAlertingEnabled && hasRuleReadPermissions); + + const isGraph = plugin.meta.id === 'graph'; + const isTimeseries = plugin.meta.id === 'timeseries'; + + if ((isAlertingAvailable && isGraph) || isTimeseries) { tabs.push({ id: PanelEditorTabId.Alert, text: 'Alert', From 060ccacbf97f36de7548dd33d9f75e42c9fa6188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Philippe=20Qu=C3=A9m=C3=A9ner?= Date: Thu, 14 Apr 2022 14:21:36 +0200 Subject: [PATCH 05/40] Alerting: unwrap upsert into insert and update function (#47731) * Alerting: unwrap upsert into insert and update function * add changelog entry * remove changelog entry * rename upsertrule to updaterule * use directly alertrule model for inserts * add test for updating a rule with a conflicting name --- pkg/services/ngalert/CHANGELOG.md | 2 +- pkg/services/ngalert/api/api_ruler.go | 18 +-- pkg/services/ngalert/store/alert_rule.go | 123 ++++++++++++------- pkg/services/ngalert/store/testing.go | 12 +- pkg/services/ngalert/tests/util.go | 41 ++++--- pkg/tests/api/alerting/api_ruler_test.go | 146 +++++++++++++++++------ 6 files changed, 231 insertions(+), 111 deletions(-) diff --git a/pkg/services/ngalert/CHANGELOG.md b/pkg/services/ngalert/CHANGELOG.md index c5650250638..15d9aadcf60 100644 --- a/pkg/services/ngalert/CHANGELOG.md +++ b/pkg/services/ngalert/CHANGELOG.md @@ -48,4 +48,4 @@ Scopes must have an order to ensure consistency and ease of search, this helps u - [CHANGE] Prometheus Compatible API: Use float-like values for `api/prometheus/grafana/api/v1/alerts` and `api/prometheus/grafana/api/v1/rules` instead of the evaluation string #47216 - [BUGFIX] (Legacy) Templates: Parse notification templates using all the matches of the alert rule when going from `Alerting` to `OK` in legacy alerting #47355 - [BUGFIX] Scheduler: Fix state manager to support OK option of `AlertRule.ExecErrState` #47670 -- [ENHANCEMENT] Templates: Enable the use of classic condition values in templates #46971 \ No newline at end of file +- [ENHANCEMENT] Templates: Enable the use of classic condition values in templates #46971 diff --git a/pkg/services/ngalert/api/api_ruler.go b/pkg/services/ngalert/api/api_ruler.go index dfefb87c961..963597e7e9c 100644 --- a/pkg/services/ngalert/api/api_ruler.go +++ b/pkg/services/ngalert/api/api_ruler.go @@ -355,23 +355,25 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *models.ReqContext, namespace *mod logger.Debug("updating database with the authorized changes", "add", len(authorizedChanges.New), "update", len(authorizedChanges.New), "delete", len(authorizedChanges.Delete)) if len(authorizedChanges.Update) > 0 || len(authorizedChanges.New) > 0 { - upsert := make([]store.UpsertRule, 0, len(authorizedChanges.Update)+len(authorizedChanges.New)) + updates := make([]store.UpdateRule, 0, len(authorizedChanges.Update)) + inserts := make([]ngmodels.AlertRule, 0, len(authorizedChanges.New)) for _, update := range authorizedChanges.Update { logger.Debug("updating rule", "rule_uid", update.New.UID, "diff", update.Diff.String()) - upsert = append(upsert, store.UpsertRule{ + updates = append(updates, store.UpdateRule{ Existing: update.Existing, New: *update.New, }) } for _, rule := range authorizedChanges.New { - upsert = append(upsert, store.UpsertRule{ - Existing: nil, - New: *rule, - }) + inserts = append(inserts, *rule) } - err = srv.store.UpsertAlertRules(tranCtx, upsert) + err = srv.store.InsertAlertRules(tranCtx, inserts) if err != nil { - return fmt.Errorf("failed to add or update rules: %w", err) + return fmt.Errorf("failed to add rules: %w", err) + } + err = srv.store.UpdateAlertRules(tranCtx, updates) + if err != nil { + return fmt.Errorf("failed to update rules: %w", err) } } diff --git a/pkg/services/ngalert/store/alert_rule.go b/pkg/services/ngalert/store/alert_rule.go index bbb026e3b41..ff1df1709ce 100644 --- a/pkg/services/ngalert/store/alert_rule.go +++ b/pkg/services/ngalert/store/alert_rule.go @@ -27,7 +27,7 @@ type UpdateRuleGroupCmd struct { RuleGroupConfig apimodels.PostableRuleGroupConfig } -type UpsertRule struct { +type UpdateRule struct { Existing *ngmodels.AlertRule New ngmodels.AlertRule } @@ -42,7 +42,8 @@ type RuleStore interface { GetAlertRules(ctx context.Context, query *ngmodels.GetAlertRulesQuery) error GetUserVisibleNamespaces(context.Context, int64, *models.SignedInUser) (map[string]*models.Folder, error) GetNamespaceByTitle(context.Context, string, int64, *models.SignedInUser, bool) (*models.Folder, error) - UpsertAlertRules(ctx context.Context, rule []UpsertRule) error + InsertAlertRules(ctx context.Context, rule []ngmodels.AlertRule) error + UpdateAlertRules(ctx context.Context, rule []UpdateRule) error } func getAlertRuleByUID(sess *sqlstore.DBSession, alertRuleUID string, orgID int64) (*ngmodels.AlertRule, error) { @@ -107,52 +108,87 @@ func (st DBstore) GetAlertRuleByUID(ctx context.Context, query *ngmodels.GetAler }) } -// UpsertAlertRules is a handler for creating/updating alert rules. -func (st DBstore) UpsertAlertRules(ctx context.Context, rules []UpsertRule) error { +// InsertAlertRules is a handler for creating/updating alert rules. +func (st DBstore) InsertAlertRules(ctx context.Context, rules []ngmodels.AlertRule) error { + return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + newRules := make([]ngmodels.AlertRule, 0, len(rules)) + ruleVersions := make([]ngmodels.AlertRuleVersion, 0, len(rules)) + for i := range rules { + r := rules[i] + uid, err := GenerateNewAlertRuleUID(sess, r.OrgID, r.Title) + if err != nil { + return fmt.Errorf("failed to generate UID for alert rule %q: %w", r.Title, err) + } + r.UID = uid + r.Version = 1 + if err := st.validateAlertRule(r); err != nil { + return err + } + if err := (&r).PreSave(TimeNow); err != nil { + return err + } + newRules = append(newRules, r) + ruleVersions = append(ruleVersions, ngmodels.AlertRuleVersion{ + RuleOrgID: r.OrgID, + RuleUID: r.UID, + RuleNamespaceUID: r.NamespaceUID, + RuleGroup: r.RuleGroup, + ParentVersion: 0, + Version: r.Version, + Created: r.Updated, + Condition: r.Condition, + Title: r.Title, + Data: r.Data, + IntervalSeconds: r.IntervalSeconds, + NoDataState: r.NoDataState, + ExecErrState: r.ExecErrState, + For: r.For, + Annotations: r.Annotations, + Labels: r.Labels, + }) + } + if len(newRules) > 0 { + if _, err := sess.Insert(&newRules); err != nil { + if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) { + return ngmodels.ErrAlertRuleUniqueConstraintViolation + } + return fmt.Errorf("failed to create new rules: %w", err) + } + } + + if len(ruleVersions) > 0 { + if _, err := sess.Insert(&ruleVersions); err != nil { + return fmt.Errorf("failed to create new rule versions: %w", err) + } + } + + return nil + }) +} + +// UpdateAlertRules is a handler for creating/updating alert rules. +func (st DBstore) UpdateAlertRules(ctx context.Context, rules []UpdateRule) error { return st.SQLStore.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { newRules := make([]ngmodels.AlertRule, 0, len(rules)) ruleVersions := make([]ngmodels.AlertRuleVersion, 0, len(rules)) for _, r := range rules { var parentVersion int64 - switch r.Existing { - case nil: // new rule - uid, err := GenerateNewAlertRuleUID(sess, r.New.OrgID, r.New.Title) - if err != nil { - return fmt.Errorf("failed to generate UID for alert rule %q: %w", r.New.Title, err) - } - r.New.UID = uid - r.New.Version = 1 - - if err := st.validateAlertRule(r.New); err != nil { - return err - } - - if err := (&r.New).PreSave(TimeNow); err != nil { - return err - } - newRules = append(newRules, r.New) - default: - r.New.ID = r.Existing.ID - r.New.Version = r.Existing.Version + 1 - - if err := st.validateAlertRule(r.New); err != nil { - return err - } - - if err := (&r.New).PreSave(TimeNow); err != nil { - return err - } - - // no way to update multiple rules at once - if _, err := sess.ID(r.Existing.ID).AllCols().Update(r.New); err != nil { - if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) { - return ngmodels.ErrAlertRuleUniqueConstraintViolation - } - return fmt.Errorf("failed to update rule [%s] %s: %w", r.New.UID, r.New.Title, err) - } - parentVersion = r.Existing.Version + r.New.ID = r.Existing.ID + r.New.Version = r.Existing.Version + 1 + if err := st.validateAlertRule(r.New); err != nil { + return err } - + if err := (&r.New).PreSave(TimeNow); err != nil { + return err + } + // no way to update multiple rules at once + if _, err := sess.ID(r.Existing.ID).AllCols().Update(r.New); err != nil { + if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) { + return ngmodels.ErrAlertRuleUniqueConstraintViolation + } + return fmt.Errorf("failed to update rule [%s] %s: %w", r.New.UID, r.New.Title, err) + } + parentVersion = r.Existing.Version ruleVersions = append(ruleVersions, ngmodels.AlertRuleVersion{ RuleOrgID: r.New.OrgID, RuleUID: r.New.UID, @@ -172,7 +208,6 @@ func (st DBstore) UpsertAlertRules(ctx context.Context, rules []UpsertRule) erro Labels: r.New.Labels, }) } - if len(newRules) > 0 { if _, err := sess.Insert(&newRules); err != nil { if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) { @@ -181,13 +216,11 @@ func (st DBstore) UpsertAlertRules(ctx context.Context, rules []UpsertRule) erro return fmt.Errorf("failed to create new rules: %w", err) } } - if len(ruleVersions) > 0 { if _, err := sess.Insert(&ruleVersions); err != nil { return fmt.Errorf("failed to create new rule versions: %w", err) } } - return nil }) } diff --git a/pkg/services/ngalert/store/testing.go b/pkg/services/ngalert/store/testing.go index 396bb4431a6..0c69fd5f522 100644 --- a/pkg/services/ngalert/store/testing.go +++ b/pkg/services/ngalert/store/testing.go @@ -228,7 +228,17 @@ func (f *FakeRuleStore) GetNamespaceByTitle(_ context.Context, title string, org return nil, fmt.Errorf("not found") } -func (f *FakeRuleStore) UpsertAlertRules(_ context.Context, q []UpsertRule) error { +func (f *FakeRuleStore) UpdateAlertRules(_ context.Context, q []UpdateRule) error { + f.mtx.Lock() + defer f.mtx.Unlock() + f.RecordedOps = append(f.RecordedOps, q) + if err := f.Hook(q); err != nil { + return err + } + return nil +} + +func (f *FakeRuleStore) InsertAlertRules(_ context.Context, q []models.AlertRule) error { f.mtx.Lock() defer f.mtx.Unlock() f.RecordedOps = append(f.RecordedOps, q) diff --git a/pkg/services/ngalert/tests/util.go b/pkg/services/ngalert/tests/util.go index 35ef0930872..19de8f046a2 100644 --- a/pkg/services/ngalert/tests/util.go +++ b/pkg/services/ngalert/tests/util.go @@ -81,35 +81,34 @@ func CreateTestAlertRule(t *testing.T, ctx context.Context, dbstore *store.DBsto func CreateTestAlertRuleWithLabels(t *testing.T, ctx context.Context, dbstore *store.DBstore, intervalSeconds int64, orgID int64, labels map[string]string) *models.AlertRule { ruleGroup := fmt.Sprintf("ruleGroup-%s", util.GenerateShortUID()) - err := dbstore.UpsertAlertRules(ctx, []store.UpsertRule{ + err := dbstore.InsertAlertRules(ctx, []models.AlertRule{ { - New: models.AlertRule{ - ID: 0, - OrgID: orgID, - Title: fmt.Sprintf("an alert definition %s", util.GenerateShortUID()), - Condition: "A", - Data: []models.AlertQuery{ - { - Model: json.RawMessage(`{ + + ID: 0, + OrgID: orgID, + Title: fmt.Sprintf("an alert definition %s", util.GenerateShortUID()), + Condition: "A", + Data: []models.AlertQuery{ + { + Model: json.RawMessage(`{ "datasourceUid": "-100", "type":"math", "expression":"2 + 2 > 1" }`), - RelativeTimeRange: models.RelativeTimeRange{ - From: models.Duration(5 * time.Hour), - To: models.Duration(3 * time.Hour), - }, - RefID: "A", + RelativeTimeRange: models.RelativeTimeRange{ + From: models.Duration(5 * time.Hour), + To: models.Duration(3 * time.Hour), }, + RefID: "A", }, - Labels: labels, - Annotations: map[string]string{"testAnnoKey": "testAnnoValue"}, - IntervalSeconds: intervalSeconds, - NamespaceUID: "namespace", - RuleGroup: ruleGroup, - NoDataState: models.NoData, - ExecErrState: models.AlertingErrState, }, + Labels: labels, + Annotations: map[string]string{"testAnnoKey": "testAnnoValue"}, + IntervalSeconds: intervalSeconds, + NamespaceUID: "namespace", + RuleGroup: ruleGroup, + NoDataState: models.NoData, + ExecErrState: models.AlertingErrState, }, }) require.NoError(t, err) diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index 7af16c013d0..734d1bffbab 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -356,41 +356,8 @@ func TestAlertRuleConflictingTitle(t *testing.T) { Login: "admin", }) - interval, err := model.ParseDuration("1m") - require.NoError(t, err) + rules := newTestingRuleConfig(t) - rules := apimodels.PostableRuleGroupConfig{ - Name: "arulegroup", - Rules: []apimodels.PostableExtendedRuleNode{ - { - ApiRuleNode: &apimodels.ApiRuleNode{ - For: interval, - Labels: map[string]string{"label1": "val1"}, - Annotations: map[string]string{"annotation1": "val1"}, - }, - // this rule does not explicitly set no data and error states - // therefore it should get the default values - GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ - Title: "AlwaysFiring", - Condition: "A", - Data: []ngmodels.AlertQuery{ - { - RefID: "A", - RelativeTimeRange: ngmodels.RelativeTimeRange{ - From: ngmodels.Duration(time.Duration(5) * time.Hour), - To: ngmodels.Duration(time.Duration(3) * time.Hour), - }, - DatasourceUID: "-100", - Model: json.RawMessage(`{ - "type": "math", - "expression": "2 + 3 > 1" - }`), - }, - }, - }, - }, - }, - } buf := bytes.Buffer{} enc := json.NewEncoder(&buf) err = enc.Encode(&rules) @@ -410,7 +377,22 @@ func TestAlertRuleConflictingTitle(t *testing.T) { assert.Equal(t, http.StatusAccepted, resp.StatusCode) require.JSONEq(t, `{"message":"rule group updated successfully"}`, string(b)) + // fetch the created rules, so we can get the uid's and trigger + // and update by reusing the uid's + resp, err = http.Get(u + "/" + rules.Name) + require.NoError(t, err) + + var createdRuleGroup apimodels.GettableRuleGroupConfig + data, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + err = json.Unmarshal(data, &createdRuleGroup) + require.NoError(t, err) + require.Len(t, createdRuleGroup.Rules, 2) + t.Run("trying to create alert with same title under same folder should fail", func(t *testing.T) { + rules := newTestingRuleConfig(t) + buf := bytes.Buffer{} enc := json.NewEncoder(&buf) err = enc.Encode(&rules) @@ -428,10 +410,36 @@ func TestAlertRuleConflictingTitle(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) - require.JSONEq(t, `{"message": "failed to update rule group: failed to add or update rules: a conflicting alert rule is found: rule title under the same organisation and folder should be unique"}`, string(b)) + require.JSONEq(t, `{"message": "failed to update rule group: failed to add rules: a conflicting alert rule is found: rule title under the same organisation and folder should be unique"}`, string(b)) + }) + + t.Run("trying to update an alert to the title of an existing alert in the same folder should fail", func(t *testing.T) { + rules := newTestingRuleConfig(t) + rules.Rules[0].GrafanaManagedAlert.UID = createdRuleGroup.Rules[0].GrafanaManagedAlert.UID + rules.Rules[1].GrafanaManagedAlert.UID = createdRuleGroup.Rules[1].GrafanaManagedAlert.UID + rules.Rules[1].GrafanaManagedAlert.Title = "AlwaysFiring" + buf := bytes.Buffer{} + enc := json.NewEncoder(&buf) + err = enc.Encode(&rules) + require.NoError(t, err) + + u := fmt.Sprintf("http://admin:admin@%s/api/ruler/grafana/api/v1/rules/folder1", grafanaListedAddr) + // nolint:gosec + resp, err := http.Post(u, "application/json", &buf) + require.NoError(t, err) + t.Cleanup(func() { + err := resp.Body.Close() + require.NoError(t, err) + }) + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + + assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) + require.JSONEq(t, `{"message": "failed to update rule group: failed to update rules: a conflicting alert rule is found: rule title under the same organisation and folder should be unique"}`, string(b)) }) t.Run("trying to create alert with same title under another folder should succeed", func(t *testing.T) { + rules := newTestingRuleConfig(t) buf := bytes.Buffer{} enc := json.NewEncoder(&buf) err = enc.Encode(&rules) @@ -800,3 +808,71 @@ func TestRulerRulesFilterByDashboard(t *testing.T) { require.JSONEq(t, `{"message": "panel_id must be set with dashboard_uid"}`, string(b)) } } + +func newTestingRuleConfig(t *testing.T) apimodels.PostableRuleGroupConfig { + interval, err := model.ParseDuration("1m") + require.NoError(t, err) + + firstRule := apimodels.PostableExtendedRuleNode{ + ApiRuleNode: &apimodels.ApiRuleNode{ + For: interval, + Labels: map[string]string{"label1": "val1"}, + Annotations: map[string]string{"annotation1": "val1"}, + }, + // this rule does not explicitly set no data and error states + // therefore it should get the default values + GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ + Title: "AlwaysFiring", + Condition: "A", + Data: []ngmodels.AlertQuery{ + { + RefID: "A", + RelativeTimeRange: ngmodels.RelativeTimeRange{ + From: ngmodels.Duration(time.Duration(5) * time.Hour), + To: ngmodels.Duration(time.Duration(3) * time.Hour), + }, + DatasourceUID: "-100", + Model: json.RawMessage(`{ + "type": "math", + "expression": "2 + 3 > 1" + }`), + }, + }, + }, + } + secondRule := apimodels.PostableExtendedRuleNode{ + ApiRuleNode: &apimodels.ApiRuleNode{ + For: interval, + Labels: map[string]string{"label1": "val1"}, + Annotations: map[string]string{"annotation1": "val1"}, + }, + // this rule does not explicitly set no data and error states + // therefore it should get the default values + GrafanaManagedAlert: &apimodels.PostableGrafanaRule{ + Title: "AlwaysFiring2", + Condition: "A", + Data: []ngmodels.AlertQuery{ + { + RefID: "A", + RelativeTimeRange: ngmodels.RelativeTimeRange{ + From: ngmodels.Duration(time.Duration(5) * time.Hour), + To: ngmodels.Duration(time.Duration(3) * time.Hour), + }, + DatasourceUID: "-100", + Model: json.RawMessage(`{ + "type": "math", + "expression": "2 + 3 > 1" + }`), + }, + }, + }, + } + + return apimodels.PostableRuleGroupConfig{ + Name: "arulegroup", + Rules: []apimodels.PostableExtendedRuleNode{ + firstRule, + secondRule, + }, + } +} From ecd6cd4a929f026df589f15cb3ef51e101d3d400 Mon Sep 17 00:00:00 2001 From: idafurjes <36131195+idafurjes@users.noreply.github.com> Date: Thu, 14 Apr 2022 14:22:00 +0200 Subject: [PATCH 06/40] Split preference store (#46843) * Split preference store * Chore: Add tests to pref * Fix preference in wire * Rename and adjust * Add pref service test * Rename methods, add tests * Rename Preferences to Preference, names IDs correctly * Fix lint * Refactor Save * Refactor upsert Add new logic for QueryHistory Rename some fields according to go naming conventions Refactore tests * Roll back ID that breaks tests * Rename Id to ID in UpdatePreferenceQuery * Use preference as a model to modify store * Move pref store fakes to pref test file * Add integration tag for store tests * Adjust test Co-authored-by: yangkb09 --- pkg/server/wire.go | 3 +- pkg/services/preference/model.go | 100 ++++ pkg/services/preference/pref.go | 13 + pkg/services/preference/prefimpl/pref.go | 203 ++++++++ pkg/services/preference/prefimpl/pref_test.go | 446 ++++++++++++++++++ pkg/services/preference/prefimpl/store.go | 88 ++++ .../preference/prefimpl/store_test.go | 170 +++++++ pkg/services/preference/preftest/fake.go | 32 ++ 8 files changed, 1054 insertions(+), 1 deletion(-) create mode 100644 pkg/services/preference/model.go create mode 100644 pkg/services/preference/pref.go create mode 100644 pkg/services/preference/prefimpl/pref.go create mode 100644 pkg/services/preference/prefimpl/pref_test.go create mode 100644 pkg/services/preference/prefimpl/store.go create mode 100644 pkg/services/preference/prefimpl/store_test.go create mode 100644 pkg/services/preference/preftest/fake.go diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 61a10765e42..74418497d83 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -65,13 +65,13 @@ import ( plugindashboardsservice "github.com/grafana/grafana/pkg/services/plugindashboards/service" "github.com/grafana/grafana/pkg/services/pluginsettings" pluginSettings "github.com/grafana/grafana/pkg/services/pluginsettings/service" + "github.com/grafana/grafana/pkg/services/preference/prefimpl" "github.com/grafana/grafana/pkg/services/query" "github.com/grafana/grafana/pkg/services/queryhistory" "github.com/grafana/grafana/pkg/services/quota" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/services/schemaloader" "github.com/grafana/grafana/pkg/services/search" - "github.com/grafana/grafana/pkg/services/searchV2" "github.com/grafana/grafana/pkg/services/secrets" secretsDatabase "github.com/grafana/grafana/pkg/services/secrets/database" @@ -235,6 +235,7 @@ var wireBasicSet = wire.NewSet( wire.Bind(new(alerting.DashAlertExtractor), new(*alerting.DashAlertExtractorService)), comments.ProvideService, guardian.ProvideService, + prefimpl.ProvideService, avatar.ProvideAvatarCacheServer, authproxy.ProvideAuthProxy, statscollector.ProvideService, diff --git a/pkg/services/preference/model.go b/pkg/services/preference/model.go new file mode 100644 index 00000000000..ae8b5ca665b --- /dev/null +++ b/pkg/services/preference/model.go @@ -0,0 +1,100 @@ +package pref + +import ( + "bytes" + "encoding/json" + "errors" + "time" +) + +var ErrPrefNotFound = errors.New("preference not found") + +type Preference struct { + ID int64 `xorm:"pk autoincr 'id'"` + OrgID int64 `xorm:"org_id"` + UserID int64 `xorm:"user_id"` + TeamID int64 `xorm:"team_id"` + Teams []int64 `xorm:"extends"` + Version int + HomeDashboardID int64 `xorm:"home_dashboard_id"` + Timezone string + WeekStart string + Theme string + Created time.Time + Updated time.Time + JSONData *PreferenceJSONData `xorm:"json_data"` +} + +type GetPreferenceWithDefaultsQuery struct { + Teams []int64 + OrgID int64 + UserID int64 +} + +type GetPreferenceQuery struct { + OrgID int64 + UserID int64 + TeamID int64 +} + +type SavePreferenceCommand struct { + UserID int64 + OrgID int64 + TeamID int64 + + HomeDashboardID int64 `json:"homeDashboardId,omitempty"` + Timezone string `json:"timezone,omitempty"` + WeekStart string `json:"weekStart,omitempty"` + Theme string `json:"theme,omitempty"` + Navbar *NavbarPreference `json:"navbar,omitempty"` + QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"` +} + +type PatchPreferenceCommand struct { + UserID int64 + OrgID int64 + TeamID int64 + + HomeDashboardID *int64 `json:"homeDashboardId,omitempty"` + Timezone *string `json:"timezone,omitempty"` + WeekStart *string `json:"weekStart,omitempty"` + Theme *string `json:"theme,omitempty"` + Navbar *NavbarPreference `json:"navbar,omitempty"` + QueryHistory *QueryHistoryPreference `json:"queryHistory,omitempty"` +} + +type NavLink struct { + ID string `json:"id,omitempty"` + Text string `json:"text,omitempty"` + Url string `json:"url,omitempty"` + Target string `json:"target,omitempty"` +} + +type NavbarPreference struct { + SavedItems []NavLink `json:"savedItems"` +} + +type PreferenceJSONData struct { + Navbar NavbarPreference `json:"navbar"` + QueryHistory QueryHistoryPreference `json:"queryHistory"` +} + +type QueryHistoryPreference struct { + HomeTab string `json:"homeTab"` +} + +func (j *PreferenceJSONData) FromDB(data []byte) error { + dec := json.NewDecoder(bytes.NewBuffer(data)) + dec.UseNumber() + return dec.Decode(j) +} + +func (j *PreferenceJSONData) ToDB() ([]byte, error) { + if j == nil { + return nil, nil + } + + return json.Marshal(j) +} + +func (p Preference) TableName() string { return "preferences" } diff --git a/pkg/services/preference/pref.go b/pkg/services/preference/pref.go new file mode 100644 index 00000000000..5cf17789a25 --- /dev/null +++ b/pkg/services/preference/pref.go @@ -0,0 +1,13 @@ +package pref + +import ( + "context" +) + +type Service interface { + GetWithDefaults(context.Context, *GetPreferenceWithDefaultsQuery) (*Preference, error) + Get(context.Context, *GetPreferenceQuery) (*Preference, error) + Save(context.Context, *SavePreferenceCommand) error + Patch(ctx context.Context, cmd *PatchPreferenceCommand) error + GetDefaults() *Preference +} diff --git a/pkg/services/preference/prefimpl/pref.go b/pkg/services/preference/prefimpl/pref.go new file mode 100644 index 00000000000..c43fb7d98c5 --- /dev/null +++ b/pkg/services/preference/prefimpl/pref.go @@ -0,0 +1,203 @@ +package prefimpl + +import ( + "context" + "errors" + "time" + + pref "github.com/grafana/grafana/pkg/services/preference" + "github.com/grafana/grafana/pkg/services/sqlstore/db" + "github.com/grafana/grafana/pkg/setting" +) + +type Service struct { + store store + cfg *setting.Cfg +} + +func ProvideService(db db.DB, cfg *setting.Cfg) *Service { + return &Service{ + store: &sqlStore{ + db: db, + }, + cfg: cfg, + } +} + +func (s *Service) GetWithDefaults(ctx context.Context, query *pref.GetPreferenceWithDefaultsQuery) (*pref.Preference, error) { + listQuery := &pref.Preference{ + Teams: query.Teams, + OrgID: query.OrgID, + UserID: query.UserID, + } + prefs, err := s.store.List(ctx, listQuery) + if err != nil { + return nil, err + } + + res := s.GetDefaults() + for _, p := range prefs { + if p.Theme != "" { + res.Theme = p.Theme + } + if p.Timezone != "" { + res.Timezone = p.Timezone + } + if p.WeekStart != "" { + res.WeekStart = p.WeekStart + } + if p.HomeDashboardID != 0 { + res.HomeDashboardID = p.HomeDashboardID + } + if p.JSONData != nil { + res.JSONData = p.JSONData + } + } + + return res, err +} + +func (s *Service) Get(ctx context.Context, query *pref.GetPreferenceQuery) (*pref.Preference, error) { + getPref := &pref.Preference{ + OrgID: query.OrgID, + UserID: query.UserID, + TeamID: query.TeamID, + } + prefs, err := s.store.Get(ctx, getPref) + if err != nil && !errors.Is(err, pref.ErrPrefNotFound) { + return nil, err + } + return prefs, nil +} + +func (s *Service) Save(ctx context.Context, cmd *pref.SavePreferenceCommand) error { + preference, err := s.store.Get(ctx, &pref.Preference{ + OrgID: cmd.OrgID, + UserID: cmd.UserID, + TeamID: cmd.TeamID, + }) + if err != nil { + if errors.Is(err, pref.ErrPrefNotFound) { + preference := &pref.Preference{ + UserID: cmd.UserID, + OrgID: cmd.OrgID, + TeamID: cmd.TeamID, + HomeDashboardID: cmd.HomeDashboardID, + Timezone: cmd.Timezone, + WeekStart: cmd.WeekStart, + Theme: cmd.Theme, + Created: time.Now(), + Updated: time.Now(), + } + _, err = s.store.Insert(ctx, preference) + if err != nil { + return err + } + } + return err + } + preference.Timezone = cmd.Timezone + preference.WeekStart = cmd.WeekStart + preference.Theme = cmd.Theme + preference.Updated = time.Now() + preference.Version += 1 + preference.JSONData = &pref.PreferenceJSONData{} + + if cmd.Navbar != nil { + preference.JSONData.Navbar = *cmd.Navbar + } + if cmd.QueryHistory != nil { + preference.JSONData.QueryHistory = *cmd.QueryHistory + } + return s.store.Update(ctx, preference) +} + +func (s *Service) Patch(ctx context.Context, cmd *pref.PatchPreferenceCommand) error { + var exists bool + preference, err := s.store.Get(ctx, &pref.Preference{ + OrgID: cmd.OrgID, + UserID: cmd.UserID, + TeamID: cmd.TeamID, + }) + if err != nil && !errors.Is(err, pref.ErrPrefNotFound) { + return err + } + + if errors.Is(err, pref.ErrPrefNotFound) { + preference = &pref.Preference{ + UserID: cmd.UserID, + OrgID: cmd.OrgID, + TeamID: cmd.TeamID, + Created: time.Now(), + JSONData: &pref.PreferenceJSONData{}, + } + } else { + exists = true + } + + if cmd.Navbar != nil { + if preference.JSONData == nil { + preference.JSONData = &pref.PreferenceJSONData{} + } + if cmd.Navbar.SavedItems != nil { + preference.JSONData.Navbar.SavedItems = cmd.Navbar.SavedItems + } + } + + if cmd.QueryHistory != nil { + if preference.JSONData == nil { + preference.JSONData = &pref.PreferenceJSONData{} + } + if cmd.QueryHistory.HomeTab != "" { + preference.JSONData.QueryHistory.HomeTab = cmd.QueryHistory.HomeTab + } + } + + if cmd.HomeDashboardID != nil { + preference.HomeDashboardID = *cmd.HomeDashboardID + } + + if cmd.Timezone != nil { + preference.Timezone = *cmd.Timezone + } + + if cmd.WeekStart != nil { + preference.WeekStart = *cmd.WeekStart + } + + if cmd.Theme != nil { + preference.Theme = *cmd.Theme + } + + preference.Updated = time.Now() + preference.Version += 1 + + // Wrap this in an if statement to maintain backwards compatibility + if cmd.Navbar != nil { + if preference.JSONData == nil { + preference.JSONData = &pref.PreferenceJSONData{} + } + if cmd.Navbar.SavedItems != nil { + preference.JSONData.Navbar.SavedItems = cmd.Navbar.SavedItems + } + } + + if exists { + err = s.store.Update(ctx, preference) + } else { + _, err = s.store.Insert(ctx, preference) + } + return err +} + +func (s *Service) GetDefaults() *pref.Preference { + defaults := &pref.Preference{ + Theme: s.cfg.DefaultTheme, + Timezone: s.cfg.DateFormats.DefaultTimezone, + WeekStart: s.cfg.DateFormats.DefaultWeekStart, + HomeDashboardID: 0, + JSONData: &pref.PreferenceJSONData{}, + } + + return defaults +} diff --git a/pkg/services/preference/prefimpl/pref_test.go b/pkg/services/preference/prefimpl/pref_test.go new file mode 100644 index 00000000000..b1e439b6830 --- /dev/null +++ b/pkg/services/preference/prefimpl/pref_test.go @@ -0,0 +1,446 @@ +package prefimpl + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + pref "github.com/grafana/grafana/pkg/services/preference" + "github.com/grafana/grafana/pkg/setting" + "github.com/stretchr/testify/require" +) + +func TestPreferencesService(t *testing.T) { + prefStoreFake := newPreferenceStoreFake() + prefService := &Service{ + store: prefStoreFake, + } + + emptyNavbarPreferences := pref.NavbarPreference{} + userNavbarPreferences := pref.NavbarPreference{ + SavedItems: []pref.NavLink{{ + ID: "explore", + Text: "Explore", + Url: "/explore", + }}, + } + orgNavbarPreferences := pref.NavbarPreference{ + SavedItems: []pref.NavLink{{ + ID: "alerting", + Text: "Alerting", + Url: "/alerting", + }}, + } + team1NavbarPreferences := pref.NavbarPreference{ + SavedItems: []pref.NavLink{{ + ID: "dashboards", + Text: "Dashboards", + Url: "/dashboards", + }}, + } + team2NavbarPreferences := pref.NavbarPreference{ + SavedItems: []pref.NavLink{{ + ID: "home", + Text: "Home", + Url: "/home", + }}, + } + + emptyQueryPreference := pref.QueryHistoryPreference{} + + queryPreference := pref.QueryHistoryPreference{ + HomeTab: "hometab", + } + + queryPreference2 := pref.QueryHistoryPreference{ + HomeTab: "hometab", + } + + emptyPreferencesJsonData := pref.PreferenceJSONData{ + Navbar: emptyNavbarPreferences, + } + userPreferencesJsonData := pref.PreferenceJSONData{ + Navbar: userNavbarPreferences, + QueryHistory: queryPreference, + } + orgPreferencesJsonData := pref.PreferenceJSONData{ + Navbar: orgNavbarPreferences, + } + team2PreferencesJsonData := pref.PreferenceJSONData{ + Navbar: team2NavbarPreferences, + } + team1PreferencesJsonData := pref.PreferenceJSONData{ + Navbar: team1NavbarPreferences, + } + + t.Run("GetDefaults should return defaults", func(t *testing.T) { + prefService.cfg = setting.NewCfg() + prefService.cfg.DefaultTheme = "light" + prefService.cfg.DateFormats.DefaultTimezone = "UTC" + + preferences := prefService.GetDefaults() + expected := &pref.Preference{ + Theme: "light", + Timezone: "UTC", + HomeDashboardID: 0, + JSONData: &pref.PreferenceJSONData{}, + } + if diff := cmp.Diff(expected, preferences); diff != "" { + t.Fatalf("Result mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("GetDefaults with no saved preferences should return defaults", func(t *testing.T) { + prefStoreFake.ExpectedPreference = &pref.Preference{ + Theme: "light", + Timezone: "UTC", + } + query := &pref.GetPreferenceWithDefaultsQuery{} + preferences, err := prefService.GetWithDefaults(context.Background(), query) + require.NoError(t, err) + expected := &pref.Preference{ + Theme: "light", + Timezone: "UTC", + HomeDashboardID: 0, + JSONData: &emptyPreferencesJsonData, + } + if diff := cmp.Diff(expected, preferences); diff != "" { + t.Fatalf("Result mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("GetWithDefaults with saved org and user home dashboard should return user home dashboard", func(t *testing.T) { + prefStoreFake.ExpectedPreference = &pref.Preference{} + prefStoreFake.ExpectedListPreferences = []*pref.Preference{ + { + OrgID: 1, + HomeDashboardID: 1, + Theme: "dark", + Timezone: "UTC", + }, + { + OrgID: 1, + UserID: 1, + HomeDashboardID: 4, + Theme: "light", + WeekStart: "1", + }, + } + query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, UserID: 1} + preferences, err := prefService.GetWithDefaults(context.Background(), query) + require.NoError(t, err) + expected := &pref.Preference{ + Theme: "light", + Timezone: "UTC", + WeekStart: "1", + HomeDashboardID: 4, + JSONData: &pref.PreferenceJSONData{}, + } + if diff := cmp.Diff(expected, preferences); diff != "" { + t.Fatalf("Result mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("GetWithDefaults with saved org and other user home dashboard should return org home dashboard", func(t *testing.T) { + prefStoreFake.ExpectedPreference = &pref.Preference{} + prefStoreFake.ExpectedListPreferences = []*pref.Preference{ + { + OrgID: 1, + HomeDashboardID: 1, + Theme: "dark", + Timezone: "UTC", + WeekStart: "1", + }, + { + OrgID: 1, + UserID: 1, + HomeDashboardID: 4, + Theme: "light", + Timezone: "browser", + WeekStart: "2", + }, + } + prefService.GetDefaults().HomeDashboardID = 1 + query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, UserID: 2} + preferences, err := prefService.GetWithDefaults(context.Background(), query) + require.NoError(t, err) + expected := &pref.Preference{ + Theme: "light", + Timezone: "browser", + WeekStart: "2", + HomeDashboardID: 4, + JSONData: &pref.PreferenceJSONData{}, + } + if diff := cmp.Diff(expected, preferences); diff != "" { + t.Fatalf("Result mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("GetPreferencesWithDefaults with saved org and user json data should return user json data", func(t *testing.T) { + prefStoreFake.ExpectedPreference = &pref.Preference{} + prefStoreFake.ExpectedListPreferences = []*pref.Preference{ + { + OrgID: 1, + JSONData: &orgPreferencesJsonData, + }, + { + OrgID: 1, + UserID: 1, + JSONData: &userPreferencesJsonData, + }, + } + query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, UserID: 1} + preference, err := prefService.GetWithDefaults(context.Background(), query) + require.NoError(t, err) + require.Equal(t, &pref.Preference{ + Theme: "light", + JSONData: &userPreferencesJsonData, + Timezone: "UTC", + }, preference) + }) + + t.Run("GetWithDefaults with saved org and teams json data should return last team json data", func(t *testing.T) { + prefStoreFake.ExpectedPreference = &pref.Preference{} + prefStoreFake.ExpectedListPreferences = []*pref.Preference{ + { + OrgID: 1, + JSONData: &orgPreferencesJsonData, + }, + { + OrgID: 1, + TeamID: 2, + JSONData: &team1PreferencesJsonData, + }, + { + OrgID: 1, + TeamID: 3, + JSONData: &team2PreferencesJsonData, + }, + } + query := &pref.GetPreferenceWithDefaultsQuery{ + OrgID: 1, Teams: []int64{2, 3}, + } + preference, err := prefService.GetWithDefaults(context.Background(), query) + require.NoError(t, err) + require.Equal(t, &pref.Preference{ + Timezone: "UTC", + Theme: "light", + JSONData: &team2PreferencesJsonData, + }, preference) + }) + + t.Run("GetWithDefaults with saved org and teams home dashboard should return last team home dashboard", func(t *testing.T) { + prefStoreFake.ExpectedPreference = &pref.Preference{ + Theme: "dark", + Timezone: "UTC", + } + prefStoreFake.ExpectedListPreferences = []*pref.Preference{ + { + OrgID: 1, + HomeDashboardID: 1, + Theme: "light", + Timezone: "browser", + WeekStart: "1", + }, + { + OrgID: 1, + UserID: 1, + HomeDashboardID: 4, + Theme: "light", + Timezone: "browser", + WeekStart: "2", + }, + } + + query := &pref.GetPreferenceWithDefaultsQuery{OrgID: 1, Teams: []int64{2, 3}} + preferences, err := prefService.GetWithDefaults(context.Background(), query) + require.NoError(t, err) + expected := &pref.Preference{ + Theme: "light", + Timezone: "browser", + WeekStart: "2", + HomeDashboardID: 4, + JSONData: &pref.PreferenceJSONData{}, + } + if diff := cmp.Diff(expected, preferences); diff != "" { + t.Fatalf("Result mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("SavePreferences for a user should store correct values", func(t *testing.T) { + prefStoreFake.ExpectedPreference = &pref.Preference{ + ID: 1, + OrgID: 1, + UserID: 3, + TeamID: 6, + HomeDashboardID: 5, + Timezone: "browser", + WeekStart: "1", + Theme: "dark", + } + err := prefService.Save(context.Background(), + &pref.SavePreferenceCommand{ + Theme: "dark", + Timezone: "browser", + HomeDashboardID: 5, + WeekStart: "1"}, + ) + require.NoError(t, err) + }) + + t.Run("SavePreferences for a user should store correct values, when preference not found", func(t *testing.T) { + prefStoreFake.ExpectedGetError = pref.ErrPrefNotFound + prefStoreFake.ExpectedPreference = &pref.Preference{ + ID: 1, + OrgID: 1, + UserID: 3, + TeamID: 6, + HomeDashboardID: 5, + Timezone: "browser", + WeekStart: "1", + Theme: "dark", + } + err := prefService.Save(context.Background(), + &pref.SavePreferenceCommand{ + Theme: "dark", + Timezone: "browser", + HomeDashboardID: 5, + WeekStart: "1", + }, + ) + require.NoError(t, err) + prefStoreFake.ExpectedGetError = nil + }) + + t.Run("SavePreferences for a user should store correct values with nav and query history", func(t *testing.T) { + prefStoreFake.ExpectedPreference = &pref.Preference{ + ID: 1, + OrgID: 1, + UserID: 3, + TeamID: 6, + HomeDashboardID: 5, + Timezone: "browser", + WeekStart: "1", + Theme: "dark", + JSONData: &userPreferencesJsonData, + } + err := prefService.Save(context.Background(), + &pref.SavePreferenceCommand{ + Theme: "dark", + Timezone: "browser", + HomeDashboardID: 5, + WeekStart: "1", + Navbar: &userNavbarPreferences, + QueryHistory: &emptyQueryPreference, + }, + ) + require.NoError(t, err) + }) + + t.Run("Get for a user should store correct values", func(t *testing.T) { + prefStoreFake.ExpectedPreference = &pref.Preference{ + HomeDashboardID: 5, + Timezone: "browser", + WeekStart: "1", + Theme: "dark", + } + preference, err := prefService.Get(context.Background(), &pref.GetPreferenceQuery{}) + require.NoError(t, err) + + expected := &pref.Preference{ + ID: preference.ID, + Version: preference.Version, + HomeDashboardID: 5, + Timezone: "browser", + WeekStart: "1", + Theme: "dark", + Created: preference.Created, + Updated: preference.Updated, + } + if diff := cmp.Diff(expected, preference); diff != "" { + t.Fatalf("Result mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("Patch for a user should store correct values", func(t *testing.T) { + darkTheme := "dark" + prefStoreFake.ExpectedPreference = &pref.Preference{ + HomeDashboardID: 5, + Timezone: "browser", + WeekStart: "1", + Theme: "dark", + JSONData: &userPreferencesJsonData, + } + err := prefService.Patch(context.Background(), + &pref.PatchPreferenceCommand{ + Theme: &darkTheme, + Navbar: &userNavbarPreferences, + QueryHistory: &queryPreference2, + }) + require.NoError(t, err) + }) + + t.Run("Patch for a user should store correct values, without navbar and query history", func(t *testing.T) { + darkTheme := "dark" + prefStoreFake.ExpectedPreference = &pref.Preference{ + HomeDashboardID: 5, + Timezone: "browser", + WeekStart: "1", + Theme: "dark", + } + err := prefService.Patch(context.Background(), + &pref.PatchPreferenceCommand{ + Theme: &darkTheme, + Navbar: &userNavbarPreferences, + QueryHistory: &queryPreference2, + }) + require.NoError(t, err) + }) + + t.Run("Patch for a user should store correct values, when preference not found", func(t *testing.T) { + timezone := "browser" + weekStart := "1" + homeDashboardID := int64(5) + prefStoreFake.ExpectedGetError = pref.ErrPrefNotFound + prefStoreFake.ExpectedPreference = nil + + err := prefService.Patch(context.Background(), + &pref.PatchPreferenceCommand{ + HomeDashboardID: &homeDashboardID, + Timezone: &timezone, + WeekStart: &weekStart, + Navbar: &emptyNavbarPreferences, + QueryHistory: &emptyQueryPreference, + }) + require.NoError(t, err) + prefStoreFake.ExpectedGetError = nil + }) +} + +type FakePreferenceStore struct { + ExpectedPreference *pref.Preference + ExpectedListPreferences []*pref.Preference + ExpectedID int64 + ExpectedError error + ExpectedGetError error +} + +func newPreferenceStoreFake() *FakePreferenceStore { + return &FakePreferenceStore{} +} + +func (f *FakePreferenceStore) List(ctx context.Context, query *pref.Preference) ([]*pref.Preference, error) { + return f.ExpectedListPreferences, f.ExpectedError +} + +func (f *FakePreferenceStore) Get(ctx context.Context, query *pref.Preference) (*pref.Preference, error) { + return f.ExpectedPreference, f.ExpectedGetError +} + +func (f *FakePreferenceStore) Insert(ctx context.Context, cmd *pref.Preference) (int64, error) { + return f.ExpectedID, f.ExpectedError +} + +func (f *FakePreferenceStore) Update(ctx context.Context, cmd *pref.Preference) error { + return f.ExpectedError +} diff --git a/pkg/services/preference/prefimpl/store.go b/pkg/services/preference/prefimpl/store.go new file mode 100644 index 00000000000..139b723363b --- /dev/null +++ b/pkg/services/preference/prefimpl/store.go @@ -0,0 +1,88 @@ +package prefimpl + +import ( + "context" + "strings" + + pref "github.com/grafana/grafana/pkg/services/preference" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/grafana/grafana/pkg/services/sqlstore/db" +) + +type store interface { + Get(context.Context, *pref.Preference) (*pref.Preference, error) + List(context.Context, *pref.Preference) ([]*pref.Preference, error) + Insert(context.Context, *pref.Preference) (int64, error) + Update(context.Context, *pref.Preference) error +} + +type sqlStore struct { + db db.DB +} + +func (s *sqlStore) Get(ctx context.Context, query *pref.Preference) (*pref.Preference, error) { + var prefs pref.Preference + err := s.db.WithDbSession(ctx, func(sess *sqlstore.DBSession) error { + exist, err := sess.Where("org_id=? AND user_id=? AND team_id=?", query.OrgID, query.UserID, query.TeamID).Get(&prefs) + if err != nil { + return err + } + if !exist { + return pref.ErrPrefNotFound + } + return nil + }) + if err != nil { + return nil, err + } + return &prefs, nil +} + +func (s *sqlStore) List(ctx context.Context, query *pref.Preference) ([]*pref.Preference, error) { + prefs := make([]*pref.Preference, 0) + params := make([]interface{}, 0) + filter := "" + + if len(query.Teams) > 0 { + filter = "(org_id=? AND team_id IN (?" + strings.Repeat(",?", len(query.Teams)-1) + ")) OR " + params = append(params, query.OrgID) + for _, v := range query.Teams { + params = append(params, v) + } + } + + filter += "(org_id=? AND user_id=? AND team_id=0) OR (org_id=? AND team_id=0 AND user_id=0)" + params = append(params, query.OrgID) + params = append(params, query.UserID) + params = append(params, query.OrgID) + + err := s.db.WithDbSession(ctx, func(dbSession *sqlstore.DBSession) error { + err := dbSession.Where(filter, params...). + OrderBy("user_id ASC, team_id ASC"). + Find(&prefs) + + if err != nil { + return err + } + + return nil + }) + return prefs, err +} + +func (s *sqlStore) Update(ctx context.Context, cmd *pref.Preference) error { + return s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + _, err := sess.ID(cmd.ID).AllCols().Update(cmd) + return err + }) +} + +func (s *sqlStore) Insert(ctx context.Context, cmd *pref.Preference) (int64, error) { + var ID int64 + var err error + err = s.db.WithTransactionalDbSession(ctx, func(sess *sqlstore.DBSession) error { + ID, err = sess.Insert(cmd) + return err + }) + return ID, err +} diff --git a/pkg/services/preference/prefimpl/store_test.go b/pkg/services/preference/prefimpl/store_test.go new file mode 100644 index 00000000000..773b6dea57b --- /dev/null +++ b/pkg/services/preference/prefimpl/store_test.go @@ -0,0 +1,170 @@ +//go:build integration +// +build integration + +package prefimpl + +import ( + "context" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/grafana/grafana/pkg/models" + pref "github.com/grafana/grafana/pkg/services/preference" + "github.com/grafana/grafana/pkg/services/sqlstore" + "github.com/stretchr/testify/require" +) + +func TestPreferencesDataAccess(t *testing.T) { + ss := sqlstore.InitTestDB(t) + prefStore := sqlStore{db: ss} + orgNavbarPreferences := pref.NavbarPreference{ + SavedItems: []pref.NavLink{{ + ID: "alerting", + Text: "Alerting", + Url: "/alerting", + }}, + } + + t.Run("Get with saved org and user home dashboard returns not found", func(t *testing.T) { + query := &pref.Preference{OrgID: 1, UserID: 1, TeamID: 2} + prefs, err := prefStore.Get(context.Background(), query) + require.EqualError(t, err, pref.ErrPrefNotFound.Error()) + require.Nil(t, prefs) + }) + + t.Run("Get with saved org and user home dashboard should return user home dashboard", func(t *testing.T) { + _, err := prefStore.Insert(context.Background(), + &pref.Preference{ + OrgID: 1, + UserID: 1, + HomeDashboardID: 4, + TeamID: 2, + Created: time.Now(), + Updated: time.Now(), + }) + require.NoError(t, err) + + query := &pref.Preference{OrgID: 1, UserID: 1, TeamID: 2} + prefs, err := prefStore.Get(context.Background(), query) + require.NoError(t, err) + require.Equal(t, int64(4), prefs.HomeDashboardID) + }) + + t.Run("List with saved org and user home dashboard should return user home dashboard", func(t *testing.T) { + _, err := prefStore.Insert(context.Background(), + &pref.Preference{ + OrgID: 1, + UserID: 1, + TeamID: 3, + HomeDashboardID: 1, + Created: time.Now(), + Updated: time.Now(), + }) + require.NoError(t, err) + + query := &pref.Preference{OrgID: 1, UserID: 1, Teams: []int64{2}} + prefs, err := prefStore.List(context.Background(), query) + require.NoError(t, err) + require.Equal(t, int64(4), prefs[0].HomeDashboardID) + }) + + t.Run("List with saved org and other user home dashboard should return org home dashboard", func(t *testing.T) { + _, err := prefStore.Insert(context.Background(), + &pref.Preference{ + OrgID: 1, + UserID: 2, + TeamID: 3, + HomeDashboardID: 1, + Created: time.Now(), + Updated: time.Now(), + }) + require.NoError(t, err) + + query := &pref.Preference{OrgID: 1, UserID: 1, Teams: []int64{3}} + prefs, err := prefStore.List(context.Background(), query) + require.NoError(t, err) + require.Equal(t, int64(1), prefs[0].HomeDashboardID) + require.Equal(t, int64(1), prefs[1].HomeDashboardID) + }) + + t.Run("List with saved org and teams home dashboard should return last team home dashboard", func(t *testing.T) { + query := &pref.Preference{ + OrgID: 1, Teams: []int64{2, 3}, + } + prefs, err := prefStore.List(context.Background(), query) + require.NoError(t, err) + require.Equal(t, int64(4), prefs[0].HomeDashboardID) + require.Equal(t, int64(1), prefs[1].HomeDashboardID) + require.Equal(t, int64(1), prefs[2].HomeDashboardID) + }) + + t.Run("List with saved org and other teams home dashboard should return org home dashboard", func(t *testing.T) { + _, err := prefStore.Insert(context.Background(), &pref.Preference{OrgID: 1, HomeDashboardID: 1, Created: time.Now(), Updated: time.Now()}) + require.NoError(t, err) + _, err = prefStore.Insert(context.Background(), &pref.Preference{OrgID: 1, TeamID: 2, HomeDashboardID: 2, Created: time.Now(), Updated: time.Now()}) + require.NoError(t, err) + _, err = prefStore.Insert(context.Background(), &pref.Preference{OrgID: 1, TeamID: 3, HomeDashboardID: 3, Created: time.Now(), Updated: time.Now()}) + require.NoError(t, err) + + query := &pref.Preference{OrgID: 1} + prefs, err := prefStore.List(context.Background(), query) + require.NoError(t, err) + require.Equal(t, int64(1), prefs[0].HomeDashboardID) + }) + + t.Run("Update for a user should only modify a single value", func(t *testing.T) { + ss := sqlstore.InitTestDB(t) + prefStore := sqlStore{db: ss} + id, err := prefStore.Insert(context.Background(), &pref.Preference{ + UserID: models.SignedInUser{}.UserId, + Theme: "dark", + Timezone: "browser", + HomeDashboardID: 5, + WeekStart: "1", + JSONData: &pref.PreferenceJSONData{Navbar: orgNavbarPreferences}, + Created: time.Now(), + Updated: time.Now(), + }) + require.NoError(t, err) + + err = prefStore.Update(context.Background(), &pref.Preference{ + ID: id, + Theme: "dark", + HomeDashboardID: 5, + Timezone: "browser", + WeekStart: "1", + Created: time.Now(), + Updated: time.Now(), + JSONData: &pref.PreferenceJSONData{}, + }) + require.NoError(t, err) + query := &pref.Preference{} + prefs, err := prefStore.List(context.Background(), query) + require.NoError(t, err) + expected := &pref.Preference{ + ID: prefs[0].ID, + Version: prefs[0].Version, + HomeDashboardID: 5, + Timezone: "browser", + WeekStart: "1", + Theme: "dark", + JSONData: prefs[0].JSONData, + Created: prefs[0].Created, + Updated: prefs[0].Updated, + } + if diff := cmp.Diff(expected, prefs[0]); diff != "" { + t.Fatalf("Result mismatch (-want +got):\n%s", diff) + } + }) + t.Run("insert preference that does not exist", func(t *testing.T) { + _, err := prefStore.Insert(context.Background(), + &pref.Preference{ + UserID: models.SignedInUser{}.UserId, + Created: time.Now(), + Updated: time.Now(), + JSONData: &pref.PreferenceJSONData{}, + }) + require.NoError(t, err) + }) +} diff --git a/pkg/services/preference/preftest/fake.go b/pkg/services/preference/preftest/fake.go new file mode 100644 index 00000000000..f52acea2047 --- /dev/null +++ b/pkg/services/preference/preftest/fake.go @@ -0,0 +1,32 @@ +package preftest + +import ( + "context" + + pref "github.com/grafana/grafana/pkg/services/preference" +) + +type FakePreferenceService struct { + ExpectedPreference *pref.Preference + ExpectedError error +} + +func NewPreferenceServiceFake() *FakePreferenceService { + return &FakePreferenceService{} +} + +func (f *FakePreferenceService) GetWithDefaults(ctx context.Context, query *pref.GetPreferenceWithDefaultsQuery) (*pref.Preference, error) { + return f.ExpectedPreference, f.ExpectedError +} + +func (f *FakePreferenceService) Get(ctx context.Context, query *pref.GetPreferenceQuery) (*pref.Preference, error) { + return f.ExpectedPreference, f.ExpectedError +} + +func (f *FakePreferenceService) Save(ctx context.Context, cmd *pref.SavePreferenceCommand) error { + return f.ExpectedError +} + +func (f *FakePreferenceService) GetDefaults() *pref.Preference { + return f.ExpectedPreference +} From f79e0c68cc171474a66fc1bc642cc902ed47b563 Mon Sep 17 00:00:00 2001 From: Giordano Ricci Date: Thu, 14 Apr 2022 13:53:40 +0100 Subject: [PATCH 07/40] Docs: remove outdated section from Explore docs (#47768) --- docs/sources/explore/_index.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/docs/sources/explore/_index.md b/docs/sources/explore/_index.md index 207f5f7b7b1..b9552d5bc3b 100644 --- a/docs/sources/explore/_index.md +++ b/docs/sources/explore/_index.md @@ -55,14 +55,6 @@ In split view, timepickers for both panels can be linked (if you change one, the To close the newly created query, click on the Close Split button. -## Navigate between Explore and a dashboard - -To help accelerate workflows that involve regularly switching from Explore to a dashboard and vice-versa, Grafana provides you with the ability to return to the origin dashboard after navigating to Explore from the panel's dropdown. - -After you've navigated to Explore, you should notice a "Back" button in the Explore toolbar. Simply click it to return to the origin dashboard. To bring changes you make in Explore back to the dashboard, click the arrow next to the button to reveal a "Return to panel with changes" menu item. - -{{< figure src="/static/img/docs/explore/explore_return_dropdown-7-4.png" class="docs-image--no-shadow" max-width= "400px" caption="Screenshot of the expanded explore return dropdown" >}} - ## Share shortened link > **Note:** Available in Grafana 7.3 and later versions. From 782ec05d8c86424920d44ff0548ccb4859d8da7b Mon Sep 17 00:00:00 2001 From: Vardan Torosyan Date: Thu, 14 Apr 2022 15:09:55 +0200 Subject: [PATCH 08/40] Create fixed roles for reading API Keys and service accounts and fix listing of service account tokens (#47767) * Create fixed roles for reading API Keys and service accounts * Handle PR comments and fix the listing of token --- pkg/api/accesscontrol.go | 19 ++++++++++++++- .../serviceaccounts/database/database.go | 2 +- pkg/services/serviceaccounts/manager/roles.go | 24 ++++++++++++++++--- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go index dffe67e03a8..e1c3eed6acd 100644 --- a/pkg/api/accesscontrol.go +++ b/pkg/api/accesscontrol.go @@ -150,6 +150,23 @@ func (hs *HTTPServer) declareFixedRoles() error { Grants: []string{string(models.ROLE_VIEWER)}, } + apikeyReaderRole := ac.RoleRegistration{ + Role: ac.RoleDTO{ + Version: 1, + Name: "fixed:apikeys:reader", + DisplayName: "APIKeys reader", + Description: "Gives access to read api keys.", + Group: "API Keys", + Permissions: []ac.Permission{ + { + Action: ac.ActionAPIKeyRead, + Scope: ac.ScopeAPIKeysAll, + }, + }, + }, + Grants: []string{string(models.ROLE_ADMIN)}, + } + apikeyWriterRole := ac.RoleRegistration{ Role: ac.RoleDTO{ Version: 1, @@ -411,7 +428,7 @@ func (hs *HTTPServer) declareFixedRoles() error { orgMaintainerRole, teamsCreatorRole, teamsWriterRole, datasourcesExplorerRole, annotationsReaderRole, dashboardAnnotationsWriterRole, annotationsWriterRole, dashboardsCreatorRole, dashboardsReaderRole, dashboardsWriterRole, - foldersCreatorRole, foldersReaderRole, foldersWriterRole, apikeyWriterRole, + foldersCreatorRole, foldersReaderRole, foldersWriterRole, apikeyReaderRole, apikeyWriterRole, ) } diff --git a/pkg/services/serviceaccounts/database/database.go b/pkg/services/serviceaccounts/database/database.go index bd27655fc10..aa11773c867 100644 --- a/pkg/services/serviceaccounts/database/database.go +++ b/pkg/services/serviceaccounts/database/database.go @@ -157,7 +157,7 @@ func (s *ServiceAccountsStoreImpl) ListTokens(ctx context.Context, orgID int64, sess = dbSession. Join("inner", quotedUser, quotedUser+".id = api_key.service_account_id"). Where(quotedUser+".org_id=? AND "+quotedUser+".id=?", orgID, serviceAccountID). - Asc("name") + Asc("api_key.name") return sess.Find(&result) }) diff --git a/pkg/services/serviceaccounts/manager/roles.go b/pkg/services/serviceaccounts/manager/roles.go index 656f27881ec..7c323043a95 100644 --- a/pkg/services/serviceaccounts/manager/roles.go +++ b/pkg/services/serviceaccounts/manager/roles.go @@ -1,12 +1,30 @@ package manager import ( + "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/serviceaccounts" ) func RegisterRoles(ac accesscontrol.AccessControl) error { - role := accesscontrol.RoleRegistration{ + saReader := accesscontrol.RoleRegistration{ + Role: accesscontrol.RoleDTO{ + Version: 1, + Name: "fixed:serviceaccounts:reader", + DisplayName: "Service accounts reader", + Description: "Read service accounts and service account tokens.", + Group: "Service accounts", + Permissions: []accesscontrol.Permission{ + { + Action: serviceaccounts.ActionRead, + Scope: serviceaccounts.ScopeAll, + }, + }, + }, + Grants: []string{string(models.ROLE_ADMIN)}, + } + + saWriter := accesscontrol.RoleRegistration{ Role: accesscontrol.RoleDTO{ Version: 4, Name: "fixed:serviceaccounts:writer", @@ -31,10 +49,10 @@ func RegisterRoles(ac accesscontrol.AccessControl) error { }, }, }, - Grants: []string{"Admin"}, + Grants: []string{string(models.ROLE_ADMIN)}, } - if err := ac.DeclareFixedRoles(role); err != nil { + if err := ac.DeclareFixedRoles(saReader, saWriter); err != nil { return err } From 057ff5bcf53e5e89965913f2195483979502419a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 14 Apr 2022 15:18:03 +0200 Subject: [PATCH 09/40] Prometheus: Query builder UX tweaks and feedback link (#47655) * Prometheus: Query builder UX tweaks and feedback link * Remove . * Fixed link * added option to hide feedback links * feedback link setting name change * move config check * fixed ts issue --- conf/defaults.ini | 3 ++ conf/sample.ini | 3 ++ docs/sources/administration/configuration.md | 4 ++ packages/grafana-data/src/types/config.ts | 1 + packages/grafana-runtime/src/config.ts | 1 + pkg/api/frontendsettings.go | 1 + pkg/setting/setting.go | 5 +++ .../components/PromQueryBuilderContainer.tsx | 2 +- .../PromQueryEditorSelector.test.tsx | 10 ++--- .../components/PromQueryEditorSelector.tsx | 33 +++++++------- .../querybuilder/components/QueryPreview.tsx | 2 +- .../querybuilder/shared/FeedbackLink.tsx | 44 +++++++++++++++++++ .../plugins/datasource/prometheus/types.ts | 4 +- 13 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 public/app/plugins/datasource/prometheus/querybuilder/shared/FeedbackLink.tsx diff --git a/conf/defaults.ini b/conf/defaults.ini index 09696c5f8dc..904719d5620 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -230,6 +230,9 @@ application_insights_connection_string = # Optional. Specifies an Application Insights endpoint URL where the endpoint string is wrapped in backticks ``. application_insights_endpoint_url = +# Controls if the UI contains any links to user feedback forms +feedback_links_enabled = true + #################################### Security ############################ [security] # disable creation of admin user on first start of grafana diff --git a/conf/sample.ini b/conf/sample.ini index f20d03f7461..fbe7b6b7f8d 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -230,6 +230,9 @@ # Rudderstack Config url, optional, used by Rudderstack SDK to fetch source config ;rudderstack_config_url = +# Controls if the UI contains any links to user feedback forms +;feedback_links_enabled = true + #################################### Security #################################### [security] # disable creation of admin user on first start of grafana diff --git a/docs/sources/administration/configuration.md b/docs/sources/administration/configuration.md index 58cdcc98e3b..4a9106482d3 100644 --- a/docs/sources/administration/configuration.md +++ b/docs/sources/administration/configuration.md @@ -509,6 +509,10 @@ If you want to track Grafana usage via Azure Application Insights, then specify
+### enable_feedback_links + +If set to false will remove all feedback links from the UI. Defaults to true. + ## [security] ### disable_initial_admin_creation diff --git a/packages/grafana-data/src/types/config.ts b/packages/grafana-data/src/types/config.ts index bb003374d92..8231bb4c252 100644 --- a/packages/grafana-data/src/types/config.ts +++ b/packages/grafana-data/src/types/config.ts @@ -180,4 +180,5 @@ export interface GrafanaConfig { geomapDisableCustomBaseLayer?: boolean; unifiedAlertingEnabled: boolean; angularSupportEnabled: boolean; + feedbackLinksEnabled: boolean; } diff --git a/packages/grafana-runtime/src/config.ts b/packages/grafana-runtime/src/config.ts index f2599a42894..d5076e5ae6a 100644 --- a/packages/grafana-runtime/src/config.ts +++ b/packages/grafana-runtime/src/config.ts @@ -36,6 +36,7 @@ export class GrafanaBootConfig implements GrafanaConfig { externalUserMngLinkName = ''; externalUserMngInfo = ''; allowOrgCreate = false; + feedbackLinksEnabled = true; disableLoginForm = false; defaultDatasource = ''; // UID alertingEnabled = false; diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index cc609bb2dda..cc23170a9ed 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -113,6 +113,7 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i "rudderstackDataPlaneUrl": setting.RudderstackDataPlaneUrl, "rudderstackSdkUrl": setting.RudderstackSdkUrl, "rudderstackConfigUrl": setting.RudderstackConfigUrl, + "feedbackLinksEnabled": hs.Cfg.FeedbackLinksEnabled, "applicationInsightsConnectionString": hs.Cfg.ApplicationInsightsConnectionString, "applicationInsightsEndpointUrl": hs.Cfg.ApplicationInsightsEndpointUrl, "disableLoginForm": setting.DisableLoginForm, diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 10834bf4556..58d695f41fc 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -393,6 +393,7 @@ type Cfg struct { ReportingEnabled bool ApplicationInsightsConnectionString string ApplicationInsightsEndpointUrl string + FeedbackLinksEnabled bool // LDAP LDAPEnabled bool @@ -938,13 +939,17 @@ func (cfg *Cfg) Load(args CommandLineArgs) error { RudderstackDataPlaneUrl = analytics.Key("rudderstack_data_plane_url").String() RudderstackSdkUrl = analytics.Key("rudderstack_sdk_url").String() RudderstackConfigUrl = analytics.Key("rudderstack_config_url").String() + cfg.ReportingEnabled = analytics.Key("reporting_enabled").MustBool(true) cfg.ReportingDistributor = analytics.Key("reporting_distributor").MustString("grafana-labs") + if len(cfg.ReportingDistributor) >= 100 { cfg.ReportingDistributor = cfg.ReportingDistributor[:100] } + cfg.ApplicationInsightsConnectionString = analytics.Key("application_insights_connection_string").String() cfg.ApplicationInsightsEndpointUrl = analytics.Key("application_insights_endpoint_url").String() + cfg.FeedbackLinksEnabled = analytics.Key("feedback_links_enabled").MustBool(true) if err := readAlertingSettings(iniFile); err != nil { return err diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderContainer.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderContainer.tsx index 6d9bbe328d5..5a46feaa0ac 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderContainer.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryBuilderContainer.tsx @@ -54,7 +54,7 @@ export function PromQueryBuilderContainer(props: Props) { onRunQuery={onRunQuery} data={data} /> - {query.editorPreview && } + {query.rawQuery && } ); } diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.test.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.test.tsx index bb92ec241d0..b1283286882 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.test.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.test.tsx @@ -90,24 +90,24 @@ describe('PromQueryEditorSelector', () => { }); }); - it('Can enable preview', async () => { + it('Can enable raw query', async () => { const { onChange } = renderWithMode(QueryEditorMode.Builder); expect(screen.queryByLabelText('selector')).not.toBeInTheDocument(); - screen.getByLabelText('Preview').click(); + screen.getByLabelText('Raw query').click(); expect(onChange).toBeCalledWith({ refId: 'A', expr: defaultQuery.expr, range: true, editorMode: QueryEditorMode.Builder, - editorPreview: true, + rawQuery: true, }); }); - it('Should show preview', async () => { + it('Should show raw query', async () => { renderWithProps({ - editorPreview: true, + rawQuery: true, editorMode: QueryEditorMode.Builder, expr: 'my_metric', }); diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx index bfcd143c9f1..9bd94e3761d 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/PromQueryEditorSelector.tsx @@ -14,6 +14,7 @@ import { PromQueryBuilderContainer } from './PromQueryBuilderContainer'; import { PromQueryBuilderOptions } from './PromQueryBuilderOptions'; import { changeEditorMode, getQueryWithDefaults } from '../state'; import { PromQuery } from '../../types'; +import { FeedbackLink } from '../shared/FeedbackLink'; export const PromQueryEditorSelector = React.memo((props) => { const { onChange, onRunQuery, data } = props; @@ -44,7 +45,7 @@ export const PromQueryEditorSelector = React.memo((props) const onQueryPreviewChange = (event: SyntheticEvent) => { const isEnabled = event.currentTarget.checked; - onChange({ ...query, editorPreview: isEnabled }); + onChange({ ...query, rawQuery: isEnabled }); onRunQuery(); }; @@ -67,16 +68,6 @@ export const PromQueryEditorSelector = React.memo((props) onDismiss={() => setParseModalOpen(false)} /> - - {editorMode === QueryEditorMode.Builder && ( <> ((props) }} options={promQueryModeller.getQueryPatterns().map((x) => ({ label: x.name, value: x }))} /> + )} - + {editorMode === QueryEditorMode.Builder && ( + + )} + + diff --git a/public/app/plugins/datasource/prometheus/querybuilder/components/QueryPreview.tsx b/public/app/plugins/datasource/prometheus/querybuilder/components/QueryPreview.tsx index b95df791017..cd7a267e542 100644 --- a/public/app/plugins/datasource/prometheus/querybuilder/components/QueryPreview.tsx +++ b/public/app/plugins/datasource/prometheus/querybuilder/components/QueryPreview.tsx @@ -18,7 +18,7 @@ export function QueryPreview({ query }: Props) { return ( - +
+ + Give feedback + + + ); +} + +function getStyles(theme: GrafanaTheme2) { + return { + link: css({ + color: theme.colors.text.secondary, + fontSize: theme.typography.bodySmall.fontSize, + ':hover': { + color: theme.colors.text.link, + }, + }), + }; +} diff --git a/public/app/plugins/datasource/prometheus/types.ts b/public/app/plugins/datasource/prometheus/types.ts index 629028914ca..f2c4b494c5e 100644 --- a/public/app/plugins/datasource/prometheus/types.ts +++ b/public/app/plugins/datasource/prometheus/types.ts @@ -19,8 +19,8 @@ export interface PromQuery extends DataQuery { showingTable?: boolean; /** Code, Builder or Explain */ editorMode?: QueryEditorMode; - /** Controls if the query preview is shown */ - editorPreview?: boolean; + /** Controls if the raw query text is shown */ + rawQuery?: boolean; } export interface PromOptions extends DataSourceJsonData { From 388b3bf9084a2491e479a0baec8476a7ee28eba2 Mon Sep 17 00:00:00 2001 From: Andy Lindberg Date: Thu, 14 Apr 2022 08:49:56 -0600 Subject: [PATCH 10/40] Grafana/UI: Bundle raw SVG icon strings (#47665) * Build: bundle raw svg icon strings in grafana/ui * remove @rollup/plugin-image --- packages/grafana-ui/package.json | 2 +- packages/grafana-ui/rollup.config.ts | 4 +-- yarn.lock | 44 ++++++++++++++-------------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index a9485cc8168..d73b877eb70 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -98,7 +98,6 @@ "@grafana/tsconfig": "^1.2.0-rc1", "@mdx-js/react": "1.6.22", "@rollup/plugin-commonjs": "21.0.2", - "@rollup/plugin-image": "2.1.1", "@rollup/plugin-node-resolve": "13.1.3", "@storybook/addon-a11y": "6.4.21", "@storybook/addon-actions": "6.4.21", @@ -168,6 +167,7 @@ "rimraf": "3.0.2", "rollup": "2.70.1", "rollup-plugin-sourcemaps": "0.6.3", + "rollup-plugin-svg-import": "^1.6.0", "rollup-plugin-terser": "7.0.2", "sass-loader": "12.6.0", "storybook-dark-mode": "1.0.9", diff --git a/packages/grafana-ui/rollup.config.ts b/packages/grafana-ui/rollup.config.ts index d40c5776084..71f712df14f 100644 --- a/packages/grafana-ui/rollup.config.ts +++ b/packages/grafana-ui/rollup.config.ts @@ -1,6 +1,6 @@ import resolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; -import image from '@rollup/plugin-image'; +import svg from 'rollup-plugin-svg-import'; import { terser } from 'rollup-plugin-terser'; const pkg = require('./package.json'); @@ -41,7 +41,7 @@ const buildCjsPackage = ({ env }) => { include: /node_modules/, }), resolve(), - image(), + svg({ stringify: true }), env === 'production' && terser(), ], }; diff --git a/yarn.lock b/yarn.lock index 1cd157ebdc3..5273232fb8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4366,7 +4366,6 @@ __metadata: "@react-aria/overlays": 3.8.1 "@react-stately/menu": 3.2.6 "@rollup/plugin-commonjs": 21.0.2 - "@rollup/plugin-image": 2.1.1 "@rollup/plugin-node-resolve": 13.1.3 "@sentry/browser": 6.19.1 "@storybook/addon-a11y": 6.4.21 @@ -4476,6 +4475,7 @@ __metadata: rimraf: 3.0.2 rollup: 2.70.1 rollup-plugin-sourcemaps: 0.6.3 + rollup-plugin-svg-import: ^1.6.0 rollup-plugin-terser: 7.0.2 rxjs: 7.5.5 sass-loader: 12.6.0 @@ -7256,18 +7256,6 @@ __metadata: languageName: node linkType: hard -"@rollup/plugin-image@npm:2.1.1": - version: 2.1.1 - resolution: "@rollup/plugin-image@npm:2.1.1" - dependencies: - "@rollup/pluginutils": ^3.1.0 - mini-svg-data-uri: ^1.2.3 - peerDependencies: - rollup: ^1.20.0 || ^2.0.0 - checksum: a629c8f22233ca159c23655fdbc3449dab3c939372178ed4462fc9c525cc4ecd8b11fae359eb94be4f769d26f48b85fb18eb16ce1fbc33ed16b6a7c1f84391f6 - languageName: node - linkType: hard - "@rollup/plugin-json@npm:4.1.0": version: 4.1.0 resolution: "@rollup/plugin-json@npm:4.1.0" @@ -7308,6 +7296,16 @@ __metadata: languageName: node linkType: hard +"@rollup/pluginutils@npm:^4.1.1": + version: 4.2.0 + resolution: "@rollup/pluginutils@npm:4.2.0" + dependencies: + estree-walker: ^2.0.1 + picomatch: ^2.2.2 + checksum: 2e86d9bfb95919727bcba0bbbdbedc98e25a1e51fe3047f18ec6d85e0743d1c73e1c0de3f9fdbd2ff6b90c32f30d4b2706c9e794f3c2e7a80156980081558e2e + languageName: node + linkType: hard + "@rtsao/plugin-proposal-class-properties@npm:7.0.1-patch.1": version: 7.0.1-patch.1 resolution: "@rtsao/plugin-proposal-class-properties@npm:7.0.1-patch.1" @@ -25960,15 +25958,6 @@ __metadata: languageName: node linkType: hard -"mini-svg-data-uri@npm:^1.2.3": - version: 1.4.3 - resolution: "mini-svg-data-uri@npm:1.4.3" - bin: - mini-svg-data-uri: cli.js - checksum: bd1789c34907a36ae9157897b7c7f6ad700e71c8cb6d725bb2810ce6211abcaa3717333ca17408f6f8b2a44055e571cd5b78d478688e1864513cb7f8e023ae3f - languageName: node - linkType: hard - "minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": version: 1.0.1 resolution: "minimalistic-assert@npm:1.0.1" @@ -32390,6 +32379,17 @@ __metadata: languageName: node linkType: hard +"rollup-plugin-svg-import@npm:^1.6.0": + version: 1.6.0 + resolution: "rollup-plugin-svg-import@npm:1.6.0" + dependencies: + "@rollup/pluginutils": ^4.1.1 + peerDependencies: + rollup: ">=1.29.0 <3.0.0" + checksum: 453862c39d2301563d9d07f6647c295377ff66cf3174d2a0612389fda4bfd9fa72718d90279feb782d3525f1d6f9710e1dc78641cd9d4044360a0179f88054b0 + languageName: node + linkType: hard + "rollup-plugin-terser@npm:7.0.2": version: 7.0.2 resolution: "rollup-plugin-terser@npm:7.0.2" From ab4c7f14aaf70db9591b48de833dee0cae299c5d Mon Sep 17 00:00:00 2001 From: Kristina Date: Thu, 14 Apr 2022 10:22:09 -0500 Subject: [PATCH 11/40] Explore: Follow up NoData work (#47745) * Use theme styles and add basic test for rendering * Use util function for response --- public/app/features/explore/Explore.test.tsx | 125 +++++++++++++++++++ public/app/features/explore/NoData.tsx | 7 +- 2 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 public/app/features/explore/Explore.test.tsx diff --git a/public/app/features/explore/Explore.test.tsx b/public/app/features/explore/Explore.test.tsx new file mode 100644 index 00000000000..b9ce6c9f5f7 --- /dev/null +++ b/public/app/features/explore/Explore.test.tsx @@ -0,0 +1,125 @@ +import React from 'react'; +import { DataSourceApi, LoadingState, CoreApp, createTheme } from '@grafana/data'; +import { render, screen } from '@testing-library/react'; +import { ExploreId } from 'app/types/explore'; +import { Explore, Props } from './Explore'; +import { scanStopAction } from './state/query'; +import { Provider } from 'react-redux'; +import { configureStore } from 'app/store/configureStore'; +import { AutoSizerProps } from 'react-virtualized-auto-sizer'; +import { createEmptyQueryResponse } from './state/utils'; + +const makeEmptyQueryResponse = (loadingState: LoadingState) => { + const baseEmptyResponse = createEmptyQueryResponse(); + + baseEmptyResponse.request = { + requestId: '1', + intervalMs: 0, + interval: '1s', + dashboardId: 0, + panelId: 1, + range: baseEmptyResponse.timeRange, + scopedVars: { + apps: { + value: 'value', + text: 'text', + }, + }, + targets: [ + { + refId: 'A', + }, + ], + timezone: 'UTC', + app: CoreApp.Explore, + startTime: 0, + }; + + baseEmptyResponse.state = loadingState; + + return baseEmptyResponse; +}; + +const dummyProps: Props = { + logsResult: undefined, + changeSize: jest.fn(), + datasourceInstance: { + meta: { + metrics: true, + logs: true, + }, + components: { + QueryEditorHelp: {}, + }, + } as DataSourceApi, + datasourceMissing: false, + exploreId: ExploreId.left, + loading: false, + modifyQueries: jest.fn(), + scanStart: jest.fn(), + scanStopAction: scanStopAction, + setQueries: jest.fn(), + queryKeys: [], + isLive: false, + syncedTimes: false, + updateTimeRange: jest.fn(), + makeAbsoluteTime: jest.fn(), + graphResult: [], + absoluteRange: { + from: 0, + to: 0, + }, + timeZone: 'UTC', + queryResponse: makeEmptyQueryResponse(LoadingState.NotStarted), + addQueryRow: jest.fn(), + theme: createTheme(), + showMetrics: true, + showLogs: true, + showTable: true, + showTrace: true, + showNodeGraph: true, + splitOpen: (() => {}) as any, + logsVolumeData: undefined, + loadLogsVolumeData: () => {}, + changeGraphStyle: () => {}, + graphStyle: 'lines', +}; + +jest.mock('@grafana/runtime/src/services/dataSourceSrv', () => { + return { + getDataSourceSrv: () => ({ + get: () => Promise.resolve({}), + getList: () => [], + getInstanceSettings: () => {}, + }), + }; +}); + +// for the AutoSizer component to have a width +jest.mock('react-virtualized-auto-sizer', () => { + return ({ children }: AutoSizerProps) => children({ height: 1, width: 1 }); +}); + +const setup = (overrideProps?: Partial) => { + const store = configureStore(); + const exploreProps = { ...dummyProps, ...overrideProps }; + + return render( + + + + ); +}; + +describe('Explore', () => { + it('should not render no data with not started loading state', () => { + setup(); + expect(screen.queryByTestId('explore-no-data')).not.toBeInTheDocument(); + }); + + it('should render no data with done loading state', async () => { + const queryResp = makeEmptyQueryResponse(LoadingState.Done); + setup({ queryResponse: queryResp }); + expect(screen.getByTestId('explore-no-data')).toBeInTheDocument(); + }); +}); diff --git a/public/app/features/explore/NoData.tsx b/public/app/features/explore/NoData.tsx index 7b9c8df5f0d..d50330747f6 100644 --- a/public/app/features/explore/NoData.tsx +++ b/public/app/features/explore/NoData.tsx @@ -9,7 +9,7 @@ export const NoData = () => { const css = useStyles2(getStyles); return ( <> -
+
{'No data'}
@@ -29,9 +29,8 @@ const getStyles = (theme: GrafanaTheme2) => ({ flex-grow: 1; `, message: css` - margin-bottom: ${theme.spacing(3)}; - font-size: 2em; - padding: 6em 1em; + font-size: ${theme.typography.h2.fontSize}; + padding: ${theme.spacing(4)}; color: ${theme.colors.text.disabled}; `, }); From 41012af997083f612a3167b269f04fe9b869b74f Mon Sep 17 00:00:00 2001 From: Serge Zaitsev Date: Thu, 14 Apr 2022 17:54:49 +0200 Subject: [PATCH 12/40] Tracing: Use common traceID context value for opentracing and opentelemetry (#46411) * use common traceID context value for opentracing and opentelemetry * support sampled trace IDs as well * inject traceID into NormalResponse on errors * Finally the test passed * fix the test * fix linter * change the function parameter Co-authored-by: Ying WANG --- pkg/api/ldap_debug_test.go | 26 +++++------ pkg/api/metrics_test.go | 41 +++++++---------- pkg/api/response/response.go | 11 ++++- pkg/infra/tracing/opentelemetry_tracing.go | 1 + pkg/infra/tracing/tracing.go | 19 ++++++++ pkg/middleware/logger.go | 6 +-- pkg/middleware/request_metrics.go | 4 +- pkg/models/context.go | 5 +- pkg/services/contexthandler/contexthandler.go | 5 +- pkg/services/sqlstore/database_wrapper.go | 3 +- .../alerting/api_admin_configuration_test.go | 5 +- .../api_alertmanager_configuration_test.go | 14 ++++-- .../api/alerting/api_alertmanager_test.go | 46 +++++++++++++------ .../alerting/api_notification_channel_test.go | 2 +- pkg/tests/api/alerting/api_prometheus_test.go | 12 +++-- pkg/tests/api/alerting/api_ruler_test.go | 18 ++++++-- 16 files changed, 140 insertions(+), 78 deletions(-) diff --git a/pkg/api/ldap_debug_test.go b/pkg/api/ldap_debug_test.go index f9d796018bc..f7ed3473906 100644 --- a/pkg/api/ldap_debug_test.go +++ b/pkg/api/ldap_debug_test.go @@ -1,6 +1,7 @@ package api import ( + "encoding/json" "errors" "net/http" "net/http/httptest" @@ -146,13 +147,11 @@ func TestGetUserFromLDAPAPIEndpoint_OrgNotfound(t *testing.T) { require.Equal(t, http.StatusBadRequest, sc.resp.Code) - expected := ` - { - "error": "unable to find organization with ID '2'", - "message": "An organization was not found - Please verify your LDAP configuration" - } - ` - assert.JSONEq(t, expected, sc.resp.Body.String()) + var res map[string]interface{} + err := json.Unmarshal(sc.resp.Body.Bytes(), &res) + assert.NoError(t, err) + assert.Equal(t, "unable to find organization with ID '2'", res["error"]) + assert.Equal(t, "An organization was not found - Please verify your LDAP configuration", res["message"]) } func TestGetUserFromLDAPAPIEndpoint(t *testing.T) { @@ -470,14 +469,11 @@ func TestPostSyncUserWithLDAPAPIEndpoint_WhenGrafanaAdmin(t *testing.T) { }, &sqlstoremock) assert.Equal(t, http.StatusBadRequest, sc.resp.Code) - expected := ` - { - "error": "did not find a user", - "message": "Refusing to sync grafana super admin \"ldap-daniel\" - it would be disabled" - } - ` - - assert.JSONEq(t, expected, sc.resp.Body.String()) + var res map[string]interface{} + err := json.Unmarshal(sc.resp.Body.Bytes(), &res) + assert.NoError(t, err) + assert.Equal(t, "did not find a user", res["error"]) + assert.Equal(t, "Refusing to sync grafana super admin \"ldap-daniel\" - it would be disabled", res["message"]) } func TestPostSyncUserWithLDAPAPIEndpoint_WhenUserNotInLDAP(t *testing.T) { diff --git a/pkg/api/metrics_test.go b/pkg/api/metrics_test.go index 634ce0ee74e..5a32d5cfbba 100644 --- a/pkg/api/metrics_test.go +++ b/pkg/api/metrics_test.go @@ -2,6 +2,7 @@ package api import ( "context" + "encoding/json" "fmt" "net/http" "strconv" @@ -241,15 +242,14 @@ func TestAPIEndpoint_Metrics_QueryMetricsFromDashboard(t *testing.T) { strings.NewReader(queryDatasourceInput), t, ) + assert.Equal(t, http.StatusBadRequest, response.Code) - assert.JSONEq( - t, - fmt.Sprintf( - "{\"error\":\"%[1]s\",\"message\":\"%[1]s\"}", - models.ErrDashboardOrPanelIdentifierNotSet, - ), - response.Body.String(), - ) + + var res map[string]interface{} + err := json.Unmarshal(response.Body.Bytes(), &res) + assert.NoError(t, err) + assert.Equal(t, models.ErrDashboardOrPanelIdentifierNotSet.Error(), res["error"]) + assert.Equal(t, models.ErrDashboardOrPanelIdentifierNotSet.Error(), res["message"]) }) t.Run("Cannot query without a valid orgid", func(t *testing.T) { @@ -261,14 +261,10 @@ func TestAPIEndpoint_Metrics_QueryMetricsFromDashboard(t *testing.T) { t, ) assert.Equal(t, http.StatusBadRequest, response.Code) - assert.JSONEq( - t, - fmt.Sprintf( - "{\"error\":\"%[1]s\",\"message\":\"%[1]s\"}", - models.ErrDashboardOrPanelIdentifierNotSet, - ), - response.Body.String(), - ) + var res map[string]interface{} + assert.NoError(t, json.Unmarshal(response.Body.Bytes(), &res)) + assert.Equal(t, models.ErrDashboardOrPanelIdentifierNotSet.Error(), res["error"]) + assert.Equal(t, models.ErrDashboardOrPanelIdentifierNotSet.Error(), res["message"]) }) t.Run("Cannot query without a valid dashboard or panel ID", func(t *testing.T) { @@ -280,14 +276,11 @@ func TestAPIEndpoint_Metrics_QueryMetricsFromDashboard(t *testing.T) { t, ) assert.Equal(t, http.StatusBadRequest, response.Code) - assert.JSONEq( - t, - fmt.Sprintf( - "{\"error\":\"%[1]s\",\"message\":\"%[1]s\"}", - models.ErrDashboardOrPanelIdentifierNotSet, - ), - response.Body.String(), - ) + + var res map[string]interface{} + assert.NoError(t, json.Unmarshal(response.Body.Bytes(), &res)) + assert.Equal(t, models.ErrDashboardOrPanelIdentifierNotSet.Error(), res["error"]) + assert.Equal(t, models.ErrDashboardOrPanelIdentifierNotSet.Error(), res["message"]) }) t.Run("Cannot query when ValidatedQueries is disabled", func(t *testing.T) { diff --git a/pkg/api/response/response.go b/pkg/api/response/response.go index 57d751a407c..58de6282652 100644 --- a/pkg/api/response/response.go +++ b/pkg/api/response/response.go @@ -5,6 +5,7 @@ import ( "encoding/json" "net/http" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" jsoniter "github.com/json-iterator/go" @@ -73,7 +74,15 @@ func (r *NormalResponse) ErrMessage() string { func (r *NormalResponse) WriteTo(ctx *models.ReqContext) { if r.err != nil { - ctx.Logger.Error(r.errMessage, "error", r.err, "remote_addr", ctx.RemoteAddr()) + v := map[string]interface{}{} + traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false) + if err := json.Unmarshal(r.body.Bytes(), &v); err == nil { + v["traceID"] = traceID + if b, err := json.Marshal(v); err == nil { + r.body = bytes.NewBuffer(b) + } + } + ctx.Logger.Error(r.errMessage, "error", r.err, "remote_addr", ctx.RemoteAddr(), "traceID", traceID) } header := ctx.Resp.Header() diff --git a/pkg/infra/tracing/opentelemetry_tracing.go b/pkg/infra/tracing/opentelemetry_tracing.go index abe7fcb2fe2..61b62a850e0 100644 --- a/pkg/infra/tracing/opentelemetry_tracing.go +++ b/pkg/infra/tracing/opentelemetry_tracing.go @@ -126,6 +126,7 @@ func (ots *Opentelemetry) Start(ctx context.Context, spanName string, opts ...tr opentelemetrySpan := OpentelemetrySpan{ span: span, } + ctx = context.WithValue(ctx, traceKey{}, traceValue{span.SpanContext().TraceID().String(), span.SpanContext().IsSampled()}) return ctx, opentelemetrySpan } diff --git a/pkg/infra/tracing/tracing.go b/pkg/infra/tracing/tracing.go index 95df49b4727..c13e353e94e 100644 --- a/pkg/infra/tracing/tracing.go +++ b/pkg/infra/tracing/tracing.go @@ -14,6 +14,7 @@ import ( opentracing "github.com/opentracing/opentracing-go" "github.com/opentracing/opentracing-go/ext" ol "github.com/opentracing/opentracing-go/log" + "github.com/uber/jaeger-client-go" jaegercfg "github.com/uber/jaeger-client-go/config" "github.com/uber/jaeger-client-go/zipkin" "go.opentelemetry.io/otel/attribute" @@ -52,6 +53,21 @@ func ProvideService(cfg *setting.Cfg) (Tracer, error) { return ots, ots.initOpentelemetryTracer() } +type traceKey struct{} +type traceValue struct { + ID string + IsSampled bool +} + +func TraceIDFromContext(c context.Context, requireSampled bool) string { + v := c.Value(traceKey{}) + // Return traceID if a) it is present and b) it is sampled when requireSampled param is true + if trace, ok := v.(traceValue); ok && (!requireSampled || trace.IsSampled) { + return trace.ID + } + return "" +} + type Opentracing struct { enabled bool address string @@ -172,6 +188,9 @@ func (ts *Opentracing) Run(ctx context.Context) error { func (ts *Opentracing) Start(ctx context.Context, spanName string, opts ...trace.SpanStartOption) (context.Context, Span) { span, ctx := opentracing.StartSpanFromContext(ctx, spanName) opentracingSpan := OpentracingSpan{span: span} + if sctx, ok := span.Context().(jaeger.SpanContext); ok { + ctx = context.WithValue(ctx, traceKey{}, traceValue{sctx.TraceID().String(), sctx.IsSampled()}) + } return ctx, opentracingSpan } diff --git a/pkg/middleware/logger.go b/pkg/middleware/logger.go index 15174880807..c2f127319cb 100644 --- a/pkg/middleware/logger.go +++ b/pkg/middleware/logger.go @@ -19,10 +19,10 @@ import ( "net/http" "time" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" - cw "github.com/weaveworks/common/tracing" ) func Logger(cfg *setting.Cfg) web.Handler { @@ -58,8 +58,8 @@ func Logger(cfg *setting.Cfg) web.Handler { "referer", req.Referer(), } - traceID, exist := cw.ExtractTraceID(ctx.Req.Context()) - if exist { + traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false) + if traceID != "" { logParams = append(logParams, "traceID", traceID) } diff --git a/pkg/middleware/request_metrics.go b/pkg/middleware/request_metrics.go index d4923f233a6..6f9b2618993 100644 --- a/pkg/middleware/request_metrics.go +++ b/pkg/middleware/request_metrics.go @@ -7,10 +7,10 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/metrics" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/web" "github.com/prometheus/client_golang/prometheus" - cw "github.com/weaveworks/common/tracing" ) var ( @@ -80,7 +80,7 @@ func RequestMetrics(features featuremgmt.FeatureToggles) web.Handler { // since they dont make much sense. We should remove them later. histogram := httpRequestDurationHistogram. WithLabelValues(handler, code, req.Method) - if traceID, ok := cw.ExtractSampledTraceID(c.Req.Context()); ok { + if traceID := tracing.TraceIDFromContext(c.Req.Context(), true); traceID != "" { // Need to type-convert the Observer to an // ExemplarObserver. This will always work for a // HistogramVec. diff --git a/pkg/models/context.go b/pkg/models/context.go index dca3b2e5310..f31ea1197d6 100644 --- a/pkg/models/context.go +++ b/pkg/models/context.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web" "github.com/prometheus/client_golang/prometheus" @@ -51,9 +52,11 @@ func (ctx *ReqContext) IsApiRequest() bool { func (ctx *ReqContext) JsonApiErr(status int, message string, err error) { resp := make(map[string]interface{}) + traceID := tracing.TraceIDFromContext(ctx.Req.Context(), false) if err != nil { - ctx.Logger.Error(message, "error", err) + resp["traceID"] = traceID + ctx.Logger.Error(message, "error", err, "traceID", traceID) if setting.Env != setting.Prod { resp["error"] = err.Error() } diff --git a/pkg/services/contexthandler/contexthandler.go b/pkg/services/contexthandler/contexthandler.go index 386ea1ea1b4..d339ea5f09b 100644 --- a/pkg/services/contexthandler/contexthandler.go +++ b/pkg/services/contexthandler/contexthandler.go @@ -25,7 +25,6 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" - cw "github.com/weaveworks/common/tracing" ) const ( @@ -98,8 +97,8 @@ func (h *ContextHandler) Middleware(mContext *web.Context) { mContext.Req = mContext.Req.WithContext(context.WithValue(mContext.Req.Context(), reqContextKey{}, reqContext)) mContext.Map(mContext.Req) - traceID, exists := cw.ExtractTraceID(mContext.Req.Context()) - if exists { + traceID := tracing.TraceIDFromContext(mContext.Req.Context(), false) + if traceID != "" { reqContext.Logger = reqContext.Logger.New("traceID", traceID) } diff --git a/pkg/services/sqlstore/database_wrapper.go b/pkg/services/sqlstore/database_wrapper.go index 81079412cd3..400f823a55e 100644 --- a/pkg/services/sqlstore/database_wrapper.go +++ b/pkg/services/sqlstore/database_wrapper.go @@ -16,7 +16,6 @@ import ( "github.com/lib/pq" "github.com/mattn/go-sqlite3" "github.com/prometheus/client_golang/prometheus" - cw "github.com/weaveworks/common/tracing" "xorm.io/core" ) @@ -83,7 +82,7 @@ func (h *databaseQueryWrapper) instrument(ctx context.Context, status string, qu elapsed := time.Since(begin) histogram := databaseQueryHistogram.WithLabelValues(status) - if traceID, ok := cw.ExtractSampledTraceID(ctx); ok { + if traceID := tracing.TraceIDFromContext(ctx, true); traceID != "" { // Need to type-convert the Observer to an // ExemplarObserver. This will always work for a // HistogramVec. diff --git a/pkg/tests/api/alerting/api_admin_configuration_test.go b/pkg/tests/api/alerting/api_admin_configuration_test.go index c114d046494..3dca58e0d16 100644 --- a/pkg/tests/api/alerting/api_admin_configuration_test.go +++ b/pkg/tests/api/alerting/api_admin_configuration_test.go @@ -66,7 +66,10 @@ func TestAdminConfiguration_SendingToExternalAlertmanagers(t *testing.T) { resp := getRequest(t, alertsURL, http.StatusNotFound) // nolint b, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) - require.JSONEq(t, `{"message": "no admin configuration available"}`, string(b)) + var res map[string]interface{} + err = json.Unmarshal(b, &res) + require.NoError(t, err) + require.Equal(t, "no admin configuration available", res["message"]) } // An invalid alertmanager choice should return an error. diff --git a/pkg/tests/api/alerting/api_alertmanager_configuration_test.go b/pkg/tests/api/alerting/api_alertmanager_configuration_test.go index dd6e1fcbbc3..7405c32083c 100644 --- a/pkg/tests/api/alerting/api_alertmanager_configuration_test.go +++ b/pkg/tests/api/alerting/api_alertmanager_configuration_test.go @@ -3,6 +3,7 @@ package alerting import ( "encoding/json" "fmt" + "io/ioutil" "net/http" "regexp" "testing" @@ -89,9 +90,13 @@ func TestAlertmanagerConfigurationIsTransactional(t *testing.T) { } ` resp := postRequest(t, alertConfigURL, payload, http.StatusBadRequest) // nolint - require.JSONEq(t, `{"message": "failed to save and apply Alertmanager configuration: failed to build integration map: the receiver is invalid: failed to validate receiver \"slack.receiver\" of type \"slack\": token must be specified when using the Slack chat API"}`, getBody(t, resp.Body)) - + b, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err) + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, `failed to save and apply Alertmanager configuration: failed to build integration map: the receiver is invalid: failed to validate receiver "slack.receiver" of type "slack": token must be specified when using the Slack chat API`, res["message"]) resp = getRequest(t, alertConfigURL, http.StatusOK) // nolint + require.JSONEq(t, defaultAlertmanagerConfigJSON, getBody(t, resp.Body)) } @@ -210,7 +215,10 @@ func TestAlertmanagerConfigurationPersistSecrets(t *testing.T) { ` resp := postRequest(t, alertConfigURL, payload, http.StatusBadRequest) // nolint - require.JSONEq(t, `{"message": "unknown receiver: invalid"}`, getBody(t, resp.Body)) + s := getBody(t, resp.Body) + var res map[string]interface{} + require.NoError(t, json.Unmarshal([]byte(s), &res)) + require.Equal(t, "unknown receiver: invalid", res["message"]) } // The secure settings must be present diff --git a/pkg/tests/api/alerting/api_alertmanager_test.go b/pkg/tests/api/alerting/api_alertmanager_test.go index 0d1a6e86db1..1ed1cde55c8 100644 --- a/pkg/tests/api/alerting/api_alertmanager_test.go +++ b/pkg/tests/api/alerting/api_alertmanager_test.go @@ -437,7 +437,10 @@ func TestAlertAndGroupsQuery(t *testing.T) { b, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) require.Equal(t, http.StatusUnauthorized, resp.StatusCode) - require.JSONEq(t, `{"message": "invalid username or password"}`, string(b)) + + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, "invalid username or password", res["message"]) } // When there are no alerts available, it returns an empty list. @@ -897,7 +900,7 @@ func TestAlertRuleCRUD(t *testing.T) { Data: []ngmodels.AlertQuery{}, }, }, - expectedResponse: `{"message": "invalid rule specification at index [0]: invalid alert rule: no queries or expressions are found"}`, + expectedResponse: `{"message": "invalid rule specification at index [0]: invalid alert rule: no queries or expressions are found", "traceID":"00000000000000000000000000000000"}`, }, { desc: "alert rule with empty title", @@ -927,7 +930,7 @@ func TestAlertRuleCRUD(t *testing.T) { }, }, }, - expectedResponse: `{"message": "invalid rule specification at index [0]: alert rule title cannot be empty"}`, + expectedResponse: `{"message": "invalid rule specification at index [0]: alert rule title cannot be empty", "traceID":"00000000000000000000000000000000"}`, }, { desc: "alert rule with too long name", @@ -957,7 +960,7 @@ func TestAlertRuleCRUD(t *testing.T) { }, }, }, - expectedResponse: `{"message": "invalid rule specification at index [0]: alert rule title is too long. Max length is 190"}`, + expectedResponse: `{"message": "invalid rule specification at index [0]: alert rule title is too long. Max length is 190", "traceID":"00000000000000000000000000000000"}`, }, { desc: "alert rule with too long rulegroup", @@ -987,7 +990,7 @@ func TestAlertRuleCRUD(t *testing.T) { }, }, }, - expectedResponse: `{"message": "rule group name is too long. Max length is 190"}`, + expectedResponse: `{"message": "rule group name is too long. Max length is 190", "traceID":"00000000000000000000000000000000"}`, }, { desc: "alert rule with invalid interval", @@ -1018,7 +1021,8 @@ func TestAlertRuleCRUD(t *testing.T) { }, }, }, - expectedResponse: `{"message": "rule evaluation interval (1 second) should be positive number that is multiple of the base interval of 10 seconds"}`, + expectedResponse: `{"message": "rule evaluation interval (1 second) should be positive ` + + `number that is multiple of the base interval of 10 seconds", "traceID":"00000000000000000000000000000000"}`, }, { desc: "alert rule with unknown datasource", @@ -1048,7 +1052,8 @@ func TestAlertRuleCRUD(t *testing.T) { }, }, }, - expectedResponse: `{"message": "invalid rule specification at index [0]: failed to validate condition of alert rule AlwaysFiring: invalid query A: data source not found: unknown"}`, + expectedResponse: `{"message": "invalid rule specification at index [0]: failed to validate condition of alert rule AlwaysFiring:` + + ` invalid query A: data source not found: unknown", "traceID":"00000000000000000000000000000000"}`, }, { desc: "alert rule with invalid condition", @@ -1078,7 +1083,8 @@ func TestAlertRuleCRUD(t *testing.T) { }, }, }, - expectedResponse: `{"message": "invalid rule specification at index [0]: failed to validate condition of alert rule AlwaysFiring: condition B not found in any query or expression: it should be one of: [A]"}`, + expectedResponse: `{"message": "invalid rule specification at index [0]: failed to validate condition of alert rule AlwaysFiring: ` + + `condition B not found in any query or expression: it should be one of: [A]", "traceID":"00000000000000000000000000000000"}`, }, } @@ -1368,7 +1374,9 @@ func TestAlertRuleCRUD(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusNotFound, resp.StatusCode) - require.JSONEq(t, `{"message": "failed to update rule group: failed to update rule with UID unknown because could not find alert rule"}`, string(b)) + var res map[string]interface{} + assert.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, "failed to update rule group: failed to update rule with UID unknown because could not find alert rule", res["message"]) // let's make sure that rule definitions are not affected by the failed POST request. u = fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr) @@ -1487,7 +1495,9 @@ func TestAlertRuleCRUD(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusBadRequest, resp.StatusCode) - require.JSONEq(t, fmt.Sprintf(`{"message": "rule [1] has UID %s that is already assigned to another rule at index 0"}`, ruleUID), string(b)) + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, fmt.Sprintf("rule [1] has UID %s that is already assigned to another rule at index 0", ruleUID), res["message"]) // let's make sure that rule definitions are not affected by the failed POST request. u = fmt.Sprintf("http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default", grafanaListedAddr) @@ -1893,7 +1903,9 @@ func TestAlertRuleCRUD(t *testing.T) { require.NoError(t, err) require.Equal(t, http.StatusAccepted, resp.StatusCode) - require.JSONEq(t, `{"message":"rules deleted"}`, string(b)) + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, "rules deleted", res["message"]) }) t.Run("succeed if the rule group name does exist", func(t *testing.T) { @@ -2103,7 +2115,9 @@ func TestQuota(t *testing.T) { b, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) assert.Equal(t, http.StatusForbidden, resp.StatusCode) - require.JSONEq(t, `{"message": "quota has been exceeded"}`, string(b)) + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, "quota has been exceeded", res["message"]) }) t.Run("when quota limit exceed updating existing rule should succeed", func(t *testing.T) { @@ -2400,7 +2414,8 @@ func TestEval(t *testing.T) { } `, expectedStatusCode: http.StatusBadRequest, - expectedResponse: `{"message": "invalid condition: condition B not found in any query or expression: it should be one of: [A]"}`, + expectedResponse: `{"message": "invalid condition: condition B not found in any query or expression: it should be one of: [A]",` + + `"traceID": "00000000000000000000000000000000"}`, }, { desc: "unknown query datasource", @@ -2425,7 +2440,7 @@ func TestEval(t *testing.T) { } `, expectedStatusCode: http.StatusBadRequest, - expectedResponse: `{"message": "invalid condition: invalid query A: data source not found: unknown"}`, + expectedResponse: `{"message": "invalid condition: invalid query A: data source not found: unknown", "traceID": "00000000000000000000000000000000"}`, }, } @@ -2581,7 +2596,8 @@ func TestEval(t *testing.T) { } `, expectedStatusCode: http.StatusBadRequest, - expectedResponse: `{"message": "invalid queries or expressions: invalid query A: data source not found: unknown"}`, + expectedResponse: `{"message": "invalid queries or expressions: invalid query A: data source not found: unknown",` + + `"traceID": "00000000000000000000000000000000"}`, }, } diff --git a/pkg/tests/api/alerting/api_notification_channel_test.go b/pkg/tests/api/alerting/api_notification_channel_test.go index 20cd1b0eac5..495656e115e 100644 --- a/pkg/tests/api/alerting/api_notification_channel_test.go +++ b/pkg/tests/api/alerting/api_notification_channel_test.go @@ -61,7 +61,7 @@ func TestTestReceivers(t *testing.T) { b, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) - require.JSONEq(t, `{}`, string(b)) + require.JSONEq(t, `{"traceID":"00000000000000000000000000000000"}`, string(b)) }) t.Run("assert working receiver returns OK", func(t *testing.T) { diff --git a/pkg/tests/api/alerting/api_prometheus_test.go b/pkg/tests/api/alerting/api_prometheus_test.go index 95b059be794..28a4aec99b5 100644 --- a/pkg/tests/api/alerting/api_prometheus_test.go +++ b/pkg/tests/api/alerting/api_prometheus_test.go @@ -206,7 +206,9 @@ func TestPrometheusRules(t *testing.T) { require.NoError(t, err) assert.Equal(t, 400, resp.StatusCode) - require.JSONEq(t, `{"message": "invalid rule specification at index [0]: both annotations __dashboardUid__ and __panelId__ must be specified"}`, string(b)) + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, "invalid rule specification at index [0]: both annotations __dashboardUid__ and __panelId__ must be specified", res["message"]) } // Now, let's see how this looks like. @@ -587,7 +589,9 @@ func TestPrometheusRulesFilterByDashboard(t *testing.T) { require.Equal(t, http.StatusBadRequest, resp.StatusCode) b, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) - require.JSONEq(t, `{"message": "invalid panel_id: strconv.ParseInt: parsing \"invalid\": invalid syntax"}`, string(b)) + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, `invalid panel_id: strconv.ParseInt: parsing "invalid": invalid syntax`, res["message"]) } // Now, let's check a panel_id without dashboard_uid returns a 400 Bad Request response @@ -603,7 +607,9 @@ func TestPrometheusRulesFilterByDashboard(t *testing.T) { require.Equal(t, http.StatusBadRequest, resp.StatusCode) b, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) - require.JSONEq(t, `{"message": "panel_id must be set with dashboard_uid"}`, string(b)) + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, "panel_id must be set with dashboard_uid", res["message"]) } } diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index 734d1bffbab..b9556e26d2e 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -410,7 +410,10 @@ func TestAlertRuleConflictingTitle(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) - require.JSONEq(t, `{"message": "failed to update rule group: failed to add rules: a conflicting alert rule is found: rule title under the same organisation and folder should be unique"}`, string(b)) + + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, "failed to update rule group: failed to add rules: a conflicting alert rule is found: rule title under the same organisation and folder should be unique", res["message"]) }) t.Run("trying to update an alert to the title of an existing alert in the same folder should fail", func(t *testing.T) { @@ -435,7 +438,10 @@ func TestAlertRuleConflictingTitle(t *testing.T) { require.NoError(t, err) assert.Equal(t, http.StatusInternalServerError, resp.StatusCode) - require.JSONEq(t, `{"message": "failed to update rule group: failed to update rules: a conflicting alert rule is found: rule title under the same organisation and folder should be unique"}`, string(b)) + + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, "failed to update rule group: failed to update rules: a conflicting alert rule is found: rule title under the same organisation and folder should be unique", res["message"]) }) t.Run("trying to create alert with same title under another folder should succeed", func(t *testing.T) { @@ -789,7 +795,9 @@ func TestRulerRulesFilterByDashboard(t *testing.T) { require.Equal(t, http.StatusBadRequest, resp.StatusCode) b, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) - require.JSONEq(t, `{"message": "invalid panel_id: strconv.ParseInt: parsing \"invalid\": invalid syntax"}`, string(b)) + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, `invalid panel_id: strconv.ParseInt: parsing "invalid": invalid syntax`, res["message"]) } // Now, let's check a panel_id without dashboard_uid returns a 400 Bad Request response @@ -805,7 +813,9 @@ func TestRulerRulesFilterByDashboard(t *testing.T) { require.Equal(t, http.StatusBadRequest, resp.StatusCode) b, err := ioutil.ReadAll(resp.Body) require.NoError(t, err) - require.JSONEq(t, `{"message": "panel_id must be set with dashboard_uid"}`, string(b)) + var res map[string]interface{} + require.NoError(t, json.Unmarshal(b, &res)) + require.Equal(t, "panel_id must be set with dashboard_uid", res["message"]) } } From 6a8f9b7ebc6afeb288b84bdc6475c5c05546827a Mon Sep 17 00:00:00 2001 From: Dominik Prokop Date: Thu, 14 Apr 2022 18:35:37 +0200 Subject: [PATCH 13/40] Table panel: Fix horizontal scrolling when pagination is enabled (#47776) * Table panel: Fix horizontal scrolling when pagination is enabled * Fix failing test --- .../panel-table/table_pagination.json | 2683 +++++++++++++++++ .../grafana-ui/src/components/Table/Table.tsx | 2 +- .../grafana-ui/src/components/Table/styles.ts | 18 +- 3 files changed, 2697 insertions(+), 6 deletions(-) create mode 100644 devenv/dev-dashboards/panel-table/table_pagination.json diff --git a/devenv/dev-dashboards/panel-table/table_pagination.json b/devenv/dev-dashboards/panel-table/table_pagination.json new file mode 100644 index 00000000000..da1765cd937 --- /dev/null +++ b/devenv/dev-dashboards/panel-table/table_pagination.json @@ -0,0 +1,2683 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 1110, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "rent": { + "color": "yellow", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "property_type" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "store": { + "color": "blue", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 205 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Number of rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 66 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Description" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Show details", + "url": "${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Surface" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background-solid" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "yellow", + "value": 200 + }, + { + "color": "green", + "value": 300 + }, + { + "color": "#EAB839", + "value": 500 + }, + { + "color": "#6ED0E0", + "value": 1000 + }, + { + "color": "#EF843C", + "value": 2000 + } + ] + } + }, + { + "id": "unit", + "value": "areaM2" + }, + { + "id": "custom.width", + "value": 93 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Type" + }, + "properties": [ + { + "id": "custom.width", + "value": 94 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "City" + }, + "properties": [ + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Price" + }, + "properties": [ + { + "id": "unit", + "value": "currencyUSD" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "footer": { + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Surface" + } + ] + }, + "pluginVersion": "9.0.0-pre", + "targets": [ + { + "csvContent": "\"id\",\"created_on\",\"operation\",\"property_type\",\"place_name\",\"place_with_parent_names\",\"country_name\",\"state_name\",\"geonames_id\",\"lat_lon\",\"lat\",\"lon\",\"price\",\"currency\",\"price_aprox_local_currency\",\"price_aprox_usd\",\"surface_total_in_m2\",\"surface_covered_in_m2\",\"price_usd_per_m2\",\"price_per_m2\",\"floor\",\"rooms\",\"expenses\",\"properati_url\",\"description\",\"title\",\"image_thumbnail\"\nf73c373e0588bf568164a057dd94203ac87d47b1,2014-08-07,rent,store,Catu,|Brasil|Bahia|Catu|,Brasil,Bahia,,,,,0,,0,0,,,,,,,,http://www.properati.com.br/mfye_alugar_outros_catu,\"Otimo Imovel com o melhor valor da regiao, confira as fotos - \",Exclusividade! - Loja Em Catu,https://thumbs4.properati.com/3/jA8isZw8Xju7z3-V2I-ED8x5D2o=/trim/198x0/smart/filters:strip_icc()/galeriadefotos.net.br/backup/upload/15989/1598966287bc7/zd533ba5e829b0543093a45e1d8398bd7.jpg\nd6d377927ab49671acdd38aa6f661a460601a88b,2015-01-20,rent,apartment,Pernambuco,|Brasil|Pernambuco|,Brasil,Pernambuco,,\"-8.126271,-34.903793\",-8.126271,-34.903793,1200,BRL,1184.32,383.55,,63,6.088095238095239,19.047619047619047,1,2,,http://www.properati.com.br/w71k_alugar_apartamentos_pernambuco,\"CONTATOS: Claro: 81./ Tim: 81./ Oi: 81./ Vivo: 81.Apartamento de 2 quartos mais Dependência completa de empregada, 1.º andar de edifício localizado na Rua Dr. Gaspar Regueira da Costa, 99, Boa Viagem.Este apartamento é constituído por: 1 Sala, 2 Quartos, 2 Banheiros, Cozinha, Área de serviço, Dependência completa de empregada e 1 lugar de estacionamento.O Valor do aluguer já inclui taxas.Agende já a sua visita.Nota: O Edifício irá ser pintado exteriormente, em breve.ISI-120-T2 #ref:ISI-120-T2 - ISI-120-T2 - idalécio Soares\",\"Apartamento de 2 quartos, DCE, 1.º Andar, 63 m2, Boa Viagem - Recife\",https://thumbs4.properati.com/0/B9BCwP1HK7NvZIYINGkFNnNfOZ4=/trim/198x0/smart/filters:strip_icc()/feedmedia.egorealestate.com/Zfeed/S5/C4123/P2270979/Tphoto/ID03a72200-0000-0500-0000-00000141a7a5.JPG\nc6826a9508436920fd59b46a202354f3e4823d22,2014-10-29,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.1546253,-45.7906242\",-23.1546253,-45.7906242,0,BRL,0,0,,3800,,,,,,http://www.properati.com.br/s41f_alugar_comercial_cacapava,\"Condomínio com 22 Módulos de Galpões; 3.800m2 a 12.600m2; 12m pé-direito médio; Piso 6 TON/m2; Ã�rea total de Terreno : 324.000 m2; Ã�rea Total construída: 133.200 m2; 293 vagas para automóveis; 103 vagas para caminhões; Refeitório para 256 pessoas por turno;Benefícios Fiscais no município de Caçapava conforme Lei Lei 5055/211; Isenção de Taxas;Isenção de IPTU; Redução de ISS; Reversão de ICMS.Valores locação = R$ 15,00 o m2Tx Cond. = R$ 1,50 o m2Valores poderão ser alterados sem prévio aviso.A Heron Imóveis oferece este(a) Comercial em Caçapava, no bairro Caçapava. Gostou desse(a) Comercial em Caçapava? Então veja mais informações! Acesse o nosso site clicando no botão Contate o anunciante ou ligue - ou ainda entre em contato conosco pelo email contato@ Obrigado!\",\"Comercial em Caçapava, no bairro Caçapava\",https://thumbs4.properati.com/1/J2n8nXVlvduy6EJjyLwPneEoc28=/trim/198x0/smart/filters:strip_icc()/www.heronimoveis.com.br/fotos/F00/35/23500-DSC06177.JPG\nba957ad3cb2d12d55d1f0466eeffe788a35a6f4f,2014-08-04,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7780086,-49.3450562\",-20.7780086,-49.3450562,950,BRL,751.1,243.25,,,,,,1,,http://www.properati.com.br/lxp9_alugar_casas_sao-jose-do-rio-preto,\"Ref.: CA95876 Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Garden Village II..:Casa Condomnio Garden Village II, Casa Condominio, Dormitrios: 03, Tipo Apto.: 01, Wc. Social: 01, Sala de Visita: 01, Copa: 01, Cozinha: 01, Cozinha Planejada, Despensa, rea de Servio, Ar Condicionado, Alarme, Acabamento Em Gesso, Sanca, Campainha, Cerca Eletrica, Forro de Laje, Piso Dormitrios: Frio, Piso Salas: Frio, M2/ Terreno: 145, M2 rea Construida: 70, Garagem Descoberta: 01, Salo de Festa, Detalhes: R$ 950,00 + Iptu + Condomnio\", Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Garden Village II,https://thumbs4.properati.com/9/xcrfHjSnXdhGcOK1m1wg1jHJlsw=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/803/8860a8e8-6d62-49e6-a992-54ab8f360f35.jpg\n0999b33908c71af3b8be282ab35ab61b38a46353,2014-07-16,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,4300,BRL,4078.98,1321,,319,4.141065830721003,13.47962382445141,,4,,http://www.properati.com.br/ifuj_alugar_apartamentos_vila-olimpia,\"Apartamento na Vila Olí­mpia, com 4 quartos, sendo 2 suí­tes,Ampla sala com terraí§o, piso de madeira corrida, 1 dos quartos pode ser revertido para sala de tv ou escritório.Quartos com carpete de madeira, as suí­tes com armí¡rios embutidos, uma delas com ar condicionado.Armí¡rio corredor.Cozinha com armí¡rio, Piso de granito, na cozinha e lavanderia.  Referíªncia: AP34\",\"Apartamento no(a) Vila Olímpia , São Paulo - SP\",https://thumbs4.properati.com/8/nxrbzTDipvKnqdx1wDABEyIbSes=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/34/c789b808-bf30-460d-9376-16299ce5abbb.jpg\n011d5618639ce229b012022a32b22eff9abd213e,2014-11-19,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.9648026,-46.3855723\",-23.9648026,-46.3855723,1300,BRL,1323.12,428.5,,60,7.141666666666667,21.666666666666668,4,1,,http://www.properati.com.br/tl4s_alugar_apartamentos_sao-paulo,\"PACOTE R$ 1.300,00 OTIMO APARTAMENTO MOBILIADO COM TERRAÇO PROXIMO SHOPPING , PRAIA E COMERCIOS ACEITA FIADOR / SEG. FIANÇA A 3 Milenio oferece este(a) Apartamento em Sao Vicente, no bairro Gonzaguinha - 1 dormitórios. Gostou desse(a) Apartamento em Sao Vicente? Então veja mais informações! Acesse o nosso site clicando no botão Contate o anunciante ou ligue - ou ainda entre em contato conosco pelo email vendas@ Obrigado!\",\"Apartamento em Sao Vicente, no bairro Gonzaguinha - 1 dormitórios\",https://thumbs4.properati.com/4/x8BqTbd304ntTBY5pTf5Yi_nNMw=/trim/198x0/smart/filters:strip_icc()/www.imobiliariasdesantos.com.br/fotos/F00/74/757400-FOTOS%20HELANE%20001.jpg\n6f274a2d23ffa89cb0301f3277585beda7723ce8,2014-09-01,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8343086,-49.3805559\",-20.8343086,-49.3805559,5000,BRL,3953.34,1280.31,,,,,,,,http://www.properati.com.br/o23s_alugar_comercial_sao-jose-do-rio-preto_rua-nuno-alvares-pereira,\"Ref.: SA95668Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Pq. EstorilM2 rea Construida: 170, Detalhes: Salo de 170 M, Com Mezanino. J Com Acessibilidade. Tem Uma Linda Fachada e Estacionamento Para Clientes.\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Pq. Estoril,https://thumbs4.properati.com/8/7VqZVGV6Hl3paOrhYXv_ksowyEw=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1875/TG5yXGj9EJPaa5o0dWgL.jpg\ndb6f890847bcacc6ec3e78a0e6613132174e4c70,2014-12-05,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8178411,-49.387605\",-20.8178411,-49.387605,10000,BRL,7906.68,2560.62,,,,,,1,,http://www.properati.com.br/ue07_alugar_casas_sao-jose-do-rio-preto_rua-cila,\"Ref.: CO95026Tipo: Casa Comercial, Predio ComercialCidade: So Jos do Rio Preto - SPBairro: RedentoraSobrado, Dormitrios: 4, Tipo Apto.: 4, C/ A/e: 4, A/e C/ Interior: 4, Wc. Social: 1, Copa/cozinha: 1, Cozinha C/ A/e: 1, Despensa, Salas : 1, Sala P/ Ambientes : 4, M2 rea Construida: 330, Entrada Lateral: 1, Piso Dormitrios: Frio\",\"Ref.: CO95026 Tipo: Casa Comercial, Predio Comercial Cidade: So Jos do Rio Preto - SP Bairro: Redentora\",https://thumbs4.properati.com/2/DNREjVLgpGPzTlv8aIvhpd7KZkI=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2461/02ea33d3-3ebd-4139-a380-b60eb84c87d1.jpg\nf7d3243419c85cf439189481819f13cb22ab0498,2014-10-01,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8126181,-49.3404534\",-20.8126181,-49.3404534,3800,BRL,3004.52,973.03,,,,,,,,http://www.properati.com.br/qz8t_alugar_casas_sao-jose-do-rio-preto_avenida-miguel-damha,\"Ref.: CA95902Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Damha III..: Casa Condomnio Damha III, Casa Condominio, Dormitrios: 03, Suite: 01, Closet , Tipo Apto.: 02, C/ A/e: 02, Home, Copa/cozinha: 01, Cozinha Planejada, Piscina, Varanda, rea de Servio, Wc. de Empregada/servio, Forro de Laje, M2/ Terreno: 450, M2 rea Construida: 240, rea de Lazer, Garagem Descoberta: 02, Garagem Coberta: 02, Churrasqueira, Valor Condominio R$-: 420,00\",Ref.: CA95902 Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Damha III,https://thumbs4.properati.com/8/cqu5q3tBJOddQ526fWf2puVwINI=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2060/6064cf7e-f8ed-44f3-8a80-b6576de27d62.jpg\ncbfc4c495b3578c995363825a85a821217fab4ec,2014-10-18,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,14000,BRL,11069.36,3584.87,,,,,,,,http://www.properati.com.br/ri3p_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95918Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Mini Dist. Industrial Adail Vetorasso..: Salo Comercial No Mini Distrito Industrial Adail Vetorasso, Salo Comercial, Wcs.: 02, Cozinha: 01, Escritrio, M2/ Terreno: 2.100, M2 rea Construida: 1.350, Detalhes: Cobertura Metlica.p Direito Com 7 Metros.corredor Lateral.\",Ref.: SA95918 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Mini Dist. Industrial Adail Vetorasso,https://thumbs4.properati.com/9/INPTa4YGuWfsekPfC8ez-rqd3PI=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2285/Mhd9hMU2ZwU9YXAC9Hzi.jpg\nb4dab15f9d4411d0847d1871e0a62e107b492fdc,2014-08-02,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.8773135,-46.7592613\",-23.8773135,-46.7592613,28000,BRL,22138.72,7169.74,,,,,,,,http://www.properati.com.br/lud3_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95869Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Eng. Schimidt..: Salo Comercial Em Engenheiro Schimidt, Salo Comercial, Obs: Salo Ideal Para Transportadora Com Frente Para 03 Marginal, Esquina Com Rua Projetada 01 e Rodovia W. Luiz, M2/ Terreno: 5.500, M2 rea Construida: 2.200\",Ref.: SA95869 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Eng. Schimidt,https://thumbs4.properati.com/3/oJm3M_UtBn2PVRsZ2O4n48IvhuA=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/780/60d090bc-4a05-4929-88ae-aca5b9873a76.jpg\nd3769eb1b41c780c83f2d176adae8ad9a81b7ba3,2014-10-08,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.818728,-49.4212608\",-20.818728,-49.4212608,16000,BRL,12650.68,4096.99,,,,,,,,http://www.properati.com.br/r6d9_alugar_comercial_sao-jose-do-rio-preto_avenida-joao-batista-vetorasso,\"Ref.: SA95810Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Dist. Industrial Waldemar VerdiM2/ Terreno: 5.000, M2 rea Construida: 1.400, Detalhes: Com Recepo e 8 Salas. P Direito Com 7 M.\",Salo,https://thumbs4.properati.com/6/GFkI3LbwZQAhB8USv5fTltHxz0E=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2179/QEWBOD1T8GCOjBi0H6WI.jpg\nf51aebdb554e9b5dfa16c3538d3376040f5f2b5c,2014-08-20,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-22.1423284,-51.3750141\",-22.1423284,-51.3750141,16000,BRL,12650.68,4096.99,,,,,,,,http://www.properati.com.br/np97_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95723Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Moyss Miguel HaddadM2 rea Construida: 322, Obs: Salo Ideal Para Loja, 217m Terreo e 105m Mezanino. Estacionamento Na Frente.imvel Novo.\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Moyss Miguel Haddad,https://thumbs4.properati.com/2/viy2bV22HhxBkY_449PiH9sLQPw=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1548/9242514f-75f0-4bdc-a5b1-68a958cbda17.jpg\n7266f270625a52c84adaf69ac08a53b397617247,2014-09-01,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,6000,BRL,4744,1536.37,,,,,,,,http://www.properati.com.br/o20x_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95742Tipo: Salo IndustrialCidade: So Jos do Rio Preto - SPBairro: Quinta das PaineirasWcs.: 02, Cozinha: 01, M2/ Terreno: 720, M2 rea Construida: 458,90, Obs: Salo Industrial Grande Com 458m, Com Boa Acessibilidade e tima Localizao, 02 Quadras da Avenida Potirendaba, Fcil Acesso e Entrada Para Caminhes.\",Tipo: Salo Industrial Cidade: So Jos do Rio Preto - SP Bairro: Quinta das Paineiras,https://thumbs4.properati.com/3/0rnMsn1G6rWTjCQ9lnrLn6WR7JA=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1771/KET9NdQ0WNrtQlhGEVEq.jpg\nc1e10a8afe111ba5caa5034df1ecb56b25b772f9,2014-08-19,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-22.1423284,-51.3750141\",-22.1423284,-51.3750141,12000,BRL,9488,3072.74,,,,,,,,http://www.properati.com.br/muvz_alugar_casas_sao-jose-do-rio-preto,\"Ref.: CA95766Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Moyss Miguel HaddadWc. Social: 04, M2/ Terreno: 980, M2 rea Construida: 500, Obs: Entrada Lateral Com Acesso Para Caminho, Com 04 Wcs. Sendo 02 Wc P/ Deficiente.\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Moyss Miguel Haddad,https://thumbs4.properati.com/3/GvRImi73Q9koPTqVulTG5pg1J2s=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1519/T181YBWZd62P9tMfgs1F.jpg\nd156f7cc95141ef1fca4de81d6dc65b143c9988f,2014-08-19,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-22.1423284,-51.3750141\",-22.1423284,-51.3750141,12000,BRL,9488,3072.74,,,,,,,,http://www.properati.com.br/muwd_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95749Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Moyss Miguel HaddadM2 rea Construida: 280, Obs: Salo Ideal Para Loja, 217mterreo + 68m Mezanino.estacionamento Na Frente,\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Moyss Miguel Haddad,https://thumbs4.properati.com/5/A5TB5m6yLugDjIdQVsXy9br70YQ=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1533/eeec7b18-daa6-422b-aac1-82894f366e60.jpg\n90ed612641d0db18967ee9c07edc8b0647c97976,2014-08-19,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-22.1423284,-51.3750141\",-22.1423284,-51.3750141,12000,BRL,9488,3072.74,,,,,,,,http://www.properati.com.br/muwc_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95725Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Moyss Miguel HaddadM2 rea Construida: 280, Obs: Salo Ideal Para Loja, 217m Terreo, + 68m Mezanino.estacionamento Na Frente, Imvel Novo\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Moyss Miguel Haddad,https://thumbs4.properati.com/2/8rbyOC1iGXRWRZqh5Z6pcOsdsfQ=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1532/a142f7a0-ff97-4794-84d8-4144253429c3.jpg\nc69e1ca4e7cd3346bc510240893bf2c2c7437e8e,2014-08-19,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-22.1423284,-51.3750141\",-22.1423284,-51.3750141,12000,BRL,9488,3072.74,,,,,,,,http://www.properati.com.br/muvh_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95724Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Moyss Miguel HaddadM2/ Terreno: 280, Obs: Salo Ideal Para Loja, 217m Terreo e + 68m Mezanino.Estacionamento Na Frente,imvel Novo.\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Moyss Miguel Haddad,https://thumbs4.properati.com/1/3gyCoSFzrSme_NcQhKWIEnuoeaY=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1501/CeAfrlKKrQIRE9uzNxoa.jpg\n0c5e391bac4d007585efb02466681c760a758423,2014-09-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8379345,-49.3370139\",-20.8379345,-49.3370139,8000,BRL,6325.32,2048.49,,,,,,,,http://www.properati.com.br/q4jv_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95899Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Ch. Estancia Jockey Clube..: Salo Comercial , Salo Comercial, Wcs.: 04, Wcs. C/ A/e, Cozinha: 01, Obs: P Direito 9 Metros, M2 rea Construida: 1.000, Detalhes: Salo Novo, Sendo Os Wcs Piso Porcelanato, Cozinha Porcelanato e Piso Liso, Com Telhado Vulcanizado.\",Ref.: SA95899 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Ch. Estancia Jockey Clube,https://thumbs4.properati.com/8/OM-MkRbrVgdcQa163wlgXjWDHqU=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2007/8541c9f9-4477-448d-a193-d2b00dc5eed4.jpg\n01bdf823f7827d745574bb7a180a39e4db359463,2014-08-19,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,8000,BRL,6325.32,2048.49,,,,,,,,http://www.properati.com.br/muux_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95894Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Ch. Jockey Clube..: Salo Comercial Na Chcara Jockey Clube, Salo Comercial, M2/ Terreno: 5.000, M2 rea Construida: 1.150, Detalhes: Galpo Com Piso Cimento Polido, Estrutura Metlica, Entrada Lateral Para Caminho.p Direito Alto Com 6,/2 M. \",Ref.: SA95894 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Ch. Jockey Clube,https://thumbs4.properati.com/1/HZjM3Xdq2xWgpQ5gSrCVIASVnJA=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1481/Tz9VYOVWXC1ZVU5fUHwL.jpg\n050a20b55332403b962575877b81d609a5988a15,2014-09-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8379345,-49.3370139\",-20.8379345,-49.3370139,8000,BRL,6325.32,2048.49,,,,,,,,http://www.properati.com.br/q4ju_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95900Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Ch. Estancia Jockey Clube..: Salo Comercial , Salo Comercial, Wcs.: 04, Wcs. C/ A/e, Cozinha: 01, Obs: P Direito 9 Metros, M2 rea Construida: 1.000, Detalhes: Salo Novo, Sendo Os Wcs Piso Porcelanato, Cozinha Porcelanato e Piso Liso, Com Telhado Vulcanizado.\",Ref.: SA95900 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Ch. Estancia Jockey Clube,https://thumbs4.properati.com/3/ZL10NlMLKOM5C49dt-MjpOTaaMo=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2006/9fbe5c67-00ba-4e7b-9904-08316f433dd4.jpg\nfeb178373d5eab7df3b6c546a5d1e6a192156c7f,2014-08-19,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8457948,-49.353464\",-20.8457948,-49.353464,8000,BRL,6325.32,2048.49,,,,,,,,http://www.properati.com.br/muuv_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95892Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Ch. Jockey Clube..: Salo Comercial Na Chcara Jockey Clube, Salo Comercial, M2/ Terreno: 5.000, M2 rea Construida: 1.150, Detalhes: Galpo Com Piso Cimento Polido, Estrutura Metlica, Entrada Lateral Para Caminho.p Direito Alto Com 6,/2 M.\",Ref.: SA95892 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Ch. Jockey Clube ,https://thumbs4.properati.com/5/xwV26hopilS3ZK6xLE-O3iy4rE4=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1479/vT0mPYs7U2dAsti8LWGQ.jpg\n882a8542b44e551782d3126a1d43d907cdba854b,2014-08-18,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8442587,-49.3291753\",-20.8442587,-49.3291753,7000,BRL,5534.66,1792.43,,,,,,,,http://www.properati.com.br/mt5y_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95817Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Marginal Eng. SchimidtWc. Social: 02, Cozinha, Mesanino, Escritrio, M2/ Terreno: 2.000, M2 rea Construida: 1.100, Pintura Nova, Piso Dormitrios: Cimentado;, Piso Salas: Polido;, Detalhes: Salo Novo, Com Estrutura Metlica e Banheiro C/ Acessibilidade.\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Marginal Eng. Schimidt,https://thumbs4.properati.com/4/FPT3HYl7BCrZMA5Okj6iNxUgPRA=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1432/MaEwnQ1cL6HLcrscPFb8.jpg\n201e0ccc9fc63a78f68391bdd74ac5407e07da9b,2014-08-04,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,1000,BRL,790.66,256.06,,,,,,1,,http://www.properati.com.br/lxpd_alugar_casas_sao-jose-do-rio-preto,\"Ref.: CA95884Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Parque Liberdade III..:Casa Condomnio Parque da Liberdade III, Casa Condominio, Dormitrios: 03, C/ A/e: 02, Wc. Social: 01, Wcs. C/ A/e, Sala de Visita: 01, Cozinha: 01, Cozinha Planejada, Interfone, Acabamento Em Gesso, Sanca, Campainha, Forro de Laje, Obs: Imvel Semi Novo, Com Projeto da Arquiteta Kenia Miranda, o Imvel Tem Papel de Parede, Cortinas Nos Dormitrios e Sala, Luminrias Na Cozinha., M2/ Terreno: 143, Garagem Coberta: 02, Churrasqueira, Detalhes: R$ 1.000,00 + Iptu + Condominio\",Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Parque Liberdade III,https://thumbs4.properati.com/2/v319TrEqOc-5Hy9xmTQXh1KL2UE=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/807/7331be4a-3945-41cd-b761-4322696192f5.jpg\n05cf0728d4ba6016469b6b4a864542a1824dc25c,2014-08-15,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.7688712,-46.7100661\",-23.7688712,-46.7100661,2000,BRL,1581.32,512.12,,,,,,,,http://www.properati.com.br/mqtt_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95463Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Jardim So MarcosWc. Social: 2, Cozinha: 1, Mesanino, Recepo, M2/ Terreno: 406, M2 rea Construida: 110, Pintura Nova, Piso Salas: Piso Frio\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Jardim So Marcos,https://thumbs4.properati.com/9/EMUy3dbQiGwd8LmfHH92qsmCQ7Q=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1375/srbDQgpudCyI2lewxvjS.jpg\nf5942694fe76bfe705275acfe74148c588367f86,2014-09-01,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.7688712,-46.7100661\",-23.7688712,-46.7100661,4000,BRL,3162.64,1024.24,,,,,,8,,http://www.properati.com.br/o215_alugar_casas_sao-jose-do-rio-preto_rua-antonio-dias,\"Ref.: CO95681Tipo: Casa ComercialCidade: So Jos do Rio Preto - SPBairro: Jardim So MarcosWc. Social: 04, Sala(s): 08, M2/ Terreno: 280, Garagem Coberta: 02, Forro de Laje, Piso Salas: Frio, Iptu Valor: 58,00, Detalhes: timo Local Para Clincas.\",Tipo: Casa Comercial Cidade: So Jos do Rio Preto - SP Bairro: Jardim So Marcos,https://thumbs4.properati.com/4/F58YgAYlsu22NFL9RPPH5RURZJ8=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1779/sr0aS0NcXQGZ7HsOtyan.jpg\n1b093694ca05135b213162c0bd499e9adb708309,2014-12-10,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.6302071,-46.5818983\",-23.6302071,-46.5818983,4000,BRL,3162.64,1024.24,,,,,,,,http://www.properati.com.br/ufsq_alugar_comercial_sao-jose-do-rio-preto_avenida-marginal,\"Ref.: SA95942Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Pq. Res. Manor Daud..: Salo Comercial No Parque Residencial Manor Daud, Salo Comercial, Detalhes: Salo Com 340m, Entrada Para Caminho.frente Para Br153\",Ref.: SA95942 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Pq. Res. Manor Daud,https://thumbs4.properati.com/4/4e8bVPqS0g1ezrFKP9fcdzvSn0I=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2493/rJ3aboE1bZnwuqJHy4Lr.jpg\n50bd70c45a9a9b087c404cac6b67c491f64c211e,2014-10-18,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,2500,BRL,1976.65,640.15,,,,,,,,http://www.properati.com.br/ri3l_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95916Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Res. Caetano..: Salo Comercial No Residencial Caetano, Salo Comercial, Wcs.: 02, Cozinha: 01, M2 rea Construida: 350, Detalhes: Cozinha2 Wc Com Acessibilidade;1 Porta Na Frente1 Porta Na Lateral;barraco Novo.cobertura Com Estrutura Metlica. P Direito de 6 Metros.\",\"Ref.: SA95916 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Res. Caetano ..: Salo Comercial No Residencial Caetano, Salo Comercial, Wcs.: 02, Cozinha: 01, M2 rea Construida: 350, Detalhes: Cozinha 2 Wc Com Acessibilidade\",https://thumbs4.properati.com/9/l325GXwN3fD9xS0dglH5hmaB83A=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2281/FjKJBqBJFxhvJ4atJVuO.jpg\n50a4a96d3ec01b7b1032e5eec4a3496b38f35c4f,2014-10-09,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7780086,-49.3450562\",-20.7780086,-49.3450562,2500,BRL,1976.65,640.15,,,,,,,,http://www.properati.com.br/r9j7_alugar_casas_sao-jose-do-rio-preto_avenida-belvedere,\"Ref.: CA95904Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Village Damha Rio Preto I..: Casa Em Condomnio Village Damha Rio Preto I, Casa Condominio, Dormitrios: 03, Tipo Apto.: 01, C/ A/e: 03, Cozinha: 01, Cozinha Planejada, Varanda, rea de Servio, Wc. de Empregada/servio, Garagem Coberta: 02, Churrasqueira\",Ref.: CA95904 Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Village Damha Rio Preto I,https://thumbs4.properati.com/1/CVM0xMvGlg_DX1BluR3vWpRkhMU=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2206/7f2a5d99-2465-48fb-859c-a52311ba0c18.jpg\nf5be311dcee4a66dcb26f463f76c49b6084de8ab,2014-11-11,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,1600,BRL,1411.74,457.2,,,,,2,1,,http://www.properati.com.br/t8nu_alugar_apartamentos_tatuape_rua-serra-de-braganca,\"APTO DE 40M/2 COM 01 DORMITRIO, SALA AMPLA COM SACADA, COZINHA PLANEJADA, WC SOCIAL, REA DE SERVIO, 01 VAGA DE GARAGEM LIVRE, LAZER COMPLETO COM PISCINA, PLAYGROUD, SALO DE FESTA, CHURRASQUEIRA, SAUNA, SALA DE FITINESS, SALA DE LEITURA, SALO DE JOGOS, EXCELENTE LOCALIZAO. O APTO ESTA COM FOGO, GELADEIRA, MICROONDAS, SUGGAR, BALCO ESTILO COZINHA AMERICANA, 02 BANQUETAS, 02 MESINHAS DE CANTO, 01 HOME-OFFICER, CAMA BOX COM COLCHO, VENTILADOR DE TETO, ARMRIO NO DORMITRIO.\",TOLEDO CONSULTORIA JURIDICA E IMOBILIARIA,https://thumbs4.properati.com/6/-9lTZIOGIGHwHaddpPoJu6s92hY=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/toledo/photos/2/6c5e21eb-1c7b-41d6-9257-b6e7994ea93c.jpg\na031b9b0ccce57678aab134025713cdb4ad7ed19,2014-10-18,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7630911,-49.3810839\",-20.7630911,-49.3810839,1500,BRL,1185.99,384.09,,,,,,,,http://www.properati.com.br/ri3h_alugar_comercial_sao-jose-do-rio-preto_rua-gumercindo-tomas-de-aquino,\"Ref.: SA95914Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Dist. Industrial Centenrio da Emancipao..: Salo Comercial No Distrito Industrial Centenrio da Emancipao, Salo Comercial, Wcs.: 02, Cozinha: 01, M2/ Terreno: 200, M2 rea Construida: 100, Detalhes: Salo Em Construo, Com 2 Wc Com Acessibilidade e Cozinha.porta Frente e Lateral.estrutura Metlica, P Direito 5 Metros.\",Ref.: SA95914 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Dist. Industrial Centenrio da Emancipao,https://thumbs4.properati.com/5/cuTILpWk120n1EtpTTP7CuHqFTI=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2277/xOFbBdd0MAPLxuFIMMMc.jpg\n8198126233f30e45a7ec723640ecd09fc933f35f,2014-10-18,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7630911,-49.3810839\",-20.7630911,-49.3810839,1500,BRL,1185.99,384.09,,,,,,,,http://www.properati.com.br/ri3j_alugar_comercial_sao-jose-do-rio-preto_rua-gumercindo-tomas-de-aquino,\"Ref.: SA95915Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Dist. Industrial Centenrio da Emancipao..: Salo Comercial No Distrito Industrial Centenrio da Emancipao, Salo Comercial, Wcs.: 02, Cozinha: 01, M2/ Terreno: 200, M2 rea Construida: 100, Detalhes: Salo Em Construo, Com 2 Wc Com Acessibilidade e Cozinha.porta Frente e Lateral.estrutura Metlica, P Direito 5 Metros.\",Ref.: SA95915 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Dist. Industrial Centenrio da Emancipao,https://thumbs4.properati.com/0/LWklCEKjfuTI94hwh7U-tpkiQxw=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2279/YBRlngu70KNBBuf3pXju.jpg\ncfe8aedbfe7496306c9ee598fdb0799d1a4d43a7,2014-08-14,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8178411,-49.387605\",-20.8178411,-49.387605,3000,BRL,2371.98,768.18,,,,,,,,http://www.properati.com.br/mpsw_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95860Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: RedentoraSalo Comercial, Terrea, Acabamento Em Gesso, Forro de Laje, Piso Salas: Frio, M2 rea Construida: 160\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Redentora,https://thumbs4.properati.com/1/w-odT7rMEUYcr3TVS4fo2ausAAI=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1278/Kp7YsojHwZdQDjK4JvMz.jpg\n9002dc7d32f4f097a4f9c789ad93b7f5304396cf,2014-08-14,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8178411,-49.387605\",-20.8178411,-49.387605,3000,BRL,2371.98,768.18,,,,,,,,http://www.properati.com.br/mpsz_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95862Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: RedentoraM2 rea Construida: 190, Garagem Descoberta: 02, Forro de Laje, Piso Salas: Frio\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Redentora,https://thumbs4.properati.com/7/xW33i6TXrwRgMFrpyW_syKKKlPs=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1281/UKC5P4P7lZPyQSY9l8xC.jpg\n2128d47314a494ddecda6b6e981c46afbd3fae51,2014-10-30,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,3000,BRL,2371.98,768.18,,,,,,,,http://www.properati.com.br/s9we_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95921Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Redentora..: Salo Comercial Na Redentora, Salo Comercial, M2/ Terreno: 150, Detalhes: tima Localizao!\",Ref.: SA95921 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Redentora,https://thumbs4.properati.com/6/HVrSVpwKgIJpnnFxGcMzyHIeUx4=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2347/abTB8PxHKedvADO9yyhF.jpg\n0329fa7a5536c99eee743612010897cafed7f340,2014-10-15,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8030634,-49.4324803\",-20.8030634,-49.4324803,3000,BRL,2371.98,768.18,,,,,,,,http://www.properati.com.br/re15_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95910Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Res. Palestra..: Salo Comercial No Residencial Palestra, Salo Comercial, Wc. Social: 02, Forro de Laje, Obs: timo Salo, Novo., M2/ Terreno: 250, M2 rea Construida: 200\",Ref.: SA95910 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Res. Palestra,https://thumbs4.properati.com/8/MBuRdEvx5x-V-zpeycnxFNg-n70=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2238/NOqLX7BtA7KEH0SUs5rV.jpg\nc822a8819d8874916877cc6b3c91194cbf5968ca,2014-10-09,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7780086,-49.3450562\",-20.7780086,-49.3450562,3000,BRL,2371.98,768.18,,,,,,,,http://www.properati.com.br/r9j6_alugar_casas_sao-jose-do-rio-preto_avenida-belvedere,\"Ref.: CA95903Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Village Damha Rio Preto I..: Casa Condomnio Village Damha Rio Preto I, Casa Condominio, Dormitrios: 03, Tipo Apto.: 03, C/ A/e: 03, Cozinha: 01, Cozinha Planejada, Fogo, Lavabo, Salas : 01, Sala P/ Ambientes : 02, Piscina, Ducha Externa, Varanda, rea de Servio, Wc. de Empregada/servio, Garagem Coberta: 02, Churrasqueira\",Ref.: CA95903 Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Village Damha Rio Preto I,https://thumbs4.properati.com/2/595nD1yJ6OYSKSxIr0D1iMhNilQ=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2205/060de194-4e66-4f1e-b0cc-dd05e276539f.jpg\n2568b8aa05b9d3cb3e9e8ef74c4461c34d2d57b5,2014-10-05,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8350772,-49.369111\",-20.8350772,-49.369111,3000,BRL,2371.98,768.18,,,,,,,,http://www.properati.com.br/r1q8_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95905Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Quinta das Paineiras..: Salo Comercial No Quinta das Paineiras, Salo Comercial, Terrea, Cozinha: 01, Escritrio, M2 rea Construida: 225\",Ref.: SA95905 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Quinta das Paineiras,https://thumbs4.properati.com/6/2tu7W_u475v3n8Ubll7avmjLXPI=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2130/0y7ijJHaZqFbrC0LSLFy.jpg\n0dff14f277324eb9f04d59b6c26758ccdb744e27,2014-12-05,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8126181,-49.3404534\",-20.8126181,-49.3404534,3700,BRL,2925.47,947.43,,,,,,,,http://www.properati.com.br/udzy_alugar_casas_sao-jose-do-rio-preto_avenida-miguel-damha,\"Ref.: CA95938Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Damha III..: Linda Casa Tipo Sobrado No Condomnio Damha Iii, Casa Condominio, Sobrado, Dormitrios: 03, Suite Master: 01, C/ Hidro, Tipo Apto.: 02, C/ A/e, Ducha Dupla, Lavatrio Duplo, Cozinha: 01, Cozinha Planejada, Despensa, Escritrio, Lavabo, Salas : 01, Sala P/ Ambientes : 02, Varanda, rea de Servio, Lavanderia, Quarto de Empregada, Wc. de Empregada/servio, Forro de Laje, Garagem Descoberta: 02, Garagem Coberta: 02, Churrasqueira\",Ref.: CA95938 Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Damha III,https://thumbs4.properati.com/0/FGZdTzG9TG6qqioIMRxZ7WLDQWY=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2452/bVU2c6naoVF7KCpphJqL.jpg\n6b3bff7cc70a79dfed41fc0ae514a7ad16a328b8,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,7000,BRL,6640.22,2150.47,,88,24.437159090909088,79.54545454545455,,,,http://www.properati.com.br/ifvc_alugar_comercial_vila-olimpia,\"\tEdifício comercial de excelente padrão, localizado em ponto nobre da Vila Olímpia, próximo da Av. dos Bandeirantes e Shopping Vila Olímpia.\t Prédio conta com 3 elevadores de alta velocidade, sistema de proteção contra incêndio, ar condicionado central, próximo a bancos. Referíªncia: CO65\",\"Comercio no(a) Vila Olímpia , São Paulo - SP\",\nc26e111d703af6f65be91df94c274bbd41585a40,2014-08-21,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.6357809,-46.5324419\",-23.6357809,-46.5324419,50000,BRL,39533.44,12803.11,,,,,,,,http://www.properati.com.br/nqbl_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95307Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Rodovia Assis ChateaubriandWcs.: 4, Escritrio, M2/ Terreno: 4.000, Detalhes: Barraco Com 4.000 M\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Rodovia Assis Chateaubriand,https://thumbs4.properati.com/0/lJ2ffPr7gtS2vlQuGTg_zDX6844=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1592/WQmB3JTS7Y2N72aUI5Ud.jpg\n2e8ffab1b55b5245e917b3597009338cb92b6586,2014-10-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7807632,-49.3985157\",-20.7807632,-49.3985157,50000,BRL,39533.44,12803.11,,,,,,,,http://www.properati.com.br/rgyw_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95804Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Dist. Industrial Waldemar VerdiM2/ Terreno: 6.000, M2 rea Construida: 3.200\",Salo,https://thumbs4.properati.com/3/X38EXdJIkvCXPQCdWURfrgMMMWQ=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2270/OTptGk75zRPMzZ1pL1on.jpg\nc522d5f57ea8e14d18fa3ba746cb581cd379330a,2014-08-20,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,100000,BRL,79066.88,25606.22,,,,,,,,http://www.properati.com.br/np98_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95721Tipo: Salo IndustrialCidade: So Jos do Rio Preto - SPBairro: Dist. Industrial IWcs.: Vrios, M2/ Terreno: 13.000, M2 rea Construida: 8.500, Detalhes: tima Localizao\",Tipo: Salo Industrial Cidade: So Jos do Rio Preto - SP Bairro: Dist. Industrial I,https://thumbs4.properati.com/6/jARKWDWV0rGe-HzGUtUeE0X3knw=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1549/ZaCxToSHCaeCL7GxZ08g.jpg\n07383fafc72a9aa9f0286b841b782ca718485de8,2014-09-01,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8178198,-49.3468294\",-20.8178198,-49.3468294,3500,BRL,2767.31,896.21,,,,,,1,,http://www.properati.com.br/o246_alugar_casas_sao-jose-do-rio-preto_avenida-nadima-damha,\"Ref.: CA95827Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Damha II (Res. Jardins)Dormitrios: 03, Suite: 03, C/ A/e: 03, Sacada, Sala de Visita, Copa, Cozinha, Cozinha Planejada, Despensa, Piscina, rea de Servio, Lavanderia, Wc. de Empregada/servio, Garagem Descoberta: 02, Garagem Coberta: 02, Churrasqueira, Acabamento Em Gesso, Sanca, Forro de Laje, Piso Salas: Frio;, Documentao: Em Dia.\",Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Damha II (Res. Jardins),https://thumbs4.properati.com/6/Bq9TbjreWNnjd2VrvJ5oieGegeQ=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1889/db5caa0b-ec86-44f4-9c0f-ab26a6816b62.jpg\n179f70a47a9ce50e161ea483ffee741a8e1c7fb2,2014-08-14,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8178411,-49.387605\",-20.8178411,-49.387605,3500,BRL,2767.31,896.21,,,,,,,,http://www.properati.com.br/mpsx_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95861Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Redentora TerreaM2 rea Construida: 190, Acabamento Em Gesso, Forro de Laje, Pintura Nova, Piso Salas: Frio\",Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Redentora Terrea,https://thumbs4.properati.com/1/VQPzYdCL1X2L1IkzU17WHDuej9s=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1279/nGCLTL2QhwMJSHGtxR7y.jpg\ne48c41f6ad3dba93e0ac6038a5aa5cda39d651d0,2014-09-01,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-22.1423284,-51.3750141\",-22.1423284,-51.3750141,3500,BRL,2767.31,896.21,,,,,,,,http://www.properati.com.br/o2m6_alugar_casas_sao-jose-do-rio-preto,\"Ref.: CA95898Tipo: Casa Comercial, Casa ResidencialCidade: So Jos do Rio Preto - SPBairro: Moyss Miguel Haddad..: Casa Residencial No Moyss Miguel Haddad, Casa Residencial, Dormitrios: 03, Suite: 03, C/ Hidro: 01, C/ A/e: 03, Sacada, Wc. Social: 01, Wcs. C/ A/e, Sala de Visita: 01, Copa: 01, Cozinha: 01, Cozinha Planejada, Despensa, Escritrio, Salas : 01, Sala P/ Ambientes : 03, Piscina, Varanda, rea de Servio, Lavanderia, Quarto de Empregada, Wc. de Empregada/servio, Ar Split, Ventilador, Interfone, Acabamento Em Gesso, Sanca, Tv Cabo, Campainha, Quintal, Grade, Murado, Forro de Laje, Piso Salas: Frio, Obs: Imvel Prximo Ao Shopping Iguatemi, M2 rea Construida: 450, Garagem Descoberta: 04, Garagem Coberta: 02, Churrasqueira, Jardim de Inverno, Jardim Interno, Iptu Valor: 199,00\",\"Ref.: CA95898 Tipo: Casa Comercial, Casa Residencial Cidade: So Jos do Rio Preto - SP Bairro: Moyss Miguel Haddad\",https://thumbs4.properati.com/5/fZwdpPACH02ObP9aj7-UFvVm0lE=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1937/a7WmbhKqWo7JqFgAHcOD.jpg\n9ea36b0e269fbf2581eef1510f0148594919b330,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,3150,BRL,2988.09,967.71,,45,21.50466666666667,70,,,,http://www.properati.com.br/ifxf_alugar_comercial_chacara-santo-antonio,\"\tNovAmérica Inside Park, o novo marco imobiliário de São Paulo. É um novo espaço, um novo bairro, uma nova opção de vida. É a expansão urbana mais consciente e mais urbana. Um mundo para morar, trabalhar e viver em plena harmonia. Uma região onde a vida social e a vida familiar se equilibram. Um lugar que concretiza o prazer de viver em uma grande metrópole. 1 torre comercial, escritórios de vila e lojas, 5 condomínios residenciais independentes e 2 parques em mais de 70.000 m² de terreno.\t\tNovAmérica Office Park é um conceito totalmente novo de local de trabalho, que valoriza eficiência e a qualidade de vida de seus ocupantes. EDSA em parceria com Neusa Nakata tiveram a missão de conceber um produto inusitado, diferenciado e particular. O resultado é um cenário privilegiado para que você e seus clientes tenham bem-estar em horário comercial.\t\tA região do NovAmérica reúne as principais empresas nacionais e multinacionais, que chegaram por aqui atraídas pela infraestrutura e pelas importantes e fáceis vias de acesso. O que confirma que a região é o novo pólo econômico de São Paulo.\t\tConheça os diferenciais do NovAmérica Office Park:\t\tÚnico na Nações Unidas a integrar um complexo formado por 1 torre comercial, escritórios de vila, lojas e 5 condomínios residenciais independentes.\tConceito consagrado de bem-estar em horário comercial.\tAcesso ao parque com mais de 10 mil m² destinado à cidade.\tPotencial de clientes vindos dos 5 condomínios residenciais.\tA oportunidade de morar e trabalhar de forma integrada e independente.\tServiços by Facilities Office.\tArquitetura e decoração inspirados nas ultimas tendências mundiais.\tProjeto paisagístico assinado por um dos mais conceituados escritórios do mundo, a EDSA. Referíªncia: CO150\",\"Comercio no(a) Chácara Santo Antônio , São Paulo - SP\",https://thumbs4.properati.com/2/2S3OsO4umzIVXFHbHKTrb7dk-x8=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/150/c0d9b8fc-3da8-4a27-ac1f-4e1684d35312.jpg\na5c14fe842664d12fe4bdfad313d5af251121d89,2014-10-01,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.5882535,-46.6167776\",-23.5882535,-46.6167776,1400,BRL,1106.91,358.48,,,,,,,,http://www.properati.com.br/qz98_alugar_comercial_sao-jose-do-rio-preto_rua-da-imprensa,Ref.: SA95836Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Vila DinizDetalhes: Salo Comercial Com Aproximadamente 50 M e 02 Banheiros Com Acessibilidade.,Ref.: SA95836 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Vila Diniz,https://thumbs4.properati.com/9/CRgjdJjcRp1Lz-pPZChI7eL8ZIY=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2075/fOuQlJsX61PBHGGwhPSh.jpg\nb64a9bd212d9e91821e5e892eeff464bbc0202cd,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,9600,BRL,9106.57,2949.21,,120,24.57675,80,,,,http://www.properati.com.br/ifwi_alugar_comercial_vila-olimpia,\"\tConjunto Comercial com 120m² de área útil, 3 vagas, 3 banheiros, 1 copa. Referíªncia: CO112\",\"Comercio no(a) Vila Olímpia , São Paulo - SP\",https://thumbs4.properati.com/0/3pwYR5bzXi_fuQfUVbzkPbFkst4=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/112/93f2b4dc-86f7-4544-803f-62dfec9dc967.jpg\n59a2898d29cb6860a70355c1b2028b9ab38d89d9,2015-01-13,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.5456177,-46.563645\",-23.5456177,-46.563645,1801,BRL,1459.35,472.62,,,,,,2,,http://www.properati.com.br/vyso_alugar_apartamentos_tatuape,\"APTO DE 75M/2, COM 03 DORMITRIOS, SALA AMPLA COM SACADA, COZINHA PLANEJADA, WC SOCIAL, REA DE SERVIO COM WC, REPLETOS DE ARMRIOS, 01 VAGA DE GARAGEM FIXA, EXCELENTE ESTRUTURA DE LAZER COM PISCINA, PLAYGROUD, SALO DE FESTA, CHURRASQUEIRA, SALA DE GINASTICA, EXCELENTE LOCALIZAO, A 100 METROS DO METR.\",LOCAO TATUAPE CONDOMINIO ILHAS GREGAS,https://thumbs4.properati.com/6/-xzzzVWk3lxFITvW285d3YmP00c=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/toledo/photos/13/e7c88a20-dcac-49b2-ae42-e782447d3f07.jpg\n1925426e5575ecd98f1e63422a187081754e0f5c,2015-01-13,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.5444872,-46.5657875\",-23.5444872,-46.5657875,1499,BRL,1214.64,393.37,,,,,2,1,,http://www.properati.com.br/vysp_alugar_apartamentos_tatuape_rua-serra-de-braganca,\"APTO DE 40M/2, COM 01 DORMITRIO, SALA AMPLA COM SACADA, COZINHA, WC SOCIAL, SEMI-MOBILIADO COM FOGO, SUGGAR, GELADEIRA, MICRO-ONDAS, ARMRIOS PLANEJADOS NA COZINHA, CAMA BOX, GUARDA ROUPA, VENTILADOR DE TETO, RACK DE TV, 02 BANQUETAS, 02 MESINHAS DE CANTO, EXCELENTE ESTRUTURA DE LAZER COM PISCINA, PLAYGROUD, SALO DE FESTA, CHURRASQUEIRA, SAUNA, SALA DE FITINESS, BRINQUEDOTECA, SALO DE JOGOS, SALA DE LEITURA. EXCELENTE LOCALIZAO, PRXIMO DO SUPERMERCADO PO DE ACAR, FCIL ACESSO PARA O METR.\",NEW CONCEPT 22 A,https://thumbs4.properati.com/5/SXF7T5Zfxal6lcQifCYOufBKjYA=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/toledo/photos/14/0522b83e-c37e-4736-ac81-ca8540d2d806.jpg\nfda45d95b6fe8daf9215dcc80ad057531879eda4,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,4575,BRL,4339.84,1405.48,,120,11.712333333333333,38.125,,,,http://www.properati.com.br/ifxz_alugar_comercial_vila-olimpia,\"\tConjunto Comercial na Vila Olimpia, com 61m², podendo aumentar a área para 117m². Referíªncia: CO179\",\"Comercio no(a) Vila Olímpia , São Paulo - SP\",https://thumbs4.properati.com/5/cZY_8L_8bfKOV9ay-F-6DsnRZ-c=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/179/0df8ffa9-49a9-49c0-9968-37c13719d904.jpg\n7eb9fb8ad21ff1da490b75fd04643939fd72edf1,2015-01-13,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.5508578,-46.5679013\",-23.5508578,-46.5679013,1550,BRL,1255.99,406.76,,,,,11,1,,http://www.properati.com.br/vysn_alugar_apartamentos_tatuape,\"APTO DE 57M/2, COM 02 DORMITRIOS, SALA AMPLA COM SACADA, WC SOCIAL, COZINHA PLANEJADA, AMPLA REA DE SERVIO COM WC, 01 VAGA DE GARAGEM, SALO DE FESTA, CHURRASQUEIRA, PLAYGROUD, QUADRA DE FUTEBOL, EXCELENTE LOCALIZAO, PRXIMO AO SHOPP/METR BOULEVAR TATUAP.\",Apartamento,https://thumbs4.properati.com/4/mwisK4iz0H0viKmBVOGr8B40c1o=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/toledo/photos/12/612ab301-0ac4-4c4c-9691-7278200c2b62.jpg\n59a2934bc5a4c3530613fae471a210ae63498d80,2014-09-02,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.826094,-49.376396\",-20.826094,-49.376396,1200,BRL,960.76,311.15,,,,,,1,,http://www.properati.com.br/ob16_alugar_apartamentos_sao-jose-do-rio-preto,\"Apartamento mobiliado, pronto para morar e com excelente localizao, na zona sul.2 dormitrios sendo 1 tipo apto todos com armrio, ventilador de teto e ar-condicionadosala com sacada para 2 ambientes com sofa e estantecozinha planejada com mesa com 4 cadeiras e fogo2 banheiros com box blindex, gabinete e chuveirorea de servio com armrio, varal e mquina de lavar1 vaga de garagem pilotisCondomnio: Porto eletrnico, portaria 24h, elevador, churrasqueira e piscina.\",Apartamento com 2 dormitrios em Rio Preto,https://thumbs4.properati.com/5/VXcYDJRN6VtNxvgVw-mxJOlL_sA=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/premier/photos/111/91c65753-86b6-40fd-8673-7f8f107a8459.jpg\nd08581768058f5286ca4f209222d59a3f10af84d,2015-01-13,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-22.8877586,-47.081265\",-22.8877586,-47.081265,2401,BRL,1945.56,630.08,,,,,19,2,,http://www.properati.com.br/vysm_alugar_apartamentos_tatuape,\"APTO DE 93M/2, COM 03 DORMITRIOS, SENDO UMA SUTE, SALA AMPLA COM SACADA E DOIS AMBIENTES, WC SOCIAL, COZINHA, AMPLA REA DE SERVIO, REPLETO DE ARMRIOS, 02 VAGAS DE GARAGEM, LAZER COMPLETO COM PISCINA, PLAYGROUD, SALO DE FESTA, SALO DE JOGOS, SAUNA, CHURRASQUEIRA, EXCELENTE LOCALIZAO, PRXIMO DA PRAA SILVIO ROMERO.\",APTO LOCAO TATUAP,https://thumbs4.properati.com/3/d45ivnQIryA-xDQvwZrOc1pBPlE=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/toledo/photos/11/85e5aee8-90fa-4295-bcb5-dce1ed7e789c.jpg\n7739326a3915df1654866f5c27d42e40f0038365,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,2200,BRL,2086.92,675.86,,102,6.626078431372549,21.568627450980394,,,,http://www.properati.com.br/ifwp_alugar_comercial_vila-olimpia,\"\tConjunto Comercial com 46m², 2 banheiros, 3 salas, 1 vaga. Referíªncia: CO122\",\"Comercio no(a) Vila Olímpia , São Paulo - SP\",https://thumbs4.properati.com/6/QhZrO4ITAg_PJagrwPKR0ywe4GI=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/122/80e20053-aaba-4f3b-a8f7-9dc2cdcb44a7.jpg\n4d949c2fcb78e76cf0cb2376ae3a3acc3b27994a,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,2200,BRL,2086.92,675.86,,46,14.692608695652174,47.82608695652174,,,,http://www.properati.com.br/ifut_alugar_comercial_vila-olimpia,\"Conjunto Comercial na Rua Helena, com 46m², 1 vaga. Referíªncia: CO44\",\"Comercio no(a) Vila Olímpia , São Paulo - SP\",https://thumbs4.properati.com/7/rah-1WLiSRF_nrLl-U2mEY27Gus=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/44/3d3a55f5-dd0c-4969-bb57-19f9a554c1f2.jpg\nc98dc531eb6cb68bcec0e60d7dc042bd663caa4d,2014-08-02,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.815191,-49.389435\",-20.815191,-49.389435,5000,BRL,4003.24,1296.47,,,,,,,,http://www.properati.com.br/ludd_alugar_casas_sao-jose-do-rio-preto,\"Imvel com excelente localizao, com recepo, 1 sala com banheiro privativo, 3 salas amplas + 1 interna, todos os ambientes com ar condicionado, cozinha, despensa, garagem interna para vrios veculos. IMVEL SER ENTREGUE REFORMADO E COM AS ACESSIBILIDADES EXIGIDAS!\",Casa Comercial,https://thumbs4.properati.com/8/3TeTIOIgyjtraKZz08buZLIU8W8=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/premier/photos/80/6a0fcb3e-3e78-434f-9624-a5621798b2b2.jpg\ne85c67a2bc55f4834be8d4cf1fd96340ef4ce129,2014-08-04,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8379877,-49.385659\",-20.8379877,-49.385659,800,BRL,632.5,204.84,,,,,,1,,http://www.properati.com.br/lxpc_alugar_casas_higienopolis,\"Ref.: AP95874 Tipo: ApartamentoCidade: So Jos do Rio Preto - SPBairro: Higienpolis..: Apartamento Novo No Jardim Higienopolis, Apartamento, rea Util M2: 50, Dormitrios: 01, C/ A/e, Sacada, Wc. Social: 01, Cozinha C/ A/e, Lavanderia, Elevador, Piso Dormitrios: Porcelanato, Piso Salas: Porcelanato, Obs: Prdio Novo.apto Nunca Habitado., rea de Lazer, Garagem Coberta: 01, Churrasqueira, Espao Gourmet\",Tipo: Apartamento Cidade: So Jos do Rio Preto - SP Bairro: Higienpolis,https://thumbs4.properati.com/6/8tmxgf5PTdg11vI4rVdPwXy39gw=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/806/2077f853-c360-4cd7-b5c8-a33f76415c40.jpg\n33b22b30a043e5a868df61862bb97698b453df89,2014-12-05,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7910821,-49.3781115\",-20.7910821,-49.3781115,800,BRL,632.5,204.84,,,,,,1,,http://www.properati.com.br/ue05_alugar_apartamentos_sao-jose-do-rio-preto_rua-pedro-mulati,\"Ref.: AP95816Tipo: ApartamentoCidade: So Jos do Rio Preto - SPBairro: Jardim ConceioDormitrios: 02, C/ A/e, Wc. Social, Box Wcs., Sala de Estar, Cozinha, Piscina, rea de Servio, Garagem Coberta: 01, rea Util M2: 60, Condominio C/ rea de Lazer, Piscina Adulto, Playground, Valor Condominio R$-: 190,00\",Ref.: AP95816 Tipo: Apartamento Cidade: So Jos do Rio Preto - SP Bairro: Jardim Conceio,https://thumbs4.properati.com/2/4RA-EyT8LswOeQ6lcBbkzLlKjII=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2459/jWyxMNGlETMMxgj2uW9o.jpg\ne2235d97414fcc1a31865b1abe71c1cf85ae01fa,2014-11-17,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7780086,-49.3450562\",-20.7780086,-49.3450562,2700,BRL,2134.78,691.36,,,,,,,,http://www.properati.com.br/tdi5_alugar_casas_sao-jose-do-rio-preto_avenida-belvedere,\"Ref.: CA95926Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Village Damha Rio Preto I..: Casa Condomnio Village Damha Rio Preto, Casa Condominio, Terrea, Dormitrios: 3, Suite: 1, Tipo Apto.: 2, C/ A/e, Lavatrio Duplo: 1, Cozinha C/ A/e: 1, Cozinha Planejada, Escritrio, Lavabo, Salas : 1, Sala P/ Ambientes : 2, Varanda, rea de Servio, Wc. de Empregada/servio, Obs: Espao Para Colocar Piscina, M2 rea Construida: 180, Garagem Descoberta: 2, Garagem Coberta: 2, Churrasqueira, Detalhes: Com Armrios .\",Ref.: CA95926 Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Village Damha Rio Preto I ,https://thumbs4.properati.com/3/ZZ3MVCiFvwDaZxqgfl5oQJ1Vp6c=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2390/5d2ae653-6549-4772-b486-cd9360af6ebd.jpg\n1334a26f10895f5a21b6db0adf96e2ff1dc83799,2014-09-16,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7780086,-49.3450562\",-20.7780086,-49.3450562,1250,BRL,988.31,320.07,,,,,,,,http://www.properati.com.br/q4jz_alugar_casas_sao-jose-do-rio-preto_avenida-belvedere,\"Ref.: CA95901Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Maria Stella..: Casa Condomnio Maria Stella, Casa Condominio, Terrea, Dormitrios: 3, Tipo Apto.: 1, Wc. Social: 1, Cozinha: 1, Cozinha Planejada, Salas : 01, Sala P/ Ambientes : 02, Varanda, rea de Servio, Lavanderia, Garagem Coberta: 02, Salo de Festa, Playground, Piscina Adulto, Quadras Poliesportivas: 1, Campo de Futebol: 1, Valor Condominio R$-: 260,00\",Ref.: CA95901 Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Maria Stella,https://thumbs4.properati.com/3/nKZAghWN-90yQywuniXpPPLO9sk=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2011/e55aad60-d657-4c71-b732-96032067b76f.jpg\n40c34e74b932ff6e10ff49774a8f7fb6e982c1c2,2014-11-30,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8081017,-49.3505771\",-20.8081017,-49.3505771,4500,BRL,3558.01,1152.28,,,,,,,,http://www.properati.com.br/tz24_alugar_comercial_sao-jose-do-rio-preto_rua-valdomiro-nadotti,\"Ref.: SA95928Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Tres Maria..: Salo Comercial No Bairro Trs Maria, Salo Comercial, Detalhes: 160 Metros Mais 40 Metros de Mesanino.\",Ref.: SA95928 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Tres Maria,https://thumbs4.properati.com/3/Yg9eVpfZKwd2s12w6w1yQ4YYVhY=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2402/MTVaVFERWIxvf0IcXQaQ.jpg\n716428a7c9f46f98fd78e36a72109b0c0ec43753,2014-11-30,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8081017,-49.3505771\",-20.8081017,-49.3505771,4500,BRL,3558.01,1152.28,,,,,,,,http://www.properati.com.br/tz25_alugar_comercial_sao-jose-do-rio-preto_rua-valdomiro-nadotti,\"Ref.: SA95929Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Tres Maria..: Salo Comercial No Bairro Trs Maria, Salo Comercial, M2/ Terreno: 200, Detalhes: 140 Metros Mais 40 Metros de Mesanino.\",Ref.: SA95929 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Tres Maria,https://thumbs4.properati.com/9/a_rVYKLimoBl774T612e8o5Icfo=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2403/fhkihJbLNgNCmHo7eBRG.jpg\n2102860e4c561ee2afec1c15fb71f7c6cfafc28d,2014-08-19,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,9000,BRL,7116.02,2304.56,,,,,,,,http://www.properati.com.br/muuy_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95893Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Ch. Jockey Clube..: Salo Comercial Na Chcara Jockey Clube, Salo Comercial, M2/ Terreno: 5.000, M2 rea Construida: 1.200, Detalhes: Galpo Com Piso Cimento Polido, Estrutura Metlica, Escritrio, Cozinha, 02 Wc, Entrada Pela Frente e Lateral Para Caminho.p Direito Alto Com 6,/2 M.\",Ref.: SA95893 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Ch. Jockey Clube,https://thumbs4.properati.com/3/H-b0Ol05_NX0A5soGXBvD_dA3wk=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1482/gCB4KEDxOoIlG9HlGbCW.jpg\ndb16c4572d4f19875153b041d1dcb6b82115e6a7,2014-11-03,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8174527,-49.4306981\",-20.8174527,-49.4306981,18000,BRL,14232.04,4609.12,,,,,,,,http://www.properati.com.br/sjyy_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95923Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Parque Industrial..: Salo Comercial No Parque Industrial, Salo Comercial, M2/ Terreno: 3.000, M2 rea Construida: 1.780, Detalhes: 1.000m de Patio.escritrio Com Elevado Vrias Salas, Cozinha, Wc, Vestirio Mas/fem;04 Salas de Descanso Para Motorista Com Vestirio, 02 Chuveiros e 04 Wc;cozinha Externa Com Quiosque + 02 Wc Mas/fem;parte Operacional Com P Direito de 7 Metros, e A Lateral Toda Com Embarque e Desembarque;ptio Com Espao Para Carretas, Caminhes e Carros;portaria Na Entrada do Ptio.\",Ref.: SA95923 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Parque Industrial,https://thumbs4.properati.com/0/wtHJ8Xcu4pqyV7YSP06g_N1yrfg=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2356/rO0uXvqSM9ilLshNDz0f.jpg\nc8c81389391a237255bade4a0608212fa62e13b1,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,4200,BRL,3984.12,1290.28,,107,12.058691588785047,39.25233644859813,,,,http://www.properati.com.br/ifxy_alugar_comercial_vila-olimpia,\"\tConjunto Comercial na Vila Olimpia, com 56m² de área útil, excelente localização.  Referíªncia: CO178\",\"Comercio no(a) Vila Olímpia , São Paulo - SP\",https://thumbs4.properati.com/7/YjqSlFDSnOcayBrp2UtD_X2xaJM=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/178/fa4df6b6-bb36-40a4-a46b-7b1438c77d03.jpg\ne1f129a3f1296a7e7a3b60d5b96d61c49159900c,2014-11-19,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.9295411,-46.3418105\",-23.9295411,-46.3418105,1600,BRL,1628.47,527.39,,32,16.4809375,50,,1,,http://www.properati.com.br/tkvf_alugar_apartamentos_sao-paulo,\"A 3 Milenio oferece este(a) Apartamento em Sao Vicente, no bairro Morro dos Barbosas - 1 dormitórios. Gostou desse(a) Apartamento em Sao Vicente? Então veja mais informações! Acesse o nosso site clicando no botão Contate o anunciante ou ligue - ou ainda entre em contato conosco pelo email vendas@ Obrigado!\",\"Apartamento em Sao Vicente, no bairro Morro dos Barbosas - 1 dormitórios\",https://thumbs4.properati.com/5/FvlCdG4KWDtvmzsszDUttj3AuAY=/trim/198x0/smart/filters:strip_icc()/www.imobiliariasdesantos.com.br/fotos/F00/72/807200-DSC03573.JPG\na8821d1c24cccd45d37ef69cf2b29ef3be744fad,2014-11-17,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8456698,-49.4030718\",-20.8456698,-49.4030718,3200,BRL,2530.11,819.39,,,,,,1,,http://www.properati.com.br/tdi2_alugar_apartamentos_sao-jose-do-rio-preto_avenida-anisio-haddad,\"Ref.: AP95925Tipo: ApartamentoCidade: So Jos do Rio Preto - SPBairro: Cond. Green Fields..: Apartamento No Condomnio Green Fields, Apartamento, rea Util M2: 152, Dormitrios: 03, Suite Master: 01, Closet , Tipo Apto.: 02, C/ A/e: 02, Wc. Social: 02, Sala de Visita: 01, Cozinha Planejada, Garagem Coberta: 02\",Ref.: AP95925 Tipo: Apartamento Cidade: So Jos do Rio Preto - SP Bairro: Cond. Green Fields,https://thumbs4.properati.com/4/oRIx16Fpw4LPkRsB59wlBS_mxVo=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2387/ae2a6d87-5760-44ff-a47f-d237a5321c3c.jpg\n08918f4a93c54f4505b783a8e31ef11842c55e56,2014-10-01,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.5882535,-46.6167776\",-23.5882535,-46.6167776,80000,BRL,63253.52,20484.98,,,,,,,,http://www.properati.com.br/qz9a_alugar_comercial_sao-jose-do-rio-preto_rua-da-imprensa,\"Ref.: SA95835Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Vila DinizM2/ Terreno: 12.000, Detalhes: - Salo Com 5.000 M e Bastante rea Para Estacionamento Aberto.Ideal Para Atacadista, Hipermercado e Distribuidora, Entre Outros.\",Ref.: SA95835 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Vila Diniz,https://thumbs4.properati.com/1/PkKXX6cNiwu9krqBtdWDinLG89A=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2077/vSsX8s9IzXdbNCXPId0W.jpg\n1ce358d0b5c35f146d70dff4089a8b3d287cb7b9,2014-10-08,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8085627,-49.3452813\",-20.8085627,-49.3452813,5500,BRL,4348.67,1408.34,,,,,,,,http://www.properati.com.br/r6de_alugar_comercial_sao-jose-do-rio-preto_avenida-sebastiao-tavares-da-silva,\"Ref.: SA95809Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Vista AlegreM2/ Terreno: 360, M2 rea Construida: 360, Detalhes: Salo Com Vrias Divisrias.\",Salo,https://thumbs4.properati.com/8/dhtnMWfIFZ8mMCFVDpxpvjoF0LA=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2184/l63oynNjEecn8u1Dzf3W.jpg\nb6ed51b61bb57831cc00d904741b4f544cafc28f,2014-08-04,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8178198,-49.3468294\",-20.8178198,-49.3468294,5500,BRL,4348.67,1408.34,,,,,,1,,http://www.properati.com.br/lxoz_alugar_casas_sao-jose-do-rio-preto,\"Ref.: CA95886Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Damha II(Res. Jardins)..: Casa Condomnio Damha II, Casa Condominio, Sobrado, Dormitrios: 03, Suite: 01, C/ Hidro, Tipo Apto.: 02, C/ A/e: 03, Roupeiro, Home, Cozinha: 01, Cozinha Planejada, Despensa, Escritrio, Lavabo, Salas : 01, Sala P/ Ambientes : 03, Piscina, Varanda, rea de Servio, Quarto de Empregada, Wc. de Empregada/servio, Aquecedor Solar, M2 rea Construida: 350, rea de Lazer, Garagem Descoberta: 03, Garagem Coberta: 03, Churrasqueira\", Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Damha II,https://thumbs4.properati.com/1/gEw1FIkmFLn2_zGBjqnuUzcifYU=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/793/e9d2d588-8d92-4070-b7d6-5b55e4484038.jpg\n1273c64fb8b9402dfad98d3984dec2998d8bdb68,2014-08-15,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8126181,-49.3404534\",-20.8126181,-49.3404534,5500,BRL,4348.67,1408.34,,,,,,1,,http://www.properati.com.br/mqtl_alugar_casas_sao-jose-do-rio-preto_avenida-miguel-damha,\"Ref.: CA95731Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Damha IVDormitrios: 03, Suite: 01, Closet : 01, C/ Hidro, Tipo Apto.: 02, C/ A/e: 02, Wcs.: 05, Wcs. C/ A/e: 04, Box Wcs.: 04, Copa: 01, Cozinha: 01, Cozinha C/ A/e, Despensa, Escritrio, Lavabo, Salas : 01, Sala P/ Ambientes : 02, Piscina, Varanda, rea de Servio, Wc. de Empregada/servio, M2/ Terreno: 420, M2 rea Construida: 380, rea de Lazer, Garagem Descoberta: 02, Garagem Coberta: 02, Churrasqueira, Piso Dormitrios: Porcelanatoq, Piso Salas: Porcelanato, Obs: Imvel Muito Bem Conservado, timo Acabamento., Valor Condominio R$-: 300,00, Detalhes: Toda Reformada.\",Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Damha IV,https://thumbs4.properati.com/6/H5vuIYMzk8pMuWJ-OjcdH4GItq8=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1367/9717e95d-fe6f-4cbc-bb5d-21c2fe92d294.jpg\n6aa6bf8a204f123441efb9f97699ee24cf8f9932,2014-08-15,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8178198,-49.3468294\",-20.8178198,-49.3468294,5500,BRL,4348.67,1408.34,,,,,,1,,http://www.properati.com.br/mqtn_alugar_casas_sao-jose-do-rio-preto_avenida-nadima-damha,\"Ref.: CA95797Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Damha ISobrado, Dormitrios: 03, Suite: 01, Tipo Apto.: 02, Wcs.: 01, Cozinha Planejada, Escritrio, Lavabo, Salas : 01, Sala P/ Ambientes : 03, Piscina, rea de Servio, Wc. de Empregada/servio, rea de Lazer, Garagem Coberta: 03, Churrasqueira, Ar Condicionado, Ventilador, Interfone, Piso Dormitrios: Taco, Piso Salas: Frio\",Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Damha I,https://thumbs4.properati.com/0/NAjAu7cPHQwzwT2j3k1G4X7-A3U=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1369/7b6ca8dd-648a-4e18-b9f5-272073bfbf69.jpg\n8a977c0c4b9ef700024cb2ee3e59de53bcf599c8,2014-07-16,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,2000,BRL,1897.2,614.42,,70,8.77742857142857,28.571428571428573,,2,,http://www.properati.com.br/ifzl_alugar_apartamentos_vila-olimpia,\"\tApartamento na Vila Olimpia, sala para 2 ambientes, 2 dormitórios com armários embutidos, cozinha, lavanderia, quarto na área de serviço, 2 banheiros, 1 vaga. Referíªncia: AP237\",\"Apartamento no(a) Vila Olímpia , São Paulo - SP\",https://thumbs4.properati.com/9/waPGo7zWMk2XTzN-br6B0h4Nrgo=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/237/75daca8f-ee37-4813-97af-b4fa00920d4f.jpg\nb48b18eca01fc2732d6afe024904eb1e900cc52a,2014-07-16,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,2000,BRL,1897.2,614.42,,54,11.378148148148147,37.03703703703704,,2,,http://www.properati.com.br/ifzn_alugar_apartamentos_saude,\"\tÓtimo apartamento proximo ao Metrê Praça da Arvore, sala para 2 ambientes, 2 dormitórios com armários embutidos, cozinha com armários, banheiro social e de serviço, 1 vaga na garagem, quadra, piscina e play.\tCom pintura nova, pronto para morar!! Referíªncia: AP239\",\"Apartamento no(a) Saúde , São Paulo - SP\",https://thumbs4.properati.com/0/Zof1-heqqvAgC3xdyYRYiQHIX2I=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/239/450071da-5288-4f26-bbb4-66bcacaef572.jpg\n636cbd951ef34773973cfdef37746b911d07a24c,2014-07-16,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,4000,BRL,3794.41,1228.84,,200,6.1442,20,,4,,http://www.properati.com.br/ifxa_alugar_casas_saude,\"\tAluga sobrado em zona mista, residencial e ou serviços, a uma quadra da Av. do Cursino e a 1000m, do Shopping Plaza Sul, 4 dormitórios, sendo uma suite, ampla sala, mais sala de jantar, cozinha, edicula com 3 quartos, mais lavanderia, garagem para 3 veiculos, casa de esquina, com portões automaticos.  Referíªncia: CA145\",\"Casa no(a) Saúde , São Paulo - SP\",https://thumbs4.properati.com/3/0sie3N08lMquoZ655zNy1CeDUbU=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/145/9fef5234-2fa4-4974-b24c-2b3cf6c4c542.jpg\n0812a419c00dcbaaaa926ad4e8de47da7959f9d9,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,5700,BRL,5407.01,1751.09,,127,13.788110236220472,44.881889763779526,,,,http://www.properati.com.br/ifzp_alugar_comercial_saude,\"\tConjunto Comercial proximo do Metrô São Judas, com 109m² de área útil, dividido em 5 salas, com possibilidade de vão livre, 3 banheiro, 1 copa, 3 vagas, prédio 24 horas.  Referíªncia: CO241\",\"Comercio no(a) Saúde , São Paulo - SP\",https://thumbs4.properati.com/1/0kl3BRCM4riJX3LhCObIu4wuBxQ=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/241/25ac4ee5-f867-4808-b226-837aefd7ba2c.jpg\n570fece17687d05345022d1567c79dd80bd1dcb0,2014-12-19,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8312527,-49.360102\",-20.8312527,-49.360102,15000,BRL,11860.02,3840.93,,,,,,,,http://www.properati.com.br/viin_alugar_comercial_sao-jose-do-rio-preto_avenida-marginal-lineu-alcantara-gil,\"Ref.: SA95950Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Dist. Industrial Campo Verde..: Salo Comercial No Distrito Industrial Campo Verde, Salo Comercial, M2/ Terreno: 5.000, M2 rea Construida: 3.000, Detalhes: tima Localizao Com Asfalto Para Rua, Estacionamento Para 60 Carros.barraco 5.000m2c/ Escritrio Isolado Ao Lado do Galpo C/ 6 Salas Grandes Von Livre Cond. Port.24h.\",Ref.: SA95950 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Dist. Industrial Campo Verde,https://thumbs4.properati.com/6/7lTVZ92lzm8ap5amDGM_eb1EHOU=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2528/yS1CaXFW8XYwJdPuhgGn.jpg\n2ee8e3818d61fdc4e01a5d89be181974d74e4380,2014-08-15,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8424141,-49.3549387\",-20.8424141,-49.3549387,15000,BRL,11860.02,3840.93,,,,,,,,http://www.properati.com.br/mqtc_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95109Tipo: Salo Comercial, Salo IndustrialCidade: So Jos do Rio Preto - SPBairro: Dist. Industrial Tancredo Neves .Terrea M2/ Terreno: 2.100, M2 rea Construida: 936, Detalhes: Estacionamento Na Frente,entrada Lateral Para Carga e Descarga,prox. do Carrefour\",\"Tipo: Salo Comercial, Salo Industrial Cidade: So Jos do Rio Preto - SP Bairro: Dist. Industrial Tancredo Neves .\",https://thumbs4.properati.com/9/KVlFV9eR-3HhzpzA5EtDKJICEgY=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1358/k6zhNAyNgH9P1excuuTM.jpg\n0f0577616ba85404705aaead7f35ff6e8b4a91da,2014-08-18,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.5733308,-46.544242\",-23.5733308,-46.544242,15000,BRL,11860.02,3840.93,,,,,,,,http://www.properati.com.br/mt62_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: CO95825 Tipo: Predio Comercial, Salo Comercial, Salo Industrial Cidade: So Jos do Rio Preto - SP Bairro: Pq. IndustrialWc. Social: 02, Salas : 06, M2/ Terreno: 1.113, Documentao: Em Dia., Detalhes: Banheiro Feminino e Masculino, 06 Salas Amplas, Sendo de 420 M a Maior.o Salo Possui Ambientes Para Reunies, Sala de Refeitrio de 93 M e Armrio Embutido, Alarme, Campainha, Interfone, Cerca Eltrica e Estacionamento de 258 M Com 25 Vagas Cobertas.\",\"Tipo: Predio Comercial, Salo Comercial, Salo Industrial Cidade: So Jos do Rio Preto - SP Bairro: Pq. Industrial\",https://thumbs4.properati.com/5/ug_ZifuqqZGKLp9_hQAm2sSvkgE=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/1436/Q14vgp5W0wZIU71vOSui.jpg\n8d6a13266a268bd8dd46f19df78ef71e3509ee5a,2014-10-18,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7700326,-49.3956468\",-20.7700326,-49.3956468,6500,BRL,5139.33,1664.4,,,,,,,,http://www.properati.com.br/ri3n_alugar_comercial_sao-jose-do-rio-preto_avenida-ada-marzochi-polachini,\"Ref.: SA95917Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Mini Dist. Industrial Adail Vetorasso..: Salo Comercial No Mini Destrito Industrial Adail Vetorasso, Salo Comercial, Wcs.: 02, Cozinha: 01, Escritrio, M2/ Terreno: 1.050, M2 rea Construida: 680, Detalhes: Barraco Novo, Com Pilar No Centro;porta Na Frente e Lateral;cobertura Metlica;p Direito 6 Metros.\",Ref.: SA95917 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Mini Dist. Industrial Adail Vetorasso,https://thumbs4.properati.com/2/3gLCbr6emuqSINA3fAYUW1zaIUM=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2283/grmYrDDM8HnJtcT4RzOD.jpg\ndac06563fc851c6596496ceaff68cbf177bb6dde,2014-10-01,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7863702,-49.3690389\",-20.7863702,-49.3690389,6500,BRL,5139.33,1664.4,,,,,,,,http://www.properati.com.br/qz9p_alugar_comercial_sao-jose-do-rio-preto_avenida-solon-da-silva-varginha,\"Ref.: SA95834Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Vila NovaesWc. Social: 03, Mesanino, M2/ Terreno: 530, M2 rea Construida: 480, Detalhes: Salo Comercial Com P Direito de 6m e Mezanino de 55 M.possui Aparelhagem de Ar Condicionado, Som Ambiente, Cmeras de Segurana e Estacionamento Para 18 Carros.local Ideal Para Lojas, Mercados, Igreja, Depsito, Entre Outros.\",Ref.: SA95834 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Vila Novaes,https://thumbs4.properati.com/8/qD9kCsVbiYyB0WGFw5WesI2Ny6s=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2092/d62f4d68-2ea5-4f83-bde9-fdc53bb504f2.jpg\n1ccba6f23edef69215ca9cfe74214a5276597714,2014-10-18,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,6500,BRL,5139.33,1664.4,,,,,,,,http://www.properati.com.br/ri3q_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: SA95919Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Dist. Industrial Centenrio da Emancipao..: Salo Comercial No Distrito Industrial Centenrio da Emancipao., Salo Comercial, Cozinha: 02, Mesanino, Escritrio, M2 rea Construida: 1.015, Detalhes: Escritrio, Cozinha, 02 Wc, Mezanino, 2 Salas, Entrada Lateral.salo 700 Metrosdiviso 315 Metros.\",Ref.: SA95919 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Dist. Industrial Centenrio da Emancipao,https://thumbs4.properati.com/0/FlEAiRaerT0pRZEeW7AhhCuJjJc=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2286/v22UnUeHl8YbhJaP8IaU.jpg\na9d04cd869b56cac7bcb3a54a27147996a973a3d,2015-01-20,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.5418835,-46.5583192\",-23.5418835,-46.5583192,1900,BRL,1539.57,498.6,,,,,16,1,,http://www.properati.com.br/w730_alugar_apartamentos_tatuape,\"APTO DE 58M/2, COM 02 DORMITRIOS, SENDO UMA SUTE, SALA AMPLA COM SACADA, COZINHA, WC SOCIAL, REA DE SERVIO, COM ALGUMAS MOBILIAS, TUDO DE PRIMEIRA, 02 VAGAS DE GARAGEM, LAZER COMPLETE, EXCELENTE LOCALIZAO, A UMA QUADRA DO METR.\",MOBILIADO EXCLUSIVE,https://thumbs4.properati.com/2/KuQXATs4BGrSaZvfCzOSMN2pqWs=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/toledo/photos/19/30ad0c15-c9ef-48f7-a06e-2a0201bb7433.jpg\n5de8d0e3c8872b0af03bdc517a4a12021c48da59,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,24700,BRL,23430.5,7588.09,,289,26.25636678200692,85.46712802768167,,,,http://www.properati.com.br/ifvd_alugar_comercial_vila-olimpia,\"EDIFí�CIO COMERCIAL DE ALTí�SSIMO PADRí�O,  LOCALIZADO NO PONTO MAIS NOBRE DA REGIí�O, EM FRENTE AOSHOPPING VILA OLí�MPIA, ENTRE AV. DOS BANDEIRANTES E JUSCELINO KUBITSCHEK. OPRí�DIO CONTA COM 6 ELEVADORES DE ALTA VELOCIDADE, SENDO 1 PANORí�MICO, ARCONDICIONADO CENTRAL, FACHADA EM CORTINA DE VIDRO. EDIFí�CIO INTELIGENTE COMSOFISTICADO SISTEMA DE CONTROLE DE INCí�NDIO, CONSUMO DE ENERGIA. Referíªncia: CO66\",\"Comercio no(a) Vila Olímpia , São Paulo - SP\",\n705fdcf676aca2c9f51fcc0a332fcff39ee1706c,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,19500,BRL,18497.74,5990.59,,260,23.04073076923077,75,,,,http://www.properati.com.br/ifyk_alugar_comercial_vila-olimpia,\"\tConjunto Comercial com 260m² de área útil, 6 vagas, Ar condicionado Central. Referíªncia: CO200\",\"Comercio no(a) Vila Olímpia , São Paulo - SP\",https://thumbs4.properati.com/0/-wzyHwNVYSdVIvzDSj7dz-RtyfY=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/200/f2ef8797-b051-4b62-b4fe-e8fdb7523a26.jpg\n179ce00d44c6fafbac93e139f052ab4ce304100d,2014-08-04,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,4200,BRL,3320.8,1075.46,,,,,,,,http://www.properati.com.br/lxpa_alugar_casas_sao-jose-do-rio-preto,\"Ref.: CA95877 Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SPBairro: Cond. Village La MontangneDormitrios: 3, Suite: 1, C/ Hidro: 1, Tipo Apto.: 2, C/ A/e: 3, Cozinha Planejada, Escritrio, Lavabo, Salas : 1, Sala P/ Ambientes : 3, Piscina, rea de Servio, M2/ Terreno: 360, M2 rea Construida: 250, Garagem Descoberta: 2, Garagem Coberta: 2\",Tipo: Casa Condominio Cidade: So Jos do Rio Preto - SP Bairro: Cond. Village La Montangne,https://thumbs4.properati.com/8/XTvYImGZcecKNC8xwzjnrrqWibg=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/804/ce485ed8-97e3-4af6-bc5e-3d347e06b31d.jpg\n868d3ff8ea898557e9819638705d2f823431d000,2015-01-13,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.521407,-46.5769202\",-23.521407,-46.5769202,1500,BRL,1215.45,393.63,,,,,13,1,,http://www.properati.com.br/vysr_alugar_apartamentos_tatuape,\"APTO DE 60M/2, COM 02 DORMITRIO, SALA AMPLA COM SACADA, WC SOCIAL, COZINHA, REA DE SERVIO, 01 VAGA DE GARAGEM, EXCELENTE ESTRUTURA DE LAZER COM PISCINA, PLAYGROUD, SALO DE FESTA, CHURRASQUEIRA, EXCELENTE LOCALIZAO, FCIL ACESSO AO SHOPPING METR/TATUAP.\",131 VITORIA,https://thumbs4.properati.com/0/yyioUci-ExAIuKlfIudXKNKaMAs=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/toledo/photos/16/ff90ae98-dbde-47dc-9f04-32cdb2d2d0d3.jpg\nb9de26b4b13ad0f58469677b72b5171c2b04ae1f,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,13500,BRL,12806.12,4147.33,,183,22.663005464480875,73.77049180327869,,,,http://www.properati.com.br/ifv7_alugar_comercial_vila-olimpia,CONJUNTO COMERCIAL - RECEPCAO - HD - 4 SALAS - 2 WC - COPA  Referíªncia: CO60,\"Comercio no(a) Vila Olímpia , São Paulo - SP\",\n416be9056b69e83819532b65a04c6d1f7135f1ca,2014-12-11,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,2400,BRL,1897.57,614.54,,,,,,,,http://www.properati.com.br/uict_alugar_comercial_jardim-maracana,\"Ref.: SA95945Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Jardim Maracan..: Salo Comercial No Jardim Maracan, Salo Comercial, Wc. Social: 02, Cozinha: 01, Forro de Laje, Piso Salas: Frio, M2/ Terreno: 225, M2 rea Construida: 130\",Ref.: SA95945 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Jardim Maracan,https://thumbs4.properati.com/5/3964ltfTag6YXQlyxEM19akYy1s=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2500/f25a8d90-6759-426c-a4ed-d8aefc6b19a4.jpg\nc1aff8edd38c3254c5775927802a8b8fae45a1d8,2014-08-05,rent,house,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,2400,BRL,1897.57,614.54,,,,,,1,,http://www.properati.com.br/mel5_alugar_casas_bosque-da-saude,\"Ref.: CA95883Tipo: Casa ResidencialCidade: So Jos do Rio Preto - SPBairro: Bosque Da Saude..: Casa Residencial No Bosque da Saude, Casa Residencial, Dormitrios: 03, Tipo Apto.: 01, C/ A/e: 03, Wc. Social: 01, Copa: 01, Cozinha: 01, Despensa, Salas : 02, Varanda, Garagem Descoberta: 02, Garagem Coberta: 02\",Tipo: Casa Residencial Cidade: So Jos do Rio Preto - SP Bairro: Bosque Da Saude,https://thumbs4.properati.com/0/aAdm951DKitDcqkiJ8p68O6xdNA=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/848/083f9a71-b22a-4897-bf14-72fbe585dea5.jpg\ne4d569301fb8a21047d67f193929ecf360e9405e,2015-01-11,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,750,BRL,600.6,194.51,,,,,,1,,http://www.properati.com.br/vxnv_alugar_apartamentos_jardim-satelite,\"Excelente apartamento em frente ao condomnio Quinta das Flores totalmente reformado com 2 dormitrios, sala, cozinha planejada, rea de servio, lavabo com gabinete, wc com box blindex e 1 vaga de garagem.Ideal para casal sem filhos e sem animais\",Excelente apartamento no Jardim Satlite,https://thumbs4.properati.com/1/RhUtbG6YnK8FCa_yOduiEf3yyj0=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/tyrreno/photos/1074/71219c81-463f-4710-a46d-5e7e2fee9362.jpg\n6b59e27767f7570db49b2175077b1c059fa20b88,2014-10-01,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-23.5715766,-46.6697673\",-23.5715766,-46.6697673,3100,BRL,2451.06,793.79,,,,,,,,http://www.properati.com.br/qz9r_alugar_comercial_sao-jose-do-rio-preto_avenida-brasil,\"Ref.: SA95828Tipo: Salo ComercialCidade: So Jos do Rio Preto - SPBairro: Jardim MugnaniWc. Social: 02, Cozinha, M2/ Terreno: 350, M2 rea Construida: 210, Detalhes: - Salo Novo!\", Ref.: SA95828 Tipo: Salo Comercial Cidade: So Jos do Rio Preto - SP Bairro: Jardim Mugnani,https://thumbs4.properati.com/5/ShU0-2rvOmZQkWG3Ml57SSATz_s=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2094/VmmbZZ9w1FykW0ldwpGM.jpg\n3931096ab81c0a74496858654375447e859df8f2,2014-12-05,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.7910821,-49.3781115\",-20.7910821,-49.3781115,880,BRL,695.77,225.33,,,,,,1,,http://www.properati.com.br/ue04_alugar_apartamentos_sao-jose-do-rio-preto_rua-pedro-mulati,\"Ref.: AP95815Tipo: ApartamentoCidade: So Jos do Rio Preto - SPBairro: Jardim ConceioCobertura, Dormitrios: 02, C/ A/e, Wc. Social, Box Wcs., Sala de Estar, Cozinha, Piscina, rea de Servio, rea de Lazer, Garagem Coberta: 01, rea Util M2: 65, Playground\",Ref.: AP95815 Tipo: Apartamento Cidade: So Jos do Rio Preto - SP Bairro: Jardim Conceio,https://thumbs4.properati.com/3/0zqbkQ73LUnQeQMp0yntvzoZrq8=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2458/Dv0976z6fjHNbJf7oAX0.jpg\nc15e079e557ec6ad9d86baede07324e678686764,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,3800,BRL,3604.66,1167.39,,88,13.265795454545456,43.18181818181818,,,,http://www.properati.com.br/ifzr_alugar_comercial_saude,\"\tConjunto Comercial proximo do Metrô São Judas, com 76m², 2 banheiros, 2 vagas, 1 copa, predio 24 horas. Referíªncia: CO243\",\"Comercio no(a) Saúde , São Paulo - SP\",https://thumbs4.properati.com/4/h5_FesPCfkGnmz5ac8c3DvXy_cE=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/243/68c5314b-39b1-4ebd-af49-694d915540b0.jpg\n62cbd166c6e120af08202b4db367db83240ed7e1,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,8500,BRL,8063.11,2611.28,,300,8.704266666666667,28.333333333333332,,,,http://www.properati.com.br/ifw2_alugar_comercial_vila-olimpia,\"\t\t\tLindo(a) Comercio de 300 metros quadrados no bairro Vila Olímpia na cidade de São Paulo - SP.\t\t\t, 2 banheiro(s), 6 vagas(s) de garagem.\t \t\tCondições de Pagamento: \t \t \t \t\tCaracteristicas: Referíªncia: CO93\",\"Comercio no(a) Vila Olímpia , São Paulo - SP\",https://thumbs4.properati.com/1/gH6VwXVOHsfAy5JV8elaJg59lGA=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/93/4276fcba-994a-4296-9a35-65c580711457.jpg\n5a5bfb194fdbec1a633f5894a66c8bf45c8ae463,2015-01-06,rent,apartment,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,1100,BRL,869.7,281.66,,,,,,,,http://www.properati.com.br/vv2s_alugar_apartamentos_sao-jose-do-rio-preto,\"Ref.: AP95935Tipo: ApartamentoCidade: So Jos do Rio Preto - SPBairro: Vila Itlia..: Apartamento Residencial Na Vila Itlia, Apartamento, rea Util M2: 63, Dormitrios: 02, Closet , Tipo Apto.: 01, Wc. Social: 01, Sala de Tv, Cozinha: 01, Cozinha C/ A/e, Garagem Coberta: 01, Detalhes: Prdio Novo.\",Ref.: AP95935 Tipo: Apartamento Cidade: So Jos do Rio Preto - SP Bairro: Vila Itlia,https://thumbs4.properati.com/5/XPnfndqmnEKY04smTHRwdeanGd8=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2535/atPPaTZcVegFbIYjH45d.jpg\n8f0adde258e0b9ad5897ba55288b52005ca749a8,2014-07-16,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,,,,3200,BRL,3035.52,983.07,,100,9.8307,32,,,,http://www.properati.com.br/ifxp_alugar_comercial_republica,\"\tLoja/Galpão com mezanino, pé direito duplo, localizada na Av. Amaral Guegel, proximo da Consolação, 2 banheiro e 2 vagas demarcadas. Referíªncia: CO169\",\"Comercio no(a) República , São Paulo - SP\",https://thumbs4.properati.com/6/XJyKobKfgKlfwiO0SB2Tt2i0Y9w=/trim/198x0/smart/filters:strip_icc()/souzapinto.imobfusion01.com.br/accounts/souzapinto/data/photo/169/50b1ac29-6a0b-4488-aaa4-07bc131dd969.jpg\n480229dcb811d916f90a02ffd9c5b62e8a72ccbe,2014-10-15,rent,store,São Paulo,|Brasil|São Paulo|,Brasil,São Paulo,,\"-20.8168432,-49.3930989\",-20.8168432,-49.3930989,17000,BRL,13441.34,4353.05,,,,,,,,http://www.properati.com.br/re16_alugar_comercial_sao-jose-do-rio-preto,\"Ref.: CO95289Tipo: Predio ComercialCidade: So Jos do Rio Preto - SPBairro: Vila dos BancariosM2 rea Construida: 800, Garagem Descoberta: 20, Detalhes: Predio Comercial Com 10m Salas de 80 M Cada, Fica Em Um Pavimento Com Elevador, 20 Vagas Para Estacionar .\",Loja,https://thumbs4.properati.com/4/Z-A_sLSPQOFzDbPb8MkkcY9IwMU=/trim/198x0/smart/filters:strip_icc()/static.if2.com.br/acc/interplan/photos/2239/bsfsEYepcv9Cu4EHDvjU.jpg\n\n", + "datasource": { + "type": "testdata", + "uid": "PD8C576611E62080A" + }, + "refId": "A", + "scenarioId": "csv_content" + } + ], + "title": "No pagination, no footer", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "country_name": true, + "created_on": true, + "expenses": true, + "floor": true, + "geonames_id": true, + "id": true, + "image_thumbnail": true, + "lat": true, + "lat_lon": true, + "lon": true, + "operation": true, + "place_with_parent_names": true, + "price_aprox_local_currency": true, + "price_aprox_usd": true, + "state_name": true, + "surface_total_in_m2": true + }, + "indexByName": { + "country_name": 13, + "created_on": 9, + "currency": 19, + "description": 5, + "expenses": 24, + "floor": 7, + "geonames_id": 15, + "id": 0, + "image_thumbnail": 26, + "lat": 17, + "lat_lon": 16, + "lon": 18, + "operation": 8, + "place_name": 4, + "place_with_parent_names": 12, + "price": 11, + "price_aprox_local_currency": 20, + "price_aprox_usd": 21, + "price_per_m2": 23, + "price_usd_per_m2": 22, + "properati_url": 25, + "property_type": 2, + "rooms": 6, + "state_name": 14, + "surface_covered_in_m2": 3, + "surface_total_in_m2": 10, + "title": 1 + }, + "renameByName": { + "description": "Description", + "place_name": "City", + "price": "Price", + "properati_url": "URL", + "property_type": "Type", + "rooms": "Rooms", + "surface_covered_in_m2": "Surface", + "title": "Property name" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "rent": { + "color": "yellow", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "property_type" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "store": { + "color": "blue", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 205 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Number of rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 66 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Description" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Show details", + "url": "${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Surface" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background-solid" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "yellow", + "value": 200 + }, + { + "color": "green", + "value": 300 + }, + { + "color": "#EAB839", + "value": 500 + }, + { + "color": "#6ED0E0", + "value": 1000 + }, + { + "color": "#EF843C", + "value": 2000 + } + ] + } + }, + { + "id": "unit", + "value": "areaM2" + }, + { + "id": "custom.width", + "value": 93 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Type" + }, + "properties": [ + { + "id": "custom.width", + "value": 94 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "City" + }, + "properties": [ + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Price" + }, + "properties": [ + { + "id": "unit", + "value": "currencyUSD" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 0 + }, + "id": 6, + "options": { + "footer": { + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": true + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Surface" + } + ] + }, + "pluginVersion": "9.0.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "No pagination, footer", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "country_name": true, + "created_on": true, + "expenses": true, + "floor": true, + "geonames_id": true, + "id": true, + "image_thumbnail": true, + "lat": true, + "lat_lon": true, + "lon": true, + "operation": true, + "place_with_parent_names": true, + "price_aprox_local_currency": true, + "price_aprox_usd": true, + "state_name": true, + "surface_total_in_m2": true + }, + "indexByName": { + "country_name": 13, + "created_on": 9, + "currency": 19, + "description": 5, + "expenses": 24, + "floor": 7, + "geonames_id": 15, + "id": 0, + "image_thumbnail": 26, + "lat": 17, + "lat_lon": 16, + "lon": 18, + "operation": 8, + "place_name": 4, + "place_with_parent_names": 12, + "price": 11, + "price_aprox_local_currency": 20, + "price_aprox_usd": 21, + "price_per_m2": 23, + "price_usd_per_m2": 22, + "properati_url": 25, + "property_type": 2, + "rooms": 6, + "state_name": 14, + "surface_covered_in_m2": 3, + "surface_total_in_m2": 10, + "title": 1 + }, + "renameByName": { + "description": "Description", + "place_name": "City", + "price": "Price", + "properati_url": "URL", + "property_type": "Type", + "rooms": "Rooms", + "surface_covered_in_m2": "Surface", + "title": "Property name" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "rent": { + "color": "yellow", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "property_type" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "store": { + "color": "blue", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 205 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Number of rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 66 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Description" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Show details", + "url": "${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Surface" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background-solid" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "yellow", + "value": 200 + }, + { + "color": "green", + "value": 300 + }, + { + "color": "#EAB839", + "value": 500 + }, + { + "color": "#6ED0E0", + "value": 1000 + }, + { + "color": "#EF843C", + "value": 2000 + } + ] + } + }, + { + "id": "unit", + "value": "areaM2" + }, + { + "id": "custom.width", + "value": 93 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Type" + }, + "properties": [ + { + "id": "custom.width", + "value": 94 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "City" + }, + "properties": [ + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Price" + }, + "properties": [ + { + "id": "unit", + "value": "currencyUSD" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 0 + }, + "id": 9, + "options": { + "footer": { + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": false, + "sortBy": [ + { + "desc": true, + "displayName": "Surface" + } + ] + }, + "pluginVersion": "9.0.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "country_name": true, + "created_on": true, + "expenses": true, + "floor": true, + "geonames_id": true, + "id": true, + "image_thumbnail": true, + "lat": true, + "lat_lon": true, + "lon": true, + "operation": true, + "place_with_parent_names": true, + "price_aprox_local_currency": true, + "price_aprox_usd": true, + "state_name": true, + "surface_total_in_m2": true + }, + "indexByName": { + "country_name": 13, + "created_on": 9, + "currency": 19, + "description": 5, + "expenses": 24, + "floor": 7, + "geonames_id": 15, + "id": 0, + "image_thumbnail": 26, + "lat": 17, + "lat_lon": 16, + "lon": 18, + "operation": 8, + "place_name": 4, + "place_with_parent_names": 12, + "price": 11, + "price_aprox_local_currency": 20, + "price_aprox_usd": 21, + "price_per_m2": 23, + "price_usd_per_m2": 22, + "properati_url": 25, + "property_type": 2, + "rooms": 6, + "state_name": 14, + "surface_covered_in_m2": 3, + "surface_total_in_m2": 10, + "title": 1 + }, + "renameByName": { + "description": "Description", + "place_name": "City", + "price": "Price", + "properati_url": "URL", + "property_type": "Type", + "rooms": "Rooms", + "surface_covered_in_m2": "Surface", + "title": "Property name" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "rent": { + "color": "yellow", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "property_type" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "store": { + "color": "blue", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 205 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Number of rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 66 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Description" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Show details", + "url": "${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Surface" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background-solid" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "yellow", + "value": 200 + }, + { + "color": "green", + "value": 300 + }, + { + "color": "#EAB839", + "value": 500 + }, + { + "color": "#6ED0E0", + "value": 1000 + }, + { + "color": "#EF843C", + "value": 2000 + } + ] + } + }, + { + "id": "unit", + "value": "areaM2" + }, + { + "id": "custom.width", + "value": 93 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Type" + }, + "properties": [ + { + "id": "custom.width", + "value": 94 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "City" + }, + "properties": [ + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Price" + }, + "properties": [ + { + "id": "unit", + "value": "currencyUSD" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 7, + "options": { + "footer": { + "enablePagination": true, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Surface" + } + ] + }, + "pluginVersion": "9.0.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "Pagination, no footer", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "country_name": true, + "created_on": true, + "expenses": true, + "floor": true, + "geonames_id": true, + "id": true, + "image_thumbnail": true, + "lat": true, + "lat_lon": true, + "lon": true, + "operation": true, + "place_with_parent_names": true, + "price_aprox_local_currency": true, + "price_aprox_usd": true, + "state_name": true, + "surface_total_in_m2": true + }, + "indexByName": { + "country_name": 13, + "created_on": 9, + "currency": 19, + "description": 5, + "expenses": 24, + "floor": 7, + "geonames_id": 15, + "id": 0, + "image_thumbnail": 26, + "lat": 17, + "lat_lon": 16, + "lon": 18, + "operation": 8, + "place_name": 4, + "place_with_parent_names": 12, + "price": 11, + "price_aprox_local_currency": 20, + "price_aprox_usd": 21, + "price_per_m2": 23, + "price_usd_per_m2": 22, + "properati_url": 25, + "property_type": 2, + "rooms": 6, + "state_name": 14, + "surface_covered_in_m2": 3, + "surface_total_in_m2": 10, + "title": 1 + }, + "renameByName": { + "description": "Description", + "place_name": "City", + "price": "Price", + "properati_url": "URL", + "property_type": "Type", + "rooms": "Rooms", + "surface_covered_in_m2": "Surface", + "title": "Property name" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "rent": { + "color": "yellow", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "property_type" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "store": { + "color": "blue", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 205 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Number of rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 66 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Description" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Show details", + "url": "${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Surface" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background-solid" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "yellow", + "value": 200 + }, + { + "color": "green", + "value": 300 + }, + { + "color": "#EAB839", + "value": 500 + }, + { + "color": "#6ED0E0", + "value": 1000 + }, + { + "color": "#EF843C", + "value": 2000 + } + ] + } + }, + { + "id": "unit", + "value": "areaM2" + }, + { + "id": "custom.width", + "value": 93 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Type" + }, + "properties": [ + { + "id": "custom.width", + "value": 94 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "City" + }, + "properties": [ + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Price" + }, + "properties": [ + { + "id": "unit", + "value": "currencyUSD" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 8, + "options": { + "footer": { + "enablePagination": true, + "fields": "", + "reducer": [ + "sum" + ], + "show": true + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Surface" + } + ] + }, + "pluginVersion": "9.0.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "Pagination and footer", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "country_name": true, + "created_on": true, + "expenses": true, + "floor": true, + "geonames_id": true, + "id": true, + "image_thumbnail": true, + "lat": true, + "lat_lon": true, + "lon": true, + "operation": true, + "place_with_parent_names": true, + "price_aprox_local_currency": true, + "price_aprox_usd": true, + "state_name": true, + "surface_total_in_m2": true + }, + "indexByName": { + "country_name": 13, + "created_on": 9, + "currency": 19, + "description": 5, + "expenses": 24, + "floor": 7, + "geonames_id": 15, + "id": 0, + "image_thumbnail": 26, + "lat": 17, + "lat_lon": 16, + "lon": 18, + "operation": 8, + "place_name": 4, + "place_with_parent_names": 12, + "price": 11, + "price_aprox_local_currency": 20, + "price_aprox_usd": 21, + "price_per_m2": 23, + "price_usd_per_m2": 22, + "properati_url": 25, + "property_type": 2, + "rooms": 6, + "state_name": 14, + "surface_covered_in_m2": 3, + "surface_total_in_m2": 10, + "title": 1 + }, + "renameByName": { + "description": "Description", + "place_name": "City", + "price": "Price", + "properati_url": "URL", + "property_type": "Type", + "rooms": "Rooms", + "surface_covered_in_m2": "Surface", + "title": "Property name" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "rent": { + "color": "yellow", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "property_type" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "store": { + "color": "blue", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 205 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Number of rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 66 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Description" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Show details", + "url": "${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Surface" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background-solid" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "yellow", + "value": 200 + }, + { + "color": "green", + "value": 300 + }, + { + "color": "#EAB839", + "value": 500 + }, + { + "color": "#6ED0E0", + "value": 1000 + }, + { + "color": "#EF843C", + "value": 2000 + } + ] + } + }, + { + "id": "unit", + "value": "areaM2" + }, + { + "id": "custom.width", + "value": 93 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Type" + }, + "properties": [ + { + "id": "custom.width", + "value": 94 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "City" + }, + "properties": [ + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Price" + }, + "properties": [ + { + "id": "unit", + "value": "currencyUSD" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 0, + "y": 16 + }, + "id": 10, + "options": { + "footer": { + "enablePagination": true, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": false, + "sortBy": [ + { + "desc": true, + "displayName": "Surface" + } + ] + }, + "pluginVersion": "9.0.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "Pagination, no footer, no header", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "country_name": true, + "created_on": true, + "expenses": true, + "floor": true, + "geonames_id": true, + "id": true, + "image_thumbnail": true, + "lat": true, + "lat_lon": true, + "lon": true, + "operation": true, + "place_with_parent_names": true, + "price_aprox_local_currency": true, + "price_aprox_usd": true, + "state_name": true, + "surface_total_in_m2": true + }, + "indexByName": { + "country_name": 13, + "created_on": 9, + "currency": 19, + "description": 5, + "expenses": 24, + "floor": 7, + "geonames_id": 15, + "id": 0, + "image_thumbnail": 26, + "lat": 17, + "lat_lon": 16, + "lon": 18, + "operation": 8, + "place_name": 4, + "place_with_parent_names": 12, + "price": 11, + "price_aprox_local_currency": 20, + "price_aprox_usd": 21, + "price_per_m2": 23, + "price_usd_per_m2": 22, + "properati_url": 25, + "property_type": 2, + "rooms": 6, + "state_name": 14, + "surface_covered_in_m2": 3, + "surface_total_in_m2": 10, + "title": 1 + }, + "renameByName": { + "description": "Description", + "place_name": "City", + "price": "Price", + "properati_url": "URL", + "property_type": "Type", + "rooms": "Rooms", + "surface_covered_in_m2": "Surface", + "title": "Property name" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "rent": { + "color": "yellow", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "property_type" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "store": { + "color": "blue", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 205 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Number of rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 66 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Description" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Show details", + "url": "${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Surface" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background-solid" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "yellow", + "value": 200 + }, + { + "color": "green", + "value": 300 + }, + { + "color": "#EAB839", + "value": 500 + }, + { + "color": "#6ED0E0", + "value": 1000 + }, + { + "color": "#EF843C", + "value": 2000 + } + ] + } + }, + { + "id": "unit", + "value": "areaM2" + }, + { + "id": "custom.width", + "value": 93 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Type" + }, + "properties": [ + { + "id": "custom.width", + "value": 94 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "City" + }, + "properties": [ + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Price" + }, + "properties": [ + { + "id": "unit", + "value": "currencyUSD" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 8, + "y": 16 + }, + "id": 11, + "options": { + "footer": { + "enablePagination": true, + "fields": "", + "reducer": [ + "sum" + ], + "show": true + }, + "showHeader": false, + "sortBy": [ + { + "desc": true, + "displayName": "Surface" + } + ] + }, + "pluginVersion": "9.0.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "title": "Pagination, footer, no header", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "country_name": true, + "created_on": true, + "expenses": true, + "floor": true, + "geonames_id": true, + "id": true, + "image_thumbnail": true, + "lat": true, + "lat_lon": true, + "lon": true, + "operation": true, + "place_with_parent_names": true, + "price_aprox_local_currency": true, + "price_aprox_usd": true, + "state_name": true, + "surface_total_in_m2": true + }, + "indexByName": { + "country_name": 13, + "created_on": 9, + "currency": 19, + "description": 5, + "expenses": 24, + "floor": 7, + "geonames_id": 15, + "id": 0, + "image_thumbnail": 26, + "lat": 17, + "lat_lon": 16, + "lon": 18, + "operation": 8, + "place_name": 4, + "place_with_parent_names": 12, + "price": 11, + "price_aprox_local_currency": 20, + "price_aprox_usd": 21, + "price_per_m2": 23, + "price_usd_per_m2": 22, + "properati_url": 25, + "property_type": 2, + "rooms": 6, + "state_name": 14, + "surface_covered_in_m2": 3, + "surface_total_in_m2": 10, + "title": 1 + }, + "renameByName": { + "description": "Description", + "place_name": "City", + "price": "Price", + "properati_url": "URL", + "property_type": "Type", + "rooms": "Rooms", + "surface_covered_in_m2": "Surface", + "title": "Property name" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "rent": { + "color": "yellow", + "index": 0 + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "property_type" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "store": { + "color": "blue", + "index": 0 + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "custom.width", + "value": 205 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Number of rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 66 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Description" + }, + "properties": [ + { + "id": "custom.inspect", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "URL" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "Show details", + "url": "${__value.raw}" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Surface" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background-solid" + }, + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "orange", + "value": 100 + }, + { + "color": "yellow", + "value": 200 + }, + { + "color": "green", + "value": 300 + }, + { + "color": "#EAB839", + "value": 500 + }, + { + "color": "#6ED0E0", + "value": 1000 + }, + { + "color": "#EF843C", + "value": 2000 + } + ] + } + }, + { + "id": "unit", + "value": "areaM2" + }, + { + "id": "custom.width", + "value": 93 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Type" + }, + "properties": [ + { + "id": "custom.width", + "value": 94 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "City" + }, + "properties": [ + { + "id": "custom.width", + "value": 120 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Rooms" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Price" + }, + "properties": [ + { + "id": "unit", + "value": "currencyUSD" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 8, + "x": 16, + "y": 16 + }, + "id": 12, + "options": { + "footer": { + "enablePagination": true, + "fields": "", + "reducer": [ + "sum" + ], + "show": true + }, + "showHeader": false, + "sortBy": [ + { + "desc": true, + "displayName": "Surface" + } + ] + }, + "pluginVersion": "9.0.0-pre", + "targets": [ + { + "datasource": { + "type": "datasource", + "uid": "-- Dashboard --" + }, + "panelId": 2, + "refId": "A" + } + ], + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "country_name": true, + "created_on": true, + "expenses": true, + "floor": true, + "geonames_id": true, + "id": true, + "image_thumbnail": true, + "lat": true, + "lat_lon": true, + "lon": true, + "operation": true, + "place_with_parent_names": true, + "price_aprox_local_currency": true, + "price_aprox_usd": true, + "state_name": true, + "surface_total_in_m2": true + }, + "indexByName": { + "country_name": 13, + "created_on": 9, + "currency": 19, + "description": 5, + "expenses": 24, + "floor": 7, + "geonames_id": 15, + "id": 0, + "image_thumbnail": 26, + "lat": 17, + "lat_lon": 16, + "lon": 18, + "operation": 8, + "place_name": 4, + "place_with_parent_names": 12, + "price": 11, + "price_aprox_local_currency": 20, + "price_aprox_usd": 21, + "price_per_m2": 23, + "price_usd_per_m2": 22, + "properati_url": 25, + "property_type": 2, + "rooms": 6, + "state_name": 14, + "surface_covered_in_m2": 3, + "surface_total_in_m2": 10, + "title": 1 + }, + "renameByName": { + "description": "Description", + "place_name": "City", + "price": "Price", + "properati_url": "URL", + "property_type": "Type", + "rooms": "Rooms", + "surface_covered_in_m2": "Surface", + "title": "Property name" + } + } + } + ], + "type": "table" + } + ], + "schemaVersion": 32, + "style": "dark", + "tags": [ + "gdev", + "panel-tests" + ], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Table panel - Pagination", + "uid": "1YGJxPUnz", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 1366744fd91..1097fad630f 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -304,9 +304,9 @@ export const Table: FC = memo((props: Props) => { totalColumnsWidth={totalColumnsWidth} /> )} - {paginationEl}
+ {paginationEl}
); }); diff --git a/packages/grafana-ui/src/components/Table/styles.ts b/packages/grafana-ui/src/components/Table/styles.ts index c70f0c9e199..3682b5da17e 100644 --- a/packages/grafana-ui/src/components/Table/styles.ts +++ b/packages/grafana-ui/src/components/Table/styles.ts @@ -184,6 +184,9 @@ export const getTableStyles = (theme: GrafanaTheme2) => { div:not(:only-child):first-child { flex-grow: 0.6; } + position: absolute; + bottom: 0; + left: 0; `, paginationSummary: css` color: ${theme.colors.text.secondary}; @@ -191,11 +194,16 @@ export const getTableStyles = (theme: GrafanaTheme2) => { margin-left: auto; `, - tableContentWrapper: (totalColumnsWidth: number) => css` - width: ${totalColumnsWidth ?? '100%'}; - display: flex; - flex-direction: column; - `, + tableContentWrapper: (totalColumnsWidth: number) => { + const width = totalColumnsWidth !== undefined ? `${totalColumnsWidth}px` : '100%'; + + return css` + label: tableContentWrapper; + width: ${width}; + display: flex; + flex-direction: column; + `; + }, row: css` label: row; border-bottom: 1px solid ${borderColor}; From a126c07e541733462c6b69970bec829f93a4c68b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 14 Apr 2022 19:58:08 +0200 Subject: [PATCH 14/40] DashboardPage: Remember scroll position when coming back panel edit / view panel (#47639) * DashboardPage: Remember scroll position when coming back panel edit / view panel * Use scollElement callback * Fixed ts issue --- .../dashboard/containers/DashboardPage.tsx | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/public/app/features/dashboard/containers/DashboardPage.tsx b/public/app/features/dashboard/containers/DashboardPage.tsx index 860e645f6bb..c73c0c6fb16 100644 --- a/public/app/features/dashboard/containers/DashboardPage.tsx +++ b/public/app/features/dashboard/containers/DashboardPage.tsx @@ -4,7 +4,6 @@ import { connect, ConnectedProps } from 'react-redux'; import { locationService } from '@grafana/runtime'; import { selectors } from '@grafana/e2e-selectors'; import { CustomScrollbar, stylesFactory, Themeable2, withTheme2 } from '@grafana/ui'; - import { createErrorNotification } from 'app/core/copy/appNotification'; import { Branding } from 'app/core/components/Branding/Branding'; import { DashboardGrid } from '../dashgrid/DashboardGrid'; @@ -78,6 +77,7 @@ export interface State { showLoadingState: boolean; panelNotFound: boolean; editPanelAccessDenied: boolean; + scrollElement?: HTMLDivElement; } export class UnthemedDashboardPage extends PureComponent { @@ -224,14 +224,14 @@ export class UnthemedDashboardPage extends PureComponent { } if (dashboard.canEditPanel(panel)) { - return { ...state, editPanel: panel }; + return { ...state, editPanel: panel, rememberScrollTop: state.scrollElement?.scrollTop }; } else { return { ...state, editPanelAccessDenied: true }; } } // Leaving edit mode else if (state.editPanel && !urlEditPanelId) { - return { ...state, editPanel: null }; + return { ...state, editPanel: null, updateScrollTop: state.rememberScrollTop }; } // Entering view mode @@ -245,11 +245,7 @@ export class UnthemedDashboardPage extends PureComponent { // Should move this state out of dashboard in the future dashboard.initViewPanel(panel); - return { - ...state, - viewPanel: panel, - updateScrollTop: 0, - }; + return { ...state, viewPanel: panel, rememberScrollTop: state.scrollElement?.scrollTop, updateScrollTop: 0 }; } // Leaving view mode else if (state.viewPanel && !urlViewPanelId) { @@ -290,6 +286,10 @@ export class UnthemedDashboardPage extends PureComponent { this.setState({ updateScrollTop: 0 }); }; + setScrollRef = (scrollElement: HTMLDivElement): void => { + this.setState({ scrollElement }); + }; + getInspectPanel() { const { dashboard, queryParams } = this.props; @@ -346,6 +346,7 @@ export class UnthemedDashboardPage extends PureComponent {
Date: Thu, 14 Apr 2022 13:06:21 -0500 Subject: [PATCH 15/40] Alerting: Refactor GET/POST alerting config routes to be more extensible (#47229) * Refactor GET am config to be extensible * Extract post config route * Fix tests * Remove temporary duplication * Fix broken test due to layer shift * Fix duplicated error message * Properly return 400 on config rejection * Revert weird half method extraction * Move things to notifier package and avoid redundant interface * Simplify documentation * Split encryption service and depend on minimal abstractions * Properly initialize things all the way up to the composition root * Encryption -> Crypto * Address misc feedback * Missing docstring * Few more simple polish improvements * Unify on MultiOrgAlertmanager. Discover bug in existing test * Fix rebase conflicts * Misc feedback, renames, docs * Access crypto hanging off MultiOrgAlertmanager rather than having a separate API to initialize --- pkg/services/ngalert/api/api.go | 2 +- pkg/services/ngalert/api/api_alertmanager.go | 196 +++--------------- .../ngalert/api/api_alertmanager_test.go | 5 +- .../api/tooling/definitions/alertmanager.go | 3 + pkg/services/ngalert/ngalert.go | 2 +- .../ngalert/notifier/alertmanager_config.go | 119 +++++++++++ pkg/services/ngalert/notifier/crypto.go | 118 +++++++++++ .../ngalert/notifier/multiorg_alertmanager.go | 7 +- .../notifier/multiorg_alertmanager_test.go | 6 +- .../ngalert/schedule/schedule_unit_test.go | 2 +- 10 files changed, 283 insertions(+), 177 deletions(-) create mode 100644 pkg/services/ngalert/notifier/alertmanager_config.go create mode 100644 pkg/services/ngalert/notifier/crypto.go diff --git a/pkg/services/ngalert/api/api.go b/pkg/services/ngalert/api/api.go index be3a4623a7a..0b6123b6603 100644 --- a/pkg/services/ngalert/api/api.go +++ b/pkg/services/ngalert/api/api.go @@ -91,7 +91,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) { api.RegisterAlertmanagerApiEndpoints(NewForkedAM( api.DatasourceCache, NewLotexAM(proxy, logger), - &AlertmanagerSrv{store: api.AlertingStore, mam: api.MultiOrgAlertmanager, secrets: api.SecretsService, log: logger, ac: api.AccessControl}, + &AlertmanagerSrv{crypto: api.MultiOrgAlertmanager.Crypto, log: logger, ac: api.AccessControl, mam: api.MultiOrgAlertmanager}, ), m) // Register endpoints for proxying to Prometheus-compatible backends. api.RegisterPrometheusApiEndpoints(NewForkedProm( diff --git a/pkg/services/ngalert/api/api_alertmanager.go b/pkg/services/ngalert/api/api_alertmanager.go index c23cfc9985f..ee22a4145ab 100644 --- a/pkg/services/ngalert/api/api_alertmanager.go +++ b/pkg/services/ngalert/api/api_alertmanager.go @@ -2,7 +2,6 @@ package api import ( "context" - "encoding/base64" "errors" "fmt" "net/http" @@ -15,10 +14,8 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" - ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/ngalert/notifier" "github.com/grafana/grafana/pkg/services/ngalert/store" - "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/web" ) @@ -29,11 +26,10 @@ const ( ) type AlertmanagerSrv struct { - mam *notifier.MultiOrgAlertmanager - secrets secrets.Service - store AlertingStore - log log.Logger - ac accesscontrol.AccessControl + log log.Logger + ac accesscontrol.AccessControl + mam *notifier.MultiOrgAlertmanager + crypto notifier.Crypto } type UnknownReceiverError struct { @@ -44,81 +40,6 @@ func (e UnknownReceiverError) Error() string { return fmt.Sprintf("unknown receiver: %s", e.UID) } -func (srv AlertmanagerSrv) loadSecureSettings(ctx context.Context, orgId int64, receivers []*apimodels.PostableApiReceiver) error { - // Get the last known working configuration - query := ngmodels.GetLatestAlertmanagerConfigurationQuery{OrgID: orgId} - if err := srv.store.GetLatestAlertmanagerConfiguration(ctx, &query); err != nil { - // If we don't have a configuration there's nothing for us to know and we should just continue saving the new one - if !errors.Is(err, store.ErrNoAlertmanagerConfiguration) { - return fmt.Errorf("failed to get latest configuration: %w", err) - } - } - - currentReceiverMap := make(map[string]*apimodels.PostableGrafanaReceiver) - if query.Result != nil { - currentConfig, err := notifier.Load([]byte(query.Result.AlertmanagerConfiguration)) - // If the current config is un-loadable, treat it as if it never existed. Providing a new, valid config should be able to "fix" this state. - if err != nil { - srv.log.Warn("Last known alertmanager configuration was invalid. Overwriting...") - } else { - currentReceiverMap = currentConfig.GetGrafanaReceiverMap() - } - } - - // Copy the previously known secure settings - for i, r := range receivers { - for j, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers { - if gr.UID == "" { // new receiver - continue - } - - cgmr, ok := currentReceiverMap[gr.UID] - if !ok { - // it tries to update a receiver that didn't previously exist - return UnknownReceiverError{UID: gr.UID} - } - - // frontend sends only the secure settings that have to be updated - // therefore we have to copy from the last configuration only those secure settings not included in the request - for key := range cgmr.SecureSettings { - _, ok := gr.SecureSettings[key] - if !ok { - decryptedValue, err := srv.getDecryptedSecret(cgmr, key) - if err != nil { - return fmt.Errorf("failed to decrypt stored secure setting: %s: %w", key, err) - } - - if receivers[i].PostableGrafanaReceivers.GrafanaManagedReceivers[j].SecureSettings == nil { - receivers[i].PostableGrafanaReceivers.GrafanaManagedReceivers[j].SecureSettings = make(map[string]string, len(cgmr.SecureSettings)) - } - - receivers[i].PostableGrafanaReceivers.GrafanaManagedReceivers[j].SecureSettings[key] = decryptedValue - } - } - } - } - return nil -} - -func (srv AlertmanagerSrv) getDecryptedSecret(r *apimodels.PostableGrafanaReceiver, key string) (string, error) { - storedValue, ok := r.SecureSettings[key] - if !ok { - return "", nil - } - - decodeValue, err := base64.StdEncoding.DecodeString(storedValue) - if err != nil { - return "", err - } - - decryptedValue, err := srv.secrets.Decrypt(context.Background(), decodeValue) - if err != nil { - return "", err - } - - return string(decryptedValue), nil -} - func (srv AlertmanagerSrv) RouteGetAMStatus(c *models.ReqContext) response.Response { am, errResp := srv.AlertmanagerFor(c.OrgId) if errResp != nil { @@ -192,59 +113,14 @@ func (srv AlertmanagerSrv) RouteDeleteSilence(c *models.ReqContext) response.Res } func (srv AlertmanagerSrv) RouteGetAlertingConfig(c *models.ReqContext) response.Response { - query := ngmodels.GetLatestAlertmanagerConfigurationQuery{OrgID: c.OrgId} - if err := srv.store.GetLatestAlertmanagerConfiguration(c.Req.Context(), &query); err != nil { + config, err := srv.mam.GetAlertmanagerConfiguration(c.Req.Context(), c.OrgId) + if err != nil { if errors.Is(err, store.ErrNoAlertmanagerConfiguration) { return ErrResp(http.StatusNotFound, err, "") } - return ErrResp(http.StatusInternalServerError, err, "failed to get latest configuration") + return ErrResp(http.StatusInternalServerError, err, err.Error()) } - - cfg, err := notifier.Load([]byte(query.Result.AlertmanagerConfiguration)) - if err != nil { - return ErrResp(http.StatusInternalServerError, err, "failed to unmarshal alertmanager configuration") - } - - result := apimodels.GettableUserConfig{ - TemplateFiles: cfg.TemplateFiles, - AlertmanagerConfig: apimodels.GettableApiAlertingConfig{ - Config: cfg.AlertmanagerConfig.Config, - }, - } - for _, recv := range cfg.AlertmanagerConfig.Receivers { - receivers := make([]*apimodels.GettableGrafanaReceiver, 0, len(recv.PostableGrafanaReceivers.GrafanaManagedReceivers)) - for _, pr := range recv.PostableGrafanaReceivers.GrafanaManagedReceivers { - secureFields := make(map[string]bool, len(pr.SecureSettings)) - for k := range pr.SecureSettings { - decryptedValue, err := srv.getDecryptedSecret(pr, k) - if err != nil { - return ErrResp(http.StatusInternalServerError, err, "failed to decrypt stored secure setting: %s", k) - } - if decryptedValue == "" { - continue - } - secureFields[k] = true - } - gr := apimodels.GettableGrafanaReceiver{ - UID: pr.UID, - Name: pr.Name, - Type: pr.Type, - DisableResolveMessage: pr.DisableResolveMessage, - Settings: pr.Settings, - SecureFields: secureFields, - } - receivers = append(receivers, &gr) - } - gettableApiReceiver := apimodels.GettableApiReceiver{ - GettableGrafanaReceivers: apimodels.GettableGrafanaReceivers{ - GrafanaManagedReceivers: receivers, - }, - } - gettableApiReceiver.Name = recv.Name - result.AlertmanagerConfig.Receivers = append(result.AlertmanagerConfig.Receivers, &gettableApiReceiver) - } - - return response.JSON(http.StatusOK, result) + return response.JSON(http.StatusOK, config) } func (srv AlertmanagerSrv) RouteGetAMAlertGroups(c *models.ReqContext) response.Response { @@ -334,41 +210,26 @@ func (srv AlertmanagerSrv) RouteGetSilences(c *models.ReqContext) response.Respo } func (srv AlertmanagerSrv) RoutePostAlertingConfig(c *models.ReqContext, body apimodels.PostableUserConfig) response.Response { - // Get the last known working configuration - query := ngmodels.GetLatestAlertmanagerConfigurationQuery{OrgID: c.OrgId} - if err := srv.store.GetLatestAlertmanagerConfiguration(c.Req.Context(), &query); err != nil { - // If we don't have a configuration there's nothing for us to know and we should just continue saving the new one - if !errors.Is(err, store.ErrNoAlertmanagerConfiguration) { - return ErrResp(http.StatusInternalServerError, err, "failed to get latest configuration") - } + err := srv.mam.ApplyAlertmanagerConfiguration(c.Req.Context(), c.OrgId, body) + if err == nil { + return response.JSON(http.StatusAccepted, util.DynMap{"message": "configuration created"}) + } + var unknownReceiverError notifier.UnknownReceiverError + if errors.As(err, &unknownReceiverError) { + return ErrResp(http.StatusBadRequest, unknownReceiverError, "") + } + var configRejectedError notifier.AlertmanagerConfigRejectedError + if errors.As(err, &configRejectedError) { + return ErrResp(http.StatusBadRequest, configRejectedError, "") + } + if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) { + return response.Error(http.StatusNotFound, err.Error(), err) + } + if errors.Is(err, notifier.ErrAlertmanagerNotReady) { + return response.Error(http.StatusConflict, err.Error(), err) } - if err := srv.loadSecureSettings(c.Req.Context(), c.OrgId, body.AlertmanagerConfig.Receivers); err != nil { - var unknownReceiverError UnknownReceiverError - if errors.As(err, &unknownReceiverError) { - return ErrResp(http.StatusBadRequest, err, "") - } - return ErrResp(http.StatusInternalServerError, err, "") - } - - if err := body.ProcessConfig(srv.secrets.Encrypt); err != nil { - return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration") - } - - am, errResp := srv.AlertmanagerFor(c.OrgId) - if errResp != nil { - // It's okay if the alertmanager isn't ready yet, we're changing its config anyway. - if !errors.Is(errResp.Err(), notifier.ErrAlertmanagerNotReady) { - return errResp - } - } - - if err := am.SaveAndApplyConfig(c.Req.Context(), &body); err != nil { - srv.log.Error("unable to save and apply alertmanager configuration", "err", err) - return ErrResp(http.StatusBadRequest, err, "failed to save and apply Alertmanager configuration") - } - - return response.JSON(http.StatusAccepted, util.DynMap{"message": "configuration created"}) + return ErrResp(http.StatusInternalServerError, err, "") } func (srv AlertmanagerSrv) RoutePostAMAlerts(_ *models.ReqContext, _ apimodels.PostableAlerts) response.Response { @@ -376,7 +237,7 @@ func (srv AlertmanagerSrv) RoutePostAMAlerts(_ *models.ReqContext, _ apimodels.P } func (srv AlertmanagerSrv) RoutePostTestReceivers(c *models.ReqContext, body apimodels.TestReceiversConfigBodyParams) response.Response { - if err := srv.loadSecureSettings(c.Req.Context(), c.OrgId, body.Receivers); err != nil { + if err := srv.crypto.LoadSecureSettings(c.Req.Context(), c.OrgId, body.Receivers); err != nil { var unknownReceiverError UnknownReceiverError if errors.As(err, &unknownReceiverError) { return ErrResp(http.StatusBadRequest, err, "") @@ -384,7 +245,7 @@ func (srv AlertmanagerSrv) RoutePostTestReceivers(c *models.ReqContext, body api return ErrResp(http.StatusInternalServerError, err, "") } - if err := body.ProcessConfig(srv.secrets.Encrypt); err != nil { + if err := body.ProcessConfig(srv.crypto.Encrypt); err != nil { return ErrResp(http.StatusInternalServerError, err, "failed to post process Alertmanager configuration") } @@ -514,7 +375,6 @@ func (srv AlertmanagerSrv) AlertmanagerFor(orgID int64) (Alertmanager, *response if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) { return nil, response.Error(http.StatusNotFound, err.Error(), err) } - if errors.Is(err, notifier.ErrAlertmanagerNotReady) { return am, response.Error(http.StatusConflict, err.Error(), err) } diff --git a/pkg/services/ngalert/api/api_alertmanager_test.go b/pkg/services/ngalert/api/api_alertmanager_test.go index 75ee2176024..eec2b513e00 100644 --- a/pkg/services/ngalert/api/api_alertmanager_test.go +++ b/pkg/services/ngalert/api/api_alertmanager_test.go @@ -357,7 +357,8 @@ func createSut(t *testing.T, accessControl accesscontrol.AccessControl) Alertman accessControl = acMock.New().WithDisabled() } log := log.NewNopLogger() - return AlertmanagerSrv{mam: mam, store: &configStore, secrets: secrets, ac: accessControl, log: log} + crypto := notifier.NewCrypto(secrets, &configStore, log) + return AlertmanagerSrv{mam: mam, crypto: crypto, ac: accessControl, log: log} } func createAmConfigRequest(t *testing.T) apimodels.PostableUserConfig { @@ -395,7 +396,7 @@ func createMultiOrgAlertmanager(t *testing.T) *notifier.MultiOrgAlertmanager { }, // do not poll in tests. } - mam, err := notifier.NewMultiOrgAlertmanager(cfg, &configStore, &orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger")) + mam, err := notifier.NewMultiOrgAlertmanager(cfg, &configStore, &orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) err = mam.LoadAndSyncAlertmanagersForOrgs(context.Background()) require.NoError(t, err) diff --git a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go index 94eb137d7b7..ad75f5215b3 100644 --- a/pkg/services/ngalert/api/tooling/definitions/alertmanager.go +++ b/pkg/services/ngalert/api/tooling/definitions/alertmanager.go @@ -18,6 +18,7 @@ import ( "gopkg.in/yaml.v3" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/services/ngalert/models" "github.com/grafana/grafana/pkg/services/secrets" "github.com/grafana/grafana/pkg/util" ) @@ -712,6 +713,8 @@ type Route struct { GroupWait *model.Duration `yaml:"group_wait,omitempty" json:"group_wait,omitempty"` GroupInterval *model.Duration `yaml:"group_interval,omitempty" json:"group_interval,omitempty"` RepeatInterval *model.Duration `yaml:"repeat_interval,omitempty" json:"repeat_interval,omitempty"` + + Provenance models.Provenance `yaml:"provenance,omitempty" json:"provenance,omitempty"` } // UnmarshalYAML implements the yaml.Unmarshaler interface for Route. This is a copy of alertmanager's upstream except it removes validation on the label key. diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 9d03ecfe9cd..49158bc52b5 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -99,7 +99,7 @@ func (ng *AlertNG) init() error { decryptFn := ng.SecretsService.GetDecryptedValue multiOrgMetrics := ng.Metrics.GetMultiOrgAlertmanagerMetrics() - ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, store, store, ng.KVStore, decryptFn, multiOrgMetrics, ng.NotificationService, log.New("ngalert.multiorg.alertmanager")) + ng.MultiOrgAlertmanager, err = notifier.NewMultiOrgAlertmanager(ng.Cfg, store, store, ng.KVStore, decryptFn, multiOrgMetrics, ng.NotificationService, log.New("ngalert.multiorg.alertmanager"), ng.SecretsService) if err != nil { return err } diff --git a/pkg/services/ngalert/notifier/alertmanager_config.go b/pkg/services/ngalert/notifier/alertmanager_config.go new file mode 100644 index 00000000000..7efd6561589 --- /dev/null +++ b/pkg/services/ngalert/notifier/alertmanager_config.go @@ -0,0 +1,119 @@ +package notifier + +import ( + "context" + "errors" + "fmt" + + "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/store" +) + +type UnknownReceiverError struct { + UID string +} + +func (e UnknownReceiverError) Error() string { + return fmt.Sprintf("unknown receiver: %s", e.UID) +} + +type AlertmanagerConfigRejectedError struct { + Inner error +} + +func (e AlertmanagerConfigRejectedError) Error() string { + return fmt.Sprintf("failed to save and apply Alertmanager configuration: %s", e.Inner.Error()) +} + +type configurationStore interface { + GetLatestAlertmanagerConfiguration(ctx context.Context, query *models.GetLatestAlertmanagerConfigurationQuery) error +} + +func (moa *MultiOrgAlertmanager) GetAlertmanagerConfiguration(ctx context.Context, org int64) (definitions.GettableUserConfig, error) { + query := models.GetLatestAlertmanagerConfigurationQuery{OrgID: org} + err := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, &query) + if err != nil { + return definitions.GettableUserConfig{}, fmt.Errorf("failed to get latest configuration: %w", err) + } + cfg, err := Load([]byte(query.Result.AlertmanagerConfiguration)) + if err != nil { + return definitions.GettableUserConfig{}, fmt.Errorf("failed to unmarshal alertmanager configuration: %w", err) + } + + result := definitions.GettableUserConfig{ + TemplateFiles: cfg.TemplateFiles, + AlertmanagerConfig: definitions.GettableApiAlertingConfig{ + Config: cfg.AlertmanagerConfig.Config, + }, + } + + for _, recv := range cfg.AlertmanagerConfig.Receivers { + receivers := make([]*definitions.GettableGrafanaReceiver, 0, len(recv.PostableGrafanaReceivers.GrafanaManagedReceivers)) + for _, pr := range recv.PostableGrafanaReceivers.GrafanaManagedReceivers { + secureFields := make(map[string]bool, len(pr.SecureSettings)) + for k := range pr.SecureSettings { + decryptedValue, err := moa.Crypto.getDecryptedSecret(pr, k) + if err != nil { + return definitions.GettableUserConfig{}, fmt.Errorf("failed to decrypt stored secure setting: %w", err) + } + if decryptedValue == "" { + continue + } + secureFields[k] = true + } + gr := definitions.GettableGrafanaReceiver{ + UID: pr.UID, + Name: pr.Name, + Type: pr.Type, + DisableResolveMessage: pr.DisableResolveMessage, + Settings: pr.Settings, + SecureFields: secureFields, + } + receivers = append(receivers, &gr) + } + gettableApiReceiver := definitions.GettableApiReceiver{ + GettableGrafanaReceivers: definitions.GettableGrafanaReceivers{ + GrafanaManagedReceivers: receivers, + }, + } + gettableApiReceiver.Name = recv.Name + result.AlertmanagerConfig.Receivers = append(result.AlertmanagerConfig.Receivers, &gettableApiReceiver) + } + + return result, nil +} + +func (moa *MultiOrgAlertmanager) ApplyAlertmanagerConfiguration(ctx context.Context, org int64, config definitions.PostableUserConfig) error { + // Get the last known working configuration + query := models.GetLatestAlertmanagerConfigurationQuery{OrgID: org} + if err := moa.configStore.GetLatestAlertmanagerConfiguration(ctx, &query); err != nil { + // If we don't have a configuration there's nothing for us to know and we should just continue saving the new one + if !errors.Is(err, store.ErrNoAlertmanagerConfiguration) { + return fmt.Errorf("failed to get latest configuration %w", err) + } + } + + if err := moa.Crypto.LoadSecureSettings(ctx, org, config.AlertmanagerConfig.Receivers); err != nil { + return err + } + + if err := config.ProcessConfig(moa.Crypto.Encrypt); err != nil { + return fmt.Errorf("failed to post process Alertmanager configuration: %w", err) + } + + am, err := moa.AlertmanagerFor(org) + if err != nil { + // It's okay if the alertmanager isn't ready yet, we're changing its config anyway. + if !errors.Is(err, ErrAlertmanagerNotReady) { + return err + } + } + + if err := am.SaveAndApplyConfig(ctx, &config); err != nil { + moa.logger.Error("unable to save and apply alertmanager configuration", "err", err) + return AlertmanagerConfigRejectedError{err} + } + + return nil +} diff --git a/pkg/services/ngalert/notifier/crypto.go b/pkg/services/ngalert/notifier/crypto.go new file mode 100644 index 00000000000..0d5e06a55f1 --- /dev/null +++ b/pkg/services/ngalert/notifier/crypto.go @@ -0,0 +1,118 @@ +package notifier + +import ( + "context" + "encoding/base64" + "errors" + "fmt" + + "github.com/grafana/grafana/pkg/infra/log" + "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/store" + "github.com/grafana/grafana/pkg/services/secrets" +) + +// Crypto allows decryption of Alertmanager Configuration and encryption of arbitrary payloads. +type Crypto interface { + LoadSecureSettings(ctx context.Context, orgId int64, receivers []*definitions.PostableApiReceiver) error + Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error) + + getDecryptedSecret(r *definitions.PostableGrafanaReceiver, key string) (string, error) +} + +// alertmanagerCrypto implements decryption of Alertmanager configuration and encryption of arbitrary payloads based on Grafana's encryptions. +type alertmanagerCrypto struct { + secrets secrets.Service + configs configurationStore + log log.Logger +} + +func NewCrypto(secrets secrets.Service, configs configurationStore, log log.Logger) Crypto { + return &alertmanagerCrypto{ + secrets: secrets, + configs: configs, + log: log, + } +} + +// LoadSecureSettings adds the corresponding unencrypted secrets stored to the list of input receivers. +func (c *alertmanagerCrypto) LoadSecureSettings(ctx context.Context, orgId int64, receivers []*definitions.PostableApiReceiver) error { + // Get the last known working configuration. + query := models.GetLatestAlertmanagerConfigurationQuery{OrgID: orgId} + if err := c.configs.GetLatestAlertmanagerConfiguration(ctx, &query); err != nil { + // If we don't have a configuration there's nothing for us to know and we should just continue saving the new one. + if !errors.Is(err, store.ErrNoAlertmanagerConfiguration) { + return fmt.Errorf("failed to get latest configuration: %w", err) + } + } + + currentReceiverMap := make(map[string]*definitions.PostableGrafanaReceiver) + if query.Result != nil { + currentConfig, err := Load([]byte(query.Result.AlertmanagerConfiguration)) + // If the current config is un-loadable, treat it as if it never existed. Providing a new, valid config should be able to "fix" this state. + if err != nil { + c.log.Warn("Last known alertmanager configuration was invalid. Overwriting...") + } else { + currentReceiverMap = currentConfig.GetGrafanaReceiverMap() + } + } + + // Copy the previously known secure settings. + for i, r := range receivers { + for j, gr := range r.PostableGrafanaReceivers.GrafanaManagedReceivers { + if gr.UID == "" { // new receiver + continue + } + + cgmr, ok := currentReceiverMap[gr.UID] + if !ok { + // It tries to update a receiver that didn't previously exist + return UnknownReceiverError{UID: gr.UID} + } + + // Frontend sends only the secure settings that have to be updated + // Therefore we have to copy from the last configuration only those secure settings not included in the request + for key := range cgmr.SecureSettings { + _, ok := gr.SecureSettings[key] + if !ok { + decryptedValue, err := c.getDecryptedSecret(cgmr, key) + if err != nil { + return fmt.Errorf("failed to decrypt stored secure setting: %s: %w", key, err) + } + + if receivers[i].PostableGrafanaReceivers.GrafanaManagedReceivers[j].SecureSettings == nil { + receivers[i].PostableGrafanaReceivers.GrafanaManagedReceivers[j].SecureSettings = make(map[string]string, len(cgmr.SecureSettings)) + } + + receivers[i].PostableGrafanaReceivers.GrafanaManagedReceivers[j].SecureSettings[key] = decryptedValue + } + } + } + } + return nil +} + +func (c *alertmanagerCrypto) getDecryptedSecret(r *definitions.PostableGrafanaReceiver, key string) (string, error) { + storedValue, ok := r.SecureSettings[key] + if !ok { + return "", nil + } + + decodeValue, err := base64.StdEncoding.DecodeString(storedValue) + if err != nil { + return "", err + } + + decryptedValue, err := c.secrets.Decrypt(context.Background(), decodeValue) + if err != nil { + return "", err + } + + return string(decryptedValue), nil +} + +// Encrypt delegates encryption to secrets.Service. +func (c *alertmanagerCrypto) Encrypt(ctx context.Context, payload []byte, opt secrets.EncryptionOptions) ([]byte, error) { + return c.secrets.Encrypt(ctx, payload, opt) +} diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager.go b/pkg/services/ngalert/notifier/multiorg_alertmanager.go index 3c8a2f1d987..d34677c3e44 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager.go @@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/services/ngalert/notifier/channels" "github.com/grafana/grafana/pkg/services/notifications" + "github.com/grafana/grafana/pkg/services/secrets" "github.com/prometheus/alertmanager/cluster" "github.com/prometheus/client_golang/prometheus" @@ -29,6 +30,8 @@ var ( ) type MultiOrgAlertmanager struct { + Crypto Crypto + alertmanagersMtx sync.RWMutex alertmanagers map[int64]*Alertmanager @@ -51,9 +54,11 @@ type MultiOrgAlertmanager struct { func NewMultiOrgAlertmanager(cfg *setting.Cfg, configStore store.AlertingStore, orgStore store.OrgStore, kvStore kvstore.KVStore, decryptFn channels.GetDecryptedValueFn, m *metrics.MultiOrgAlertmanager, - ns notifications.Service, l log.Logger, + ns notifications.Service, l log.Logger, s secrets.Service, ) (*MultiOrgAlertmanager, error) { moa := &MultiOrgAlertmanager{ + Crypto: NewCrypto(s, configStore, l), + logger: l, settings: cfg, alertmanagers: map[int64]*Alertmanager{}, diff --git a/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go b/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go index 6dd2f020be0..56899182532 100644 --- a/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go +++ b/pkg/services/ngalert/notifier/multiorg_alertmanager_test.go @@ -44,7 +44,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgs(t *testing.T) { DisabledOrgs: map[int64]struct{}{5: {}}, }, // do not poll in tests. } - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger")) + mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) ctx := context.Background() @@ -168,7 +168,7 @@ func TestMultiOrgAlertmanager_SyncAlertmanagersForOrgsWithFailures(t *testing.T) DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration(), }, // do not poll in tests. } - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger")) + mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) ctx := context.Background() @@ -218,7 +218,7 @@ func TestMultiOrgAlertmanager_AlertmanagerFor(t *testing.T) { decryptFn := secretsService.GetDecryptedValue reg := prometheus.NewPedanticRegistry() m := metrics.NewNGAlert(reg) - mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger")) + mam, err := NewMultiOrgAlertmanager(cfg, configStore, orgStore, kvStore, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) ctx := context.Background() diff --git a/pkg/services/ngalert/schedule/schedule_unit_test.go b/pkg/services/ngalert/schedule/schedule_unit_test.go index 4824f05d95f..7f5f8da4cac 100644 --- a/pkg/services/ngalert/schedule/schedule_unit_test.go +++ b/pkg/services/ngalert/schedule/schedule_unit_test.go @@ -1015,7 +1015,7 @@ func setupScheduler(t *testing.T, rs store.RuleStore, is store.InstanceStore, ac m := metrics.NewNGAlert(registry) secretsService := secretsManager.SetupTestService(t, fakes.NewFakeSecretsStore()) decryptFn := secretsService.GetDecryptedValue - moa, err := notifier.NewMultiOrgAlertmanager(&setting.Cfg{}, ¬ifier.FakeConfigStore{}, ¬ifier.FakeOrgStore{}, ¬ifier.FakeKVStore{}, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger")) + moa, err := notifier.NewMultiOrgAlertmanager(&setting.Cfg{}, ¬ifier.FakeConfigStore{}, ¬ifier.FakeOrgStore{}, ¬ifier.FakeKVStore{}, decryptFn, m.GetMultiOrgAlertmanagerMetrics(), nil, log.New("testlogger"), secretsService) require.NoError(t, err) schedCfg := SchedulerCfg{ From 2a178bd73c67001e3a42989c4b35d44a16c4b3bb Mon Sep 17 00:00:00 2001 From: sam boyer Date: Thu, 14 Apr 2022 14:54:35 -0400 Subject: [PATCH 16/40] Introduce coremodels framework (extracted from intent-api) (#47653) * Copy over most of coremodel from intent-api branch * Fix import paths * Fix incorrect provider name * Add root compgen file, fixup componentroot.yaml * go mod tidy * Remove compgen for now * Add dashboard coremodel * Remove datasource coremodel, for now * Tweak comments on dashboard struct model * devenv: add dashboard testing directly * Fixup dashboard schema for openness, heatmap * Update Thema to tip * Fix wire/registry references * Fix hclog version --- devenv/dev-dashboards/dashboards.go | 6 + devenv/dev-dashboards/dashboards_test.go | 84 +++++ go.mod | 42 ++- go.sum | 84 +++-- pkg/coremodel/dashboard/coremodel.go | 40 ++ pkg/coremodel/dashboard/lineage.cue | 344 ++++++++++++++++++ pkg/coremodel/dashboard/schema.go | 89 +++++ pkg/cuectx/ctx.go | 116 ++++++ pkg/framework/coremodel/helpers.go | 1 + pkg/framework/coremodel/interface.go | 25 ++ pkg/framework/coremodel/registry.go | 78 ++++ .../coremodel/staticregistry/provide.go | 17 + pkg/server/wire.go | 8 + 13 files changed, 890 insertions(+), 44 deletions(-) create mode 100644 devenv/dev-dashboards/dashboards.go create mode 100644 devenv/dev-dashboards/dashboards_test.go create mode 100644 pkg/coremodel/dashboard/coremodel.go create mode 100644 pkg/coremodel/dashboard/lineage.cue create mode 100644 pkg/coremodel/dashboard/schema.go create mode 100644 pkg/cuectx/ctx.go create mode 100644 pkg/framework/coremodel/helpers.go create mode 100644 pkg/framework/coremodel/interface.go create mode 100644 pkg/framework/coremodel/registry.go create mode 100644 pkg/framework/coremodel/staticregistry/provide.go diff --git a/devenv/dev-dashboards/dashboards.go b/devenv/dev-dashboards/dashboards.go new file mode 100644 index 00000000000..4531431bc7a --- /dev/null +++ b/devenv/dev-dashboards/dashboards.go @@ -0,0 +1,6 @@ +package dev_dashboards + +import "embed" + +//go:embed *.json */*.json +var DevDashboardFS embed.FS diff --git a/devenv/dev-dashboards/dashboards_test.go b/devenv/dev-dashboards/dashboards_test.go new file mode 100644 index 00000000000..66f29040f84 --- /dev/null +++ b/devenv/dev-dashboards/dashboards_test.go @@ -0,0 +1,84 @@ +package dev_dashboards + +import ( + "encoding/json" + "io" + "io/fs" + "path/filepath" + "strings" + "testing" + + "cuelang.org/go/cue/errors" + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/coremodel/dashboard" + "github.com/grafana/grafana/pkg/cuectx" +) + +func TestDevenvDashboardValidity(t *testing.T) { + m, err := themaTestableDashboards() + require.NoError(t, err) + cm, err := dashboard.ProvideCoremodel(cuectx.ProvideThemaLibrary()) + require.NoError(t, err) + + for path, b := range m { + t.Run(path, func(t *testing.T) { + // The path arg here only matters for error output + cv, err := cuectx.JSONtoCUE(path, b) + require.NoError(t, err, "error while decoding dashboard JSON into a CUE value") + + _, err = cm.CurrentSchema().Validate(cv) + if err != nil { + // Testify trims errors to short length. We want the full text + errstr := errors.Details(err, nil) + t.Log(errstr) + if strings.Contains(errstr, "null") { + t.Log("validation failure appears to involve nulls - see if scripts/stripnulls.sh has any effect?") + } + t.FailNow() + } + }) + } +} + +func themaTestableDashboards() (map[string][]byte, error) { + m := make(map[string][]byte) + in := DevDashboardFS + + err := fs.WalkDir(in, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() || filepath.Ext(d.Name()) != ".json" { + return nil + } + + // nolint:gosec + f, err := in.Open(path) + if err != nil { + return err + } + defer f.Close() // nolint: errcheck + + b, err := io.ReadAll(f) + if err != nil { + return err + } + + jtree := make(map[string]interface{}) + json.Unmarshal(b, &jtree) + if oldschemav, has := jtree["schemaVersion"]; !has || !(oldschemav.(float64) > 32) { + return nil + } + + m[path] = b + return nil + }) + + if err != nil { + return nil, err + } + + return m, nil +} diff --git a/go.mod b/go.mod index 28dc2616525..dee9c1e4cbd 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ replace github.com/russellhaering/goxmldsig@v1.1.0 => github.com/russellhaering/ require ( cloud.google.com/go/storage v1.18.2 - cuelang.org/go v0.4.0 + cuelang.org/go v0.4.1 github.com/Azure/azure-sdk-for-go v59.3.0+incompatible github.com/Azure/go-autorest/autorest v0.11.22 github.com/BurntSushi/toml v0.3.1 @@ -31,7 +31,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/denisenkom/go-mssqldb v0.11.0 github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 - github.com/fatih/color v1.10.0 + github.com/fatih/color v1.13.0 github.com/gchaincl/sqlhooks v1.3.0 github.com/getsentry/sentry-go v0.10.0 github.com/go-kit/kit v0.11.0 // indirect @@ -56,7 +56,7 @@ require ( github.com/grafana/grafana-plugin-sdk-go v0.131.0 github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 - github.com/hashicorp/go-hclog v0.16.1 + github.com/hashicorp/go-hclog v1.0.0 github.com/hashicorp/go-plugin v1.4.3 github.com/hashicorp/go-version v1.3.0 github.com/influxdata/influxdb-client-go/v2 v2.6.0 @@ -69,7 +69,7 @@ require ( github.com/linkedin/goavro/v2 v2.10.0 github.com/m3db/prometheus_remote_client_golang v0.4.4 github.com/magefile/mage v1.12.1 - github.com/mattn/go-isatty v0.0.12 + github.com/mattn/go-isatty v0.0.14 github.com/mattn/go-sqlite3 v1.14.7 github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f @@ -110,11 +110,11 @@ require ( golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 golang.org/x/tools v0.1.9 gonum.org/v1/gonum v0.11.0 - google.golang.org/api v0.60.0 + google.golang.org/api v0.62.0 google.golang.org/grpc v1.42.0 google.golang.org/protobuf v1.27.1 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect - gopkg.in/ini.v1 v1.62.0 + gopkg.in/ini.v1 v1.66.2 gopkg.in/ldap.v3 v3.1.0 gopkg.in/mail.v2 v2.3.1 gopkg.in/square/go-jose.v2 v2.5.1 @@ -126,7 +126,6 @@ require ( ) require ( - cloud.google.com/go v0.97.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect @@ -141,7 +140,6 @@ require ( github.com/alecthomas/units v0.0.0-20210912230133-d1bdfacee922 // indirect github.com/andybalholm/brotli v1.0.3 github.com/apache/arrow/go/arrow v0.0.0-20211112161151-bc219186db40 // indirect - github.com/armon/go-metrics v0.3.8 // indirect github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/buger/jsonparser v1.1.1 // indirect @@ -151,7 +149,6 @@ require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cheekybits/genny v1.0.0 // indirect github.com/cockroachdb/apd/v2 v2.0.2 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect github.com/deepmap/oapi-codegen v1.8.2 // indirect github.com/dennwc/varint v1.0.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect @@ -187,12 +184,10 @@ require ( github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-immutable-radix v1.3.0 // indirect github.com/hashicorp/go-msgpack v0.5.5 // indirect github.com/hashicorp/go-multierror v1.1.0 // indirect github.com/hashicorp/go-sockaddr v1.0.2 // indirect github.com/hashicorp/golang-lru v0.5.4 - github.com/hashicorp/memberlist v0.2.4 // indirect github.com/hashicorp/yamux v0.0.0-20210826001029-26ff87cf9493 // indirect github.com/igm/sockjs-go/v3 v3.0.1 // indirect github.com/jessevdk/go-flags v1.5.0 // indirect @@ -202,11 +197,9 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mattermost/xml-roundtrip-validator v0.1.0 // indirect github.com/mattetti/filebuffer v1.0.1 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-runewidth v0.0.9 // indirect github.com/miekg/dns v1.1.43 // indirect github.com/mitchellh/go-testing-interface v1.14.0 // indirect - github.com/mitchellh/mapstructure v1.4.2 // indirect github.com/mna/redisc v1.3.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -224,13 +217,11 @@ require ( github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc // indirect github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect github.com/rs/cors v1.8.0 // indirect - github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect github.com/segmentio/encoding v0.3.2 github.com/sercand/kuberesolver v2.4.0+incompatible // indirect github.com/sergi/go-diff v1.0.0 // indirect github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect - github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/shurcooL/vfsgen v0.0.0-20200824052919-0d455de96546 // indirect github.com/sirupsen/logrus v1.8.1 // indirect github.com/stretchr/objx v0.2.0 // indirect @@ -246,7 +237,7 @@ require ( golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 + google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect ) @@ -257,9 +248,22 @@ require ( github.com/Azure/go-autorest/autorest/adal v0.9.17 github.com/golang-migrate/migrate/v4 v4.7.0 github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f + github.com/grafana/thema v0.0.0-20220413232647-fc54c169b508 gocloud.dev v0.24.0 ) +require ( + cloud.google.com/go v0.99.0 // indirect + github.com/armon/go-metrics v0.3.10 // indirect + github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/memberlist v0.3.0 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mitchellh/mapstructure v1.4.3 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect +) + require ( github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 // indirect @@ -267,7 +271,6 @@ require ( github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect - github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect github.com/containerd/containerd v1.5.9 // indirect github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect github.com/envoyproxy/go-control-plane v0.10.1 // indirect @@ -292,6 +295,11 @@ replace github.com/apache/thrift => github.com/apache/thrift v0.14.1 replace github.com/hashicorp/consul => github.com/hashicorp/consul v1.10.2 +// Thema's thema CLI requires cobra, which eventually works its way down to go-hclog@v1.0.0. +// Upgrading affects backend plugins: https://github.com/grafana/grafana/pull/47653#discussion_r850508593 +// No harm to Thema because it's only a dependency in its main package. +replace github.com/hashicorp/go-hclog => github.com/hashicorp/go-hclog v0.16.1 + // TODO: remove once gocloud.dev releases 0.25.x // `fileblob` implementation has buggy key ordering in 0.24.0 replace gocloud.dev v0.24.0 => github.com/google/go-cloud v0.24.1-0.20220209172924-99801bbb523a diff --git a/go.sum b/go.sum index 8ffd434c46a..8756dd4d304 100644 --- a/go.sum +++ b/go.sum @@ -30,8 +30,10 @@ cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWc cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -71,8 +73,9 @@ contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeS contrib.go.opencensus.io/exporter/prometheus v0.3.0/go.mod h1:rpCPVQKhiyH8oomWgm34ZmgIdZa8OVYO5WAIygPbBBE= contrib.go.opencensus.io/exporter/stackdriver v0.13.10/go.mod h1:I5htMbyta491eUxufwwZPQdcKvvgzMB4O9ni41YnIM8= contrib.go.opencensus.io/integrations/ocsql v0.1.7/go.mod h1:8DsSdjz3F+APR+0z0WkU1aRorQCFfRxvqjUUPMbF3fE= -cuelang.org/go v0.4.0 h1:GLJblw6m2WGGCA3k1v6Wbk9gTOt2qto48ahO2MmSd6I= cuelang.org/go v0.4.0/go.mod h1:tz/edkPi+T37AZcb5GlPY+WJkL6KiDlDVupKwL3vvjs= +cuelang.org/go v0.4.1 h1:rxG2cyZzdAymrNICI0L/wagK2EitoR8Xsiiq2D7qkaQ= +cuelang.org/go v0.4.1/go.mod h1:P09/R4UfAEzLkV9DXxwlxQnIZbkaT4uIhiEgs6Vsz2Q= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= @@ -327,8 +330,9 @@ github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQh github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= github.com/armon/go-metrics v0.3.3/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-metrics v0.3.6/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= -github.com/armon/go-metrics v0.3.8 h1:oOxq3KPj0WhCuy50EhzwiyMyG2ovRQZpZLXQuOh2a/M= github.com/armon/go-metrics v0.3.8/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-metrics v0.3.10 h1:FR+drcQStOe+32sYyJYyZ7FIdgoGGBnwLl+flodp8Uo= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= @@ -519,8 +523,9 @@ github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 h1:zH8ljVhhq7yC0MIeUL/IviMtY8hx2mK8cN9wEYb8ggw= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 h1:KwaoQzs/WeUxxJqiJsZ4euOly1Az/IgZXXSxlD/UBNk= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd/v2 v2.0.1/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw= @@ -662,8 +667,9 @@ github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFl github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= 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.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -832,8 +838,8 @@ github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb/go.mod h1:bH6Xx7IW github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= -github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= @@ -1290,6 +1296,7 @@ github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210827144239-02619b876842/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/tcpproxy v0.0.0-20180808230851-dfa16c61dad2/go.mod h1:DavVbd41y+b7ukKDmlnPR4nGYmkWXR6vHUkjQNiHPBs= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -1379,6 +1386,8 @@ github.com/grafana/loki v1.6.2-0.20211015002020-7832783b1caa/go.mod h1:0O8o/juxN github.com/grafana/saml v0.0.0-20211007135653-aed1b2edd86b h1:YiSGp34F4V0G08HHx1cJBf2GVgwYAkXQjzuVs1t8jYk= github.com/grafana/saml v0.0.0-20211007135653-aed1b2edd86b/go.mod h1:q83kyQoMD0vhy+RzFLlbw0UgHJ6TAihQpuXvdFmm4s4= github.com/grafana/sqlds/v2 v2.3.2/go.mod h1:34uyqPBWsEvg4V/xxh6V4uIqwu1qLfOfsmScll/ukrk= +github.com/grafana/thema v0.0.0-20220413232647-fc54c169b508 h1:6k0scTj6kRDjn/qLigsLc9OTiMMaWkRUxjKBFowWEWg= +github.com/grafana/thema v0.0.0-20220413232647-fc54c169b508/go.mod h1:KuqTKX9lfM87uu9vt9DS/q+REqSrAm2xYMnBBvlmevA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -1417,8 +1426,9 @@ github.com/hashicorp/consul/api v1.7.0/go.mod h1:1NSuaUUkFaJzMasbfq/11wKYWSR67Xn github.com/hashicorp/consul/api v1.8.1/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= github.com/hashicorp/consul/api v1.9.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/api v1.10.0/go.mod h1:sDjTOq0yUyv5G4h+BqSea7Fn6BU+XbolEz1952UB+mk= -github.com/hashicorp/consul/api v1.10.1 h1:MwZJp86nlnL+6+W1Zly4JUuVn9YHhMggBirMpHGD7kw= github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/api v1.11.0 h1:Hw/G8TtRvOElqxVIhBzXciiSTbapq8hZ2XKZsXk5ZCE= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/consul/sdk v0.4.0/go.mod h1:fY08Y9z5SvJqevyZNy6WWPXiG3KwBPAvlcdx16zZ0fM= @@ -1432,24 +1442,19 @@ github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brv github.com/hashicorp/go-bexpr v0.1.2/go.mod h1:ANbpTX1oAql27TZkKVeW8p1w8NTdnyzPe/0qqPCKohU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-connlimit v0.3.0/go.mod h1:OUj9FGL1tPIhl/2RCfzYHrIiWj+VVPGNyVPnUX8AqS0= github.com/hashicorp/go-discover v0.0.0-20200501174627-ad1e96bde088/go.mod h1:vZu6Opqf49xX5lsFAu7iFNewkcVF1sn/wyapZh5ytlg= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= -github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.12.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= -github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o= github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.2.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-immutable-radix v1.3.0 h1:8exGP7ego3OmkfksihtSouGMZ+hQrhxx+FVELeXpVPE= github.com/hashicorp/go-immutable-radix v1.3.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-kms-wrapping/entropy v0.1.0/go.mod h1:d1g9WGtAunDNpek8jUIEJnBlbgKS1N2Q61QkHiZyR1g= github.com/hashicorp/go-memdb v1.3.1/go.mod h1:Mluclgwib3R93Hk5fxEfiRhB+6Dar64wWh71LpNSe3g= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= @@ -1505,8 +1510,9 @@ github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p github.com/hashicorp/memberlist v0.2.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/memberlist v0.2.3/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= -github.com/hashicorp/memberlist v0.2.4 h1:OOhYzSvFnkFQXm1ysE8RjXTHsqSRDyP4emusC9K7DYg= github.com/hashicorp/memberlist v0.2.4/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj34fMA= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/net-rpc-msgpackrpc v0.0.0-20151116020338-a14192a58a69/go.mod h1:/z+jUGRBlwVpUZfjute9jWaF6/HuhjuFQuL1YXzVD1Q= github.com/hashicorp/raft v1.1.1/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= github.com/hashicorp/raft v1.2.0/go.mod h1:vPAJM8Asw6u8LxC3eJCUZmRP/E4QmUGE1R7g7k8sG/8= @@ -1519,8 +1525,9 @@ github.com/hashicorp/serf v0.8.3/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc github.com/hashicorp/serf v0.8.5/go.mod h1:UpNcs7fFbpKIyZaUuSW6EPiH+eZC7OuyFD+wc1oal+k= github.com/hashicorp/serf v0.9.0/go.mod h1:YL0HO+FifKOW2u1ke99DGVu1zhcpZzNwrLIqBC7vbYU= github.com/hashicorp/serf v0.9.3/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= -github.com/hashicorp/serf v0.9.5 h1:EBWvyu9tcRszt3Bxp3KNssBMP1KuHWyO51lz9+786iM= github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6 h1:uuEX1kLR6aoda1TBttmJQKDLZE1Ob7KN0NPdE7EtCDc= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q= github.com/hashicorp/vault/api v1.0.5-0.20200717191844-f687267c8086/go.mod h1:R3Umvhlxi2TN7Ex2hzOowyeNb+SfbVWI973N+ctaFMk= github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M= @@ -1805,8 +1812,10 @@ github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20191113090002-7c0f6868bffe/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= @@ -1818,8 +1827,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= @@ -1896,8 +1906,9 @@ github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1-0.20210112042008-8ebf2d61a8b4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2 h1:6h7AQ0yhTcIsmFmnAwQls75jp2Gzs4iB8W7pjMO+rqo= github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/pointerstructure v1.0.0/go.mod h1:k4XwG94++jLVsSiTxo7qdIfXA9pj9EAeo0QsNNJOLZ8= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -2075,6 +2086,7 @@ github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUr github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterh/liner v1.0.1-0.20180619022028-8c1271fcf47f/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc= @@ -2277,8 +2289,9 @@ github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5se github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= github.com/ruudk/golang-pdf417 v0.0.0-20201230142125-a7e3863a1245/go.mod h1:pQAZKsJ8yyVxGRWYNEm9oFB8ieLgKFnamEyDmSA0BRk= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= @@ -2286,6 +2299,7 @@ github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFo github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20200218184317-f459e2d13664/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190810000440-0ceca61e4d75/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -2328,7 +2342,6 @@ github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20180825020608-02ddb050ef6b/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= @@ -2374,6 +2387,7 @@ github.com/spf13/afero v1.3.4/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= @@ -2381,6 +2395,7 @@ github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHN github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -2393,6 +2408,7 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= github.com/streadway/amqp v0.0.0-20180528204448-e5adc2ada8b8/go.mod h1:1WNBiOZtZQLpVAyu0iTduoJL9hEsMloAK5XWrtW0xdY= @@ -2594,9 +2610,12 @@ go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3C go.etcd.io/etcd v3.3.25+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI= go.etcd.io/etcd/api/v3 v3.5.0-alpha.0/go.mod h1:mPcW6aZJukV6Aa81LSKpBjQXTWlXB5r74ymPoSWa3Sw= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0-alpha.0/go.mod h1:kdV+xzCJ3luEBSIeQyB/OEKkWKd8Zkux4sbDeANrosU= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v3 v3.5.0-alpha.0/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8= go.etcd.io/etcd/client/v3 v3.5.0-alpha.0.0.20210225194612-fa82d11a958a/go.mod h1:wKt7jgDgf/OfKiYmCq5WFGxOFAkVMLxiiXgLDFhECr8= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= @@ -2739,6 +2758,7 @@ golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= @@ -3086,6 +3106,7 @@ golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -3093,6 +3114,7 @@ golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= @@ -3293,8 +3315,10 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= -google.golang.org/api v0.60.0 h1:eq/zs5WPH4J9undYM9IP1O7dSr7Yh8Y0GtSCpzGzIUk= google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0 h1:PhGymJMXfGBzc4lBRmrx9+1w4w2wEzURHNGF/sD/xGc= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -3396,8 +3420,12 @@ google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211019152133-63b7e35f4404/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 h1:b9mVrqYfq3P4bCdaLg1qtBnPzUYgglsIdjZkL/fQVOE= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -3437,6 +3465,7 @@ google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= @@ -3491,8 +3520,9 @@ gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2 h1:XfR1dOYubytKy4Shzc2LHrrGhU0lDCfDGG1yLPmpgsI= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= diff --git a/pkg/coremodel/dashboard/coremodel.go b/pkg/coremodel/dashboard/coremodel.go new file mode 100644 index 00000000000..ed36c36dc7b --- /dev/null +++ b/pkg/coremodel/dashboard/coremodel.go @@ -0,0 +1,40 @@ +package dashboard + +import ( + "github.com/grafana/thema" +) + +// Coremodel contains the foundational schema declaration for dashboards. +type Coremodel struct { + lin thema.Lineage +} + +// Lineage returns the canonical dashboard Lineage. +func (c *Coremodel) Lineage() thema.Lineage { + return c.lin +} + +func (c *Coremodel) CurrentSchema() thema.Schema { + sch, err := c.lin.Schema(currentVersion) + if err != nil { + // Only reachable if our own schema currentVersion does not exist, which + // can really only happen transitionally during development + panic(err) + } + return sch +} + +func (c *Coremodel) GoType() interface{} { + return &model{} +} + +func ProvideCoremodel(lib thema.Library) (*Coremodel, error) { + lin, err := Lineage(lib) + if err != nil { + return nil, err + } + + return &Coremodel{ + lin: lin, + }, nil +} diff --git a/pkg/coremodel/dashboard/lineage.cue b/pkg/coremodel/dashboard/lineage.cue new file mode 100644 index 00000000000..1910539d5a6 --- /dev/null +++ b/pkg/coremodel/dashboard/lineage.cue @@ -0,0 +1,344 @@ +package dashboard + +import "github.com/grafana/thema" + +thema.#Lineage +name: "dashboard" +seqs: [ + { + schemas: [ + { // 0.0 + // Unique numeric identifier for the dashboard. + // TODO must isolate or remove identifiers local to a Grafana instance...? + id?: int64 + // Unique dashboard identifier that can be generated by anyone. string (8-40) + uid?: string + // Title of dashboard. + title?: string + // Description of dashboard. + description?: string + + gnetId?: string + // Tags associated with dashboard. + tags?: [...string] + // Theme of dashboard. + style: *"light" | "dark" + // Timezone of dashboard, + timezone?: *"browser" | "utc" | "" + // Whether a dashboard is editable or not. + editable: bool | *true + // 0 for no shared crosshair or tooltip (default). + // 1 for shared crosshair. + // 2 for shared crosshair AND shared tooltip. + graphTooltip: uint8 & >=0 & <=2 | *0 + // Time range for dashboard, e.g. last 6 hours, last 7 days, etc + time?: { + from: string | *"now-6h" + to: string | *"now" + } + // Timepicker metadata. + timepicker?: { + // Whether timepicker is collapsed or not. + collapse: bool | *false + // Whether timepicker is enabled or not. + enable: bool | *true + // Whether timepicker is visible or not. + hidden: bool | *false + // Selectable intervals for auto-refresh. + refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + } + // Templating. + templating?: list: [...{...}] + // Annotations. + annotations?: list: [...{ + builtIn: uint8 | *0 + // Datasource to use for annotation. + datasource: string + // Whether annotation is enabled. + enable: bool | *true + // Whether to hide annotation. + hide?: bool | *false + // Annotation icon color. + iconColor?: string + // Name of annotation. + name?: string + type: string | *"dashboard" + // Query for annotation data. + rawQuery?: string + showIn: uint8 | *0 + }] + // Auto-refresh interval. + refresh?: string | false + // Version of the JSON schema, incremented each time a Grafana update brings + // changes to said schema. + // FIXME this is the old schema numbering system, and will be replaced by Thema's themaVersion + schemaVersion: uint16 | *33 + // Version of the dashboard, incremented each time the dashboard is updated. + version?: uint32 + panels?: [...(#Panel | #GraphPanel | #HeatmapPanel | #RowPanel)] + + // TODO docs + #FieldColorModeId: "thresholds" | "palette-classic" | "palette-saturated" | "continuous-GrYlRd" | "fixed" @cuetsy(kind="enum") + + // TODO docs + #FieldColorSeriesByMode: "min" | "max" | "last" @cuetsy(kind="type") + + // TODO docs + #FieldColor: { + // The main color scheme mode + mode: #FieldColorModeId | string + // Stores the fixed color value if mode is fixed + fixedColor?: string + // Some visualizations need to know how to assign a series color from by value color schemes + seriesBy?: #FieldColorSeriesByMode + } @cuetsy(kind="interface") + + // TODO docs + #Threshold: { + // TODO docs + // FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON + value?: number + // TODO docs + color: string + // TODO docs + // TODO are the values here enumerable into a disjunction? + // Some seem to be listed in typescript comment + state?: string + } @cuetsy(kind="interface") + + #ThresholdsMode: "absolute" | "percentage" @cuetsy(kind="enum") + + #ThresholdsConfig: { + mode: #ThresholdsMode + + // Must be sorted by 'value', first value is always -Infinity + steps: [...#Threshold] + } @cuetsy(kind="interface") + + // TODO docs + // FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it + #Transformation: { + id: string + options: {...} + } + + // Schema for panel targets is specified by datasource + // plugins. We use a placeholder definition, which the Go + // schema loader either left open/as-is with the Base + // variant of the Dashboard and Panel families, or filled + // with types derived from plugins in the Instance variant. + // When working directly from CUE, importers can extend this + // type directly to achieve the same effect. + #Target: {...} + + // Dashboard panels. Panels are canonically defined inline + // because they share a version timeline with the dashboard + // schema; they do not evolve independently. + #Panel: { + // The panel plugin type id. + type: !="" + + // TODO docs + id?: uint32 + + // FIXME this almost certainly has to be changed in favor of scuemata versions + pluginVersion?: string + + // TODO docs + tags?: [...string] + + // Internal - the exact major and minor versions of the panel plugin + // schema. Hidden and therefore not a part of the data model, but + // expected to be filled with panel plugin schema versions so that it's + // possible to figure out which schema version matched on a successful + // unification. + // _pv: { maj: int, min: int } + // The major and minor versions of the panel plugin for this schema. + // TODO 2-tuple list instead of struct? + // panelSchema?: { maj: number, min: number } + panelSchema?: [number, number] + + // TODO docs + targets?: [...#Target] + + // Panel title. + title?: string + // Description. + description?: string + // Whether to display the panel without a background. + transparent: bool | *false + // The datasource used in all targets. + datasource?: { + type?: string + uid?: string + } + // Grid position. + gridPos?: { + // Panel + h: uint32 & >0 | *9 + // Panel + w: uint32 & >0 & <=24 | *12 + // Panel x + x: uint32 & >=0 & <24 | *0 + // Panel y + y: uint32 & >=0 | *0 + // true if fixed + static?: bool + } + // Panel links. + // FIXME this is temporarily specified as a closed list so + // that validation will pass when no links are present, but + // to force a failure as soon as it's checked against there + // being anything in the list so it can be fixed in + // accordance with that object + links?: [] + + // Name of template variable to repeat for. + repeat?: string + // Direction to repeat in if 'repeat' is set. + // "h" for horizontal, "v" for vertical. + repeatDirection: *"h" | "v" + + // TODO docs + maxDataPoints?: number + + // TODO docs + thresholds?: [...] + + // TODO docs + timeRegions?: [...] + + transformations: [...#Transformation] + + // TODO docs + // TODO tighter constraint + interval?: string + + // TODO docs + // TODO tighter constraint + timeFrom?: string + + // TODO docs + // TODO tighter constraint + timeShift?: string + + // options is specified by the PanelOptions field in panel + // plugin schemas. + options: {...} + + fieldConfig: { + defaults: { + // The display value for this field. This supports template variables blank is auto + displayName?: string + + // This can be used by data sources that return and explicit naming structure for values and labels + // When this property is configured, this value is used rather than the default naming strategy. + displayNameFromDS?: string + + // Human readable field metadata + description?: string + + // An explict path to the field in the datasource. When the frame meta includes a path, + // This will default to `${frame.meta.path}/${field.name} + // + // When defined, this value can be used as an identifier within the datasource scope, and + // may be used to update the results + path?: string + + // True if data source can write a value to the path. Auth/authz are supported separately + writeable?: bool + + // True if data source field supports ad-hoc filters + filterable?: bool + + // Numeric Options + unit?: string + + // Significant digits (for display) + decimals?: number + + min?: number + max?: number + + // Convert input values into a display string + // + // TODO this one corresponds to a complex type with + // generics on the typescript side. Ouch. Will + // either need special care, or we'll just need to + // accept a very loosely specified schema. It's very + // unlikely we'll be able to translate cue to + // typescript generics in the general case, though + // this particular one *may* be able to work. + mappings?: [...{...}] + + // Map numeric values to states + thresholds?: #ThresholdsConfig + + // // Map values to a display color + color?: #FieldColor + + // // Used when reducing field values + // nullValueMode?: NullValueMode + + // // The behavior when clicking on a result + links?: [...] + + // Alternative to empty string + noValue?: string + + // custom is specified by the PanelFieldConfig field + // in panel plugin schemas. + custom?: {...} + } + overrides: [...{ + matcher: { + id: string | *"" + options?: _ + } + properties: [...{ + id: string | *"" + value?: _ + }] + }] + } + } + + // Row panel + #RowPanel: { + type: "row" + collapsed: bool | *false + title?: string + + // Name of default datasource. + datasource?: string + + gridPos?: { + // Panel + h: uint32 & >0 | *9 + // Panel + w: uint32 & >0 & <=24 | *12 + // Panel x + x: uint32 & >=0 & <24 | *0 + // Panel y + y: uint32 & >=0 | *0 + // true if fixed + static?: bool + } + id: uint32 + panels: [...(#Panel | #GraphPanel | #HeatmapPanel)] + // Name of template variable to repeat for. + repeat?: string + } + // Support for legacy graph and heatmap panels. + #GraphPanel: { + ... + type: "graph" + } + #HeatmapPanel: { + ... + type: "heatmap" + } + } + ] + } +] diff --git a/pkg/coremodel/dashboard/schema.go b/pkg/coremodel/dashboard/schema.go new file mode 100644 index 00000000000..96c235a1712 --- /dev/null +++ b/pkg/coremodel/dashboard/schema.go @@ -0,0 +1,89 @@ +package dashboard + +import ( + "embed" + "path/filepath" + + "github.com/grafana/thema" + + "github.com/grafana/grafana/pkg/cuectx" +) + +var ( + //go:embed lineage.cue + cueFS embed.FS + + // TODO: this should be generated by Thema. + currentVersion = thema.SV(0, 0) +) + +// Lineage returns the Thema lineage representing Grafana dashboards. The +// lineage is the canonical specification of the current datasource schema, all +// prior schema versions, and the mappings that allow migration between schema +// versions. +// +// This is the base variant of the schema, which does not include any composed +// plugin schemas. +func Lineage(lib thema.Library, opts ...thema.BindOption) (thema.Lineage, error) { + return cuectx.LoadGrafanaInstancesWithThema(filepath.Join("pkg", "coremodel", "dashboard"), cueFS, lib, opts...) +} + +// Model is a dummy struct stand-in for dashboards. +// +// It exists solely to trick compgen into accepting the dashboard coremodel as valid. +type Model struct{} + +// model is a hacky Go struct representing a dashboard. +// +// This exists solely because the coremodel framework enforces that there is a Go struct to which +// all valid Thema schema instances can be assigned, per Thema's assignability checker. See +// https://github.com/grafana/thema/blob/main/docs/invariants.md#go-assignability for rules. +// +// DO NOT RELY ON THIS FOR ANYTHING REAL. It is unclear whether we will ever attempt to have a correct, complete +// Go struct representation of dashboards, let alone compress it into a single struct. +type model struct { + Title string `json:"title"` + Description string `json:"description"` + GnetId string `json:"gnetId"` + Tags []string `json:"tags"` + Style string `json:"style"` + Timezone string `json:"timezone"` + Editable bool `json:"editable"` + GraphTooltip uint8 `json:"graphTooltip"` + Time struct { + From string `json:"from"` + To string `json:"to"` + } `json:"time"` + Timepicker struct { + Collapse bool `json:"collapse"` + Enable bool `json:"enable"` + Hidden bool `json:"hidden"` + RefreshIntervals []string `json:"refresh_intervals"` + } `json:"timepicker"` + Templating struct { + List []interface{} `json:"list"` + } `json:"templating"` + Annotations struct { + List []struct { + Name string `json:"name"` + Type string `json:"type"` + BuiltIn uint8 `json:"builtIn"` + Datasource string `json:"datasource"` + Enable bool `json:"enable"` + Hide bool `json:"hide,omitempty"` + IconColor string `json:"iconColor"` + RawQuery string `json:"rawQuery,omitempty"` + ShowIn int `json:"showIn"` + } `json:"list"` + } `json:"annotations"` + Refresh interface{} `json:"refresh"` // (bool|string) + SchemaVersion int `json:"schemaVersion"` + Panels []interface{} `json:"panels"` + + //// + + Uid string `json:"uid"` + // OrgId int64 `json:"orgId"` + Id int64 `json:"id,omitempty"` + Version int `json:"version"` +} diff --git a/pkg/cuectx/ctx.go b/pkg/cuectx/ctx.go new file mode 100644 index 00000000000..bddbe1ad1a0 --- /dev/null +++ b/pkg/cuectx/ctx.go @@ -0,0 +1,116 @@ +// Package cuectx provides a single, central CUE context (runtime) and Thema +// library that can be used uniformly across Grafana, and related helper +// functions for loading Thema lineages. + +package cuectx + +import ( + "io" + "io/fs" + "path/filepath" + "testing/fstest" + + "cuelang.org/go/cue" + "cuelang.org/go/cue/cuecontext" + "github.com/grafana/thema" + "github.com/grafana/thema/kernel" + "github.com/grafana/thema/load" +) + +var ctx *cue.Context = cuecontext.New() +var lib thema.Library = thema.NewLibrary(ctx) + +// ProvideCUEContext is a wire service provider of a central cue.Context. +func ProvideCUEContext() *cue.Context { + return ctx +} + +// ProvideThemaLibrary is a wire service provider of a central thema.Library. +func ProvideThemaLibrary() thema.Library { + return lib +} + +// JSONtoCUE attempts to decode the given []byte into a cue.Value, relying on +// the central Grafana cue.Context provided in this package. +// +// The provided path argument determines the name given to the input bytes if +// later CUE operations (e.g. Thema validation) produce errors related to the +// returned cue.Value. +// +// This is a convenience function for one-off JSON decoding. It's wasteful to +// call it repeatedly. Most use cases use cases should probably prefer making +// their own Thema/CUE decoders. +func JSONtoCUE(path string, b []byte) (cue.Value, error) { + return kernel.NewJSONDecoder(path)(ctx, b) +} + +// LoadGrafanaInstancesWithThema loads CUE files containing a lineage +// representing some Grafana core model schema. It is expected to be used when +// implementing a thema.LineageFactory. +// +// This function primarily juggles paths to make CUE's loader happy. Provide the +// path from the grafana root to the directory containing the lineage.cue. The +// lineage.cue file must be the sole contents of the provided fs.FS. +// +// More details on underlying behavior can be found in the docs for github.com/grafana/thema/load.InstancesWithThema. +func LoadGrafanaInstancesWithThema( + path string, + cueFS fs.FS, + lib thema.Library, + opts ...thema.BindOption, +) (thema.Lineage, error) { + prefix := filepath.FromSlash(path) + fs, err := prefixWithGrafanaCUE(prefix, cueFS) + if err != nil { + return nil, err + } + inst, err := load.InstancesWithThema(fs, prefix) + + // Need to trick loading by creating the embedded file and + // making it look like a module in the root dir. + if err != nil { + return nil, err + } + + val := lib.Context().BuildInstance(inst) + + lin, err := thema.BindLineage(val, lib, opts...) + if err != nil { + return nil, err + } + + return lin, nil +} + +func prefixWithGrafanaCUE(prefix string, inputfs fs.FS) (fs.FS, error) { + m := fstest.MapFS{ + filepath.Join("cue.mod", "module.cue"): &fstest.MapFile{Data: []byte(`module: "github.com/grafana/grafana"`)}, + } + + prefix = filepath.FromSlash(prefix) + err := fs.WalkDir(inputfs, ".", (func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + return nil + } + + f, err := inputfs.Open(path) + if err != nil { + return err + } + defer f.Close() // nolint: errcheck + + b, err := io.ReadAll(f) + if err != nil { + return err + } + + m[filepath.Join(prefix, path)] = &fstest.MapFile{Data: b} + return nil + })) + + return m, err +} diff --git a/pkg/framework/coremodel/helpers.go b/pkg/framework/coremodel/helpers.go new file mode 100644 index 00000000000..d3f88a856d5 --- /dev/null +++ b/pkg/framework/coremodel/helpers.go @@ -0,0 +1 @@ +package coremodel diff --git a/pkg/framework/coremodel/interface.go b/pkg/framework/coremodel/interface.go new file mode 100644 index 00000000000..07957dd3b44 --- /dev/null +++ b/pkg/framework/coremodel/interface.go @@ -0,0 +1,25 @@ +package coremodel + +import ( + "github.com/grafana/thema" +) + +// Interface is the primary coremodel interface that must be implemented by all +// Grafana coremodels. A coremodel is the foundational, canonical schema for +// some known-at-compile-time Grafana object. +// +// Currently, all Coremodels are expressed as Thema lineages. +type Interface interface { + // Lineage should return the canonical Thema lineage for the coremodel. + Lineage() thema.Lineage + + // CurrentSchema should return the schema of the version that the Grafana backend + // is currently written against. (While Grafana can accept data from all + // older versions of the Thema schema, backend Go code is written against a + // single version for simplicity) + CurrentSchema() thema.Schema + + // GoType should return a pointer to the Go struct type that corresponds to + // the Current() schema. + GoType() interface{} +} diff --git a/pkg/framework/coremodel/registry.go b/pkg/framework/coremodel/registry.go new file mode 100644 index 00000000000..0fe44809d2f --- /dev/null +++ b/pkg/framework/coremodel/registry.go @@ -0,0 +1,78 @@ +package coremodel + +import ( + "errors" + "fmt" + "sync" + + "github.com/grafana/thema" +) + +var ( + // ErrModelAlreadyRegistered is returned when trying to register duplicate model to Registry. + ErrModelAlreadyRegistered = errors.New("error registering duplicate model") +) + +// Registry is a registry of coremodel instances. +type Registry struct { + lock sync.RWMutex + models []Interface + modelIdx map[string]Interface +} + +// NewRegistry returns a new Registry with the provided coremodel instances. +func NewRegistry(models ...Interface) (*Registry, error) { + r := &Registry{ + models: make([]Interface, 0, len(models)), + modelIdx: make(map[string]Interface, len(models)), + } + + if err := r.addModels(models); err != nil { + return nil, err + } + + return r, nil +} + +// Register adds coremodels to the Registry. +func (r *Registry) Register(models ...Interface) error { + return r.addModels(models) +} + +// List returns all coremodels registered in this Registry. +func (r *Registry) List() []Interface { + r.lock.RLock() + defer r.lock.RUnlock() + + return r.models +} + +func (r *Registry) addModels(models []Interface) error { + r.lock.Lock() + defer r.lock.Unlock() + + // Update model index and return an error if trying to register a duplicate. + for _, m := range models { + k := m.Lineage().Name() + + // Ensure assignability first. TODO will this blow up for dashboards? + if err := thema.AssignableTo(m.CurrentSchema(), m.GoType()); err != nil { + return fmt.Errorf("%s schema version %v not assignable to provided Go type: %w", k, m.CurrentSchema().Version(), err) + } + + if _, ok := r.modelIdx[k]; ok { + return ErrModelAlreadyRegistered + } + + r.modelIdx[k] = m + } + + // Remake model list. + // TODO: this can be more performant (proper resizing, maybe single loop with index building, etc.). + r.models = r.models[:0] + for _, m := range r.modelIdx { + r.models = append(r.models, m) + } + + return nil +} diff --git a/pkg/framework/coremodel/staticregistry/provide.go b/pkg/framework/coremodel/staticregistry/provide.go new file mode 100644 index 00000000000..09a12bd3c77 --- /dev/null +++ b/pkg/framework/coremodel/staticregistry/provide.go @@ -0,0 +1,17 @@ +package staticregistry + +import ( + "github.com/grafana/grafana/pkg/coremodel/dashboard" + "github.com/grafana/grafana/pkg/framework/coremodel" +) + +// ProvideRegistry provides a simple static Registry for coremodels. +// Coremodels have to be manually added. +// TODO dynamism +func ProvideRegistry( + dashboard *dashboard.Coremodel, +) (*coremodel.Registry, error) { + return coremodel.NewRegistry( + dashboard, + ) +} diff --git a/pkg/server/wire.go b/pkg/server/wire.go index 74418497d83..7c22a7dc356 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -7,11 +7,15 @@ import ( "github.com/google/wire" sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient" + "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/api/avatar" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/coremodel/dashboard" + "github.com/grafana/grafana/pkg/cuectx" "github.com/grafana/grafana/pkg/expr" + cmreg "github.com/grafana/grafana/pkg/framework/coremodel/staticregistry" "github.com/grafana/grafana/pkg/infra/httpclient" "github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider" "github.com/grafana/grafana/pkg/infra/kvstore" @@ -239,6 +243,10 @@ var wireBasicSet = wire.NewSet( avatar.ProvideAvatarCacheServer, authproxy.ProvideAuthProxy, statscollector.ProvideService, + dashboard.ProvideCoremodel, + cmreg.ProvideRegistry, + cuectx.ProvideCUEContext, + cuectx.ProvideThemaLibrary, ) var wireSet = wire.NewSet( From b43e9b50b497f315f31fb35a006d820871d88dda Mon Sep 17 00:00:00 2001 From: Eric Leijonmarck Date: Thu, 14 Apr 2022 23:06:08 +0100 Subject: [PATCH 17/40] Service accounts: RBAC the service account UI (#47788) * WIP * fix: bug for saving name did not remove edit * refactor: better error msg * Display the column Roles even when user can't see the role picker * Remove spaces when building the search query request * Disable Edit button and fix token addition and deletion * Fix the error message text Co-authored-by: Vardan Torosyan --- pkg/services/serviceaccounts/api/api.go | 2 +- .../serviceaccounts/database/errors.go | 8 ++++---- .../serviceaccounts/database/token_store.go | 2 +- .../serviceaccounts/ServiceAccountPage.tsx | 18 ++++++++++++++--- .../serviceaccounts/ServiceAccountProfile.tsx | 20 ++++++++++++++----- .../ServiceAccountTokensTable.tsx | 12 +++++++---- .../ServiceAccountsListItem.tsx | 8 ++++---- 7 files changed, 48 insertions(+), 22 deletions(-) diff --git a/pkg/services/serviceaccounts/api/api.go b/pkg/services/serviceaccounts/api/api.go index d4da2814925..5acc84f090c 100644 --- a/pkg/services/serviceaccounts/api/api.go +++ b/pkg/services/serviceaccounts/api/api.go @@ -93,7 +93,7 @@ func (api *ServiceAccountsAPI) CreateServiceAccount(c *models.ReqContext) respon serviceAccount, err := api.store.CreateServiceAccount(c.Req.Context(), c.OrgId, cmd.Name) switch { case errors.Is(err, &database.ErrSAInvalidName{}): - return response.Error(http.StatusBadRequest, "Invalid service account name", err) + return response.Error(http.StatusBadRequest, "Failed due to %s", err) case err != nil: return response.Error(http.StatusInternalServerError, "Failed to create service account", err) } diff --git a/pkg/services/serviceaccounts/database/errors.go b/pkg/services/serviceaccounts/database/errors.go index 3cd555480a6..8001b2bd889 100644 --- a/pkg/services/serviceaccounts/database/errors.go +++ b/pkg/services/serviceaccounts/database/errors.go @@ -17,14 +17,14 @@ func (e *ErrSAInvalidName) Unwrap() error { return models.ErrUserAlreadyExists } -type ErrMisingSAToken struct { +type ErrMissingSAToken struct { } -func (e *ErrMisingSAToken) Error() string { +func (e *ErrMissingSAToken) Error() string { return "service account token not found" } -func (e *ErrMisingSAToken) Unwrap() error { +func (e *ErrMissingSAToken) Unwrap() error { return models.ErrApiKeyNotFound } @@ -44,7 +44,7 @@ type ErrDuplicateSAToken struct { } func (e *ErrDuplicateSAToken) Error() string { - return fmt.Sprintf("service account token %s already exists", e.name) + return fmt.Sprintf("service account token %s already exists in the organization", e.name) } func (e *ErrDuplicateSAToken) Unwrap() error { diff --git a/pkg/services/serviceaccounts/database/token_store.go b/pkg/services/serviceaccounts/database/token_store.go index 320bc2f1ed5..4a4b04f9585 100644 --- a/pkg/services/serviceaccounts/database/token_store.go +++ b/pkg/services/serviceaccounts/database/token_store.go @@ -57,7 +57,7 @@ func (s *ServiceAccountsStoreImpl) DeleteServiceAccountToken(ctx context.Context if err != nil { return err } else if n == 0 { - return &ErrMisingSAToken{} + return &ErrMissingSAToken{} } return nil }) diff --git a/public/app/features/serviceaccounts/ServiceAccountPage.tsx b/public/app/features/serviceaccounts/ServiceAccountPage.tsx index 66e59e0cc83..061e7a0d309 100644 --- a/public/app/features/serviceaccounts/ServiceAccountPage.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountPage.tsx @@ -3,7 +3,7 @@ import { connect, ConnectedProps } from 'react-redux'; import { getNavModel } from 'app/core/selectors/navModel'; import Page from 'app/core/components/Page/Page'; import { ServiceAccountProfile } from './ServiceAccountProfile'; -import { StoreState, ServiceAccountDTO, ApiKey, Role } from 'app/types'; +import { StoreState, ServiceAccountDTO, ApiKey, Role, AccessControlAction } from 'app/types'; import { GrafanaRouteComponentProps } from 'app/core/navigation/types'; import { deleteServiceAccountToken, @@ -114,12 +114,24 @@ const ServiceAccountPageUnconnected = ({

Tokens

- +
{tokens && ( )} - + {contextSrv.hasPermission(AccessControlAction.ServiceAccountsWrite) && ( + + )} ); diff --git a/public/app/features/serviceaccounts/ServiceAccountProfile.tsx b/public/app/features/serviceaccounts/ServiceAccountProfile.tsx index 408d4346d25..368134f9077 100644 --- a/public/app/features/serviceaccounts/ServiceAccountProfile.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountProfile.tsx @@ -1,9 +1,10 @@ import React, { PureComponent, useRef, useState } from 'react'; -import { Role, ServiceAccountDTO } from 'app/types'; +import { Role, ServiceAccountDTO, AccessControlAction } from 'app/types'; import { css, cx } from '@emotion/css'; import { dateTimeFormat, GrafanaTheme2, OrgRole, TimeZone } from '@grafana/data'; import { Button, ConfirmButton, ConfirmModal, Input, LegacyInputStatus, useStyles2 } from '@grafana/ui'; import { ServiceAccountRoleRow } from './ServiceAccountRoleRow'; +import { contextSrv } from 'app/core/core'; interface Props { serviceAccount: ServiceAccountDTO; @@ -26,6 +27,8 @@ export function ServiceAccountProfile({ const [showDeleteModal, setShowDeleteModal] = useState(false); const [showDisableModal, setShowDisableModal] = useState(false); + const ableToWrite = contextSrv.hasPermission(AccessControlAction.ServiceAccountsWrite); + const deleteServiceAccountRef = useRef(null); const showDeleteServiceAccountModal = (show: boolean) => () => { setShowDeleteModal(show); @@ -83,9 +86,10 @@ export function ServiceAccountProfile({ Delete service account @@ -123,7 +128,7 @@ export function ServiceAccountProfile({ /> {serviceAccount.isDisabled ? ( - ) : ( @@ -133,6 +138,7 @@ export function ServiceAccountProfile({ variant="secondary" onClick={showDisableServiceAccountModal(true)} ref={disableServiceAccountRef} + disabled={!ableToWrite} > Disable service account @@ -168,6 +174,7 @@ interface ServiceAccountProfileRowProps { value?: string; inputType?: string; onChange?: (value: string) => void; + disabled?: boolean; } interface ServiceAccountProfileRowState { @@ -226,6 +233,7 @@ export class ServiceAccountProfileRow extends PureComponent< }; onSave = () => { + this.setState({ editing: false }); if (this.props.onChange) { this.props.onChange(this.state.value); } @@ -248,7 +256,7 @@ export class ServiceAccountProfileRow extends PureComponent< - + {contextSrv.hasPermission(AccessControlAction.ServiceAccountsDelete) && ( + + )} ); })} @@ -82,7 +86,7 @@ const TokenExpiration = ({ timeZone, token }: TokenExpirationProps) => { Expired - + diff --git a/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx b/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx index d0de3b2e9e9..b2d2d3aabb8 100644 --- a/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountsListItem.tsx @@ -65,8 +65,8 @@ const ServiceAccountListItem = memo( {contextSrv.licensedAccessControlEnabled() ? ( - displayRolePicker && ( - - ) + )} + ) : (
- {this.state.editing ? ( + {!this.props.disabled && this.state.editing ? ( {this.props.onChange && ( Edit diff --git a/public/app/features/serviceaccounts/ServiceAccountTokensTable.tsx b/public/app/features/serviceaccounts/ServiceAccountTokensTable.tsx index be500c1f26e..e3c517bdef3 100644 --- a/public/app/features/serviceaccounts/ServiceAccountTokensTable.tsx +++ b/public/app/features/serviceaccounts/ServiceAccountTokensTable.tsx @@ -2,8 +2,10 @@ import React, { FC } from 'react'; import { DeleteButton, Icon, Tooltip, useStyles2, useTheme2 } from '@grafana/ui'; import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data'; +import { AccessControlAction } from 'app/types'; import { ApiKey } from '../../types'; import { css } from '@emotion/css'; +import { contextSrv } from 'app/core/core'; interface Props { tokens: ApiKey[]; @@ -35,9 +37,11 @@ export const ServiceAccountTokensTable: FC = ({ tokens, timeZone, onDelet {formatDate(timeZone, key.created)} - onDelete(key)} /> - + onDelete(key)} /> +
+ + {displayRolePicker && ( - Date: Fri, 15 Apr 2022 09:31:54 +0200 Subject: [PATCH 18/40] Swagger: update swagger for datasource proxy api (#47690) * update the swagger for datasource proxy api * add enterprise spec changes --- pkg/api/docs/definitions/datasources.go | 48 ++- public/api-merged.json | 518 ++++++++++++++++++++---- public/api-spec.json | 329 +++++++++++---- 3 files changed, 740 insertions(+), 155 deletions(-) diff --git a/pkg/api/docs/definitions/datasources.go b/pkg/api/docs/definitions/datasources.go index c5eb1d54752..e911644d0bb 100644 --- a/pkg/api/docs/definitions/datasources.go +++ b/pkg/api/docs/definitions/datasources.go @@ -167,6 +167,20 @@ import ( // 404: notFoundError // 500: internalServerError +// swagger:route GET /datasources/proxy/uid/{datasource_uid}/{datasource_proxy_route} datasources datasourceProxyGETByUIDcalls +// +// Data source proxy GET calls. +// +// Proxies all calls to the actual data source. +// +// Responses: +// 200: +// 400: badRequestError +// 401: unauthorisedError +// 403: forbiddenError +// 404: notFoundError +// 500: internalServerError + // swagger:route POST /datasources/proxy/{datasource_id}/{datasource_proxy_route} datasources datasourceProxyPOSTcalls // // Data source proxy POST calls. @@ -182,6 +196,21 @@ import ( // 404: notFoundError // 500: internalServerError +// swagger:route POST /datasources/proxy/uid/{datasource_uid}/{datasource_proxy_route} datasources datasourceProxyPOSTByUIDcalls +// +// Data source proxy POST calls. +// +// Proxies all calls to the actual data source. The data source should support POST methods for the specific path and role as defined +// +// Responses: +// 201: +// 202: +// 400: badRequestError +// 401: unauthorisedError +// 403: forbiddenError +// 404: notFoundError +// 500: internalServerError + // swagger:route DELETE /datasources/proxy/{datasource_id}/{datasource_proxy_route} datasources datasourceProxyDELETEcalls // // Data source proxy DELETE calls. @@ -196,6 +225,20 @@ import ( // 404: notFoundError // 500: internalServerError +// swagger:route DELETE /datasources/proxy/uid/{datasource_uid}/{datasource_proxy_route} datasources datasourceProxyDELETEByUIDcalls +// +// Data source proxy DELETE calls. +// +// Proxies all calls to the actual data source. +// +// Responses: +// 202: +// 400: badRequestError +// 401: unauthorisedError +// 403: forbiddenError +// 404: notFoundError +// 500: internalServerError + // swagger:route POST /tsdb/query datasources queryDatasource // // Query metrics. @@ -227,7 +270,7 @@ type DatasourceID struct { DatasourceID string `json:"datasource_id"` } -// swagger:parameters deleteDatasourceByUID getDatasourceByUID +// swagger:parameters deleteDatasourceByUID getDatasourceByUID datasourceProxyGETByUIDcalls datasourceProxyPOSTByUIDcalls datasourceProxyDELETEByUIDcalls type DatasourceUID struct { // in:path // required:true @@ -241,7 +284,8 @@ type DatasourceName struct { DatasourceName string `json:"datasource_name"` } -// swagger:parameters datasourceProxyGETcalls datasourceProxyPOSTcalls datasourceProxyDELETEcalls +// swagger:parameters datasourceProxyGETcalls datasourceProxyPOSTcalls datasourceProxyDELETEcalls datasourceProxyGETByUIDcalls +// swagger:parameters datasourceProxyPOSTByUIDcalls datasourceProxyDELETEByUIDcalls type DatasourceProxyRouteParam struct { // in:path // required:true diff --git a/public/api-merged.json b/public/api-merged.json index ff5e20db5e9..2a329b78226 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -506,6 +506,14 @@ "summary": "Add a user role assignment.", "operationId": "addUserRole", "parameters": [ + { + "type": "integer", + "format": "int64", + "x-go-name": "UserID", + "name": "user_id", + "in": "path", + "required": true + }, { "x-go-name": "Body", "name": "body", @@ -514,14 +522,6 @@ "schema": { "$ref": "#/definitions/AddUserRoleCommand" } - }, - { - "type": "integer", - "format": "int64", - "x-go-name": "UserID", - "name": "user_id", - "in": "path", - "required": true } ], "responses": { @@ -547,6 +547,14 @@ "summary": "Remove a user role assignment.", "operationId": "removeUserRole", "parameters": [ + { + "type": "integer", + "format": "int64", + "x-go-name": "UserID", + "name": "user_id", + "in": "path", + "required": true + }, { "type": "string", "x-go-name": "RoleUID", @@ -560,14 +568,6 @@ "description": "A flag indicating if the assignment is global or not. If set to false, the default org ID of the authenticated user will be used from the request to remove assignment.", "name": "global", "in": "query" - }, - { - "type": "integer", - "format": "int64", - "x-go-name": "UserID", - "name": "user_id", - "in": "path", - "required": true } ], "responses": { @@ -1025,7 +1025,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:delete` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:delete` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Delete global User.", "operationId": "deleteUser", @@ -1065,7 +1065,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:list` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:list` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Return a list of all auth tokens (devices) that the user currently have logged in from.", "operationId": "getAuthTokens", @@ -1102,7 +1102,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:disable` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:disable` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Disable user.", "operationId": "disableUser", @@ -1142,7 +1142,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:enable` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:enable` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Enable user.", "operationId": "enableUser", @@ -1182,7 +1182,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.logout` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.logout` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Logout user revokes all auth tokens (devices) for the user. User of issued auth tokens (devices) will no longer be logged in and will be required to authenticate again upon next activity.", "operationId": "logoutUser", @@ -1225,7 +1225,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.password:update` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.password:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Set password for user.", "operationId": "setPassword", @@ -1269,7 +1269,7 @@ }, "/admin/users/{user_id}/permissions": { "put": { - "description": "Only works with Basic Authentication (username and password). See introduction for an explanation.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.permissions:update` and scope `global:users:*`.", + "description": "Only works with Basic Authentication (username and password). See introduction for an explanation.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.permissions:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Set permissions for user.", "operationId": "setPermissions", @@ -1318,7 +1318,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:list` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:list` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Fetch user quota.", "operationId": "getUserQuota", @@ -1358,7 +1358,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:update` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:update` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Update user quota.", "operationId": "updateUserQuota", @@ -1414,7 +1414,7 @@ "basic": [] } ], - "description": "Revokes the given auth token (device) for the user. User of issued auth token (device) will no longer be logged in and will be required to authenticate again upon next activity.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:update` and scope `global:users:*`.", + "description": "Revokes the given auth token (device) for the user. User of issued auth token (device) will no longer be logged in and will be required to authenticate again upon next activity.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Revoke auth token for user.", "operationId": "revokeAuthToken", @@ -3150,7 +3150,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/DeleteAnnotationsCmd" + "$ref": "#/definitions/MassDeleteAnnotationsCmd" } } ], @@ -4063,6 +4063,137 @@ } } }, + "/datasources/proxy/uid/{datasource_uid}/{datasource_proxy_route}": { + "get": { + "description": "Proxies all calls to the actual data source.", + "tags": ["datasources"], + "summary": "Data source proxy GET calls.", + "operationId": "datasourceProxyGETByUIDcalls", + "parameters": [ + { + "type": "string", + "x-go-name": "DatasourceUID", + "name": "datasource_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "x-go-name": "DatasourceProxyRoute", + "name": "datasource_proxy_route", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "post": { + "description": "Proxies all calls to the actual data source. The data source should support POST methods for the specific path and role as defined", + "tags": ["datasources"], + "summary": "Data source proxy POST calls.", + "operationId": "datasourceProxyPOSTByUIDcalls", + "parameters": [ + { + "type": "string", + "x-go-name": "DatasourceUID", + "name": "datasource_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "x-go-name": "DatasourceProxyRoute", + "name": "datasource_proxy_route", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "" + }, + "202": { + "description": "" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "delete": { + "description": "Proxies all calls to the actual data source.", + "tags": ["datasources"], + "summary": "Data source proxy DELETE calls.", + "operationId": "datasourceProxyDELETEByUIDcalls", + "parameters": [ + { + "type": "string", + "x-go-name": "DatasourceUID", + "name": "datasource_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "x-go-name": "DatasourceProxyRoute", + "name": "datasource_proxy_route", + "in": "path", + "required": true + } + ], + "responses": { + "202": { + "description": "" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/datasources/proxy/{datasource_id}/{datasource_proxy_route}": { "get": { "description": "Proxies all calls to the actual data source.", @@ -4273,7 +4404,7 @@ }, "/datasources/{datasource_id}": { "get": { - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Get a single data source by Id.", "operationId": "getDatasourceByID", @@ -4308,7 +4439,7 @@ } }, "put": { - "description": "Similar to creating a data source, `password` and `basicAuthPassword` should be defined under\nsecureJsonData in order to be stored securely as an encrypted blob in the database. Then, the\nencrypted fields are listed under secureJsonFields section in the response.\n\nIf you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "Similar to creating a data source, `password` and `basicAuthPassword` should be defined under\nsecureJsonData in order to be stored securely as an encrypted blob in the database. Then, the\nencrypted fields are listed under secureJsonFields section in the response.\n\nIf you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Update an existing data source.", "operationId": "updateDatasource", @@ -4345,7 +4476,7 @@ } }, "delete": { - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Delete an existing data source by id.", "operationId": "deleteDatasourceByID", @@ -4494,15 +4625,15 @@ "parameters": [ { "type": "string", - "x-go-name": "PermissionID", - "name": "permissionId", + "x-go-name": "DatasourceID", + "name": "datasource_id", "in": "path", "required": true }, { "type": "string", - "x-go-name": "DatasourceID", - "name": "datasource_id", + "x-go-name": "PermissionID", + "name": "permissionId", "in": "path", "required": true } @@ -6469,6 +6600,145 @@ } } }, + "/provisioning/contact-points": { + "get": { + "tags": ["provisioning"], + "summary": "Get all the contact points.", + "operationId": "RouteGetContactpoints", + "responses": { + "200": { + "description": "Route", + "schema": { + "$ref": "#/definitions/Route" + } + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + }, + "put": { + "consumes": ["application/json"], + "tags": ["provisioning"], + "summary": "Update an existing contact point.", + "operationId": "RoutePutContactpoints", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/EmbeddedContactPoint" + } + } + ], + "responses": { + "202": { + "$ref": "#/responses/Accepted" + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + }, + "post": { + "consumes": ["application/json"], + "tags": ["provisioning"], + "summary": "Create a contact point.", + "operationId": "RoutePostContactpoints", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/EmbeddedContactPoint" + } + } + ], + "responses": { + "202": { + "$ref": "#/responses/Accepted" + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + } + }, + "/provisioning/contact-points/{ID}": { + "delete": { + "consumes": ["application/json"], + "tags": ["provisioning"], + "summary": "Delete a contact point.", + "operationId": "RouteDeleteContactpoints", + "responses": { + "202": { + "$ref": "#/responses/Accepted" + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + } + }, + "/provisioning/policies": { + "get": { + "tags": ["provisioning"], + "summary": "Get the notification policy tree.", + "operationId": "RouteGetPolicyTree", + "responses": { + "200": { + "description": "Route", + "schema": { + "$ref": "#/definitions/Route" + } + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + }, + "post": { + "consumes": ["application/json"], + "tags": ["provisioning"], + "summary": "Sets the notification policy tree.", + "operationId": "RoutePostPolicyTree", + "parameters": [ + { + "name": "Body", + "in": "body", + "schema": { + "$ref": "#/definitions/Route" + } + } + ], + "responses": { + "202": { + "$ref": "#/responses/Accepted" + }, + "400": { + "description": "ValidationError", + "schema": { + "$ref": "#/definitions/ValidationError" + } + } + } + } + }, "/recording-rules": { "get": { "tags": ["recording_rules", "enterprise"], @@ -10122,6 +10392,36 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" }, + "AnnotationActions": { + "type": "object", + "properties": { + "canAdd": { + "type": "boolean", + "x-go-name": "CanAdd" + }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, + "canEdit": { + "type": "boolean", + "x-go-name": "CanEdit" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, + "AnnotationPermission": { + "type": "object", + "properties": { + "dashboard": { + "$ref": "#/definitions/AnnotationActions" + }, + "organization": { + "$ref": "#/definitions/AnnotationActions" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "ApiKeyDTO": { "type": "object", "properties": { @@ -10240,7 +10540,7 @@ "x-go-name": "ReportLogo" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CalculateDiffTarget": { "type": "object", @@ -10390,7 +10690,7 @@ "x-go-name": "UserID" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CreateAlertNotificationCommand": { "type": "object", @@ -10576,7 +10876,7 @@ "x-go-name": "TemplateVars" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CreateOrgCommand": { "type": "object", @@ -10836,6 +11136,9 @@ "DashboardMeta": { "type": "object", "properties": { + "annotationsPermissions": { + "$ref": "#/definitions/AnnotationPermission" + }, "canAdmin": { "type": "boolean", "x-go-name": "CanAdmin" @@ -11499,32 +11802,6 @@ }, "x-go-package": "github.com/prometheus/alertmanager/timeinterval" }, - "DeleteAnnotationsCmd": { - "type": "object", - "properties": { - "alertId": { - "type": "integer", - "format": "int64", - "x-go-name": "AlertId" - }, - "annotationId": { - "type": "integer", - "format": "int64", - "x-go-name": "AnnotationId" - }, - "dashboardId": { - "type": "integer", - "format": "int64", - "x-go-name": "DashboardId" - }, - "panelId": { - "type": "integer", - "format": "int64", - "x-go-name": "PanelId" - } - }, - "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" - }, "DeleteTokenCommand": { "type": "object", "properties": { @@ -11631,6 +11908,36 @@ }, "x-go-package": "github.com/prometheus/alertmanager/config" }, + "EmbeddedContactPoint": { + "description": "EmbeddedContactPoint is the contact point type that is used\nby grafanas embedded alertmanager implementation.", + "type": "object", + "properties": { + "disableResolveMessage": { + "type": "boolean", + "x-go-name": "DisableResolveMessage" + }, + "name": { + "type": "string", + "x-go-name": "Name" + }, + "provanance": { + "type": "string", + "x-go-name": "Provenance" + }, + "settings": { + "$ref": "#/definitions/Json" + }, + "type": { + "type": "string", + "x-go-name": "Type" + }, + "uid": { + "type": "string", + "x-go-name": "UID" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + }, "ErrorResponseBody": { "type": "object", "required": ["message"], @@ -12479,18 +12786,18 @@ "x-go-name": "URL" } }, - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HitList": { "type": "array", "items": { "$ref": "#/definitions/Hit" }, - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HitType": { "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HostPort": { "type": "object", @@ -13033,6 +13340,27 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/libraryelements" }, + "MassDeleteAnnotationsCmd": { + "type": "object", + "properties": { + "annotationId": { + "type": "integer", + "format": "int64", + "x-go-name": "AnnotationId" + }, + "dashboardId": { + "type": "integer", + "format": "int64", + "x-go-name": "DashboardId" + }, + "panelId": { + "type": "integer", + "format": "int64", + "x-go-name": "PanelId" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "MatchRegexps": { "type": "object", "title": "MatchRegexps represents a map of Regexp.", @@ -13691,6 +14019,9 @@ "navbar": { "$ref": "#/definitions/NavbarPreference" }, + "queryHistory": { + "$ref": "#/definitions/QueryHistoryPreference" + }, "theme": { "type": "string", "enum": ["light", "dark"], @@ -14153,6 +14484,9 @@ "navbar": { "$ref": "#/definitions/NavbarPreference" }, + "queryHistory": { + "$ref": "#/definitions/QueryHistoryPreference" + }, "theme": { "type": "string", "x-go-name": "Theme" @@ -14250,6 +14584,16 @@ }, "x-go-package": "github.com/grafana/grafana-plugin-sdk-go/backend" }, + "QueryHistoryPreference": { + "type": "object", + "properties": { + "homeTab": { + "type": "string", + "x-go-name": "HomeTab" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/models" + }, "Receiver": { "type": "object", "title": "Receiver configuration provides configuration on how to contact a receiver.", @@ -14427,7 +14771,7 @@ "x-go-name": "UseEmailsFromReport" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "ReportOptionsDTO": { "type": "object", @@ -14444,7 +14788,7 @@ "$ref": "#/definitions/TimeRangeDTO" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "ResponseDetails": { "type": "object", @@ -14900,7 +15244,7 @@ "x-go-name": "WorkdaysOnly" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "SearchTeamQueryResult": { "type": "object", @@ -15020,7 +15364,7 @@ "x-go-name": "UserID" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "SigV4Config": { "description": "SigV4Config is the configuration for signing remote write requests with\nAWS's SigV4 verification process. Empty values will be retrieved using the\nAWS default credentials chain.", @@ -15713,7 +16057,7 @@ "x-go-name": "To" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "Token": { "type": "object", @@ -15814,6 +16158,15 @@ "format": "int64", "x-go-name": "TokenExpiresWarnDays" }, + "trial": { + "type": "boolean", + "x-go-name": "Trial" + }, + "trial_exp": { + "type": "integer", + "format": "int64", + "x-go-name": "TrialExpires" + }, "update_days": { "type": "integer", "format": "int64", @@ -15856,9 +16209,8 @@ "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" }, "URL": { - "description": "The general form represented is:\n\n[scheme:][//[userinfo@]host][/]path[?query][#fragment]\n\nURLs that do not start with a slash after the scheme are interpreted as:\n\nscheme:opaque[?query][#fragment]\n\nNote that the Path field is stored in decoded form: /%47%6f%2f becomes /Go/.\nA consequence is that it is impossible to tell which slashes in the Path were\nslashes in the raw URL and which were %2f. This distinction is rarely important,\nbut when it is, the code should use RawPath, an optional field which only gets\nset if the default encoding is different from Path.\n\nURL's String method uses the EscapedPath method to obtain the path. See the\nEscapedPath method for more details.", "type": "object", - "title": "A URL represents a parsed URL (technically, a URI reference).", + "title": "URL is a custom URL type that allows validation at configuration load time.", "properties": { "ForceQuery": { "type": "boolean" @@ -15891,7 +16243,7 @@ "$ref": "#/definitions/Userinfo" } }, - "x-go-package": "net/url" + "x-go-package": "github.com/prometheus/common/config" }, "UpdateAlertNotificationCommand": { "type": "object", @@ -16204,6 +16556,9 @@ "navbar": { "$ref": "#/definitions/NavbarPreference" }, + "queryHistory": { + "$ref": "#/definitions/QueryHistoryPreference" + }, "theme": { "type": "string", "enum": ["light", "dark"], @@ -16792,12 +17147,11 @@ } }, "alertGroups": { + "description": "AlertGroups alert groups", "type": "array", "items": { "$ref": "#/definitions/alertGroup" - }, - "x-go-name": "AlertGroups", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + } }, "alertStatus": { "description": "AlertStatus alert status", @@ -16900,7 +17254,6 @@ "$ref": "#/definitions/Duration" }, "gettableAlert": { - "description": "GettableAlert gettable alert", "type": "object", "required": ["labels", "annotations", "endsAt", "fingerprint", "receivers", "startsAt", "status", "updatedAt"], "properties": { @@ -16950,7 +17303,9 @@ "format": "date-time", "x-go-name": "UpdatedAt" } - } + }, + "x-go-name": "GettableAlert", + "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" }, "gettableAlerts": { "description": "GettableAlerts gettable alerts", @@ -17006,12 +17361,11 @@ } }, "gettableSilences": { + "description": "GettableSilences gettable silences", "type": "array", "items": { "$ref": "#/definitions/gettableSilence" - }, - "x-go-name": "GettableSilences", - "x-go-package": "github.com/prometheus/alertmanager/api/v2/models" + } }, "labelSet": { "description": "LabelSet label set", diff --git a/public/api-spec.json b/public/api-spec.json index 2d5f6cbbf2b..117b03382c3 100644 --- a/public/api-spec.json +++ b/public/api-spec.json @@ -506,6 +506,14 @@ "summary": "Add a user role assignment.", "operationId": "addUserRole", "parameters": [ + { + "type": "integer", + "format": "int64", + "x-go-name": "UserID", + "name": "user_id", + "in": "path", + "required": true + }, { "x-go-name": "Body", "name": "body", @@ -514,14 +522,6 @@ "schema": { "$ref": "#/definitions/AddUserRoleCommand" } - }, - { - "type": "integer", - "format": "int64", - "x-go-name": "UserID", - "name": "user_id", - "in": "path", - "required": true } ], "responses": { @@ -547,6 +547,14 @@ "summary": "Remove a user role assignment.", "operationId": "removeUserRole", "parameters": [ + { + "type": "integer", + "format": "int64", + "x-go-name": "UserID", + "name": "user_id", + "in": "path", + "required": true + }, { "type": "string", "x-go-name": "RoleUID", @@ -560,14 +568,6 @@ "description": "A flag indicating if the assignment is global or not. If set to false, the default org ID of the authenticated user will be used from the request to remove assignment.", "name": "global", "in": "query" - }, - { - "type": "integer", - "format": "int64", - "x-go-name": "UserID", - "name": "user_id", - "in": "path", - "required": true } ], "responses": { @@ -1025,7 +1025,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:delete` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:delete` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Delete global User.", "operationId": "deleteUser", @@ -1065,7 +1065,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:list` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:list` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Return a list of all auth tokens (devices) that the user currently have logged in from.", "operationId": "getAuthTokens", @@ -1102,7 +1102,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:disable` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:disable` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Disable user.", "operationId": "disableUser", @@ -1142,7 +1142,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:enable` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users:enable` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Enable user.", "operationId": "enableUser", @@ -1182,7 +1182,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.logout` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.logout` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Logout user revokes all auth tokens (devices) for the user. User of issued auth tokens (devices) will no longer be logged in and will be required to authenticate again upon next activity.", "operationId": "logoutUser", @@ -1225,7 +1225,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.password:update` and scope `global:users:*`.", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.password:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Set password for user.", "operationId": "setPassword", @@ -1269,7 +1269,7 @@ }, "/admin/users/{user_id}/permissions": { "put": { - "description": "Only works with Basic Authentication (username and password). See introduction for an explanation.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.permissions:update` and scope `global:users:*`.", + "description": "Only works with Basic Authentication (username and password). See introduction for an explanation.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.permissions:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Set permissions for user.", "operationId": "setPermissions", @@ -1318,7 +1318,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:list` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:list` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Fetch user quota.", "operationId": "getUserQuota", @@ -1358,7 +1358,7 @@ "basic": [] } ], - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:update` and scope `global:users:1` (userIDScope).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.quotas:update` and scope `global.users:1` (userIDScope).", "tags": ["admin_users"], "summary": "Update user quota.", "operationId": "updateUserQuota", @@ -1414,7 +1414,7 @@ "basic": [] } ], - "description": "Revokes the given auth token (device) for the user. User of issued auth token (device) will no longer be logged in and will be required to authenticate again upon next activity.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:update` and scope `global:users:*`.", + "description": "Revokes the given auth token (device) for the user. User of issued auth token (device) will no longer be logged in and will be required to authenticate again upon next activity.\nIf you are running Grafana Enterprise and have Fine-grained access control enabled, you need to have a permission with action `users.authtoken:update` and scope `global.users:*`.", "tags": ["admin_users"], "summary": "Revoke auth token for user.", "operationId": "revokeAuthToken", @@ -2200,7 +2200,7 @@ "in": "body", "required": true, "schema": { - "$ref": "#/definitions/DeleteAnnotationsCmd" + "$ref": "#/definitions/MassDeleteAnnotationsCmd" } } ], @@ -3113,6 +3113,137 @@ } } }, + "/datasources/proxy/uid/{datasource_uid}/{datasource_proxy_route}": { + "get": { + "description": "Proxies all calls to the actual data source.", + "tags": ["datasources"], + "summary": "Data source proxy GET calls.", + "operationId": "datasourceProxyGETByUIDcalls", + "parameters": [ + { + "type": "string", + "x-go-name": "DatasourceUID", + "name": "datasource_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "x-go-name": "DatasourceProxyRoute", + "name": "datasource_proxy_route", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "post": { + "description": "Proxies all calls to the actual data source. The data source should support POST methods for the specific path and role as defined", + "tags": ["datasources"], + "summary": "Data source proxy POST calls.", + "operationId": "datasourceProxyPOSTByUIDcalls", + "parameters": [ + { + "type": "string", + "x-go-name": "DatasourceUID", + "name": "datasource_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "x-go-name": "DatasourceProxyRoute", + "name": "datasource_proxy_route", + "in": "path", + "required": true + } + ], + "responses": { + "201": { + "description": "" + }, + "202": { + "description": "" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + }, + "delete": { + "description": "Proxies all calls to the actual data source.", + "tags": ["datasources"], + "summary": "Data source proxy DELETE calls.", + "operationId": "datasourceProxyDELETEByUIDcalls", + "parameters": [ + { + "type": "string", + "x-go-name": "DatasourceUID", + "name": "datasource_uid", + "in": "path", + "required": true + }, + { + "type": "string", + "x-go-name": "DatasourceProxyRoute", + "name": "datasource_proxy_route", + "in": "path", + "required": true + } + ], + "responses": { + "202": { + "description": "" + }, + "400": { + "$ref": "#/responses/badRequestError" + }, + "401": { + "$ref": "#/responses/unauthorisedError" + }, + "403": { + "$ref": "#/responses/forbiddenError" + }, + "404": { + "$ref": "#/responses/notFoundError" + }, + "500": { + "$ref": "#/responses/internalServerError" + } + } + } + }, "/datasources/proxy/{datasource_id}/{datasource_proxy_route}": { "get": { "description": "Proxies all calls to the actual data source.", @@ -3323,7 +3454,7 @@ }, "/datasources/{datasource_id}": { "get": { - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:read` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Get a single data source by Id.", "operationId": "getDatasourceByID", @@ -3358,7 +3489,7 @@ } }, "put": { - "description": "Similar to creating a data source, `password` and `basicAuthPassword` should be defined under\nsecureJsonData in order to be stored securely as an encrypted blob in the database. Then, the\nencrypted fields are listed under secureJsonFields section in the response.\n\nIf you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "Similar to creating a data source, `password` and `basicAuthPassword` should be defined under\nsecureJsonData in order to be stored securely as an encrypted blob in the database. Then, the\nencrypted fields are listed under secureJsonFields section in the response.\n\nIf you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:write` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Update an existing data source.", "operationId": "updateDatasource", @@ -3395,7 +3526,7 @@ } }, "delete": { - "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:id:*` and `datasources:id:1` (single data source).", + "description": "If you are running Grafana Enterprise and have Fine-grained access control enabled\nyou need to have a permission with action: `datasources:delete` and scopes: `datasources:*`, `datasources:uid:*` and `datasources:uid:1` (single data source).", "tags": ["datasources"], "summary": "Delete an existing data source by id.", "operationId": "deleteDatasourceByID", @@ -3544,15 +3675,15 @@ "parameters": [ { "type": "string", - "x-go-name": "PermissionID", - "name": "permissionId", + "x-go-name": "DatasourceID", + "name": "datasource_id", "in": "path", "required": true }, { "type": "string", - "x-go-name": "DatasourceID", - "name": "datasource_id", + "x-go-name": "PermissionID", + "name": "permissionId", "in": "path", "required": true } @@ -8374,6 +8505,36 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" }, + "AnnotationActions": { + "type": "object", + "properties": { + "canAdd": { + "type": "boolean", + "x-go-name": "CanAdd" + }, + "canDelete": { + "type": "boolean", + "x-go-name": "CanDelete" + }, + "canEdit": { + "type": "boolean", + "x-go-name": "CanEdit" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, + "AnnotationPermission": { + "type": "object", + "properties": { + "dashboard": { + "$ref": "#/definitions/AnnotationActions" + }, + "organization": { + "$ref": "#/definitions/AnnotationActions" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "ApiKeyDTO": { "type": "object", "properties": { @@ -8421,7 +8582,7 @@ "x-go-name": "ReportLogo" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CalculateDiffTarget": { "type": "object", @@ -8537,7 +8698,7 @@ "x-go-name": "UserID" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CreateAlertNotificationCommand": { "type": "object", @@ -8723,7 +8884,7 @@ "x-go-name": "TemplateVars" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "CreateOrgCommand": { "type": "object", @@ -8983,6 +9144,9 @@ "DashboardMeta": { "type": "object", "properties": { + "annotationsPermissions": { + "$ref": "#/definitions/AnnotationPermission" + }, "canAdmin": { "type": "boolean", "x-go-name": "CanAdmin" @@ -9625,32 +9789,6 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/tsdb/legacydata" }, - "DeleteAnnotationsCmd": { - "type": "object", - "properties": { - "alertId": { - "type": "integer", - "format": "int64", - "x-go-name": "AlertId" - }, - "annotationId": { - "type": "integer", - "format": "int64", - "x-go-name": "AnnotationId" - }, - "dashboardId": { - "type": "integer", - "format": "int64", - "x-go-name": "DashboardId" - }, - "panelId": { - "type": "integer", - "format": "int64", - "x-go-name": "PanelId" - } - }, - "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" - }, "DeleteTokenCommand": { "type": "object", "properties": { @@ -9952,18 +10090,18 @@ "x-go-name": "URL" } }, - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HitList": { "type": "array", "items": { "$ref": "#/definitions/Hit" }, - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "HitType": { "type": "string", - "x-go-package": "github.com/grafana/grafana/pkg/services/search" + "x-go-package": "github.com/grafana/grafana/pkg/models" }, "ImportDashboardInput": { "type": "object", @@ -10395,6 +10533,27 @@ }, "x-go-package": "github.com/grafana/grafana/pkg/services/libraryelements" }, + "MassDeleteAnnotationsCmd": { + "type": "object", + "properties": { + "annotationId": { + "type": "integer", + "format": "int64", + "x-go-name": "AnnotationId" + }, + "dashboardId": { + "type": "integer", + "format": "int64", + "x-go-name": "DashboardId" + }, + "panelId": { + "type": "integer", + "format": "int64", + "x-go-name": "PanelId" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/api/dtos" + }, "Metadata": { "description": "Metadata contains user accesses for a given resource\nEx: map[string]bool{\"create\":true, \"delete\": true}", "type": "object", @@ -10711,6 +10870,9 @@ "navbar": { "$ref": "#/definitions/NavbarPreference" }, + "queryHistory": { + "$ref": "#/definitions/QueryHistoryPreference" + }, "theme": { "type": "string", "enum": ["light", "dark"], @@ -10857,6 +11019,9 @@ "navbar": { "$ref": "#/definitions/NavbarPreference" }, + "queryHistory": { + "$ref": "#/definitions/QueryHistoryPreference" + }, "theme": { "type": "string", "x-go-name": "Theme" @@ -10901,6 +11066,16 @@ }, "x-go-package": "github.com/grafana/grafana-plugin-sdk-go/backend" }, + "QueryHistoryPreference": { + "type": "object", + "properties": { + "homeTab": { + "type": "string", + "x-go-name": "HomeTab" + } + }, + "x-go-package": "github.com/grafana/grafana/pkg/models" + }, "RecordingRuleJSON": { "description": "RecordingRuleJSON is the external representation of a recording rule", "type": "object", @@ -10984,7 +11159,7 @@ "x-go-name": "UseEmailsFromReport" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "ReportOptionsDTO": { "type": "object", @@ -11001,7 +11176,7 @@ "$ref": "#/definitions/TimeRangeDTO" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "Responses": { "description": "The QueryData method the QueryDataHandler method will set the RefId\nproperty on the DataRespones' frames based on these RefIDs.", @@ -11175,7 +11350,7 @@ "x-go-name": "WorkdaysOnly" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "SearchTeamQueryResult": { "type": "object", @@ -11286,7 +11461,7 @@ "x-go-name": "UserID" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "Status": { "type": "object", @@ -11562,7 +11737,7 @@ "x-go-name": "To" } }, - "x-go-package": "github.com/grafana/grafana/pkg/extensions/report" + "x-go-package": "github.com/grafana/grafana/pkg/extensions/report/api" }, "Token": { "type": "object", @@ -11663,6 +11838,15 @@ "format": "int64", "x-go-name": "TokenExpiresWarnDays" }, + "trial": { + "type": "boolean", + "x-go-name": "Trial" + }, + "trial_exp": { + "type": "integer", + "format": "int64", + "x-go-name": "TrialExpires" + }, "update_days": { "type": "integer", "format": "int64", @@ -12015,6 +12199,9 @@ "navbar": { "$ref": "#/definitions/NavbarPreference" }, + "queryHistory": { + "$ref": "#/definitions/QueryHistoryPreference" + }, "theme": { "type": "string", "enum": ["light", "dark"], From 508cc58a637342a9206286d4acd84ebac6f5f7e8 Mon Sep 17 00:00:00 2001 From: Dimitris Sotirakis Date: Fri, 15 Apr 2022 10:42:58 +0300 Subject: [PATCH 19/40] CI: Remove unused `--github-token` flag (#47773) * Remove github token flag * Minor cleanup * Update grabpl --- .drone.yml | 128 ++++++++++----------------- scripts/drone/pipelines/release.star | 14 +-- scripts/drone/steps/lib.star | 20 ++--- 3 files changed, 54 insertions(+), 108 deletions(-) diff --git a/.drone.yml b/.drone.yml index 5ef3316da98..a634bf1ec4d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,7 +15,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -126,7 +126,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -152,7 +152,6 @@ steps: depends_on: - gen-version - wire-install - environment: {} image: grafana/build-container:1.5.3 name: build-backend - commands: @@ -378,7 +377,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -446,7 +445,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -534,7 +533,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -623,7 +622,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -732,7 +731,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -795,7 +794,6 @@ steps: depends_on: - gen-version - wire-install - environment: {} image: grafana/build-container:1.5.3 name: build-backend - commands: @@ -853,8 +851,6 @@ steps: - build-frontend - build-frontend-packages environment: - GITHUB_TOKEN: - from_secret: github_token GPG_KEY_PASSWORD: from_secret: gpg_key_password GPG_PRIV_KEY: @@ -1167,7 +1163,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1239,7 +1235,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -1326,7 +1322,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1410,7 +1406,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1431,19 +1427,14 @@ steps: image: grafana/build-container:1.5.3 name: yarn-install - commands: - - ./bin/grabpl build-backend --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} - ${DRONE_TAG} + - ./bin/grabpl build-backend --jobs 8 --edition oss ${DRONE_TAG} depends_on: - gen-version - wire-install - environment: - GITHUB_TOKEN: - from_secret: github_token image: grafana/build-container:1.5.3 name: build-backend - commands: - - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --edition - oss ${DRONE_TAG} + - ./bin/grabpl build-frontend --jobs 8 --edition oss ${DRONE_TAG} depends_on: - gen-version - yarn-install @@ -1452,8 +1443,7 @@ steps: image: grafana/build-container:1.5.3 name: build-frontend - commands: - - ./bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN} - --edition oss ${DRONE_TAG} + - ./bin/grabpl build-frontend-packages --jobs 8 --edition oss ${DRONE_TAG} depends_on: - gen-version - yarn-install @@ -1491,16 +1481,13 @@ steps: image: grafana/build-container:1.5.3 name: ensure-cuetsified - commands: - - ./bin/grabpl package --jobs 8 --edition oss --github-token $${GITHUB_TOKEN} --sign - ${DRONE_TAG} + - ./bin/grabpl package --jobs 8 --edition oss --sign ${DRONE_TAG} depends_on: - build-plugins - build-backend - build-frontend - build-frontend-packages environment: - GITHUB_TOKEN: - from_secret: github_token GPG_KEY_PASSWORD: from_secret: gpg_key_password GPG_PRIV_KEY: @@ -1725,7 +1712,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1849,7 +1836,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1926,7 +1913,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -1985,7 +1972,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2037,19 +2024,14 @@ steps: image: grafana/build-container:1.5.3 name: gen-version - commands: - - ./bin/grabpl build-backend --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} - ${DRONE_TAG} + - ./bin/grabpl build-backend --jobs 8 --edition enterprise ${DRONE_TAG} depends_on: - gen-version - wire-install - environment: - GITHUB_TOKEN: - from_secret: github_token image: grafana/build-container:1.5.3 name: build-backend - commands: - - ./bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} --edition - enterprise ${DRONE_TAG} + - ./bin/grabpl build-frontend --jobs 8 --edition enterprise ${DRONE_TAG} depends_on: - gen-version - yarn-install @@ -2058,8 +2040,7 @@ steps: image: grafana/build-container:1.5.3 name: build-frontend - commands: - - ./bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN} - --edition enterprise ${DRONE_TAG} + - ./bin/grabpl build-frontend-packages --jobs 8 --edition enterprise ${DRONE_TAG} depends_on: - gen-version - yarn-install @@ -2097,19 +2078,14 @@ steps: image: grafana/build-container:1.5.3 name: ensure-cuetsified - commands: - - ./bin/grabpl build-backend --jobs 8 --edition enterprise2 --github-token $${GITHUB_TOKEN} - ${DRONE_TAG} + - ./bin/grabpl build-backend --jobs 8 --edition enterprise2 ${DRONE_TAG} depends_on: - gen-version - wire-install - environment: - GITHUB_TOKEN: - from_secret: github_token image: grafana/build-container:1.5.3 name: build-backend-enterprise2 - commands: - - ./bin/grabpl package --jobs 8 --edition enterprise --github-token $${GITHUB_TOKEN} - --sign ${DRONE_TAG} + - ./bin/grabpl package --jobs 8 --edition enterprise --sign ${DRONE_TAG} depends_on: - build-plugins - build-backend @@ -2117,8 +2093,6 @@ steps: - build-frontend-packages - build-backend-enterprise2 environment: - GITHUB_TOKEN: - from_secret: github_token GPG_KEY_PASSWORD: from_secret: gpg_key_password GPG_PRIV_KEY: @@ -2277,8 +2251,7 @@ steps: image: grafana/grafana-ci-deploy:1.3.1 name: store-npm-packages - commands: - - ./bin/grabpl package --jobs 8 --edition enterprise2 --github-token $${GITHUB_TOKEN} - --sign ${DRONE_TAG} + - ./bin/grabpl package --jobs 8 --edition enterprise2 --sign ${DRONE_TAG} depends_on: - build-plugins - build-backend @@ -2286,8 +2259,6 @@ steps: - build-frontend-packages - build-backend-enterprise2 environment: - GITHUB_TOKEN: - from_secret: github_token GPG_KEY_PASSWORD: from_secret: gpg_key_password GPG_PRIV_KEY: @@ -2358,7 +2329,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2537,7 +2508,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2661,7 +2632,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -2736,7 +2707,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2814,7 +2785,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2875,7 +2846,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2954,7 +2925,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3016,7 +2987,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3054,7 +3025,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3092,7 +3063,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3142,7 +3113,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3209,7 +3180,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3234,7 +3205,6 @@ steps: depends_on: - gen-version - wire-install - environment: {} image: grafana/build-container:1.5.3 name: build-backend - commands: @@ -3292,8 +3262,6 @@ steps: - build-frontend - build-frontend-packages environment: - GITHUB_TOKEN: - from_secret: github_token GPG_KEY_PASSWORD: from_secret: gpg_key_password GPG_PRIV_KEY: @@ -3488,7 +3456,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3606,7 +3574,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3677,7 +3645,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -3725,7 +3693,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3778,7 +3746,6 @@ steps: depends_on: - gen-version - wire-install - environment: {} image: grafana/build-container:1.5.3 name: build-backend - commands: @@ -3835,7 +3802,6 @@ steps: depends_on: - gen-version - wire-install - environment: {} image: grafana/build-container:1.5.3 name: build-backend-enterprise2 - commands: @@ -3848,8 +3814,6 @@ steps: - build-frontend-packages - build-backend-enterprise2 environment: - GITHUB_TOKEN: - from_secret: github_token GPG_KEY_PASSWORD: from_secret: gpg_key_password GPG_PRIV_KEY: @@ -4021,8 +3985,6 @@ steps: - build-frontend-packages - build-backend-enterprise2 environment: - GITHUB_TOKEN: - from_secret: github_token GPG_KEY_PASSWORD: from_secret: gpg_key_password GPG_PRIV_KEY: @@ -4086,7 +4048,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4256,7 +4218,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4371,7 +4333,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.35/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -4568,6 +4530,6 @@ kind: secret name: gcp_upload_artifacts_key --- kind: signature -hmac: 6b2cdb67f637bdd527ba527741fd7d0af7e61b0700487be912821b93d8d16ca1 +hmac: 203f82c4bb5060b26ecabb9a5d979ad3ae1eea3c586afddb083d685bbd4328f8 ... diff --git a/scripts/drone/pipelines/release.star b/scripts/drone/pipelines/release.star index cb7a8e9cb6b..24f21985882 100644 --- a/scripts/drone/pipelines/release.star +++ b/scripts/drone/pipelines/release.star @@ -224,7 +224,7 @@ def get_steps(edition, ver_mode): build_steps.append(build_storybook) if include_enterprise2: - integration_test_steps.extend([redis_integration_tests_step(edition=edition2, ver_mode=ver_mode), memcached_integration_tests_step(edition=edition2, ver_mode=ver_mode)]) + integration_test_steps.extend([redis_integration_tests_step(), memcached_integration_tests_step()]) if should_upload: publish_steps.append(upload_cdn_step(edition=edition, ver_mode=ver_mode, trigger=trigger_oss)) @@ -431,9 +431,8 @@ def publish_npm_pipelines(mode): name='publish-npm-packages-{}'.format(mode), trigger=trigger, steps = steps, edition="all" )] -def release_pipelines(ver_mode='release', trigger=None, environment=None): +def release_pipelines(ver_mode='release', trigger=None): # 'enterprise' edition services contain both OSS and enterprise services - services = integration_test_services(edition='enterprise') if not trigger: trigger = { 'event': { @@ -447,8 +446,6 @@ def release_pipelines(ver_mode='release', trigger=None, environment=None): }, } - should_publish = ver_mode == 'release' - # The release pipelines include also enterprise ones, so both editions are built for a release. # We could also solve this by triggering a downstream build for the enterprise repo, but by including enterprise # in OSS release builds, we simplify the UX for the release engineer. @@ -457,13 +454,6 @@ def release_pipelines(ver_mode='release', trigger=None, environment=None): pipelines = oss_pipelines + enterprise_pipelines - # if ver_mode == 'release': - # pipelines.append(publish_artifacts_pipelines()) - #pipelines.append(notify_pipeline( - # name='notify-{}'.format(ver_mode), slack_channel='grafana-ci-notifications', trigger=dict(trigger, status = ['failure']), - # depends_on=[p['name'] for p in pipelines], template=failure_template, secret='slack_webhook', - #)) - return pipelines def get_e2e_suffix(): diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star index 44e4c6ae5b7..77e7391394e 100644 --- a/scripts/drone/steps/lib.star +++ b/scripts/drone/steps/lib.star @@ -1,6 +1,6 @@ load('scripts/drone/vault.star', 'from_secret', 'github_token', 'pull_secret', 'drone_token', 'prerelease_bucket') -grabpl_version = 'v2.9.35' +grabpl_version = 'v2.9.36' build_image = 'grafana/build-container:1.5.3' publish_image = 'grafana/grafana-ci-deploy:1.3.1' deploy_docker_image = 'us.gcr.io/kubernetes-dev/drone/plugins/deploy-image' @@ -368,17 +368,13 @@ def build_backend_step(edition, ver_mode, variants=None): # TODO: Convert number of jobs to percentage if ver_mode == 'release': - env = { - 'GITHUB_TOKEN': from_secret(github_token), - } cmds = [ - './bin/grabpl build-backend --jobs 8 --edition {} --github-token $${{GITHUB_TOKEN}} ${{DRONE_TAG}}'.format( + './bin/grabpl build-backend --jobs 8 --edition {} ${{DRONE_TAG}}'.format( edition, ), ] else: build_no = '${DRONE_BUILD_NUMBER}' - env = {} cmds = [ './bin/grabpl build-backend --jobs 8 --edition {} --build-id {}{}'.format( edition, build_no, variants_str, @@ -388,7 +384,6 @@ def build_backend_step(edition, ver_mode, variants=None): return { 'name': 'build-backend' + enterprise2_suffix(edition), 'image': build_image, - 'environment': env, 'depends_on': [ 'gen-version', 'wire-install', @@ -403,7 +398,7 @@ def build_frontend_step(edition, ver_mode): # TODO: Use percentage for num jobs if ver_mode == 'release': cmds = [ - './bin/grabpl build-frontend --jobs 8 --github-token $${GITHUB_TOKEN} ' + \ + './bin/grabpl build-frontend --jobs 8 ' + \ '--edition {} ${{DRONE_TAG}}'.format(edition), ] else: @@ -432,7 +427,7 @@ def build_frontend_package_step(edition, ver_mode): # TODO: Use percentage for num jobs if ver_mode == 'release': cmds = [ - './bin/grabpl build-frontend-packages --jobs 8 --github-token $${GITHUB_TOKEN} ' + \ + './bin/grabpl build-frontend-packages --jobs 8 ' + \ '--edition {} ${{DRONE_TAG}}'.format(edition), ] else: @@ -654,7 +649,6 @@ def package_step(edition, ver_mode, include_enterprise2=False, variants=None): sign_args = ' --sign' env = { 'GRAFANA_API_KEY': from_secret('grafana_api_key'), - 'GITHUB_TOKEN': from_secret(github_token), 'GPG_PRIV_KEY': from_secret('gpg_priv_key'), 'GPG_PUB_KEY': from_secret('gpg_pub_key'), 'GPG_KEY_PASSWORD': from_secret('gpg_key_password'), @@ -669,7 +663,7 @@ def package_step(edition, ver_mode, include_enterprise2=False, variants=None): if ver_mode == 'release': cmds = [ '{}./bin/grabpl package --jobs 8 --edition {} '.format(test_args, edition) + \ - '--github-token $${{GITHUB_TOKEN}}{} ${{DRONE_TAG}}'.format( + '{} ${{DRONE_TAG}}'.format( sign_args ), ] @@ -884,7 +878,7 @@ def mysql_integration_tests_step(edition, ver_mode): } -def redis_integration_tests_step(edition, ver_mode): +def redis_integration_tests_step(): deps = [] deps.extend(['grabpl']) return { @@ -901,7 +895,7 @@ def redis_integration_tests_step(edition, ver_mode): } -def memcached_integration_tests_step(edition, ver_mode): +def memcached_integration_tests_step(): deps = [] deps.extend(['grabpl']) return { From adbe37475495a7faad774a29d93b777a41076040 Mon Sep 17 00:00:00 2001 From: Armand Grillet <2117580+armandgrillet@users.noreply.github.com> Date: Fri, 15 Apr 2022 09:57:45 +0200 Subject: [PATCH 20/40] Alerting: fix SCC-S1017 linting issue in codebase (#47683) --- pkg/services/alerting/notifiers/hipchat.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/services/alerting/notifiers/hipchat.go b/pkg/services/alerting/notifiers/hipchat.go index 7a21903fb6c..78a141cea7e 100644 --- a/pkg/services/alerting/notifiers/hipchat.go +++ b/pkg/services/alerting/notifiers/hipchat.go @@ -55,9 +55,7 @@ const ( // for the HipChatNotifier func NewHipChatNotifier(model *models.AlertNotification, _ alerting.GetDecryptedValueFn, ns notifications.Service) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() - if strings.HasSuffix(url, "/") { - url = url[:len(url)-1] - } + url = strings.TrimSuffix(url, "/") if url == "" { return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} } From 5579dcaa6fa97b719dcbf3d8a67dd8069ef0ee1a Mon Sep 17 00:00:00 2001 From: Gabriel MABILLE Date: Fri, 15 Apr 2022 10:33:28 +0200 Subject: [PATCH 21/40] Chore: Fix TeamStore interface (#47810) --- pkg/services/sqlstore/team.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/services/sqlstore/team.go b/pkg/services/sqlstore/team.go index 6e266fed697..46c88f8f0bc 100644 --- a/pkg/services/sqlstore/team.go +++ b/pkg/services/sqlstore/team.go @@ -21,7 +21,6 @@ type TeamStore interface { RemoveTeamMember(ctx context.Context, cmd *models.RemoveTeamMemberCommand) error GetTeamMembers(ctx context.Context, cmd *models.GetTeamMembersQuery) error GetUserTeamMemberships(ctx context.Context, orgID, userID int64, external bool) ([]*models.TeamMemberDTO, error) - AddOrUpdateTeamMember(userID, orgID, teamID int64, isExternal bool, permission models.PermissionType) error } func getFilteredUsers(signedInUser *models.SignedInUser, hiddenUsers map[string]struct{}) []string { From 7905957ee836e4aed96256189583a2037f311557 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Fri, 15 Apr 2022 10:32:53 +0100 Subject: [PATCH 22/40] Navigation: Add shortcut to add new alert rule to alerting section behind feature toggle (#47713) * Add new alert rule to alerting section * Check access control for ability to create --- pkg/api/index.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index 6f1bf97ee54..ab2c2270e7e 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -233,7 +233,7 @@ func (hs *HTTPServer) getNavTree(c *models.ReqContext, hasEditPerm bool) ([]*dto uaVisibleForOrg := hs.Cfg.UnifiedAlerting.IsEnabled() && !uaIsDisabledForOrg if setting.AlertingEnabled != nil && *setting.AlertingEnabled || uaVisibleForOrg { - navTree = append(navTree, hs.buildAlertNavLinks(c, uaVisibleForOrg)...) + navTree = append(navTree, hs.buildAlertNavLinks(c, uaVisibleForOrg, hasEditPerm)...) } appLinks, err := hs.getAppLinks(c) @@ -479,7 +479,7 @@ func (hs *HTTPServer) buildDashboardNavLinks(c *models.ReqContext, hasEditPerm b return dashboardChildNavs } -func (hs *HTTPServer) buildAlertNavLinks(c *models.ReqContext, uaVisibleForOrg bool) []*dtos.NavLink { +func (hs *HTTPServer) buildAlertNavLinks(c *models.ReqContext, uaVisibleForOrg bool, hasEditPerm bool) []*dtos.NavLink { hasAccess := ac.HasAccess(hs.AccessControl, c) var alertChildNavs []*dtos.NavLink @@ -516,6 +516,19 @@ func (hs *HTTPServer) buildAlertNavLinks(c *models.ReqContext, uaVisibleForOrg b }) } + if hs.Features.IsEnabled(featuremgmt.FlagNewNavigation) { + if uaVisibleForOrg && hasEditPerm && hasAccess(ac.ReqSignedIn, ac.EvalPermission(ac.ActionAlertingRuleCreate)) { + alertChildNavs = append(alertChildNavs, &dtos.NavLink{ + Text: "Divider", Divider: true, Id: "divider", HideFromTabs: true, + }) + + alertChildNavs = append(alertChildNavs, &dtos.NavLink{ + Text: "Alert rule", SubTitle: "Create an alert rule", Id: "alert", + Icon: "plus", Url: hs.Cfg.AppSubURL + "/alerting/new", HideFromTabs: true, ShowIconInNavbar: true, + }) + } + } + if len(alertChildNavs) > 0 { return []*dtos.NavLink{ { From 720c6aac62f12937d068f5265e0208499a1fff35 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Fri, 15 Apr 2022 12:05:53 +0100 Subject: [PATCH 23/40] Dashboard: Add vertical spacing between variables when they wrap (#47762) --- public/sass/components/_submenu.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/sass/components/_submenu.scss b/public/sass/components/_submenu.scss index b9c8d3afc73..b253b8b86be 100644 --- a/public/sass/components/_submenu.scss +++ b/public/sass/components/_submenu.scss @@ -4,6 +4,7 @@ flex-wrap: wrap; align-content: flex-start; align-items: flex-start; + gap: $space-sm $space-md; padding: 0 0 $space-sm 0; } @@ -18,7 +19,6 @@ .submenu-item { display: inline-block; - margin-right: 15px; .fa-caret-down { font-size: 75%; From f263cad8ab027216b8736ec7c6e7113b32a12825 Mon Sep 17 00:00:00 2001 From: Sofia Papagiannaki <1632407+papagian@users.noreply.github.com> Date: Fri, 15 Apr 2022 14:32:20 +0300 Subject: [PATCH 24/40] Devenv: fix alert_webhook_listener (#44820) --- devenv/docker/blocks/alert_webhook_listener/Dockerfile | 2 +- devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/devenv/docker/blocks/alert_webhook_listener/Dockerfile b/devenv/docker/blocks/alert_webhook_listener/Dockerfile index a6bb87f15f5..84c2b31588e 100644 --- a/devenv/docker/blocks/alert_webhook_listener/Dockerfile +++ b/devenv/docker/blocks/alert_webhook_listener/Dockerfile @@ -2,6 +2,6 @@ FROM golang:latest ADD main.go / WORKDIR / -RUN go build -o main . +RUN go mod init alert_webhook_listener && go build -o main . EXPOSE 3010 ENTRYPOINT ["/main"] diff --git a/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml b/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml index 3e6c5389f6c..087ce5bac00 100644 --- a/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml +++ b/devenv/docker/blocks/alert_webhook_listener/docker-compose.yaml @@ -1,5 +1,4 @@ alert_webhook_listener: build: docker/blocks/alert_webhook_listener - network_mode: host ports: - "3010:3010" From 7ddae870e7f81980b921f06da1af63464aaeea27 Mon Sep 17 00:00:00 2001 From: ying-jeanne <74549700+ying-jeanne@users.noreply.github.com> Date: Fri, 15 Apr 2022 14:01:58 +0200 Subject: [PATCH 25/40] fix status code 200 (#47818) --- pkg/api/admin.go | 2 +- pkg/api/admin_users.go | 2 +- pkg/api/alerting.go | 38 ++++++++++++------------- pkg/api/annotations.go | 8 +++--- pkg/api/apikey.go | 4 +-- pkg/api/comments.go | 4 +-- pkg/api/dashboard.go | 24 ++++++++-------- pkg/api/dashboard_permission.go | 2 +- pkg/api/dashboard_snapshot.go | 12 ++++---- pkg/api/datasources.go | 18 ++++++------ pkg/api/folder.go | 12 ++++---- pkg/api/folder_permission.go | 4 +-- pkg/api/frontendsettings.go | 3 +- pkg/api/index.go | 3 +- pkg/api/ldap_debug.go | 2 +- pkg/api/login.go | 8 +++--- pkg/api/org.go | 8 +++--- pkg/api/org_invite.go | 8 +++--- pkg/api/org_users.go | 10 +++---- pkg/api/password.go | 2 +- pkg/api/playlist.go | 14 ++++----- pkg/api/plugins.go | 10 +++---- pkg/api/preferences.go | 2 +- pkg/api/quota.go | 4 +-- pkg/api/response/response.go | 2 +- pkg/api/search.go | 2 +- pkg/api/short_url.go | 2 +- pkg/api/signup.go | 6 ++-- pkg/api/swagger.go | 8 ++++-- pkg/api/team.go | 6 ++-- pkg/api/team_members.go | 4 +-- pkg/api/user.go | 12 ++++---- pkg/api/user_token.go | 6 ++-- pkg/middleware/middleware_test.go | 4 +-- pkg/middleware/rate_limit_test.go | 2 +- pkg/services/featuremgmt/manager.go | 3 +- pkg/services/libraryelements/api.go | 14 ++++----- pkg/services/live/live.go | 4 +-- pkg/services/searchusers/searchusers.go | 6 ++-- pkg/services/store/http.go | 10 ++++--- pkg/services/thumbs/dummy.go | 7 +++-- pkg/services/thumbs/service.go | 11 +++---- pkg/tsdb/loki/framing_test.go | 2 +- pkg/tsdb/loki/loki_bench_test.go | 3 +- 44 files changed, 166 insertions(+), 152 deletions(-) diff --git a/pkg/api/admin.go b/pkg/api/admin.go index 453db021900..2ebe625d398 100644 --- a/pkg/api/admin.go +++ b/pkg/api/admin.go @@ -25,7 +25,7 @@ func (hs *HTTPServer) AdminGetStats(c *models.ReqContext) response.Response { return response.Error(500, "Failed to get admin stats from database", err) } - return response.JSON(200, statsQuery.Result) + return response.JSON(http.StatusOK, statsQuery.Result) } func (hs *HTTPServer) getAuthorizedSettings(ctx context.Context, user *models.SignedInUser, bag setting.SettingsBag) (setting.SettingsBag, error) { diff --git a/pkg/api/admin_users.go b/pkg/api/admin_users.go index c9fa2407533..78393beb864 100644 --- a/pkg/api/admin_users.go +++ b/pkg/api/admin_users.go @@ -58,7 +58,7 @@ func (hs *HTTPServer) AdminCreateUser(c *models.ReqContext) response.Response { Id: user.Id, } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (hs *HTTPServer) AdminUpdateUserPassword(c *models.ReqContext) response.Response { diff --git a/pkg/api/alerting.go b/pkg/api/alerting.go index 86a57e8e9a6..4fa1589a9c7 100644 --- a/pkg/api/alerting.go +++ b/pkg/api/alerting.go @@ -54,7 +54,7 @@ func (hs *HTTPServer) GetAlertStatesForDashboard(c *models.ReqContext) response. return response.Error(500, "Failed to fetch alert states", err) } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } // GET /api/alerts @@ -106,7 +106,7 @@ func (hs *HTTPServer) GetAlerts(c *models.ReqContext) response.Response { // if we didn't find any dashboards, return empty result if len(dashboardIDs) == 0 { - return response.JSON(200, []*models.AlertListItemDTO{}) + return response.JSON(http.StatusOK, []*models.AlertListItemDTO{}) } } @@ -132,7 +132,7 @@ func (hs *HTTPServer) GetAlerts(c *models.ReqContext) response.Response { alert.Url = models.GetDashboardUrl(alert.DashboardUid, alert.DashboardSlug) } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } // POST /api/alerts/test @@ -176,7 +176,7 @@ func (hs *HTTPServer) AlertTest(c *models.ReqContext) response.Response { dtoRes.TimeMs = fmt.Sprintf("%1.3fms", res.GetDurationMs()) - return response.JSON(200, dtoRes) + return response.JSON(http.StatusOK, dtoRes) } // GET /api/alerts/:id @@ -191,17 +191,17 @@ func (hs *HTTPServer) GetAlert(c *models.ReqContext) response.Response { return response.Error(500, "List alerts failed", err) } - return response.JSON(200, &query.Result) + return response.JSON(http.StatusOK, &query.Result) } func (hs *HTTPServer) GetAlertNotifiers(ngalertEnabled bool) func(*models.ReqContext) response.Response { return func(_ *models.ReqContext) response.Response { if ngalertEnabled { - return response.JSON(200, notifier.GetAvailableNotifiers()) + return response.JSON(http.StatusOK, notifier.GetAvailableNotifiers()) } // TODO(codesome): This wont be required in 8.0 since ngalert // will be enabled by default with no disabling. This is to be removed later. - return response.JSON(200, alerting.GetNotifiers()) + return response.JSON(http.StatusOK, alerting.GetNotifiers()) } } @@ -217,7 +217,7 @@ func (hs *HTTPServer) GetAlertNotificationLookup(c *models.ReqContext) response. result = append(result, dtos.NewAlertNotificationLookup(notification)) } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (hs *HTTPServer) GetAlertNotifications(c *models.ReqContext) response.Response { @@ -232,7 +232,7 @@ func (hs *HTTPServer) GetAlertNotifications(c *models.ReqContext) response.Respo result = append(result, dtos.NewAlertNotification(notification)) } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (hs *HTTPServer) getAlertNotificationsInternal(c *models.ReqContext) ([]*models.AlertNotification, error) { @@ -267,7 +267,7 @@ func (hs *HTTPServer) GetAlertNotificationByID(c *models.ReqContext) response.Re return response.Error(404, "Alert notification not found", nil) } - return response.JSON(200, dtos.NewAlertNotification(query.Result)) + return response.JSON(http.StatusOK, dtos.NewAlertNotification(query.Result)) } func (hs *HTTPServer) GetAlertNotificationByUID(c *models.ReqContext) response.Response { @@ -288,7 +288,7 @@ func (hs *HTTPServer) GetAlertNotificationByUID(c *models.ReqContext) response.R return response.Error(404, "Alert notification not found", nil) } - return response.JSON(200, dtos.NewAlertNotification(query.Result)) + return response.JSON(http.StatusOK, dtos.NewAlertNotification(query.Result)) } func (hs *HTTPServer) CreateAlertNotification(c *models.ReqContext) response.Response { @@ -309,7 +309,7 @@ func (hs *HTTPServer) CreateAlertNotification(c *models.ReqContext) response.Res return response.Error(500, "Failed to create alert notification", err) } - return response.JSON(200, dtos.NewAlertNotification(cmd.Result)) + return response.JSON(http.StatusOK, dtos.NewAlertNotification(cmd.Result)) } func (hs *HTTPServer) UpdateAlertNotification(c *models.ReqContext) response.Response { @@ -344,7 +344,7 @@ func (hs *HTTPServer) UpdateAlertNotification(c *models.ReqContext) response.Res return response.Error(500, "Failed to get alert notification", err) } - return response.JSON(200, dtos.NewAlertNotification(query.Result)) + return response.JSON(http.StatusOK, dtos.NewAlertNotification(query.Result)) } func (hs *HTTPServer) UpdateAlertNotificationByUID(c *models.ReqContext) response.Response { @@ -376,7 +376,7 @@ func (hs *HTTPServer) UpdateAlertNotificationByUID(c *models.ReqContext) respons return response.Error(500, "Failed to get alert notification", err) } - return response.JSON(200, dtos.NewAlertNotification(query.Result)) + return response.JSON(http.StatusOK, dtos.NewAlertNotification(query.Result)) } func (hs *HTTPServer) fillWithSecureSettingsData(ctx context.Context, cmd *models.UpdateAlertNotificationCommand) error { @@ -469,7 +469,7 @@ func (hs *HTTPServer) DeleteAlertNotificationByUID(c *models.ReqContext) respons return response.Error(500, "Failed to delete alert notification", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Notification deleted", "id": cmd.DeletedAlertNotificationId, }) @@ -536,11 +536,11 @@ func (hs *HTTPServer) PauseAlert(c *models.ReqContext) response.Response { if query.Result.State != models.AlertStatePaused && !dto.Paused { result["state"] = "un-paused" result["message"] = "Alert is already un-paused" - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } else if query.Result.State == models.AlertStatePaused && dto.Paused { result["state"] = models.AlertStatePaused result["message"] = "Alert is already paused" - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } cmd := models.PauseAlertCommand{ @@ -562,7 +562,7 @@ func (hs *HTTPServer) PauseAlert(c *models.ReqContext) response.Response { result["state"] = resp result["message"] = "Alert " + pausedState - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } // POST /api/admin/pause-all-alerts @@ -592,5 +592,5 @@ func (hs *HTTPServer) PauseAllAlerts(c *models.ReqContext) response.Response { "alertsAffected": updateCmd.ResultCount, } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } diff --git a/pkg/api/annotations.go b/pkg/api/annotations.go index 9fd7cc61195..195c931fe76 100644 --- a/pkg/api/annotations.go +++ b/pkg/api/annotations.go @@ -47,7 +47,7 @@ func (hs *HTTPServer) GetAnnotations(c *models.ReqContext) response.Response { } } - return response.JSON(200, items) + return response.JSON(http.StatusOK, items) } type AnnotationError struct { @@ -111,7 +111,7 @@ func (hs *HTTPServer) PostAnnotation(c *models.ReqContext) response.Response { startID := item.Id - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Annotation added", "id": startID, }) @@ -174,7 +174,7 @@ func (hs *HTTPServer) PostGraphiteAnnotation(c *models.ReqContext) response.Resp return response.Error(500, "Failed to save Graphite annotation", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Graphite annotation added", "id": item.Id, }) @@ -433,7 +433,7 @@ func (hs *HTTPServer) GetAnnotationTags(c *models.ReqContext) response.Response return response.Error(500, "Failed to find annotation tags", err) } - return response.JSON(200, annotations.GetAnnotationTagsResponse{Result: result}) + return response.JSON(http.StatusOK, annotations.GetAnnotationTagsResponse{Result: result}) } // AnnotationTypeScopeResolver provides an AttributeScopeResolver able to diff --git a/pkg/api/apikey.go b/pkg/api/apikey.go index 15599f037f6..9d1aab24bf3 100644 --- a/pkg/api/apikey.go +++ b/pkg/api/apikey.go @@ -36,7 +36,7 @@ func (hs *HTTPServer) GetAPIKeys(c *models.ReqContext) response.Response { } } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } // DeleteAPIKey deletes an API key @@ -107,5 +107,5 @@ func (hs *HTTPServer) AddAPIKey(c *models.ReqContext) response.Response { Key: newKeyInfo.ClientSecret, } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } diff --git a/pkg/api/comments.go b/pkg/api/comments.go index d84d4a8cd8f..7f32d64ac08 100644 --- a/pkg/api/comments.go +++ b/pkg/api/comments.go @@ -23,7 +23,7 @@ func (hs *HTTPServer) commentsGet(c *models.ReqContext) response.Response { } return response.Error(http.StatusInternalServerError, "internal error", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "comments": items, }) } @@ -43,7 +43,7 @@ func (hs *HTTPServer) commentsCreate(c *models.ReqContext) response.Response { } return response.Error(http.StatusInternalServerError, "internal error", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "comment": comment, }) } diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 343841cabaf..791a30172ce 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -72,7 +72,7 @@ func (hs *HTTPServer) TrimDashboard(c *models.ReqContext) response.Response { } c.TimeRequest(metrics.MApiDashboardGet) - return response.JSON(200, dto) + return response.JSON(http.StatusOK, dto) } func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response { @@ -196,7 +196,7 @@ func (hs *HTTPServer) GetDashboard(c *models.ReqContext) response.Response { } c.TimeRequest(metrics.MApiDashboardGet) - return response.JSON(200, dto) + return response.JSON(http.StatusOK, dto) } func (hs *HTTPServer) getAnnotationPermissionsByScope(c *models.ReqContext, actions *dtos.AnnotationActions, scope string) { @@ -282,7 +282,7 @@ func (hs *HTTPServer) deleteDashboard(c *models.ReqContext) response.Response { hs.log.Error("Failed to broadcast delete info", "dashboard", dash.Uid, "error", err) } } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "title": dash.Title, "message": fmt.Sprintf("Dashboard %s deleted", dash.Title), "id": dash.Id, @@ -396,7 +396,7 @@ func (hs *HTTPServer) postDashboard(c *models.ReqContext, cmd models.SaveDashboa } c.TimeRequest(metrics.MApiDashboardSave) - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "status": "success", "slug": dashboard.Slug, "version": dashboard.Version, @@ -417,7 +417,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *models.ReqContext) response.Response { if prefsQuery.Result.HomeDashboardId == 0 && len(homePage) > 0 { homePageRedirect := dtos.DashboardRedirect{RedirectUri: homePage} - return response.JSON(200, &homePageRedirect) + return response.JSON(http.StatusOK, &homePageRedirect) } if prefsQuery.Result.HomeDashboardId != 0 { @@ -426,7 +426,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *models.ReqContext) response.Response { if err == nil { url := models.GetDashboardUrl(slugQuery.Result.Uid, slugQuery.Result.Slug) dashRedirect := dtos.DashboardRedirect{RedirectUri: url} - return response.JSON(200, &dashRedirect) + return response.JSON(http.StatusOK, &dashRedirect) } hs.log.Warn("Failed to get slug from database", "err", err) } @@ -462,7 +462,7 @@ func (hs *HTTPServer) GetHomeDashboard(c *models.ReqContext) response.Response { hs.addGettingStartedPanelToHomeDashboard(c, dash.Dashboard) - return response.JSON(200, &dash) + return response.JSON(http.StatusOK, &dash) } func (hs *HTTPServer) addGettingStartedPanelToHomeDashboard(c *models.ReqContext, dash *simplejson.Json) { @@ -530,7 +530,7 @@ func (hs *HTTPServer) GetDashboardVersions(c *models.ReqContext) response.Respon } } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } // GetDashboardVersion returns the dashboard version with the given ID. @@ -573,7 +573,7 @@ func (hs *HTTPServer) GetDashboardVersion(c *models.ReqContext) response.Respons CreatedBy: creator, } - return response.JSON(200, dashVersionMeta) + return response.JSON(http.StatusOK, dashVersionMeta) } // POST /api/dashboards/calculate-diff performs diffs on two dashboards @@ -648,10 +648,10 @@ func (hs *HTTPServer) CalculateDashboardDiff(c *models.ReqContext) response.Resp } if options.DiffType == dashdiffs.DiffDelta { - return response.Respond(200, result.Delta).SetHeader("Content-Type", "application/json") + return response.Respond(http.StatusOK, result.Delta).SetHeader("Content-Type", "application/json") } - return response.Respond(200, result.Delta).SetHeader("Content-Type", "text/html") + return response.Respond(http.StatusOK, result.Delta).SetHeader("Content-Type", "text/html") } // RestoreDashboardVersion restores a dashboard to the given version. @@ -703,5 +703,5 @@ func (hs *HTTPServer) GetDashboardTags(c *models.ReqContext) { return } - c.JSON(200, query.Result) + c.JSON(http.StatusOK, query.Result) } diff --git a/pkg/api/dashboard_permission.go b/pkg/api/dashboard_permission.go index 34ea0b797f8..0415fe0262e 100644 --- a/pkg/api/dashboard_permission.go +++ b/pkg/api/dashboard_permission.go @@ -56,7 +56,7 @@ func (hs *HTTPServer) GetDashboardPermissionList(c *models.ReqContext) response. filteredAcls = append(filteredAcls, perm) } - return response.JSON(200, filteredAcls) + return response.JSON(http.StatusOK, filteredAcls) } func (hs *HTTPServer) UpdateDashboardPermissions(c *models.ReqContext) response.Response { diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index 1d5c45cc5f7..93fa34cd385 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -24,7 +24,7 @@ var client = &http.Client{ } func GetSharingOptions(c *models.ReqContext) { - c.JSON(200, util.DynMap{ + c.JSON(http.StatusOK, util.DynMap{ "externalSnapshotURL": setting.ExternalSnapshotUrl, "externalSnapshotName": setting.ExternalSnapshotName, "externalEnabled": setting.ExternalEnabled, @@ -138,7 +138,7 @@ func (hs *HTTPServer) CreateDashboardSnapshot(c *models.ReqContext) response.Res return nil } - c.JSON(200, util.DynMap{ + c.JSON(http.StatusOK, util.DynMap{ "key": cmd.Key, "deleteKey": cmd.DeleteKey, "url": url, @@ -181,7 +181,7 @@ func (hs *HTTPServer) GetDashboardSnapshot(c *models.ReqContext) response.Respon metrics.MApiDashboardSnapshotGet.Inc() - return response.JSON(200, dto).SetHeader("Cache-Control", "public, max-age=3600") + return response.JSON(http.StatusOK, dto).SetHeader("Cache-Control", "public, max-age=3600") } func deleteExternalDashboardSnapshot(externalUrl string) error { @@ -242,7 +242,7 @@ func (hs *HTTPServer) DeleteDashboardSnapshotByDeleteKey(c *models.ReqContext) r return response.Error(500, "Failed to delete dashboard snapshot", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches.", "id": query.Result.Id, }) @@ -290,7 +290,7 @@ func (hs *HTTPServer) DeleteDashboardSnapshot(c *models.ReqContext) response.Res return response.Error(500, "Failed to delete dashboard snapshot", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Snapshot deleted. It might take an hour before it's cleared from any CDN caches.", "id": query.Result.Id, }) @@ -333,5 +333,5 @@ func (hs *HTTPServer) SearchDashboardSnapshots(c *models.ReqContext) response.Re } } - return response.JSON(200, dtos) + return response.JSON(http.StatusOK, dtos) } diff --git a/pkg/api/datasources.go b/pkg/api/datasources.go index 71e18314d72..61a25517107 100644 --- a/pkg/api/datasources.go +++ b/pkg/api/datasources.go @@ -68,7 +68,7 @@ func (hs *HTTPServer) GetDataSources(c *models.ReqContext) response.Response { sort.Sort(result) - return response.JSON(200, &result) + return response.JSON(http.StatusOK, &result) } // GET /api/datasources/:id @@ -102,7 +102,7 @@ func (hs *HTTPServer) GetDataSourceById(c *models.ReqContext) response.Response // Add accesscontrol metadata dto.AccessControl = hs.getAccessControlMetadata(c, c.OrgId, datasources.ScopePrefix, dto.UID) - return response.JSON(200, &dto) + return response.JSON(http.StatusOK, &dto) } // DELETE /api/datasources/:id @@ -161,7 +161,7 @@ func (hs *HTTPServer) GetDataSourceByUID(c *models.ReqContext) response.Response // Add accesscontrol metadata dto.AccessControl = hs.getAccessControlMetadata(c, c.OrgId, datasources.ScopePrefix, dto.UID) - return response.JSON(200, &dto) + return response.JSON(http.StatusOK, &dto) } // DELETE /api/datasources/uid/:uid @@ -193,7 +193,7 @@ func (hs *HTTPServer) DeleteDataSourceByUID(c *models.ReqContext) response.Respo hs.Live.HandleDatasourceDelete(c.OrgId, ds.Uid) - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Data source deleted", "id": ds.Id, }) @@ -227,7 +227,7 @@ func (hs *HTTPServer) DeleteDataSourceByName(c *models.ReqContext) response.Resp hs.Live.HandleDatasourceDelete(c.OrgId, getCmd.Result.Uid) - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Data source deleted", "id": getCmd.Result.Id, }) @@ -266,7 +266,7 @@ func (hs *HTTPServer) AddDataSource(c *models.ReqContext) response.Response { } ds := convertModelToDtos(cmd.Result) - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Datasource added", "id": cmd.Result.Id, "name": cmd.Result.Name, @@ -331,7 +331,7 @@ func (hs *HTTPServer) UpdateDataSource(c *models.ReqContext) response.Response { hs.Live.HandleDatasourceUpdate(c.OrgId, datasourceDTO.UID) - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Datasource updated", "id": cmd.Id, "name": cmd.Name, @@ -409,7 +409,7 @@ func (hs *HTTPServer) GetDataSourceByName(c *models.ReqContext) response.Respons } dto := convertModelToDtos(filtered[0]) - return response.JSON(200, &dto) + return response.JSON(http.StatusOK, &dto) } // Get /api/datasources/id/:name @@ -428,7 +428,7 @@ func (hs *HTTPServer) GetDataSourceIdByName(c *models.ReqContext) response.Respo Id: ds.Id, } - return response.JSON(200, &dtos) + return response.JSON(http.StatusOK, &dtos) } // /api/datasources/:id/resources/* diff --git a/pkg/api/folder.go b/pkg/api/folder.go index faee784f8a8..3a14be06d33 100644 --- a/pkg/api/folder.go +++ b/pkg/api/folder.go @@ -34,7 +34,7 @@ func (hs *HTTPServer) GetFolders(c *models.ReqContext) response.Response { }) } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response { @@ -44,7 +44,7 @@ func (hs *HTTPServer) GetFolderByUID(c *models.ReqContext) response.Response { } g := guardian.New(c.Req.Context(), folder.Id, c.OrgId, c.SignedInUser) - return response.JSON(200, hs.toFolderDto(c.Req.Context(), g, folder)) + return response.JSON(http.StatusOK, hs.toFolderDto(c.Req.Context(), g, folder)) } func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response { @@ -58,7 +58,7 @@ func (hs *HTTPServer) GetFolderByID(c *models.ReqContext) response.Response { } g := guardian.New(c.Req.Context(), folder.Id, c.OrgId, c.SignedInUser) - return response.JSON(200, hs.toFolderDto(c.Req.Context(), g, folder)) + return response.JSON(http.StatusOK, hs.toFolderDto(c.Req.Context(), g, folder)) } func (hs *HTTPServer) CreateFolder(c *models.ReqContext) response.Response { @@ -72,7 +72,7 @@ func (hs *HTTPServer) CreateFolder(c *models.ReqContext) response.Response { } g := guardian.New(c.Req.Context(), folder.Id, c.OrgId, c.SignedInUser) - return response.JSON(200, hs.toFolderDto(c.Req.Context(), g, folder)) + return response.JSON(http.StatusOK, hs.toFolderDto(c.Req.Context(), g, folder)) } func (hs *HTTPServer) UpdateFolder(c *models.ReqContext) response.Response { @@ -86,7 +86,7 @@ func (hs *HTTPServer) UpdateFolder(c *models.ReqContext) response.Response { } g := guardian.New(c.Req.Context(), cmd.Result.Id, c.OrgId, c.SignedInUser) - return response.JSON(200, hs.toFolderDto(c.Req.Context(), g, cmd.Result)) + return response.JSON(http.StatusOK, hs.toFolderDto(c.Req.Context(), g, cmd.Result)) } func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { // temporarily adding this function to HTTPServer, will be removed from HTTPServer when librarypanels featuretoggle is removed @@ -103,7 +103,7 @@ func (hs *HTTPServer) DeleteFolder(c *models.ReqContext) response.Response { // return apierrors.ToFolderErrorResponse(err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "title": f.Title, "message": fmt.Sprintf("Folder %s deleted", f.Title), "id": f.Id, diff --git a/pkg/api/folder_permission.go b/pkg/api/folder_permission.go index 548201c7ba2..830c71b7afd 100644 --- a/pkg/api/folder_permission.go +++ b/pkg/api/folder_permission.go @@ -55,7 +55,7 @@ func (hs *HTTPServer) GetFolderPermissionList(c *models.ReqContext) response.Res filteredAcls = append(filteredAcls, perm) } - return response.JSON(200, filteredAcls) + return response.JSON(http.StatusOK, filteredAcls) } func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Response { @@ -141,7 +141,7 @@ func (hs *HTTPServer) UpdateFolderPermissions(c *models.ReqContext) response.Res return response.Error(500, "Failed to create permission", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "Folder permissions updated", "id": folder.Id, "title": folder.Title, diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index cc23170a9ed..2c341d53979 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -2,6 +2,7 @@ package api import ( "context" + "net/http" "strconv" "github.com/grafana/grafana/pkg/models" @@ -21,7 +22,7 @@ func (hs *HTTPServer) GetFrontendSettings(c *models.ReqContext) { return } - c.JSON(200, settings) + c.JSON(http.StatusOK, settings) } // getFrontendSettingsMap returns a json object with all the settings needed for front end initialisation. diff --git a/pkg/api/index.go b/pkg/api/index.go index ab2c2270e7e..d7d8f5e133a 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "net/http" "path" "sort" "strings" @@ -750,7 +751,7 @@ func (hs *HTTPServer) Index(c *models.ReqContext) { c.Handle(hs.Cfg, 500, "Failed to get settings", err) return } - c.HTML(200, "index", data) + c.HTML(http.StatusOK, "index", data) } func (hs *HTTPServer) NotFoundHandler(c *models.ReqContext) { diff --git a/pkg/api/ldap_debug.go b/pkg/api/ldap_debug.go index a477bceedd6..97b1e5142fe 100644 --- a/pkg/api/ldap_debug.go +++ b/pkg/api/ldap_debug.go @@ -314,7 +314,7 @@ func (hs *HTTPServer) GetUserFromLDAP(c *models.ReqContext) response.Response { return response.Error(http.StatusBadRequest, "Unable to find the teams for this user", err) } - return response.JSON(200, u) + return response.JSON(http.StatusOK, u) } // splitName receives the full name of a user and splits it into two parts: A name and a surname. diff --git a/pkg/api/login.go b/pkg/api/login.go index 49a33fa5744..b158afcbef8 100644 --- a/pkg/api/login.go +++ b/pkg/api/login.go @@ -85,7 +85,7 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) { urlParams := c.Req.URL.Query() if _, disableAutoLogin := urlParams["disableAutoLogin"]; disableAutoLogin { hs.log.Debug("Auto login manually disabled") - c.HTML(200, getViewIndex(), viewData) + c.HTML(http.StatusOK, getViewIndex(), viewData) return } @@ -109,7 +109,7 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) { // to login again via OAuth and enter to a redirect loop cookies.DeleteCookie(c.Resp, loginErrorCookieName, hs.CookieOptionsFromCfg) viewData.Settings["loginError"] = loginError - c.HTML(200, getViewIndex(), viewData) + c.HTML(http.StatusOK, getViewIndex(), viewData) return } @@ -144,7 +144,7 @@ func (hs *HTTPServer) LoginView(c *models.ReqContext) { return } - c.HTML(200, getViewIndex(), viewData) + c.HTML(http.StatusOK, getViewIndex(), viewData) } func (hs *HTTPServer) tryOAuthAutoLogin(c *models.ReqContext) bool { @@ -167,7 +167,7 @@ func (hs *HTTPServer) tryOAuthAutoLogin(c *models.ReqContext) bool { func (hs *HTTPServer) LoginAPIPing(c *models.ReqContext) response.Response { if c.IsSignedIn || c.IsAnonymous { - return response.JSON(200, "Logged in") + return response.JSON(http.StatusOK, "Logged in") } return response.Error(401, "Unauthorized", nil) diff --git a/pkg/api/org.go b/pkg/api/org.go index 21abb8fe5f0..d6cd5b3799b 100644 --- a/pkg/api/org.go +++ b/pkg/api/org.go @@ -54,7 +54,7 @@ func (hs *HTTPServer) GetOrgByName(c *models.ReqContext) response.Response { }, } - return response.JSON(200, &result) + return response.JSON(http.StatusOK, &result) } func (hs *HTTPServer) getOrgHelper(ctx context.Context, orgID int64) response.Response { @@ -81,7 +81,7 @@ func (hs *HTTPServer) getOrgHelper(ctx context.Context, orgID int64) response.Re }, } - return response.JSON(200, &result) + return response.JSON(http.StatusOK, &result) } // POST /api/orgs @@ -105,7 +105,7 @@ func (hs *HTTPServer) CreateOrg(c *models.ReqContext) response.Response { metrics.MApiOrgCreate.Inc() - return response.JSON(200, &util.DynMap{ + return response.JSON(http.StatusOK, &util.DynMap{ "orgId": cmd.Result.Id, "message": "Organization created", }) @@ -226,5 +226,5 @@ func (hs *HTTPServer) SearchOrgs(c *models.ReqContext) response.Response { return response.Error(500, "Failed to search orgs", err) } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } diff --git a/pkg/api/org_invite.go b/pkg/api/org_invite.go index 957053455dd..312971cdedc 100644 --- a/pkg/api/org_invite.go +++ b/pkg/api/org_invite.go @@ -28,7 +28,7 @@ func (hs *HTTPServer) GetPendingOrgInvites(c *models.ReqContext) response.Respon invite.Url = setting.ToAbsUrl("invite/" + invite.Code) } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } func (hs *HTTPServer) AddOrgInvite(c *models.ReqContext) response.Response { @@ -131,7 +131,7 @@ func (hs *HTTPServer) inviteExistingUserToOrg(c *models.ReqContext, user *models } } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": fmt.Sprintf("Existing Grafana user %s added to org %s", user.NameOrFallback(), c.OrgName), "userId": user.Id, }) @@ -162,7 +162,7 @@ func (hs *HTTPServer) GetInviteInfoByCode(c *models.ReqContext) response.Respons return response.Error(404, "Invite not found", nil) } - return response.JSON(200, dtos.InviteInfo{ + return response.JSON(http.StatusOK, dtos.InviteInfo{ Email: invite.Email, Name: invite.Name, Username: invite.Email, @@ -225,7 +225,7 @@ func (hs *HTTPServer) CompleteInvite(c *models.ReqContext) response.Response { metrics.MApiUserSignUpCompleted.Inc() metrics.MApiUserSignUpInvite.Inc() - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "User created and logged in", "id": user.Id, }) diff --git a/pkg/api/org_users.go b/pkg/api/org_users.go index dcd45bbe523..b10aae80c45 100644 --- a/pkg/api/org_users.go +++ b/pkg/api/org_users.go @@ -64,7 +64,7 @@ func (hs *HTTPServer) addOrgUserHelper(ctx context.Context, cmd models.AddOrgUse return response.Error(500, "Could not add user to organization", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "User added to organization", "userId": cmd.UserId, }) @@ -83,7 +83,7 @@ func (hs *HTTPServer) GetOrgUsersForCurrentOrg(c *models.ReqContext) response.Re return response.Error(500, "Failed to get users for current organization", err) } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } // GET /api/org/users/lookup @@ -109,7 +109,7 @@ func (hs *HTTPServer) GetOrgUsersForCurrentOrgLookup(c *models.ReqContext) respo }) } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } // GET /api/orgs/:orgId/users @@ -130,7 +130,7 @@ func (hs *HTTPServer) GetOrgUsers(c *models.ReqContext) response.Response { return response.Error(500, "Failed to get users for organization", err) } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (hs *HTTPServer) getOrgUsersHelper(c *models.ReqContext, query *models.GetOrgUsersQuery, signedInUser *models.SignedInUser) ([]*models.OrgUserDTO, error) { @@ -201,7 +201,7 @@ func (hs *HTTPServer) SearchOrgUsersWithPaging(c *models.ReqContext) response.Re query.Result.Page = page query.Result.PerPage = perPage - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } // PATCH /api/org/users/:userId diff --git a/pkg/api/password.go b/pkg/api/password.go index 487c1e1c33d..4454a486001 100644 --- a/pkg/api/password.go +++ b/pkg/api/password.go @@ -29,7 +29,7 @@ func (hs *HTTPServer) SendResetPasswordEmail(c *models.ReqContext) response.Resp if err := hs.SQLStore.GetUserByLogin(c.Req.Context(), &userQuery); err != nil { c.Logger.Info("Requested password reset for user that was not found", "user", userQuery.LoginOrEmail) - return response.Error(200, "Email sent", err) + return response.Error(http.StatusOK, "Email sent", err) } emailCmd := models.SendResetPasswordEmailCommand{User: userQuery.Result} diff --git a/pkg/api/playlist.go b/pkg/api/playlist.go index 5e496c0fd0e..d30f8c58b23 100644 --- a/pkg/api/playlist.go +++ b/pkg/api/playlist.go @@ -54,7 +54,7 @@ func (hs *HTTPServer) SearchPlaylists(c *models.ReqContext) response.Response { return response.Error(500, "Search failed", err) } - return response.JSON(200, searchQuery.Result) + return response.JSON(http.StatusOK, searchQuery.Result) } func (hs *HTTPServer) GetPlaylist(c *models.ReqContext) response.Response { @@ -78,7 +78,7 @@ func (hs *HTTPServer) GetPlaylist(c *models.ReqContext) response.Response { Items: playlistDTOs, } - return response.JSON(200, dto) + return response.JSON(http.StatusOK, dto) } func (hs *HTTPServer) LoadPlaylistItemDTOs(ctx context.Context, id int64) ([]models.PlaylistItemDTO, error) { @@ -125,7 +125,7 @@ func (hs *HTTPServer) GetPlaylistItems(c *models.ReqContext) response.Response { return response.Error(500, "Could not load playlist items", err) } - return response.JSON(200, playlistDTOs) + return response.JSON(http.StatusOK, playlistDTOs) } func (hs *HTTPServer) GetPlaylistDashboards(c *models.ReqContext) response.Response { @@ -139,7 +139,7 @@ func (hs *HTTPServer) GetPlaylistDashboards(c *models.ReqContext) response.Respo return response.Error(500, "Could not load dashboards", err) } - return response.JSON(200, playlists) + return response.JSON(http.StatusOK, playlists) } func (hs *HTTPServer) DeletePlaylist(c *models.ReqContext) response.Response { @@ -153,7 +153,7 @@ func (hs *HTTPServer) DeletePlaylist(c *models.ReqContext) response.Response { return response.Error(500, "Failed to delete playlist", err) } - return response.JSON(200, "") + return response.JSON(http.StatusOK, "") } func (hs *HTTPServer) CreatePlaylist(c *models.ReqContext) response.Response { @@ -167,7 +167,7 @@ func (hs *HTTPServer) CreatePlaylist(c *models.ReqContext) response.Response { return response.Error(500, "Failed to create playlist", err) } - return response.JSON(200, cmd.Result) + return response.JSON(http.StatusOK, cmd.Result) } func (hs *HTTPServer) UpdatePlaylist(c *models.ReqContext) response.Response { @@ -192,5 +192,5 @@ func (hs *HTTPServer) UpdatePlaylist(c *models.ReqContext) response.Response { } cmd.Result.Items = playlistDTOs - return response.JSON(200, cmd.Result) + return response.JSON(http.StatusOK, cmd.Result) } diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 25a1dcbfe60..227a6d6fba1 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -111,7 +111,7 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response { } sort.Sort(result) - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (hs *HTTPServer) GetPluginSettingByID(c *models.ReqContext) response.Response { @@ -163,7 +163,7 @@ func (hs *HTTPServer) GetPluginSettingByID(c *models.ReqContext) response.Respon dto.HasUpdate = true } - return response.JSON(200, dto) + return response.JSON(http.StatusOK, dto) } func (hs *HTTPServer) UpdatePluginSetting(c *models.ReqContext) response.Response { @@ -217,7 +217,7 @@ func (hs *HTTPServer) GetPluginMarkdown(c *models.ReqContext) response.Response } } - resp := response.Respond(200, content) + resp := response.Respond(http.StatusOK, content) resp.SetHeader("Content-Type", "text/plain; charset=utf-8") return resp } @@ -343,7 +343,7 @@ func (hs *HTTPServer) CheckHealth(c *models.ReqContext) response.Response { return response.JSON(503, payload) } - return response.JSON(200, payload) + return response.JSON(http.StatusOK, payload) } // CallResource passes a resource call from a plugin to the backend plugin. @@ -354,7 +354,7 @@ func (hs *HTTPServer) CallResource(c *models.ReqContext) { } func (hs *HTTPServer) GetPluginErrorsList(_ *models.ReqContext) response.Response { - return response.JSON(200, hs.pluginErrorResolver.PluginErrors()) + return response.JSON(http.StatusOK, hs.pluginErrorResolver.PluginErrors()) } func (hs *HTTPServer) InstallPlugin(c *models.ReqContext) response.Response { diff --git a/pkg/api/preferences.go b/pkg/api/preferences.go index c15530d8475..3792bee5a90 100644 --- a/pkg/api/preferences.go +++ b/pkg/api/preferences.go @@ -56,7 +56,7 @@ func (hs *HTTPServer) getPreferencesFor(ctx context.Context, orgID, userID, team dto.QueryHistory = prefsQuery.Result.JsonData.QueryHistory } - return response.JSON(200, &dto) + return response.JSON(http.StatusOK, &dto) } // PUT /api/user/preferences diff --git a/pkg/api/quota.go b/pkg/api/quota.go index cdd8c9762d1..79d304dbf9e 100644 --- a/pkg/api/quota.go +++ b/pkg/api/quota.go @@ -32,7 +32,7 @@ func (hs *HTTPServer) getOrgQuotasHelper(c *models.ReqContext, orgID int64) resp return response.Error(500, "Failed to get org quotas", err) } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } func (hs *HTTPServer) UpdateOrgQuota(c *models.ReqContext) response.Response { @@ -76,7 +76,7 @@ func (hs *HTTPServer) GetUserQuotas(c *models.ReqContext) response.Response { return response.Error(500, "Failed to get org quotas", err) } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } func (hs *HTTPServer) UpdateUserQuota(c *models.ReqContext) response.Response { diff --git a/pkg/api/response/response.go b/pkg/api/response/response.go index 58de6282652..e08ed4eb974 100644 --- a/pkg/api/response/response.go +++ b/pkg/api/response/response.go @@ -180,7 +180,7 @@ func JSONStreaming(status int, body interface{}) StreamingResponse { func Success(message string) *NormalResponse { resp := make(map[string]interface{}) resp["message"] = message - return JSON(200, resp) + return JSON(http.StatusOK, resp) } // Error creates an error response. diff --git a/pkg/api/search.go b/pkg/api/search.go index 2f1a2cbf407..a981250d4d4 100644 --- a/pkg/api/search.go +++ b/pkg/api/search.go @@ -67,7 +67,7 @@ func (hs *HTTPServer) Search(c *models.ReqContext) response.Response { } c.TimeRequest(metrics.MApiDashboardSearch) - return response.JSON(200, searchQuery.Result) + return response.JSON(http.StatusOK, searchQuery.Result) } func (hs *HTTPServer) ListSortOptions(c *models.ReqContext) response.Response { diff --git a/pkg/api/short_url.go b/pkg/api/short_url.go index 180aeedfd73..6e9d1614d1f 100644 --- a/pkg/api/short_url.go +++ b/pkg/api/short_url.go @@ -47,7 +47,7 @@ func (hs *HTTPServer) createShortURL(c *models.ReqContext) response.Response { URL: url, } - return response.JSON(200, dto) + return response.JSON(http.StatusOK, dto) } func (hs *HTTPServer) redirectFromShortURL(c *models.ReqContext) { diff --git a/pkg/api/signup.go b/pkg/api/signup.go index f3568d03daf..69591220c93 100644 --- a/pkg/api/signup.go +++ b/pkg/api/signup.go @@ -18,7 +18,7 @@ import ( // GET /api/user/signup/options func GetSignUpOptions(c *models.ReqContext) response.Response { - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "verifyEmailEnabled": setting.VerifyEmailEnabled, "autoAssignOrg": setting.AutoAssignOrg, }) @@ -64,7 +64,7 @@ func (hs *HTTPServer) SignUp(c *models.ReqContext) response.Response { metrics.MApiUserSignUpStarted.Inc() - return response.JSON(200, util.DynMap{"status": "SignUpCreated"}) + return response.JSON(http.StatusOK, util.DynMap{"status": "SignUpCreated"}) } func (hs *HTTPServer) SignUpStep2(c *models.ReqContext) response.Response { @@ -135,7 +135,7 @@ func (hs *HTTPServer) SignUpStep2(c *models.ReqContext) response.Response { metrics.MApiUserSignUpCompleted.Inc() - return response.JSON(200, apiResponse) + return response.JSON(http.StatusOK, apiResponse) } func (hs *HTTPServer) verifyUserSignUpEmail(ctx context.Context, email string, code string) (bool, response.Response) { diff --git a/pkg/api/swagger.go b/pkg/api/swagger.go index 37d88541e72..7c96b576b9f 100644 --- a/pkg/api/swagger.go +++ b/pkg/api/swagger.go @@ -1,7 +1,11 @@ package api -import "github.com/grafana/grafana/pkg/models" +import ( + "net/http" + + "github.com/grafana/grafana/pkg/models" +) func swaggerUI(c *models.ReqContext) { - c.HTML(200, "swagger", nil) + c.HTML(http.StatusOK, "swagger", nil) } diff --git a/pkg/api/team.go b/pkg/api/team.go index 77416f5bce8..79c0eac9982 100644 --- a/pkg/api/team.go +++ b/pkg/api/team.go @@ -44,7 +44,7 @@ func (hs *HTTPServer) CreateTeam(c *models.ReqContext) response.Response { c.Logger.Warn("Could not add creator to team because is not a real user") } } - return response.JSON(200, &util.DynMap{ + return response.JSON(http.StatusOK, &util.DynMap{ "teamId": team.Id, "message": "Team created", }) @@ -151,7 +151,7 @@ func (hs *HTTPServer) SearchTeams(c *models.ReqContext) response.Response { query.Result.Page = page query.Result.PerPage = perPage - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } // UserFilter returns the user ID used in a filter when querying a team @@ -198,7 +198,7 @@ func (hs *HTTPServer) GetTeamByID(c *models.ReqContext) response.Response { query.Result.AccessControl = hs.getAccessControlMetadata(c, c.OrgId, "teams:id:", strconv.FormatInt(query.Result.Id, 10)) query.Result.AvatarUrl = dtos.GetGravatarUrlWithDefault(query.Result.Email, query.Result.Name) - return response.JSON(200, &query.Result) + return response.JSON(http.StatusOK, &query.Result) } // GET /api/teams/:teamId/preferences diff --git a/pkg/api/team_members.go b/pkg/api/team_members.go index e39028872e8..a8c44cde66a 100644 --- a/pkg/api/team_members.go +++ b/pkg/api/team_members.go @@ -54,7 +54,7 @@ func (hs *HTTPServer) GetTeamMembers(c *models.ReqContext) response.Response { filteredMembers = append(filteredMembers, member) } - return response.JSON(200, filteredMembers) + return response.JSON(http.StatusOK, filteredMembers) } // POST /api/teams/:teamId/members @@ -89,7 +89,7 @@ func (hs *HTTPServer) AddTeamMember(c *models.ReqContext) response.Response { return response.Error(500, "Failed to add Member to Team", err) } - return response.JSON(200, &util.DynMap{ + return response.JSON(http.StatusOK, &util.DynMap{ "message": "Member added to Team", }) } diff --git a/pkg/api/user.go b/pkg/api/user.go index 95b69d0b462..8b0c59debf7 100644 --- a/pkg/api/user.go +++ b/pkg/api/user.go @@ -49,7 +49,7 @@ func (hs *HTTPServer) getUserUserProfile(c *models.ReqContext, userID int64) res query.Result.AccessControl = hs.getAccessControlMetadata(c, c.OrgId, "global.users:id:", strconv.FormatInt(userID, 10)) query.Result.AvatarUrl = dtos.GetGravatarUrl(query.Result.Email) - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } // GET /api/users/lookup @@ -73,7 +73,7 @@ func (hs *HTTPServer) GetUserByLoginOrEmail(c *models.ReqContext) response.Respo UpdatedAt: user.Updated, CreatedAt: user.Created, } - return response.JSON(200, &result) + return response.JSON(http.StatusOK, &result) } // POST /api/user @@ -176,7 +176,7 @@ func (hs *HTTPServer) getUserTeamList(ctx context.Context, orgID int64, userID i for _, team := range query.Result { team.AvatarUrl = dtos.GetGravatarUrlWithDefault(team.Email, team.Name) } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } // GET /api/users/:id/orgs @@ -195,7 +195,7 @@ func (hs *HTTPServer) getUserOrgList(ctx context.Context, userID int64) response return response.Error(500, "Failed to get user organizations", err) } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } func (hs *HTTPServer) validateUsingOrg(ctx context.Context, userID int64, orgID int64) bool { @@ -321,7 +321,7 @@ func (hs *HTTPServer) SetHelpFlag(c *models.ReqContext) response.Response { return response.Error(500, "Failed to update help flag", err) } - return response.JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1}) + return response.JSON(http.StatusOK, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1}) } func (hs *HTTPServer) ClearHelpFlags(c *models.ReqContext) response.Response { @@ -334,7 +334,7 @@ func (hs *HTTPServer) ClearHelpFlags(c *models.ReqContext) response.Response { return response.Error(500, "Failed to update help flag", err) } - return response.JSON(200, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1}) + return response.JSON(http.StatusOK, &util.DynMap{"message": "Help flag set", "helpFlags1": cmd.HelpFlags1}) } func GetAuthProviderLabel(authModule string) string { diff --git a/pkg/api/user_token.go b/pkg/api/user_token.go index 53b040bd531..edb5c34da41 100644 --- a/pkg/api/user_token.go +++ b/pkg/api/user_token.go @@ -43,7 +43,7 @@ func (hs *HTTPServer) logoutUserFromAllDevicesInternal(ctx context.Context, user return response.Error(500, "Failed to logout user", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "User logged out", }) } @@ -112,7 +112,7 @@ func (hs *HTTPServer) getUserAuthTokensInternal(c *models.ReqContext, userID int }) } - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (hs *HTTPServer) revokeUserAuthTokenInternal(c *models.ReqContext, userID int64, cmd models.RevokeAuthTokenCmd) response.Response { @@ -144,7 +144,7 @@ func (hs *HTTPServer) revokeUserAuthTokenInternal(c *models.ReqContext, userID i return response.Error(500, "Failed to revoke user auth token", err) } - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "message": "User auth token revoked", }) } diff --git a/pkg/middleware/middleware_test.go b/pkg/middleware/middleware_test.go index 76dbd87b1be..a4fbd7c7629 100644 --- a/pkg/middleware/middleware_test.go +++ b/pkg/middleware/middleware_test.go @@ -111,7 +111,7 @@ func TestMiddlewareContext(t *testing.T) { NavTree: []*dtos.NavLink{}, } t.Log("Calling HTML", "data", data) - c.HTML(200, "index-template", data) + c.HTML(http.StatusOK, "index-template", data) t.Log("Returned HTML with code 200") } sc.fakeReq("GET", "/").exec() @@ -616,7 +616,7 @@ func middlewareScenario(t *testing.T, desc string, fn scenarioFunc, cbs ...func( t.Log("Returning JSON OK") resp := make(map[string]interface{}) resp["message"] = "OK" - c.JSON(200, resp) + c.JSON(http.StatusOK, resp) } } diff --git a/pkg/middleware/rate_limit_test.go b/pkg/middleware/rate_limit_test.go index 641e385018a..88fb7e021c5 100644 --- a/pkg/middleware/rate_limit_test.go +++ b/pkg/middleware/rate_limit_test.go @@ -25,7 +25,7 @@ func rateLimiterScenario(t *testing.T, desc string, rps int, burst int, fn rateL defaultHandler := func(c *models.ReqContext) { resp := make(map[string]interface{}) resp["message"] = "OK" - c.JSON(200, resp) + c.JSON(http.StatusOK, resp) } currentTime := time.Now() diff --git a/pkg/services/featuremgmt/manager.go b/pkg/services/featuremgmt/manager.go index 1b795d0348d..8593e6635cf 100644 --- a/pkg/services/featuremgmt/manager.go +++ b/pkg/services/featuremgmt/manager.go @@ -3,6 +3,7 @@ package featuremgmt import ( "context" "fmt" + "net/http" "reflect" "github.com/grafana/grafana/pkg/infra/log" @@ -157,7 +158,7 @@ func (fm *FeatureManager) HandleGetSettings(c *models.ReqContext) { res["info"] = vv - response.JSON(200, res).WriteTo(c) + response.JSON(http.StatusOK, res).WriteTo(c) } // WithFeatures is used to define feature toggles for testing. diff --git a/pkg/services/libraryelements/api.go b/pkg/services/libraryelements/api.go index 8babbe60046..f14c876b8e4 100644 --- a/pkg/services/libraryelements/api.go +++ b/pkg/services/libraryelements/api.go @@ -35,7 +35,7 @@ func (l *LibraryElementService) createHandler(c *models.ReqContext) response.Res return toLibraryElementError(err, "Failed to create library element") } - return response.JSON(200, LibraryElementResponse{Result: element}) + return response.JSON(http.StatusOK, LibraryElementResponse{Result: element}) } // deleteHandler handles DELETE /api/library-elements/:uid. @@ -45,7 +45,7 @@ func (l *LibraryElementService) deleteHandler(c *models.ReqContext) response.Res return toLibraryElementError(err, "Failed to delete library element") } - return response.JSON(200, DeleteLibraryElementResponse{ + return response.JSON(http.StatusOK, DeleteLibraryElementResponse{ Message: "Library element deleted", ID: id, }) @@ -58,7 +58,7 @@ func (l *LibraryElementService) getHandler(c *models.ReqContext) response.Respon return toLibraryElementError(err, "Failed to get library element") } - return response.JSON(200, LibraryElementResponse{Result: element}) + return response.JSON(http.StatusOK, LibraryElementResponse{Result: element}) } // getAllHandler handles GET /api/library-elements/. @@ -78,7 +78,7 @@ func (l *LibraryElementService) getAllHandler(c *models.ReqContext) response.Res return toLibraryElementError(err, "Failed to get library elements") } - return response.JSON(200, LibraryElementSearchResponse{Result: elementsResult}) + return response.JSON(http.StatusOK, LibraryElementSearchResponse{Result: elementsResult}) } // patchHandler handles PATCH /api/library-elements/:uid @@ -93,7 +93,7 @@ func (l *LibraryElementService) patchHandler(c *models.ReqContext) response.Resp return toLibraryElementError(err, "Failed to update library element") } - return response.JSON(200, LibraryElementResponse{Result: element}) + return response.JSON(http.StatusOK, LibraryElementResponse{Result: element}) } // getConnectionsHandler handles GET /api/library-panels/:uid/connections/. @@ -103,7 +103,7 @@ func (l *LibraryElementService) getConnectionsHandler(c *models.ReqContext) resp return toLibraryElementError(err, "Failed to get connections") } - return response.JSON(200, LibraryElementConnectionsResponse{Result: connections}) + return response.JSON(http.StatusOK, LibraryElementConnectionsResponse{Result: connections}) } // getByNameHandler handles GET /api/library-elements/name/:name/. @@ -113,7 +113,7 @@ func (l *LibraryElementService) getByNameHandler(c *models.ReqContext) response. return toLibraryElementError(err, "Failed to get library element") } - return response.JSON(200, LibraryElementArrayResponse{Result: elements}) + return response.JSON(http.StatusOK, LibraryElementArrayResponse{Result: elements}) } func toLibraryElementError(err error, message string) response.Response { diff --git a/pkg/services/live/live.go b/pkg/services/live/live.go index 3924d6f810e..1dba33d32b3 100644 --- a/pkg/services/live/live.go +++ b/pkg/services/live/live.go @@ -1042,14 +1042,14 @@ func (g *GrafanaLive) HandleListHTTP(c *models.ReqContext) response.Response { info := streamChannelListResponse{ Channels: channels, } - return response.JSONStreaming(200, info) + return response.JSONStreaming(http.StatusOK, info) } // HandleInfoHTTP special http response for func (g *GrafanaLive) HandleInfoHTTP(ctx *models.ReqContext) response.Response { path := web.Params(ctx.Req)["*"] if path == "grafana/dashboards/gitops" { - return response.JSON(200, util.DynMap{ + return response.JSON(http.StatusOK, util.DynMap{ "active": g.GrafanaScope.Dashboards.HasGitOpsObserver(ctx.SignedInUser.OrgId), }) } diff --git a/pkg/services/searchusers/searchusers.go b/pkg/services/searchusers/searchusers.go index 79dc1b59196..fbed2051dae 100644 --- a/pkg/services/searchusers/searchusers.go +++ b/pkg/services/searchusers/searchusers.go @@ -1,6 +1,8 @@ package searchusers import ( + "net/http" + "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" @@ -27,7 +29,7 @@ func (s *OSSService) SearchUsers(c *models.ReqContext) response.Response { return response.Error(500, "Failed to fetch users", err) } - return response.JSON(200, query.Result.Users) + return response.JSON(http.StatusOK, query.Result.Users) } func (s *OSSService) SearchUsersWithPaging(c *models.ReqContext) response.Response { @@ -36,7 +38,7 @@ func (s *OSSService) SearchUsersWithPaging(c *models.ReqContext) response.Respon return response.Error(500, "Failed to fetch users", err) } - return response.JSON(200, query.Result) + return response.JSON(http.StatusOK, query.Result) } func (s *OSSService) SearchUser(c *models.ReqContext) (*models.SearchUsersQuery, error) { diff --git a/pkg/services/store/http.go b/pkg/services/store/http.go index 5d23128a8cf..7334049d395 100644 --- a/pkg/services/store/http.go +++ b/pkg/services/store/http.go @@ -1,6 +1,8 @@ package store import ( + "net/http" + "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/web" @@ -28,7 +30,7 @@ func (s *httpStorage) Upload(c *models.ReqContext) response.Response { action := "Upload" scope, path := getPathAndScope(c) - return response.JSON(200, map[string]string{ + return response.JSON(http.StatusOK, map[string]string{ "action": action, "scope": scope, "path": path, @@ -39,7 +41,7 @@ func (s *httpStorage) Read(c *models.ReqContext) response.Response { action := "Read" scope, path := getPathAndScope(c) - return response.JSON(200, map[string]string{ + return response.JSON(http.StatusOK, map[string]string{ "action": action, "scope": scope, "path": path, @@ -50,7 +52,7 @@ func (s *httpStorage) Delete(c *models.ReqContext) response.Response { action := "Delete" scope, path := getPathAndScope(c) - return response.JSON(200, map[string]string{ + return response.JSON(http.StatusOK, map[string]string{ "action": action, "scope": scope, "path": path, @@ -67,5 +69,5 @@ func (s *httpStorage) List(c *models.ReqContext) response.Response { if frame == nil { return response.Error(404, "not found", nil) } - return response.JSONStreaming(200, frame) + return response.JSONStreaming(http.StatusOK, frame) } diff --git a/pkg/services/thumbs/dummy.go b/pkg/services/thumbs/dummy.go index 6c984d412ad..769b70443a8 100644 --- a/pkg/services/thumbs/dummy.go +++ b/pkg/services/thumbs/dummy.go @@ -2,6 +2,7 @@ package thumbs import ( "context" + "net/http" "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/models" @@ -39,19 +40,19 @@ func (ds *dummyService) GetDashboardPreviewsSetupSettings(c *models.ReqContext) func (ds *dummyService) StartCrawler(c *models.ReqContext) response.Response { result := make(map[string]string) result["error"] = "Not enabled" - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (ds *dummyService) StopCrawler(c *models.ReqContext) response.Response { result := make(map[string]string) result["error"] = "Not enabled" - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (ds *dummyService) CrawlerStatus(c *models.ReqContext) response.Response { result := make(map[string]string) result["error"] = "Not enabled" - return response.JSON(200, result) + return response.JSON(http.StatusOK, result) } func (ds *dummyService) Run(ctx context.Context) error { diff --git a/pkg/services/thumbs/service.go b/pkg/services/thumbs/service.go index 4b837baaaa3..45cba2828aa 100644 --- a/pkg/services/thumbs/service.go +++ b/pkg/services/thumbs/service.go @@ -6,6 +6,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "time" "github.com/grafana/grafana/pkg/api/response" @@ -192,7 +193,7 @@ func (hs *thumbService) UpdateThumbnailState(c *models.ReqContext) { } hs.log.Info("Updated dashboard thumbnail state", "dashboardUid", req.UID, "theme", req.Theme, "newState", body.State) - c.JSON(200, map[string]string{"success": "true"}) + c.JSON(http.StatusOK, map[string]string{"success": "true"}) } func (hs *thumbService) GetImage(c *models.ReqContext) { @@ -320,7 +321,7 @@ func (hs *thumbService) SetImage(c *models.ReqContext) { return } - c.JSON(200, map[string]int{"OK": len(fileBytes)}) + c.JSON(http.StatusOK, map[string]int{"OK": len(fileBytes)}) } func (hs *thumbService) StartCrawler(c *models.ReqContext) response.Response { @@ -348,7 +349,7 @@ func (hs *thumbService) StartCrawler(c *models.ReqContext) response.Response { return response.Error(500, "error starting", err) } - return response.JSON(200, status) + return response.JSON(http.StatusOK, status) } func (hs *thumbService) StopCrawler(c *models.ReqContext) response.Response { @@ -356,7 +357,7 @@ func (hs *thumbService) StopCrawler(c *models.ReqContext) response.Response { if err != nil { return response.Error(500, "error starting", err) } - return response.JSON(200, msg) + return response.JSON(http.StatusOK, msg) } func (hs *thumbService) CrawlerStatus(c *models.ReqContext) response.Response { @@ -364,7 +365,7 @@ func (hs *thumbService) CrawlerStatus(c *models.ReqContext) response.Response { if err != nil { return response.Error(500, "error starting", err) } - return response.JSON(200, msg) + return response.JSON(http.StatusOK, msg) } // Ideally this service would not require first looking up the full dashboard just to bet the id! diff --git a/pkg/tsdb/loki/framing_test.go b/pkg/tsdb/loki/framing_test.go index 78d8df2cfec..696cc5d3fb6 100644 --- a/pkg/tsdb/loki/framing_test.go +++ b/pkg/tsdb/loki/framing_test.go @@ -59,7 +59,7 @@ func TestSuccessResponse(t *testing.T) { bytes, err := os.ReadFile(responseFileName) require.NoError(t, err) - frames, err := runQuery(context.Background(), makeMockedAPI(200, "application/json", bytes), &test.query) + frames, err := runQuery(context.Background(), makeMockedAPI(http.StatusOK, "application/json", bytes), &test.query) require.NoError(t, err) dr := &backend.DataResponse{ diff --git a/pkg/tsdb/loki/loki_bench_test.go b/pkg/tsdb/loki/loki_bench_test.go index 331b8b533f1..de0b6d50a17 100644 --- a/pkg/tsdb/loki/loki_bench_test.go +++ b/pkg/tsdb/loki/loki_bench_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "math/rand" + "net/http" "strings" "testing" ) @@ -16,7 +17,7 @@ func BenchmarkMatrixJson(b *testing.B) { b.ResetTimer() for n := 0; n < b.N; n++ { - _, _ = runQuery(context.Background(), makeMockedAPI(200, "application/json", bytes), &lokiQuery{}) + _, _ = runQuery(context.Background(), makeMockedAPI(http.StatusOK, "application/json", bytes), &lokiQuery{}) } } From d4f7ea9f014250bbe4ced0caff37ab5e320c62b4 Mon Sep 17 00:00:00 2001 From: Florian Cassayre Date: Fri, 15 Apr 2022 14:10:34 +0200 Subject: [PATCH 26/40] Chore: Fix typos (#47816) --- public/app/features/live/data/StreamingDataFrame.ts | 2 +- .../dashboards/vMInsightsRG.json | 4 ++-- .../dashboards/vMInsightsWorkspace.json | 4 ++-- .../app/plugins/panel/status-history/StatusHistoryPanel.tsx | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/public/app/features/live/data/StreamingDataFrame.ts b/public/app/features/live/data/StreamingDataFrame.ts index 0493728dee4..1c076c1c934 100644 --- a/public/app/features/live/data/StreamingDataFrame.ts +++ b/public/app/features/live/data/StreamingDataFrame.ts @@ -301,7 +301,7 @@ export class StreamingDataFrame implements DataFrame { if (values.length !== this.fields.length) { if (this.fields.length) { throw new Error( - `push message mismatch. Expected: ${this.fields.length}, recieved: ${values.length} (labels=${ + `push message mismatch. Expected: ${this.fields.length}, received: ${values.length} (labels=${ this.pushMode === PushMode.labels })` ); diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/dashboards/vMInsightsRG.json b/public/app/plugins/datasource/grafana-azure-monitor-datasource/dashboards/vMInsightsRG.json index 5dddc761e15..780b889e14f 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/dashboards/vMInsightsRG.json +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/dashboards/vMInsightsRG.json @@ -1497,7 +1497,7 @@ "subscription": "" } ], - "title": "Max Available Bytes Sent and Trend Line", + "title": "Max Available Bytes Sent and Trend Line", "transformations": [ { "id": "organize", @@ -1979,7 +1979,7 @@ "subscription": "" } ], - "title": "Max Available Bytes Recieved and Trend Line", + "title": "Max Available Bytes Received and Trend Line", "transformations": [ { "id": "organize", diff --git a/public/app/plugins/datasource/grafana-azure-monitor-datasource/dashboards/vMInsightsWorkspace.json b/public/app/plugins/datasource/grafana-azure-monitor-datasource/dashboards/vMInsightsWorkspace.json index ee18e0db44a..551b645f398 100644 --- a/public/app/plugins/datasource/grafana-azure-monitor-datasource/dashboards/vMInsightsWorkspace.json +++ b/public/app/plugins/datasource/grafana-azure-monitor-datasource/dashboards/vMInsightsWorkspace.json @@ -1494,7 +1494,7 @@ "subscription": "" } ], - "title": "Max Available Bytes Sent and Trend Line", + "title": "Max Available Bytes Sent and Trend Line", "transformations": [ { "id": "organize", @@ -1970,7 +1970,7 @@ "subscription": "" } ], - "title": "Max Available Bytes Recieved and Trend Line", + "title": "Max Available Bytes Received and Trend Line", "transformations": [ { "id": "organize", diff --git a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx index d6f2b407a63..4552ac8477e 100755 --- a/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx +++ b/public/app/plugins/panel/status-history/StatusHistoryPanel.tsx @@ -43,7 +43,7 @@ export const StatusHistoryPanel: React.FC = ({

Too many points to visualize properly.
- Update the query to return fewer points.
({frames[0].length} points recieved) + Update the query to return fewer points.
({frames[0].length} points received)

); From 67ff5f58151a315c29b4d74847059670c6121941 Mon Sep 17 00:00:00 2001 From: Petros Kolyvas Date: Fri, 15 Apr 2022 10:14:09 -0300 Subject: [PATCH 27/40] Docs: Addition of more prominent link to docker conf (#46504) * Docs: Addition of more prominent link to docker conf Saw a comment on Reddit indicating a lack of information - docs were available, just a little hard to find. Added a note pointing our community to where they might find docker image configuration help. * Update docs/sources/installation/docker.md Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Christopher Moyer <35463610+chri2547@users.noreply.github.com> --- docs/sources/installation/docker.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/installation/docker.md b/docs/sources/installation/docker.md index fbb80fa3eed..c5206ec0ee7 100755 --- a/docs/sources/installation/docker.md +++ b/docs/sources/installation/docker.md @@ -16,6 +16,8 @@ You can install and run Grafana using the official Docker images. Our docker ima Each edition is available in two variants: Alpine and Ubuntu. See below. +For documentation regarding the configuration of a docker image, refer to [configure a Grafana Docker image](https://grafana.com/docs/grafana/latest/administration/configure-docker/). + This topic also contains important information about [migrating from earlier Docker image versions](#migrate-from-previous-docker-containers-versions). > **Note:** You can use [Grafana Cloud](https://grafana.com/products/cloud/features/#cloud-logs) to avoid the overhead of installing, maintaining, and scaling your observability stack. The free forever plan includes Grafana, 10K Prometheus series, 50 GB logs, and more.[Create a free account to get started](https://grafana.com/auth/sign-up/create-user?pg=docs-grafana-install&plcmt=in-text). From 45105199411616dc2e93c2a07410a378d86b6b42 Mon Sep 17 00:00:00 2001 From: Dimitris Sotirakis Date: Fri, 15 Apr 2022 17:00:26 +0300 Subject: [PATCH 28/40] Update grabpl (#47824) --- .drone.yml | 70 ++++++++++++++++++------------------ scripts/drone/steps/lib.star | 2 +- 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/.drone.yml b/.drone.yml index a634bf1ec4d..b28736013e1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -15,7 +15,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -126,7 +126,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -377,7 +377,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -445,7 +445,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -533,7 +533,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -622,7 +622,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -731,7 +731,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1163,7 +1163,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1235,7 +1235,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -1322,7 +1322,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1406,7 +1406,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1712,7 +1712,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1836,7 +1836,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -1913,7 +1913,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -1972,7 +1972,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2329,7 +2329,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2508,7 +2508,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2632,7 +2632,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -2707,7 +2707,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2785,7 +2785,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2846,7 +2846,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2925,7 +2925,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -2987,7 +2987,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3025,7 +3025,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3063,7 +3063,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3113,7 +3113,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3180,7 +3180,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3456,7 +3456,7 @@ steps: name: identify-runner - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3574,7 +3574,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -3645,7 +3645,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe -OutFile grabpl.exe image: grafana/ci-wix:0.1.1 name: windows-init @@ -3693,7 +3693,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4048,7 +4048,7 @@ services: [] steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4218,7 +4218,7 @@ services: steps: - commands: - mkdir -p bin - - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/grabpl + - curl -fL -o bin/grabpl https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/grabpl - chmod +x bin/grabpl image: byrnedo/alpine-curl:0.1.8 name: grabpl @@ -4333,7 +4333,7 @@ steps: name: identify-runner - commands: - $$ProgressPreference = "SilentlyContinue" - - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.36/windows/grabpl.exe + - Invoke-WebRequest https://grafana-downloads.storage.googleapis.com/grafana-build-pipeline/v2.9.37/windows/grabpl.exe -OutFile grabpl.exe - git clone "https://$$env:GITHUB_TOKEN@github.com/grafana/grafana-enterprise.git" - cd grafana-enterprise @@ -4530,6 +4530,6 @@ kind: secret name: gcp_upload_artifacts_key --- kind: signature -hmac: 203f82c4bb5060b26ecabb9a5d979ad3ae1eea3c586afddb083d685bbd4328f8 +hmac: 803ec2a67b1758ce82ddda8ba1fb276fd23e4cfbfc3c5736bf2fa9e98edc2e6b ... diff --git a/scripts/drone/steps/lib.star b/scripts/drone/steps/lib.star index 77e7391394e..55c861d9968 100644 --- a/scripts/drone/steps/lib.star +++ b/scripts/drone/steps/lib.star @@ -1,6 +1,6 @@ load('scripts/drone/vault.star', 'from_secret', 'github_token', 'pull_secret', 'drone_token', 'prerelease_bucket') -grabpl_version = 'v2.9.36' +grabpl_version = 'v2.9.37' build_image = 'grafana/build-container:1.5.3' publish_image = 'grafana/grafana-ci-deploy:1.3.1' deploy_docker_image = 'us.gcr.io/kubernetes-dev/drone/plugins/deploy-image' From 031a8e140a0358cdf415f1b17b28e2ea170637ae Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Fri, 15 Apr 2022 07:48:44 -0700 Subject: [PATCH 29/40] Chore: upgrade gocloud.dev and go mod tidy (#47797) --- go.mod | 51 ++--- go.sum | 271 ++++++++++++++++------- pkg/components/imguploader/s3uploader.go | 2 + 3 files changed, 213 insertions(+), 111 deletions(-) diff --git a/go.mod b/go.mod index dee9c1e4cbd..c5e28247313 100644 --- a/go.mod +++ b/go.mod @@ -14,14 +14,14 @@ replace k8s.io/client-go => k8s.io/client-go v0.22.1 replace github.com/russellhaering/goxmldsig@v1.1.0 => github.com/russellhaering/goxmldsig v1.1.1 require ( - cloud.google.com/go/storage v1.18.2 + cloud.google.com/go/storage v1.21.0 cuelang.org/go v0.4.1 github.com/Azure/azure-sdk-for-go v59.3.0+incompatible github.com/Azure/go-autorest/autorest v0.11.22 github.com/BurntSushi/toml v0.3.1 github.com/Masterminds/semver v1.5.0 github.com/VividCortex/mysqlerr v0.0.0-20170204212430-6c6b55f8796f - github.com/aws/aws-sdk-go v1.42.8 + github.com/aws/aws-sdk-go v1.43.31 github.com/beevik/etree v1.1.0 github.com/benbjohnson/clock v1.1.0 github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b @@ -29,7 +29,7 @@ require ( github.com/cortexproject/cortex v1.10.1-0.20211014125347-85c378182d0d github.com/crewjam/saml v0.4.6-0.20210521115923-29c6295245bd github.com/davecgh/go-spew v1.1.1 - github.com/denisenkom/go-mssqldb v0.11.0 + github.com/denisenkom/go-mssqldb v0.12.0 github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 github.com/fatih/color v1.13.0 github.com/gchaincl/sqlhooks v1.3.0 @@ -93,7 +93,7 @@ require ( github.com/unknwon/com v1.0.1 github.com/urfave/cli/v2 v2.3.0 github.com/vectordotdev/go-datemath v0.1.1-0.20220323213446-f3954d0b18ae - github.com/weaveworks/common v0.0.0-20210913144402-035033b78a78 + github.com/weaveworks/common v0.0.0-20210913144402-035033b78a78 // indirect github.com/xorcare/pointer v1.1.0 github.com/yudai/gojsondiff v1.0.0 go.opentelemetry.io/collector v0.31.0 @@ -102,17 +102,17 @@ require ( go.opentelemetry.io/otel/exporters/jaeger v1.0.0 go.opentelemetry.io/otel/sdk v1.0.0 go.opentelemetry.io/otel/trace v1.6.3 - golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 + golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 golang.org/x/exp v0.0.0-20210220032938-85be41e4509f - golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 // indirect - golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 + golang.org/x/net v0.0.0-20220401154927-543a649e0bdd // indirect + golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 + golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 golang.org/x/tools v0.1.9 gonum.org/v1/gonum v0.11.0 - google.golang.org/api v0.62.0 - google.golang.org/grpc v1.42.0 - google.golang.org/protobuf v1.27.1 + google.golang.org/api v0.74.0 + google.golang.org/grpc v1.45.0 + google.golang.org/protobuf v1.28.0 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/ini.v1 v1.66.2 gopkg.in/ldap.v3 v3.1.0 @@ -171,7 +171,7 @@ require ( github.com/go-openapi/validate v0.20.2 // indirect github.com/gogo/googleapis v1.4.1 // indirect github.com/gogo/status v1.1.0 // indirect - github.com/golang-jwt/jwt/v4 v4.2.0 // indirect + github.com/golang-jwt/jwt/v4 v4.4.1 // indirect github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect @@ -179,7 +179,7 @@ require ( github.com/gomodule/redigo v2.0.0+incompatible // indirect github.com/google/btree v1.0.1 // indirect github.com/google/flatbuffers v2.0.0+incompatible // indirect - github.com/googleapis/gax-go/v2 v2.1.1 + github.com/googleapis/gax-go/v2 v2.2.0 github.com/gorilla/mux v1.8.0 // indirect github.com/grafana/grafana-google-sdk-go v0.0.0-20211104130251-b190293eaf58 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20191002090509-6af20e3a5340 // indirect @@ -232,30 +232,29 @@ require ( go.mongodb.org/mongo-driver v1.7.0 // indirect go.opencensus.io v0.23.0 // indirect go.uber.org/atomic v1.9.0 - go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 // indirect - golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 // indirect + go.uber.org/goleak v1.1.11 // indirect + golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa + google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect ) require ( - cloud.google.com/go/kms v1.1.0 + cloud.google.com/go/kms v1.4.0 github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.13.2 github.com/Azure/azure-sdk-for-go/sdk/keyvault/azkeys v0.4.0 github.com/Azure/go-autorest/autorest/adal v0.9.17 github.com/golang-migrate/migrate/v4 v4.7.0 github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f github.com/grafana/thema v0.0.0-20220413232647-fc54c169b508 - gocloud.dev v0.24.0 + gocloud.dev v0.25.0 ) require ( - cloud.google.com/go v0.99.0 // indirect + cloud.google.com/go v0.100.2 // indirect github.com/armon/go-metrics v0.3.10 // indirect - github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/hashicorp/go-immutable-radix v1.3.1 // indirect github.com/hashicorp/memberlist v0.3.0 // indirect @@ -265,23 +264,21 @@ require ( ) require ( + cloud.google.com/go/compute v1.5.0 // indirect + cloud.google.com/go/iam v0.3.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/azcore v0.22.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/keyvault/internal v0.2.1 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v0.4.0 // indirect - github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect github.com/chromedp/cdproto v0.0.0-20220208224320-6efb837e6bc2 // indirect - github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect github.com/containerd/containerd v1.5.9 // indirect github.com/elazarl/goproxy v0.0.0-20220115173737-adb46da277ac // indirect - github.com/envoyproxy/go-control-plane v0.10.1 // indirect - github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect github.com/getkin/kin-openapi v0.94.0 // indirect github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 // indirect github.com/go-logr/logr v1.2.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt v3.2.1+incompatible // indirect github.com/imdario/mergo v0.3.12 // indirect - github.com/klauspost/compress v1.13.6 // indirect + github.com/klauspost/compress v1.15.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.8 // indirect @@ -299,7 +296,3 @@ replace github.com/hashicorp/consul => github.com/hashicorp/consul v1.10.2 // Upgrading affects backend plugins: https://github.com/grafana/grafana/pull/47653#discussion_r850508593 // No harm to Thema because it's only a dependency in its main package. replace github.com/hashicorp/go-hclog => github.com/hashicorp/go-hclog v0.16.1 - -// TODO: remove once gocloud.dev releases 0.25.x -// `fileblob` implementation has buggy key ordering in 0.24.0 -replace gocloud.dev v0.24.0 => github.com/google/go-cloud v0.24.1-0.20220209172924-99801bbb523a diff --git a/go.sum b/go.sum index 8756dd4d304..291c9de9a53 100644 --- a/go.sum +++ b/go.sum @@ -32,8 +32,10 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= -cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.1/go.mod h1:fs4QogzfH5n2pBXBP9vRiU+eCny7lD2vmFZy79Iuw1U= +cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -43,29 +45,41 @@ cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM7 cloud.google.com/go/bigtable v1.1.0/go.mod h1:B6ByKcIdYmhoyDzmOnQxyOhN6r05qnewYIxxG6L0/b4= cloud.google.com/go/bigtable v1.2.0/go.mod h1:JcVAOl45lrTmQfLj7T6TxyMzIN/3FGGcFm+2xVAli2o= cloud.google.com/go/bigtable v1.3.0/go.mod h1:z5EyKrPE8OQmeg4h5MNdKvuSnI9CCT49Ki3f23aBzio= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0 h1:b1zWmYuuHz7gO9kDcM/EpHGr06UgsYNRpNJzI2kFiLM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= +cloud.google.com/go/iam v0.1.0/go.mod h1:vcUNEa0pEm0qRVpmWepWaFMIAI8/hjB9mO8rNCJtF6c= +cloud.google.com/go/iam v0.1.1/go.mod h1:CKqrcnI/suGpybEHxZ7BMehL0oA4LpdyJdUlTl9jVMw= +cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= cloud.google.com/go/kms v1.0.0/go.mod h1:nhUehi+w7zht2XrUfvTRNpxrfayBHqP4lu2NSywui/0= -cloud.google.com/go/kms v1.1.0 h1:1yc4rLqCkVDS9Zvc7m+3mJ47kw0Uo5Q5+sMjcmUVUeM= cloud.google.com/go/kms v1.1.0/go.mod h1:WdbppnCDMDpOvoYBMn1+gNmOeEoZYqAv+HeuKARGCXI= +cloud.google.com/go/kms v1.4.0 h1:iElbfoE61VeLhnZcGOltqL8HIly8Nhbe5t6JlH9GXjo= +cloud.google.com/go/kms v1.4.0/go.mod h1:fajBHndQ+6ubNw6Ss2sSd+SWvjL26RNo/dr7uxsnnOA= cloud.google.com/go/monitoring v1.1.0/go.mod h1:L81pzz7HKn14QCMaCs6NTQkdBnE87TElyanS95vIcl4= +cloud.google.com/go/monitoring v1.4.0/go.mod h1:y6xnxfwI3hTFWOdkOaD7nfJVlwuC3/mS/5kvtT131p4= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/pubsub v1.17.1/go.mod h1:4qDxMr1WsM9+aQAz36ltDwCIM+R0QdlseyFjBuNvnss= -cloud.google.com/go/secretmanager v1.0.0/go.mod h1:+Qkm5qxIJ5mk74xxIXA+87fseaY1JLYBcFPQoc/GQxg= +cloud.google.com/go/pubsub v1.19.0/go.mod h1:/O9kmSe9bb9KRnIAWkzmqhPjHo6LtzGOBYd/kr06XSs= +cloud.google.com/go/secretmanager v1.3.0/go.mod h1:+oLTkouyiYiabAQNugCeTS3PAArGiMJuBqvJnJsyH+U= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.3.0/go.mod h1:9IAwXhoyBJ7z9LcAwkj0/7NnPzYaPeZxxVp3zm+5IqA= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.18.2 h1:5NQw6tOn3eMm0oE8vTkfjau18kjL79FlMjy/CHTpmoY= -cloud.google.com/go/storage v1.18.2/go.mod h1:AiIj7BWXyhO5gGVmYJ+S8tbkCx3yb0IMjua8Aw4naVM= +cloud.google.com/go/storage v1.21.0 h1:HwnT2u2D309SFDHQII6m18HlrCi3jAXhUMTLOWXYH14= +cloud.google.com/go/storage v1.21.0/go.mod h1:XmRlxkgPjlBONznT2dDUU/5XlpU2OjMnKuqnZI01LAA= cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A= +cloud.google.com/go/trace v1.2.0/go.mod h1:Wc8y/uYyOhPy12KEnXG9XGrvfMz5F5SrYecQlbW1rwM= code.cloudfoundry.org/clock v1.0.0/go.mod h1:QD9Lzhd/ux6eNQVUDVRJX/RKTigpewimNYBi7ivZKY8= collectd.org v0.3.0/go.mod h1:A/8DzQBkF6abtvrT2j/AU/4tiBgJWYyh0y/oB/4MlWE= contrib.go.opencensus.io/exporter/aws v0.0.0-20200617204711-c478e41e60e9/go.mod h1:uu1P0UCM/6RbsMrgPa98ll8ZcHM858i/AD06a9aLRCA= @@ -175,7 +189,6 @@ github.com/Azure/go-autorest/autorest/azure/auth v0.5.8/go.mod h1:kxyKZTSfKh8OVF github.com/Azure/go-autorest/autorest/azure/auth v0.5.9/go.mod h1:hg3/1yw0Bq87O3KvvnJoAh34/0zbP7SFizX/qN5JvjU= github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= github.com/Azure/go-autorest/autorest/azure/cli v0.4.2/go.mod h1:7qkJkT+j6b+hIpzMOwPChJhTqS8VbsqqgULzMNRugoM= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.4/go.mod h1:yAQ2b6eP/CmLPnmLvxtT1ALIY3OR1oFcCqVBi8vHiTc= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= @@ -218,7 +231,7 @@ github.com/FZambia/eagle v0.0.1 h1:FN1yTkPihMb5nE8SrlRjoCf7T9H9bTKJFQOm6ach2YU= github.com/FZambia/eagle v0.0.1/go.mod h1:xq6u/JeNZ5/8mrAQ76MMhzNTodASh9FavQlCgg4j48w= github.com/FZambia/sentinel v1.1.0 h1:qrCBfxc8SvJihYNjBWgwUI93ZCvFe/PJIPTHKmlp8a8= github.com/FZambia/sentinel v1.1.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI= -github.com/GoogleCloudPlatform/cloudsql-proxy v1.27.0/go.mod h1:bn9iHmAjogMoIPkqBGyJ9R1m9cXGCjBE/cuhBs3oEsQ= +github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0/go.mod h1:spvB9eLJH9dutlbPSRmHvSXXHOwGRyeXh1jVdquA2G8= github.com/HdrHistogram/hdrhistogram-go v0.9.0/go.mod h1:nxrse8/Tzg2tg3DZcZjm6qEclQKK70g0KxO61gFFZD4= github.com/HdrHistogram/hdrhistogram-go v1.0.1/go.mod h1:BWJ+nMSHY3L41Zj7CA3uXnloDp7xxV0YvstAE7nKTaM= github.com/HdrHistogram/hdrhistogram-go v1.1.0 h1:6dpdDPTRoo78HxAJ6T1HfMiKSnqhgRRqzCuPshRkQ7I= @@ -373,49 +386,51 @@ github.com/aws/aws-sdk-go v1.38.60/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2z github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.40.11/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.40.37/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/aws/aws-sdk-go v1.42.8 h1:Tj2RP4Fas1mYchwbmw0qWLJIEATAseyp5iTa1D+LWYQ= -github.com/aws/aws-sdk-go v1.42.8/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go v1.43.31 h1:yJZIr8nMV1hXjAvvOLUFqZRJcHV7udPQBfhJqawDzI0= +github.com/aws/aws-sdk-go v1.43.31/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4= -github.com/aws/aws-sdk-go-v2 v1.11.0 h1:HxyD62DyNhCfiFGUHqJ/xITD6rAjJ7Dm/2nLxLmO4Ag= -github.com/aws/aws-sdk-go-v2 v1.11.0/go.mod h1:SQfA+m2ltnu1cA0soUkj4dRSsmITiVQUJvBIZjzfPyQ= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0 h1:yVUAwvJC/0WNPbyl0nA3j1L6CW1CN8wBubCRqtG7JLI= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.0.0/go.mod h1:Xn6sxgRuIDflLRJFj5Ev7UxABIkNbccFPV/p8itDReM= -github.com/aws/aws-sdk-go-v2/config v1.10.1 h1:z/ViqIjW6ZeuLWgTWMTSyZzaVWo/1cWeVf1Uu+RF01E= -github.com/aws/aws-sdk-go-v2/config v1.10.1/go.mod h1:auIv5pIIn3jIBHNRcVQcsczn6Pfa6Dyv80Fai0ueoJU= -github.com/aws/aws-sdk-go-v2/credentials v1.6.1 h1:A39JYth2fFCx+omN/gib/jIppx3rRnt2r7UKPq7Mh5Y= -github.com/aws/aws-sdk-go-v2/credentials v1.6.1/go.mod h1:QyvQk1IYTqBWSi1T6UgT/W8DMxBVa5pVuLFSRLLhGf8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.0 h1:OpZjuUy8Jt3CA1WgJgBC5Bz+uOjE5Ppx4NFTRaooUuA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.8.0/go.mod h1:5E1J3/TTYy6z909QNR0QnXGBpfESYGDqd3O0zqONghU= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.7.1 h1:p9Dys1g2YdaqMalnp6AwCA+tpMMdJNGw5YYKP/u3sUk= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.7.1/go.mod h1:wN/mvkow08GauDwJ70jnzJ1e+hE+Q3Q7TwpYLXOe9oI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.0 h1:zY8cNmbBXt3pzjgWgdIbzpQ6qxoCwt+Nx9JbrAf2mbY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.0/go.mod h1:NO3Q5ZTTQtO2xIg2+xTXYDiT7knSejfeDm7WGDaOo0U= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.0 h1:Z3aR/OXBnkYK9zXkNkfitHX6SmUBzSsx8VMHbH4Lvhw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.0.0/go.mod h1:anlUzBoEWglcUxUQwZA7HQOEVEnQALVZsizAapB2hq8= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.0 h1:c10Z7fWxtJCoyc8rv06jdh9xrKnu7bAJiRaKWvTb2mU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.3.0/go.mod h1:6oXGy4GLpypD3uCh8wcqztigGgmhLToMfjavgh+VySg= +github.com/aws/aws-sdk-go-v2 v1.16.2 h1:fqlCk6Iy3bnCumtrLz9r3mJ/2gUT0pJ0wLFVIdWh+JA= +github.com/aws/aws-sdk-go-v2 v1.16.2/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1 h1:SdK4Ppk5IzLs64ZMvr6MrSficMtjY2oS0WOORXTlxwU= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.1/go.mod h1:n8Bs1ElDD2wJ9kCRTczA83gYbBmjSwZp3umc6zF4EeM= +github.com/aws/aws-sdk-go-v2/config v1.15.3 h1:5AlQD0jhVXlGzwo+VORKiUuogkG7pQcLJNzIzK7eodw= +github.com/aws/aws-sdk-go-v2/config v1.15.3/go.mod h1:9YL3v07Xc/ohTsxFXzan9ZpFpdTOFl4X65BAKYaz8jg= +github.com/aws/aws-sdk-go-v2/credentials v1.11.2 h1:RQQ5fzclAKJyY5TvF+fkjJEwzK4hnxQCLOu5JXzDmQo= +github.com/aws/aws-sdk-go-v2/credentials v1.11.2/go.mod h1:j8YsY9TXTm31k4eFhspiQicfXPLZ0gYXA50i4gxPE8g= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3 h1:LWPg5zjHV9oz/myQr4wMs0gi4CjnDN/ILmyZUFYXZsU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.3/go.mod h1:uk1vhHHERfSVCUnqSqz8O48LBYDSC+k6brng09jcMOk= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3 h1:ir7iEq78s4txFGgwcLqD6q9IIPzTQNRJXulJd9h/zQo= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.11.3/go.mod h1:0dHuD2HZZSiwfJSy1FO5bX1hQ1TxVV1QXXjpn3XUE44= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9 h1:onz/VaaxZ7Z4V+WIN9Txly9XLTmoOh1oJ8XcAC3pako= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.9/go.mod h1:AnVH5pvai0pAF4lXRq0bmhbes1u9R8wTE+g+183bZNM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3 h1:9stUQR/u2KXU6HkFJYlqnZEjBnbgrVbG6I5HN09xZh0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.3/go.mod h1:ssOhaLpRlh88H3UmEcsBoVKq309quMvm3Ds8e9d4eJM= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10 h1:by9P+oy3P/CwggN4ClnW2D4oL91QV7pBzBICi1chZvQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.10/go.mod h1:8DcYQcz0+ZJaSxANlHIsbbi6S+zMwjwdDqwW3r9AzaE= github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.5.0/go.mod h1:acH3+MQoiMzozT/ivU+DbRg7Ooo2298RdRaWcOv+4vM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0 h1:lPLbw4Gn59uoKqvOfSnkJr54XWk5Ak1NK20ZEiSWb3U= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.5.0/go.mod h1:80NaCIH9YU3rzTTs/J/ECATjXuRqzo/wB6ukO6MZ0XY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.0 h1:qGZWS/WgiFY+Zgad2u0gwBHpJxz6Ne401JE7iQI1nKs= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.5.0/go.mod h1:Mq6AEc+oEjCUlBuLiK5YwW4shSOAKCQ3tXN0sQeYoBA= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.0 h1:0BOlTqnNnrEO04oYKzDxMMe68t107pmIotn18HtVonY= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.9.0/go.mod h1:xKCZ4YFSF2s4Hnb/J0TLeOsKuGzICzcElaOKNGrVnx4= -github.com/aws/aws-sdk-go-v2/service/kms v1.10.0/go.mod h1:ZkHWL8m5Nw1g9yMXqpCjnIJtSDToAmNbXXZ9gj0bO7s= -github.com/aws/aws-sdk-go-v2/service/s3 v1.19.0 h1:5mRAms4TjSTOGYsqKYte5kHr1PzpMJSyLThjF3J+hw0= -github.com/aws/aws-sdk-go-v2/service/s3 v1.19.0/go.mod h1:Gwz3aVctJe6mUY9T//bcALArPUaFmNAy2rTB9qN4No8= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.10.0/go.mod h1:qAgsrzF3Z2vvV01j79fs7D75ofCMQe81/OKBJx0rjFY= -github.com/aws/aws-sdk-go-v2/service/sns v1.11.0/go.mod h1:LIPf3BTbSY5UeVli+x/1y2Qw1w8T9DYyp7p18Qt8Zc8= -github.com/aws/aws-sdk-go-v2/service/sqs v1.12.0/go.mod h1:TDqDmQnsbgL2ZMIGUf3z9xTzCMqFX7FP1geAgIlYqvA= -github.com/aws/aws-sdk-go-v2/service/ssm v1.15.0/go.mod h1:kJa2uHklY03rKsNSbEsToeUgWJ1PambXBtRNacorRhg= -github.com/aws/aws-sdk-go-v2/service/sso v1.6.0 h1:JDgKIUZOmLFu/Rv6zXLrVTWCmzA0jcTdvsT8iFIKrAI= -github.com/aws/aws-sdk-go-v2/service/sso v1.6.0/go.mod h1:Q/l0ON1annSU+mc0JybDy1Gy6dnJxIcWjphO6qJPzvM= -github.com/aws/aws-sdk-go-v2/service/sts v1.10.0 h1:1jh8J+JjYRp+QWKOsaZt7rGUgoyrqiiVwIm+w0ymeUw= -github.com/aws/aws-sdk-go-v2/service/sts v1.10.0/go.mod h1:jLKCFqS+1T4i7HDqCP9GM4Uk75YW1cS0o82LdxpMyOE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1 h1:T4pFel53bkHjL2mMo+4DKE6r6AuoZnM0fg7k1/ratr4= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.1/go.mod h1:GeUru+8VzrTXV/83XyMJ80KpH8xO89VPoUileyNQ+tc= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3 h1:I0dcwWitE752hVSMrsLCxqNQ+UdEp3nACx2bYNMQq+k= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.3/go.mod h1:Seb8KNmD6kVTjwRjVEgOT5hPin6sq+v4C2ycJQDwuH8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3 h1:Gh1Gpyh01Yvn7ilO/b/hr01WgNpaszfbKMUgqM186xQ= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.3/go.mod h1:wlY6SVjuwvh3TVRpTqdy4I1JpBFLX4UGeKZdWntaocw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3 h1:BKjwCJPnANbkwQ8vzSbaZDKawwagDubrH/z/c0X+kbQ= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.3/go.mod h1:Bm/v2IaN6rZ+Op7zX+bOUMdL4fsrYZiD0dsjLhNKwZc= +github.com/aws/aws-sdk-go-v2/service/kms v1.16.3/go.mod h1:QuiHPBqlOFCi4LqdSskYYAWpQlx3PKmohy+rE2F+o5g= +github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3 h1:rMPtwA7zzkSQZhhz9U3/SoIDz/NZ7Q+iRn4EIO8rSyU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.26.3/go.mod h1:g1qvDuRsJY+XghsV6zg00Z4KJ7DtFFCx8fJD2a491Ak= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.15.4/go.mod h1:PJc8s+lxyU8rrre0/4a0pn2wgwiDvOEzoOjcJUBr67o= +github.com/aws/aws-sdk-go-v2/service/sns v1.17.4/go.mod h1:kElt+uCcXxcqFyc+bQqZPFD9DME/eC6oHBXvFzQ9Bcw= +github.com/aws/aws-sdk-go-v2/service/sqs v1.18.3/go.mod h1:skmQo0UPvsjsuYYSYMVmrPc1HWCbHUJyrCEp+ZaLzqM= +github.com/aws/aws-sdk-go-v2/service/ssm v1.24.1/go.mod h1:NR/xoKjdbRJ+qx0pMR4mI+N/H1I1ynHwXnO6FowXJc0= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.3 h1:frW4ikGcxfAEDfmQqWgMLp+F1n4nRo9sF39OcIb5BkQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.3/go.mod h1:7UQ/e69kU7LDPtY40OyoHYgRmgfGM4mgsLYtcObdveU= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.3 h1:cJGRyzCSVwZC7zZZ1xbx9m32UnrKydRYhOvcD1NYP9Q= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.3/go.mod h1:bfBj0iVmsUyUg4weDB4NxktD9rDGeKSVWnjTnwbx9b8= github.com/aws/smithy-go v1.5.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= -github.com/aws/smithy-go v1.9.0 h1:c7FUdEqrQA1/UVKKCNDFQPNKGp4FQg3YW4Ck5SLTG58= -github.com/aws/smithy-go v1.9.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= +github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= @@ -476,7 +491,6 @@ github.com/cenkalti/backoff/v4 v4.1.0/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq github.com/cenkalti/backoff/v4 v4.1.1 h1:G2HAfAmvm/GcKan2oOQpBXOd2tT2G57ZnZGWa1PxPBQ= github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/centrifugal/centrifuge v0.19.0 h1:YHws0dRpgsBiI73tRl1wwaB13gzuaI1AM4IFcQQQqcw= github.com/centrifugal/centrifuge v0.19.0/go.mod h1:O2elf8q3Qkie3z97wkqVqxB52pnOpPsfFUa7L88Lpy0= @@ -517,7 +531,6 @@ github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGX github.com/cncf/udpa/go v0.0.0-20200313221541-5f7e5dd04533/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 h1:hzAQntlaYRkVSFEfj9OTWlVV1H155FMD8BTKktLv0QI= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= @@ -643,6 +656,7 @@ github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= @@ -1163,9 +1177,9 @@ github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0 h1:besgBTC8w8HjP6NzQdxwKH9Z5oQMZ24ThTrHp3cZ8eU= github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-migrate/migrate/v4 v4.7.0 h1:gONcHxHApDTKXDyLH/H97gEHmpu1zcnnbAaq2zgrPrs= github.com/golang-migrate/migrate/v4 v4.7.0/go.mod h1:Qvut3N4xKWjoH3sokBccML6WyHSnggXm/DvMMnTsQIc= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= @@ -1235,8 +1249,6 @@ github.com/google/flatbuffers v1.11.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv github.com/google/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI= github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= -github.com/google/go-cloud v0.24.1-0.20220209172924-99801bbb523a h1:Kw18HR30Firrm4cB1BzcVVaFrrdDlAtnrCKgHxemgMI= -github.com/google/go-cloud v0.24.1-0.20220209172924-99801bbb523a/go.mod h1:TqAL9Q5Q8jFUfIDNbDiFbU3w0z3MQ7q4r1wm57tBhmI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -1257,7 +1269,7 @@ github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:od github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-replayers/grpcreplay v1.1.0/go.mod h1:qzAvJ8/wi57zq7gWqaE6AwLM6miiXUQwP1S+I9icmhk= -github.com/google/go-replayers/httpreplay v1.0.0/go.mod h1:LJhKoTwS5Wy5Ld/peq8dFFG5OfJyHEz7ft+DsTUv25M= +github.com/google/go-replayers/httpreplay v1.1.1/go.mod h1:gN9GeLIs7l6NUoVaSSnv2RiqK1NiwAmD0MrKeC9IIks= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -1268,8 +1280,9 @@ github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible h1:x github.com/google/martian v2.1.1-0.20190517191504-25dcb96d9e51+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -1313,8 +1326,9 @@ github.com/googleapis/gax-go v2.0.2+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0 h1:s7jOdKSaksJVOxE0Y/S32otcfiP+UQ0cL8/GTKaONwE= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= @@ -1612,10 +1626,48 @@ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7Ua github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx v3.3.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jackc/pgx v3.6.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jaegertracing/jaeger v1.24.0/go.mod h1:mqdtFDA447va5j0UewDaAWyNlGreGQyhGxXVhbF58gQ= github.com/jarcoal/httpmock v0.0.0-20180424175123-9c70cfe4a1da/go.mod h1:ks+b9deReOc7jgqp+e7LuFiCBH6Rm5hL32cLcEAArb4= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= @@ -1706,8 +1758,8 @@ github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.15.1 h1:y9FcTHGyrebwfP0ZZqFiaxTaiDnUrGkJkI+f583BL1A= +github.com/klauspost/compress v1.15.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/cpuid v0.0.0-20170728055534-ae7887de9fa5/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= @@ -1756,10 +1808,11 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx github.com/leodido/ragel-machinery v0.0.0-20181214104525-299bdde78165/go.mod h1:WZxr2/6a/Ar9bMDc2rN/LJrE/hF6bXE4LPyDSIxwAfg= github.com/leoluk/perflib_exporter v0.1.0/go.mod h1:rpV0lYj7lemdTm31t7zpCqYqPnw7xs86f+BaaNBVYFM= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk= github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= @@ -1808,6 +1861,7 @@ github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxq github.com/mattetti/filebuffer v1.0.1 h1:gG7pyfnSIZCxdoKq+cPa8T0hhYtD9NxCdI4D7PTjRLM= github.com/mattetti/filebuffer v1.0.1/go.mod h1:YdMURNDOttIiruleeVr6f56OrMc+MydEnTcXwtkxNVs= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -1820,8 +1874,10 @@ github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HN github.com/mattn/go-ieproxy v0.0.0-20190702010315-6dee0af9227d/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc= github.com/mattn/go-ieproxy v0.0.0-20191113090002-7c0f6868bffe/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= +github.com/mattn/go-ieproxy v0.0.3/go.mod h1:6ZpRmhBaYuBX1U2za+9rC9iCGLsSp2tftelZne7CPko= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= @@ -1906,7 +1962,6 @@ github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/mapstructure v1.4.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1-0.20210112042008-8ebf2d61a8b4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= @@ -2284,6 +2339,8 @@ github.com/rs/cors v1.8.0 h1:P2KMzcFwrPoSjkF1WLRPsp3UMLyql8L4v9hQpVeK5so= github.com/rs/cors v1.8.0/go.mod h1:EBwu+T5AvHOcXwvZIkQFjUN6s8Czyqw12GL/Y0tUyRM= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/zerolog v1.4.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/russellhaering/goxmldsig v1.1.0/go.mod h1:QK8GhXPB3+AfuCrfo0oRISa9NfzeCpWmxeGnqEpDF9o= github.com/russellhaering/goxmldsig v1.1.1 h1:vI0r2osGF1A9PLvsGdPUAGwEIrKa4Pj5sesSBsebIxM= github.com/russellhaering/goxmldsig v1.1.1/go.mod h1:gM4MDENBQf7M+V824SGfyIUVFWydB7n0KkEubVJl+Tw= @@ -2586,6 +2643,7 @@ github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJB github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/zenazn/goji v1.0.1/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= @@ -2689,14 +2747,14 @@ go.uber.org/automaxprocs v1.2.0/go.mod h1:YfO3fm683kQpzETxlTGZhGIVmXAhaw3gxeBADb go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723 h1:sHOAIxRGBp443oHZIPB+HsUGaksVCXVQENPxwTfQdH4= -go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -2706,10 +2764,12 @@ go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= -go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= go4.org/intern v0.0.0-20210108033219-3eb7198706b2/go.mod h1:vLqJ+12kCw61iCWsPto0EOHhBS+o4rO5VIucbc9g2Cc= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222175341-b30ae309168e/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= go4.org/unsafe/assume-no-moving-gc v0.0.0-20201222180813-1025295fd063/go.mod h1:FftLjUGFEDu5k8lt0ddY+HcrH/qU/0qk+H8j9/nTl3E= +gocloud.dev v0.25.0 h1:Y7vDq8xj7SyM848KXf32Krda2e6jQ4CLh/mTeCSqXtk= +gocloud.dev v0.25.0/go.mod h1:7HegHVCYZrMiU3IE1qtnzf/vRrDwLYnRNR3EhWX8x9Y= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180505025534-4ec37c66abab/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180608092829-8ac0e0d97ce4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -2721,6 +2781,7 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190418165655-df01cb2cc480/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -2751,6 +2812,7 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= @@ -2758,11 +2820,12 @@ golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211115234514-b4de73f9ece8/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI= -golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= +golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -2912,8 +2975,12 @@ golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4 h1:DZshvxDdVoeKIbudAdFEKi+f70l51luSy/7b76ibTY0= -golang.org/x/net v0.0.0-20211118161319-6a13c67c3ce4/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220107192237-5cfca573fb4d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220401154927-543a649e0bdd h1:zYlwaUHTmxuf6H7hwO2dgwqozQmH7zf4x+/qql4oVWc= +golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -2936,8 +3003,10 @@ golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a h1:qfl7ob3DIEs3Ml9oLuPwY2N04gymzAW04WsUQHIClgM= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -3109,18 +3178,26 @@ golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211110154304-99a53858aa08/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f h1:rlezHXNlxYWvBCzNses9Dlc7nGFaNMJeqLolcmQSSZY= +golang.org/x/sys v0.0.0-20220330033206-e17cdc41300f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -3144,8 +3221,9 @@ golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 h1:M73Iuj3xbbb9Uk1DYhzydthsj6oOd6l9bpuFcNoUvTs= +golang.org/x/time v0.0.0-20220224211638-0e9765cccd65/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -3169,6 +3247,7 @@ golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -3181,6 +3260,7 @@ golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190813034749-528a2984e271/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190918214516-5a1a30219888/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -3255,6 +3335,8 @@ golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9 h1:j9KsMiaP1c3B0OTQGth0/k+miLGTgLsAFUCrF2vLcF8= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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= @@ -3315,10 +3397,18 @@ google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqiv google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= -google.golang.org/api v0.60.0/go.mod h1:d7rl65NZAkEQ90JFzqBjcRq1TVeG5ZoGV3sSpEnnVb4= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.62.0 h1:PhGymJMXfGBzc4lBRmrx9+1w4w2wEzURHNGF/sD/xGc= google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.64.0/go.mod h1:931CdxA8Rm4t6zqTFGSsgwbAEZ2+GMYurbndwSimebM= +google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.68.0/go.mod h1:sOM8pTpwgflXRhz+oC8H2Dr+UcbMqkPPWNJo88Q7TH8= +google.golang.org/api v0.69.0/go.mod h1:boanBiw+h5c3s+tBPgEzLDRHfFLWV0qXxRHz3ws7C80= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0 h1:ExR2D+5TYIrMphWgs5JCgwRhEDlPDXXrLwHHMgPHTXE= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -3415,17 +3505,30 @@ google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEc google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211016002631-37fc39342514/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211018162055-cf77aa76bad2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211019152133-63b7e35f4404/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211021150943-2b146023228c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa h1:I0YcKz0I7OAhddo7ya8kMnvprhcWM045PmkBdMO9zN0= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211223182754-3ac035c7e7cb/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220111164026-67b88f271998/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220216160803-4663080d8bc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de h1:9Ti5SG2U4cAcluryUo/sFay3TQKoxiFMfaT0pbizU7k= +google.golang.org/genproto v0.0.0-20220401170504-314d38edb7de/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= @@ -3467,8 +3570,10 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= -google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= 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= @@ -3483,8 +3588,9 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= @@ -3513,6 +3619,7 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= gopkg.in/gorethink/gorethink.v3 v3.0.5/go.mod h1:+3yIIHJUGMBK+wyPH+iN5TP+88ikFDfZdqTlK3Y9q8I= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= diff --git a/pkg/components/imguploader/s3uploader.go b/pkg/components/imguploader/s3uploader.go index 9cb0a7f7c57..7228ac85a75 100644 --- a/pkg/components/imguploader/s3uploader.go +++ b/pkg/components/imguploader/s3uploader.go @@ -114,6 +114,8 @@ func webIdentityProvider(sess client.ConfigProvider) credentials.Provider { roleARN := os.Getenv("AWS_ROLE_ARN") tokenFilepath := os.Getenv("AWS_WEB_IDENTITY_TOKEN_FILE") roleSessionName := os.Getenv("AWS_ROLE_SESSION_NAME") + + // nolint:staticcheck return stscreds.NewWebIdentityRoleProvider(svc, roleARN, roleSessionName, tokenFilepath) } From 5e11af01211481407cbbe11d6e0777b53e39b16c Mon Sep 17 00:00:00 2001 From: sam boyer Date: Fri, 15 Apr 2022 13:41:07 -0400 Subject: [PATCH 30/40] schema: Finish converting dashboard schema datasource references to objects (#47806) * coremodel: finish string -> object datasource ref Seems we missed updating a couple of the datasource references from strings to objects. * cue fmt * Also fix dashboard in scuemata dashboard schema * Update devenv/dev-dashboards/panel-graph/graph-ng-stacking2.json Co-authored-by: Ryan McKinley Co-authored-by: Ryan McKinley --- devenv/dev-dashboards/dashboards_test.go | 2 +- .../panel-graph/graph-ng-stacking2.json | 85 +- .../src/scuemata/dashboard/dashboard.cue | 759 +++++++++--------- pkg/coremodel/dashboard/lineage.cue | 585 +++++++------- pkg/coremodel/dashboard/schema.go | 11 +- pkg/schema/load/load_test.go | 6 +- scripts/stripnulls.sh | 2 +- 7 files changed, 723 insertions(+), 727 deletions(-) diff --git a/devenv/dev-dashboards/dashboards_test.go b/devenv/dev-dashboards/dashboards_test.go index 66f29040f84..e330b53bb0f 100644 --- a/devenv/dev-dashboards/dashboards_test.go +++ b/devenv/dev-dashboards/dashboards_test.go @@ -68,7 +68,7 @@ func themaTestableDashboards() (map[string][]byte, error) { jtree := make(map[string]interface{}) json.Unmarshal(b, &jtree) - if oldschemav, has := jtree["schemaVersion"]; !has || !(oldschemav.(float64) > 32) { + if oldschemav, has := jtree["schemaVersion"]; !has || !(oldschemav.(float64) > dashboard.HandoffSchemaVersion-1) { return nil } diff --git a/devenv/dev-dashboards/panel-graph/graph-ng-stacking2.json b/devenv/dev-dashboards/panel-graph/graph-ng-stacking2.json index 29bda45b50f..410540966f0 100644 --- a/devenv/dev-dashboards/panel-graph/graph-ng-stacking2.json +++ b/devenv/dev-dashboards/panel-graph/graph-ng-stacking2.json @@ -71,8 +71,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -178,8 +177,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -266,8 +264,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -354,8 +351,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -485,8 +481,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -573,8 +568,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -661,8 +655,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -792,8 +785,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -893,8 +885,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -982,8 +973,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1069,8 +1059,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1155,8 +1144,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1253,8 +1241,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1354,8 +1341,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1455,8 +1441,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1555,8 +1540,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1692,8 +1676,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1800,8 +1783,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1909,8 +1891,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2040,8 +2021,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2128,8 +2108,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2240,8 +2219,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2356,8 +2334,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2460,8 +2437,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2564,8 +2540,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2701,8 +2676,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2838,8 +2812,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2934,7 +2907,7 @@ } ], "refresh": false, - "schemaVersion": 32, + "schemaVersion": 36, "style": "dark", "tags": [ "gdev", @@ -2954,4 +2927,4 @@ "uid": "1KxMUdE7k", "version": 1, "weekStart": "" -} \ No newline at end of file +} diff --git a/packages/grafana-schema/src/scuemata/dashboard/dashboard.cue b/packages/grafana-schema/src/scuemata/dashboard/dashboard.cue index cb7e767ca01..9040f0a28c3 100644 --- a/packages/grafana-schema/src/scuemata/dashboard/dashboard.cue +++ b/packages/grafana-schema/src/scuemata/dashboard/dashboard.cue @@ -1,434 +1,441 @@ package dashboard import ( - "list" + "list" - "github.com/grafana/grafana/cue/scuemata" + "github.com/grafana/grafana/cue/scuemata" ) Family: scuemata.#Family & { lineages: [ [ - { // 0.0 - // Unique numeric identifier for the dashboard. - // TODO must isolate or remove identifiers local to a Grafana instance...? - id?: number - // Unique dashboard identifier that can be generated by anyone. string (8-40) - uid?: string - // Title of dashboard. - title?: string - // Description of dashboard. - description?: string + {// 0.0 + // Unique numeric identifier for the dashboard. + // TODO must isolate or remove identifiers local to a Grafana instance...? + id?: number + // Unique dashboard identifier that can be generated by anyone. string (8-40) + uid?: string + // Title of dashboard. + title?: string + // Description of dashboard. + description?: string - gnetId?: string - // Tags associated with dashboard. - tags?: [...string] - // Theme of dashboard. - style: *"light" | "dark" - // Timezone of dashboard, - timezone?: *"browser" | "utc" | "" - // Whether a dashboard is editable or not. - editable: bool | *true - // 0 for no shared crosshair or tooltip (default). - // 1 for shared crosshair. - // 2 for shared crosshair AND shared tooltip. - graphTooltip: >=0 & <=2 | *0 - // Time range for dashboard, e.g. last 6 hours, last 7 days, etc - time?: { - from: string | *"now-6h" - to: string | *"now" - } - // Timepicker metadata. - timepicker?: { - // Whether timepicker is collapsed or not. - collapse: bool | *false - // Whether timepicker is enabled or not. - enable: bool | *true - // Whether timepicker is visible or not. - hidden: bool | *false - // Selectable intervals for auto-refresh. - refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] - } - // Templating. - templating?: list: [...{...}] - // Annotations. - annotations?: list: [...{ - builtIn: number | *0 - // Datasource to use for annotation. - datasource: string - // Whether annotation is enabled. - enable: bool | *true - // Whether to hide annotation. - hide?: bool | *false - // Annotation icon color. - iconColor?: string - // Name of annotation. - name?: string - type: string | *"dashboard" - // Query for annotation data. - rawQuery?: string - showIn: number | *0 - }] - // Auto-refresh interval. - refresh?: string | false - // Version of the JSON schema, incremented each time a Grafana update brings - // changes to said schema. - // FIXME this is the old schema numbering system, and will be replaced by scuemata - schemaVersion: number | *30 - // Version of the dashboard, incremented each time the dashboard is updated. - version?: number - panels?: [...(#Panel | #GraphPanel | #RowPanel)] + gnetId?: string + // Tags associated with dashboard. + tags?: [...string] + // Theme of dashboard. + style: *"light" | "dark" + // Timezone of dashboard, + timezone?: *"browser" | "utc" | "" + // Whether a dashboard is editable or not. + editable: bool | *true + // 0 for no shared crosshair or tooltip (default). + // 1 for shared crosshair. + // 2 for shared crosshair AND shared tooltip. + graphTooltip: >=0 & <=2 | *0 + // Time range for dashboard, e.g. last 6 hours, last 7 days, etc + time?: { + from: string | *"now-6h" + to: string | *"now" + } + // Timepicker metadata. + timepicker?: { + // Whether timepicker is collapsed or not. + collapse: bool | *false + // Whether timepicker is enabled or not. + enable: bool | *true + // Whether timepicker is visible or not. + hidden: bool | *false + // Selectable intervals for auto-refresh. + refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + } + // Templating. + templating?: list: [...{...}] + // Annotations. + annotations?: list: [...{ + builtIn: number | *0 + // Datasource to use for annotation. + datasource: { + type?: string + uid?: string + } + // Whether annotation is enabled. + enable: bool | *true + // Whether to hide annotation. + hide?: bool | *false + // Annotation icon color. + iconColor?: string + // Name of annotation. + name?: string + type: string | *"dashboard" + // Query for annotation data. + rawQuery?: string + showIn: number | *0 + }] + // Auto-refresh interval. + refresh?: string | false + // Version of the JSON schema, incremented each time a Grafana update brings + // changes to said schema. + // FIXME this is the old schema numbering system, and will be replaced by scuemata + schemaVersion: number | *30 + // Version of the dashboard, incremented each time the dashboard is updated. + version?: number + panels?: [...(#Panel | #GraphPanel | #RowPanel)] - // TODO docs - #FieldColorModeId: "thresholds" | "palette-classic" | "palette-saturated" | "continuous-GrYlRd" | "fixed" @cuetsy(kind="enum") + // TODO docs + #FieldColorModeId: "thresholds" | "palette-classic" | "palette-saturated" | "continuous-GrYlRd" | "fixed" @cuetsy(kind="enum") - // TODO docs - #FieldColorSeriesByMode: "min" | "max" | "last" @cuetsy(kind="type") + // TODO docs + #FieldColorSeriesByMode: "min" | "max" | "last" @cuetsy(kind="type") - // TODO docs - #FieldColor: { - // The main color scheme mode - mode: #FieldColorModeId | string - // Stores the fixed color value if mode is fixed - fixedColor?: string - // Some visualizations need to know how to assign a series color from by value color schemes - seriesBy?: #FieldColorSeriesByMode - } @cuetsy(kind="interface") + // TODO docs + #FieldColor: { + // The main color scheme mode + mode: #FieldColorModeId | string + // Stores the fixed color value if mode is fixed + fixedColor?: string + // Some visualizations need to know how to assign a series color from by value color schemes + seriesBy?: #FieldColorSeriesByMode + } @cuetsy(kind="interface") - // TODO docs - #Threshold: { - // TODO docs - // FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON - value?: number - // TODO docs - color: string - // TODO docs - // TODO are the values here enumerable into a disjunction? - // Some seem to be listed in typescript comment - state?: string - } @cuetsy(kind="interface") + // TODO docs + #Threshold: { + // TODO docs + // FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON + value?: number + // TODO docs + color: string + // TODO docs + // TODO are the values here enumerable into a disjunction? + // Some seem to be listed in typescript comment + state?: string + } @cuetsy(kind="interface") - #ThresholdsMode: "absolute" | "percentage" @cuetsy(kind="enum") + #ThresholdsMode: "absolute" | "percentage" @cuetsy(kind="enum") - #ThresholdsConfig: { - mode: #ThresholdsMode + #ThresholdsConfig: { + mode: #ThresholdsMode - // Must be sorted by 'value', first value is always -Infinity - steps: [...#Threshold] - } @cuetsy(kind="interface") + // Must be sorted by 'value', first value is always -Infinity + steps: [...#Threshold] + } @cuetsy(kind="interface") - // TODO docs - // FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it - #Transformation: { - id: string - options: {...} - } + // TODO docs + // FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it + #Transformation: { + id: string + options: {...} + } - // Schema for panel targets is specified by datasource - // plugins. We use a placeholder definition, which the Go - // schema loader either left open/as-is with the Base - // variant of the Dashboard and Panel families, or filled - // with types derived from plugins in the Instance variant. - // When working directly from CUE, importers can extend this - // type directly to achieve the same effect. - #Target: {...} + // Schema for panel targets is specified by datasource + // plugins. We use a placeholder definition, which the Go + // schema loader either left open/as-is with the Base + // variant of the Dashboard and Panel families, or filled + // with types derived from plugins in the Instance variant. + // When working directly from CUE, importers can extend this + // type directly to achieve the same effect. + #Target: {...} - // Dashboard panels. Panels are canonically defined inline - // because they share a version timeline with the dashboard - // schema; they do not evolve independently. - #Panel: { - // The panel plugin type id. - type: !="" + // Dashboard panels. Panels are canonically defined inline + // because they share a version timeline with the dashboard + // schema; they do not evolve independently. + #Panel: { + // The panel plugin type id. + type: !="" - // TODO docs - id?: number + // TODO docs + id?: number - // FIXME this almost certainly has to be changed in favor of scuemata versions - pluginVersion?: string + // FIXME this almost certainly has to be changed in favor of scuemata versions + pluginVersion?: string - // TODO docs - tags?: [...string] + // TODO docs + tags?: [...string] - // Internal - the exact major and minor versions of the panel plugin - // schema. Hidden and therefore not a part of the data model, but - // expected to be filled with panel plugin schema versions so that it's - // possible to figure out which schema version matched on a successful - // unification. - // _pv: { maj: int, min: int } - // The major and minor versions of the panel plugin for this schema. - // TODO 2-tuple list instead of struct? - // panelSchema?: { maj: number, min: number } - panelSchema?: [number, number] + // Internal - the exact major and minor versions of the panel plugin + // schema. Hidden and therefore not a part of the data model, but + // expected to be filled with panel plugin schema versions so that it's + // possible to figure out which schema version matched on a successful + // unification. + // _pv: { maj: int, min: int } + // The major and minor versions of the panel plugin for this schema. + // TODO 2-tuple list instead of struct? + // panelSchema?: { maj: number, min: number } + panelSchema?: [number, number] - // TODO docs - targets?: [...#Target] + // TODO docs + targets?: [...#Target] - // Panel title. - title?: string - // Description. - description?: string - // Whether to display the panel without a background. - transparent: bool | *false - // The datasource used in all targets. - datasource?: { - type?: string - uid?: string - } - // Grid position. - gridPos?: { - // Panel - h: number & >0 | *9 - // Panel - w: number & >0 & <=24 | *12 - // Panel x - x: number & >=0 & <24 | *0 - // Panel y - y: number & >=0 | *0 - // true if fixed - static?: bool - } - // Panel links. - // FIXME this is temporarily specified as a closed list so - // that validation will pass when no links are present, but - // to force a failure as soon as it's checked against there - // being anything in the list so it can be fixed in - // accordance with that object - links?: [] + // Panel title. + title?: string + // Description. + description?: string + // Whether to display the panel without a background. + transparent: bool | *false + // The datasource used in all targets. + datasource?: { + type?: string + uid?: string + } + // Grid position. + gridPos?: { + // Panel + h: number & >0 | *9 + // Panel + w: number & >0 & <=24 | *12 + // Panel x + x: number & >=0 & <24 | *0 + // Panel y + y: number & >=0 | *0 + // true if fixed + static?: bool + } + // Panel links. + // FIXME this is temporarily specified as a closed list so + // that validation will pass when no links are present, but + // to force a failure as soon as it's checked against there + // being anything in the list so it can be fixed in + // accordance with that object + links?: [] - // Name of template variable to repeat for. - repeat?: string - // Direction to repeat in if 'repeat' is set. - // "h" for horizontal, "v" for vertical. - repeatDirection: *"h" | "v" + // Name of template variable to repeat for. + repeat?: string + // Direction to repeat in if 'repeat' is set. + // "h" for horizontal, "v" for vertical. + repeatDirection: *"h" | "v" - // TODO docs - maxDataPoints?: number + // TODO docs + maxDataPoints?: number - // TODO docs - thresholds?: [...] + // TODO docs + thresholds?: [...] - // TODO docs - timeRegions?: [...] + // TODO docs + timeRegions?: [...] - transformations: [...#Transformation] + transformations: [...#Transformation] - // TODO docs - // TODO tighter constraint - interval?: string + // TODO docs + // TODO tighter constraint + interval?: string - // TODO docs - // TODO tighter constraint - timeFrom?: string + // TODO docs + // TODO tighter constraint + timeFrom?: string - // TODO docs - // TODO tighter constraint - timeShift?: string + // TODO docs + // TODO tighter constraint + timeShift?: string - // options is specified by the PanelOptions field in panel - // plugin schemas. - options: {} + // options is specified by the PanelOptions field in panel + // plugin schemas. + options: {} - fieldConfig: { - defaults: { - // The display value for this field. This supports template variables blank is auto - displayName?: string + fieldConfig: { + defaults: { + // The display value for this field. This supports template variables blank is auto + displayName?: string - // This can be used by data sources that return and explicit naming structure for values and labels - // When this property is configured, this value is used rather than the default naming strategy. - displayNameFromDS?: string + // This can be used by data sources that return and explicit naming structure for values and labels + // When this property is configured, this value is used rather than the default naming strategy. + displayNameFromDS?: string - // Human readable field metadata - description?: string + // Human readable field metadata + description?: string - // An explict path to the field in the datasource. When the frame meta includes a path, - // This will default to `${frame.meta.path}/${field.name} - // - // When defined, this value can be used as an identifier within the datasource scope, and - // may be used to update the results - path?: string + // An explict path to the field in the datasource. When the frame meta includes a path, + // This will default to `${frame.meta.path}/${field.name} + // + // When defined, this value can be used as an identifier within the datasource scope, and + // may be used to update the results + path?: string - // True if data source can write a value to the path. Auth/authz are supported separately - writeable?: bool + // True if data source can write a value to the path. Auth/authz are supported separately + writeable?: bool - // True if data source field supports ad-hoc filters - filterable?: bool + // True if data source field supports ad-hoc filters + filterable?: bool - // Numeric Options - unit?: string + // Numeric Options + unit?: string - // Significant digits (for display) - decimals?: number + // Significant digits (for display) + decimals?: number - min?: number - max?: number + min?: number + max?: number - // Convert input values into a display string - // - // TODO this one corresponds to a complex type with - // generics on the typescript side. Ouch. Will - // either need special care, or we'll just need to - // accept a very loosely specified schema. It's very - // unlikely we'll be able to translate cue to - // typescript generics in the general case, though - // this particular one *may* be able to work. - mappings?: [...{...}] + // Convert input values into a display string + // + // TODO this one corresponds to a complex type with + // generics on the typescript side. Ouch. Will + // either need special care, or we'll just need to + // accept a very loosely specified schema. It's very + // unlikely we'll be able to translate cue to + // typescript generics in the general case, though + // this particular one *may* be able to work. + mappings?: [...{...}] - // Map numeric values to states - thresholds?: #ThresholdsConfig + // Map numeric values to states + thresholds?: #ThresholdsConfig - // // Map values to a display color - color?: #FieldColor + // // Map values to a display color + color?: #FieldColor - // // Used when reducing field values - // nullValueMode?: NullValueMode + // // Used when reducing field values + // nullValueMode?: NullValueMode - // // The behavior when clicking on a result - links?: [...] + // // The behavior when clicking on a result + links?: [...] - // Alternative to empty string - noValue?: string + // Alternative to empty string + noValue?: string - // custom is specified by the PanelFieldConfig field - // in panel plugin schemas. - custom?: {} - } - overrides: [...{ - matcher: { - id: string | *"" - options?: _ - } - properties: [...{ - id: string | *"" - value?: _ - }] - }] - } - // Embed the disjunction of all injected panel schema, if any were injected. - if len(compose._panelSchemas) > 0 { - or(compose._panelSchemas) // TODO try to stick graph in here - } + // custom is specified by the PanelFieldConfig field + // in panel plugin schemas. + custom?: {} + } + overrides: [...{ + matcher: { + id: string | *"" + options?: _ + } + properties: [...{ + id: string | *"" + value?: _ + }] + }] + } + // Embed the disjunction of all injected panel schema, if any were injected. + if len(compose._panelSchemas) > 0 { + or(compose._panelSchemas)// TODO try to stick graph in here + } - // Make the plugin-composed subtrees open if the panel is - // of unknown types. This is important in every possible case: - // - Base (this file only): no real dashboard json - // containing any panels would ever validate - // - Dist (this file + core plugin schema): dashboard json containing - // panels with any third-party panel plugins would fail to validate, - // as well as any core plugins lacking a models.cue. The latter case - // is not normally expected, but this is not the appropriate place - // to enforce the invariant, anyway. - // - Instance (this file + core + third-party plugin schema): dashboard - // json containing panels with a third-party plugin that exists but - // is not currently installed would fail to validate. - if !list.Contains(compose._panelTypes, type) { - options: {...} - fieldConfig: defaults: custom: {...} - ... - } - } + // Make the plugin-composed subtrees open if the panel is + // of unknown types. This is important in every possible case: + // - Base (this file only): no real dashboard json + // containing any panels would ever validate + // - Dist (this file + core plugin schema): dashboard json containing + // panels with any third-party panel plugins would fail to validate, + // as well as any core plugins lacking a models.cue. The latter case + // is not normally expected, but this is not the appropriate place + // to enforce the invariant, anyway. + // - Instance (this file + core + third-party plugin schema): dashboard + // json containing panels with a third-party plugin that exists but + // is not currently installed would fail to validate. + if !list.Contains(compose._panelTypes, type) { + options: {...} + fieldConfig: defaults: custom: {...} + ... + } + } - // Row panel - #RowPanel: { - type: "row" - collapsed: bool | *false - title?: string + // Row panel + #RowPanel: { + type: "row" + collapsed: bool | *false + title?: string - // Name of default datasource. - datasource?: string + // Name of default datasource. + datasource: { + type?: string + uid?: string + } - gridPos?: { - // Panel - h: number & >0 | *9 - // Panel - w: number & >0 & <=24 | *12 - // Panel x - x: number & >=0 & <24 | *0 - // Panel y - y: number & >=0 | *0 - // true if fixed - static?: bool - } - id: number - panels: [...(#Panel | #GraphPanel)] - // Name of template variable to repeat for. - repeat?: string - } - // Support for legacy graph panels. - #GraphPanel: { - ... - type: "graph" - thresholds: [...{...}] - timeRegions?: [...{...}] - seriesOverrides: [...{...}] - aliasColors?: [string]: string - bars: bool | *false - dashes: bool | *false - dashLength: number | *10 - fill?: number - fillGradient?: number - hiddenSeries: bool | *false - legend: {...} - lines: bool | *false - linewidth?: number - nullPointMode: *"null" | "connected" | "null as zero" - percentage: bool | *false - points: bool | *false - pointradius?: number - renderer: string - spaceLength: number | *10 - stack: bool | *false - steppedLine: bool | *false - tooltip?: { - shared?: bool - sort: number | *0 - value_type: *"individual" | "cumulative" - } - } - } - ] + gridPos?: { + // Panel + h: number & >0 | *9 + // Panel + w: number & >0 & <=24 | *12 + // Panel x + x: number & >=0 & <24 | *0 + // Panel y + y: number & >=0 | *0 + // true if fixed + static?: bool + } + id: number + panels: [...(#Panel | #GraphPanel)] + // Name of template variable to repeat for. + repeat?: string + } + + // Support for legacy graph panels. + #GraphPanel: { + ... + type: "graph" + thresholds: [...{...}] + timeRegions?: [...{...}] + seriesOverrides: [...{...}] + aliasColors?: [string]: string + bars: bool | *false + dashes: bool | *false + dashLength: number | *10 + fill?: number + fillGradient?: number + hiddenSeries: bool | *false + legend: {...} + lines: bool | *false + linewidth?: number + nullPointMode: *"null" | "connected" | "null as zero" + percentage: bool | *false + points: bool | *false + pointradius?: number + renderer: string + spaceLength: number | *10 + stack: bool | *false + steppedLine: bool | *false + tooltip?: { + shared?: bool + sort: number | *0 + value_type: *"individual" | "cumulative" + } + } + }, + ], ] - compose: { - // Scuemata families for all panel types that should be composed into the - // dashboard schema. - Panel: [string]: scuemata.#PanelFamily + compose: { + // Scuemata families for all panel types that should be composed into the + // dashboard schema. + Panel: [string]: scuemata.#PanelFamily - // _panelTypes: [for typ, _ in Panel {typ}] - _panelTypes: [for typ, _ in Panel {typ}, "graph", "row"] - _panelSchemas: [for typ, scue in Panel { - for lv, lin in scue.lineages { - for sv, sch in lin { - (_mapPanel & {arg: { - type: typ - v: [lv, sv] // TODO add optionality for exact, at least, at most, any - model: sch // TODO Does this need to be close()d? - }}).out - } - } - }, { type: string }] - _mapPanel: { - arg: { - type: string & !="" - v: [number, number] - model: {...} - } - // Until CUE introduces the must() constraint, we have to enforce - // that the input model is as expected by checking for unification - // in this hidden property (see https://github.com/cue-lang/cue/issues/575). - // If we unified arg.model with the scuemata.#PanelSchema - // meta-schema directly, the struct openness (PanelOptions: {...}) - // would be applied to the actual schema instance in the arg. Here, - // where we're actually putting those in the dashboard schema, want - // those to be closed, or at least preserve closed-ness. - _checkSchema: scuemata.#PanelSchema & arg.model - out: { - type: arg.type - panelSchema: arg.v // TODO add optionality for exact, at least, at most, any - options: arg.model.PanelOptions - fieldConfig: defaults: custom: {} - if arg.model.PanelFieldConfig != _|_ { - fieldConfig: defaults: custom: arg.model.PanelFieldConfig - } - } - } - } + // _panelTypes: [for typ, _ in Panel {typ}] + _panelTypes: [ for typ, _ in Panel {typ}, "graph", "row"] + _panelSchemas: [ for typ, scue in Panel { + for lv, lin in scue.lineages { + for sv, sch in lin { + (_mapPanel & {arg: { + type: typ + v: [lv, sv] // TODO add optionality for exact, at least, at most, any + model: sch // TODO Does this need to be close()d? + }}).out + } + } + }, {type: string}] + _mapPanel: { + arg: { + type: string & !="" + v: [number, number] + model: {...} + } + // Until CUE introduces the must() constraint, we have to enforce + // that the input model is as expected by checking for unification + // in this hidden property (see https://github.com/cue-lang/cue/issues/575). + // If we unified arg.model with the scuemata.#PanelSchema + // meta-schema directly, the struct openness (PanelOptions: {...}) + // would be applied to the actual schema instance in the arg. Here, + // where we're actually putting those in the dashboard schema, want + // those to be closed, or at least preserve closed-ness. + _checkSchema: scuemata.#PanelSchema & arg.model + out: { + type: arg.type + panelSchema: arg.v // TODO add optionality for exact, at least, at most, any + options: arg.model.PanelOptions + fieldConfig: defaults: custom: {} + if arg.model.PanelFieldConfig != _|_ { + fieldConfig: defaults: custom: arg.model.PanelFieldConfig + } + } + } + } } diff --git a/pkg/coremodel/dashboard/lineage.cue b/pkg/coremodel/dashboard/lineage.cue index 1910539d5a6..bd1c1db4f2e 100644 --- a/pkg/coremodel/dashboard/lineage.cue +++ b/pkg/coremodel/dashboard/lineage.cue @@ -7,338 +7,345 @@ name: "dashboard" seqs: [ { schemas: [ - { // 0.0 - // Unique numeric identifier for the dashboard. - // TODO must isolate or remove identifiers local to a Grafana instance...? - id?: int64 - // Unique dashboard identifier that can be generated by anyone. string (8-40) - uid?: string - // Title of dashboard. - title?: string - // Description of dashboard. - description?: string + {// 0.0 + // Unique numeric identifier for the dashboard. + // TODO must isolate or remove identifiers local to a Grafana instance...? + id?: int64 + // Unique dashboard identifier that can be generated by anyone. string (8-40) + uid?: string + // Title of dashboard. + title?: string + // Description of dashboard. + description?: string - gnetId?: string - // Tags associated with dashboard. - tags?: [...string] - // Theme of dashboard. - style: *"light" | "dark" - // Timezone of dashboard, - timezone?: *"browser" | "utc" | "" - // Whether a dashboard is editable or not. - editable: bool | *true - // 0 for no shared crosshair or tooltip (default). - // 1 for shared crosshair. - // 2 for shared crosshair AND shared tooltip. - graphTooltip: uint8 & >=0 & <=2 | *0 - // Time range for dashboard, e.g. last 6 hours, last 7 days, etc - time?: { - from: string | *"now-6h" - to: string | *"now" - } - // Timepicker metadata. - timepicker?: { - // Whether timepicker is collapsed or not. - collapse: bool | *false - // Whether timepicker is enabled or not. - enable: bool | *true - // Whether timepicker is visible or not. - hidden: bool | *false - // Selectable intervals for auto-refresh. - refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] - } - // Templating. - templating?: list: [...{...}] - // Annotations. - annotations?: list: [...{ - builtIn: uint8 | *0 - // Datasource to use for annotation. - datasource: string - // Whether annotation is enabled. - enable: bool | *true - // Whether to hide annotation. - hide?: bool | *false - // Annotation icon color. - iconColor?: string - // Name of annotation. - name?: string - type: string | *"dashboard" - // Query for annotation data. - rawQuery?: string - showIn: uint8 | *0 - }] - // Auto-refresh interval. - refresh?: string | false - // Version of the JSON schema, incremented each time a Grafana update brings - // changes to said schema. - // FIXME this is the old schema numbering system, and will be replaced by Thema's themaVersion - schemaVersion: uint16 | *33 - // Version of the dashboard, incremented each time the dashboard is updated. - version?: uint32 - panels?: [...(#Panel | #GraphPanel | #HeatmapPanel | #RowPanel)] + gnetId?: string + // Tags associated with dashboard. + tags?: [...string] + // Theme of dashboard. + style: *"light" | "dark" + // Timezone of dashboard, + timezone?: *"browser" | "utc" | "" + // Whether a dashboard is editable or not. + editable: bool | *true + // 0 for no shared crosshair or tooltip (default). + // 1 for shared crosshair. + // 2 for shared crosshair AND shared tooltip. + graphTooltip: uint8 & >=0 & <=2 | *0 + // Time range for dashboard, e.g. last 6 hours, last 7 days, etc + time?: { + from: string | *"now-6h" + to: string | *"now" + } + // Timepicker metadata. + timepicker?: { + // Whether timepicker is collapsed or not. + collapse: bool | *false + // Whether timepicker is enabled or not. + enable: bool | *true + // Whether timepicker is visible or not. + hidden: bool | *false + // Selectable intervals for auto-refresh. + refresh_intervals: [...string] | *["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + } + // Templating. + templating?: list: [...{...}] + // Annotations. + annotations?: list: [...{ + builtIn: uint8 | *0 + // Datasource to use for annotation. + datasource: { + type?: string + uid?: string + } + // Whether annotation is enabled. + enable: bool | *true + // Whether to hide annotation. + hide?: bool | *false + // Annotation icon color. + iconColor?: string + // Name of annotation. + name?: string + type: string | *"dashboard" + // Query for annotation data. + rawQuery?: string + showIn: uint8 | *0 + }] + // Auto-refresh interval. + refresh?: string | false + // Version of the JSON schema, incremented each time a Grafana update brings + // changes to said schema. + // FIXME this is the old schema numbering system, and will be replaced by Thema's themaVersion + schemaVersion: uint16 | *33 + // Version of the dashboard, incremented each time the dashboard is updated. + version?: uint32 + panels?: [...(#Panel | #GraphPanel | #HeatmapPanel | #RowPanel)] - // TODO docs - #FieldColorModeId: "thresholds" | "palette-classic" | "palette-saturated" | "continuous-GrYlRd" | "fixed" @cuetsy(kind="enum") + // TODO docs + #FieldColorModeId: "thresholds" | "palette-classic" | "palette-saturated" | "continuous-GrYlRd" | "fixed" @cuetsy(kind="enum") - // TODO docs - #FieldColorSeriesByMode: "min" | "max" | "last" @cuetsy(kind="type") + // TODO docs + #FieldColorSeriesByMode: "min" | "max" | "last" @cuetsy(kind="type") - // TODO docs - #FieldColor: { - // The main color scheme mode - mode: #FieldColorModeId | string - // Stores the fixed color value if mode is fixed - fixedColor?: string - // Some visualizations need to know how to assign a series color from by value color schemes - seriesBy?: #FieldColorSeriesByMode - } @cuetsy(kind="interface") + // TODO docs + #FieldColor: { + // The main color scheme mode + mode: #FieldColorModeId | string + // Stores the fixed color value if mode is fixed + fixedColor?: string + // Some visualizations need to know how to assign a series color from by value color schemes + seriesBy?: #FieldColorSeriesByMode + } @cuetsy(kind="interface") - // TODO docs - #Threshold: { - // TODO docs - // FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON - value?: number - // TODO docs - color: string - // TODO docs - // TODO are the values here enumerable into a disjunction? - // Some seem to be listed in typescript comment - state?: string - } @cuetsy(kind="interface") + // TODO docs + #Threshold: { + // TODO docs + // FIXME the corresponding typescript field is required/non-optional, but nulls currently appear here when serializing -Infinity to JSON + value?: number + // TODO docs + color: string + // TODO docs + // TODO are the values here enumerable into a disjunction? + // Some seem to be listed in typescript comment + state?: string + } @cuetsy(kind="interface") - #ThresholdsMode: "absolute" | "percentage" @cuetsy(kind="enum") + #ThresholdsMode: "absolute" | "percentage" @cuetsy(kind="enum") - #ThresholdsConfig: { - mode: #ThresholdsMode + #ThresholdsConfig: { + mode: #ThresholdsMode - // Must be sorted by 'value', first value is always -Infinity - steps: [...#Threshold] - } @cuetsy(kind="interface") + // Must be sorted by 'value', first value is always -Infinity + steps: [...#Threshold] + } @cuetsy(kind="interface") - // TODO docs - // FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it - #Transformation: { - id: string - options: {...} - } + // TODO docs + // FIXME this is extremely underspecfied; wasn't obvious which typescript types corresponded to it + #Transformation: { + id: string + options: {...} + } - // Schema for panel targets is specified by datasource - // plugins. We use a placeholder definition, which the Go - // schema loader either left open/as-is with the Base - // variant of the Dashboard and Panel families, or filled - // with types derived from plugins in the Instance variant. - // When working directly from CUE, importers can extend this - // type directly to achieve the same effect. - #Target: {...} + // Schema for panel targets is specified by datasource + // plugins. We use a placeholder definition, which the Go + // schema loader either left open/as-is with the Base + // variant of the Dashboard and Panel families, or filled + // with types derived from plugins in the Instance variant. + // When working directly from CUE, importers can extend this + // type directly to achieve the same effect. + #Target: {...} - // Dashboard panels. Panels are canonically defined inline - // because they share a version timeline with the dashboard - // schema; they do not evolve independently. - #Panel: { - // The panel plugin type id. - type: !="" + // Dashboard panels. Panels are canonically defined inline + // because they share a version timeline with the dashboard + // schema; they do not evolve independently. + #Panel: { + // The panel plugin type id. + type: !="" - // TODO docs - id?: uint32 + // TODO docs + id?: uint32 - // FIXME this almost certainly has to be changed in favor of scuemata versions - pluginVersion?: string + // FIXME this almost certainly has to be changed in favor of scuemata versions + pluginVersion?: string - // TODO docs - tags?: [...string] + // TODO docs + tags?: [...string] - // Internal - the exact major and minor versions of the panel plugin - // schema. Hidden and therefore not a part of the data model, but - // expected to be filled with panel plugin schema versions so that it's - // possible to figure out which schema version matched on a successful - // unification. - // _pv: { maj: int, min: int } - // The major and minor versions of the panel plugin for this schema. - // TODO 2-tuple list instead of struct? - // panelSchema?: { maj: number, min: number } - panelSchema?: [number, number] + // Internal - the exact major and minor versions of the panel plugin + // schema. Hidden and therefore not a part of the data model, but + // expected to be filled with panel plugin schema versions so that it's + // possible to figure out which schema version matched on a successful + // unification. + // _pv: { maj: int, min: int } + // The major and minor versions of the panel plugin for this schema. + // TODO 2-tuple list instead of struct? + // panelSchema?: { maj: number, min: number } + panelSchema?: [number, number] - // TODO docs - targets?: [...#Target] + // TODO docs + targets?: [...#Target] - // Panel title. - title?: string - // Description. - description?: string - // Whether to display the panel without a background. - transparent: bool | *false - // The datasource used in all targets. - datasource?: { - type?: string - uid?: string - } - // Grid position. - gridPos?: { - // Panel - h: uint32 & >0 | *9 - // Panel - w: uint32 & >0 & <=24 | *12 - // Panel x - x: uint32 & >=0 & <24 | *0 - // Panel y - y: uint32 & >=0 | *0 - // true if fixed - static?: bool - } - // Panel links. - // FIXME this is temporarily specified as a closed list so - // that validation will pass when no links are present, but - // to force a failure as soon as it's checked against there - // being anything in the list so it can be fixed in - // accordance with that object - links?: [] + // Panel title. + title?: string + // Description. + description?: string + // Whether to display the panel without a background. + transparent: bool | *false + // The datasource used in all targets. + datasource?: { + type?: string + uid?: string + } + // Grid position. + gridPos?: { + // Panel + h: uint32 & >0 | *9 + // Panel + w: uint32 & >0 & <=24 | *12 + // Panel x + x: uint32 & >=0 & <24 | *0 + // Panel y + y: uint32 & >=0 | *0 + // true if fixed + static?: bool + } + // Panel links. + // FIXME this is temporarily specified as a closed list so + // that validation will pass when no links are present, but + // to force a failure as soon as it's checked against there + // being anything in the list so it can be fixed in + // accordance with that object + links?: [] - // Name of template variable to repeat for. - repeat?: string - // Direction to repeat in if 'repeat' is set. - // "h" for horizontal, "v" for vertical. - repeatDirection: *"h" | "v" + // Name of template variable to repeat for. + repeat?: string + // Direction to repeat in if 'repeat' is set. + // "h" for horizontal, "v" for vertical. + repeatDirection: *"h" | "v" - // TODO docs - maxDataPoints?: number + // TODO docs + maxDataPoints?: number - // TODO docs - thresholds?: [...] + // TODO docs + thresholds?: [...] - // TODO docs - timeRegions?: [...] + // TODO docs + timeRegions?: [...] - transformations: [...#Transformation] + transformations: [...#Transformation] - // TODO docs - // TODO tighter constraint - interval?: string + // TODO docs + // TODO tighter constraint + interval?: string - // TODO docs - // TODO tighter constraint - timeFrom?: string + // TODO docs + // TODO tighter constraint + timeFrom?: string - // TODO docs - // TODO tighter constraint - timeShift?: string + // TODO docs + // TODO tighter constraint + timeShift?: string - // options is specified by the PanelOptions field in panel - // plugin schemas. - options: {...} + // options is specified by the PanelOptions field in panel + // plugin schemas. + options: {...} - fieldConfig: { - defaults: { - // The display value for this field. This supports template variables blank is auto - displayName?: string + fieldConfig: { + defaults: { + // The display value for this field. This supports template variables blank is auto + displayName?: string - // This can be used by data sources that return and explicit naming structure for values and labels - // When this property is configured, this value is used rather than the default naming strategy. - displayNameFromDS?: string + // This can be used by data sources that return and explicit naming structure for values and labels + // When this property is configured, this value is used rather than the default naming strategy. + displayNameFromDS?: string - // Human readable field metadata - description?: string + // Human readable field metadata + description?: string - // An explict path to the field in the datasource. When the frame meta includes a path, - // This will default to `${frame.meta.path}/${field.name} - // - // When defined, this value can be used as an identifier within the datasource scope, and - // may be used to update the results - path?: string + // An explict path to the field in the datasource. When the frame meta includes a path, + // This will default to `${frame.meta.path}/${field.name} + // + // When defined, this value can be used as an identifier within the datasource scope, and + // may be used to update the results + path?: string - // True if data source can write a value to the path. Auth/authz are supported separately - writeable?: bool + // True if data source can write a value to the path. Auth/authz are supported separately + writeable?: bool - // True if data source field supports ad-hoc filters - filterable?: bool + // True if data source field supports ad-hoc filters + filterable?: bool - // Numeric Options - unit?: string + // Numeric Options + unit?: string - // Significant digits (for display) - decimals?: number + // Significant digits (for display) + decimals?: number - min?: number - max?: number + min?: number + max?: number - // Convert input values into a display string - // - // TODO this one corresponds to a complex type with - // generics on the typescript side. Ouch. Will - // either need special care, or we'll just need to - // accept a very loosely specified schema. It's very - // unlikely we'll be able to translate cue to - // typescript generics in the general case, though - // this particular one *may* be able to work. - mappings?: [...{...}] + // Convert input values into a display string + // + // TODO this one corresponds to a complex type with + // generics on the typescript side. Ouch. Will + // either need special care, or we'll just need to + // accept a very loosely specified schema. It's very + // unlikely we'll be able to translate cue to + // typescript generics in the general case, though + // this particular one *may* be able to work. + mappings?: [...{...}] - // Map numeric values to states - thresholds?: #ThresholdsConfig + // Map numeric values to states + thresholds?: #ThresholdsConfig - // // Map values to a display color - color?: #FieldColor + // // Map values to a display color + color?: #FieldColor - // // Used when reducing field values - // nullValueMode?: NullValueMode + // // Used when reducing field values + // nullValueMode?: NullValueMode - // // The behavior when clicking on a result - links?: [...] + // // The behavior when clicking on a result + links?: [...] - // Alternative to empty string - noValue?: string + // Alternative to empty string + noValue?: string - // custom is specified by the PanelFieldConfig field - // in panel plugin schemas. - custom?: {...} - } - overrides: [...{ - matcher: { - id: string | *"" - options?: _ - } - properties: [...{ - id: string | *"" - value?: _ - }] - }] - } - } + // custom is specified by the PanelFieldConfig field + // in panel plugin schemas. + custom?: {...} + } + overrides: [...{ + matcher: { + id: string | *"" + options?: _ + } + properties: [...{ + id: string | *"" + value?: _ + }] + }] + } + } - // Row panel - #RowPanel: { - type: "row" - collapsed: bool | *false - title?: string + // Row panel + #RowPanel: { + type: "row" + collapsed: bool | *false + title?: string - // Name of default datasource. - datasource?: string + // Name of default datasource. + datasource?: { + type?: string + uid?: string + } - gridPos?: { - // Panel - h: uint32 & >0 | *9 - // Panel - w: uint32 & >0 & <=24 | *12 - // Panel x - x: uint32 & >=0 & <24 | *0 - // Panel y - y: uint32 & >=0 | *0 - // true if fixed - static?: bool - } - id: uint32 - panels: [...(#Panel | #GraphPanel | #HeatmapPanel)] - // Name of template variable to repeat for. - repeat?: string - } - // Support for legacy graph and heatmap panels. - #GraphPanel: { - ... - type: "graph" - } - #HeatmapPanel: { - ... - type: "heatmap" - } - } - ] - } + gridPos?: { + // Panel + h: uint32 & >0 | *9 + // Panel + w: uint32 & >0 & <=24 | *12 + // Panel x + x: uint32 & >=0 & <24 | *0 + // Panel y + y: uint32 & >=0 | *0 + // true if fixed + static?: bool + } + id: uint32 + panels: [...(#Panel | #GraphPanel | #HeatmapPanel)] + // Name of template variable to repeat for. + repeat?: string + } + + // Support for legacy graph and heatmap panels. + #GraphPanel: { + type: "graph" + ... + } + #HeatmapPanel: { + type: "heatmap" + ... + } + }, + ] + }, ] diff --git a/pkg/coremodel/dashboard/schema.go b/pkg/coremodel/dashboard/schema.go index 96c235a1712..c7b7abc084a 100644 --- a/pkg/coremodel/dashboard/schema.go +++ b/pkg/coremodel/dashboard/schema.go @@ -17,6 +17,15 @@ var ( currentVersion = thema.SV(0, 0) ) +// HandoffSchemaVersion is the minimum schemaVersion for dashboards at which the +// Thema-based dashboard schema is known to be valid. +// +// schemaVersion is the original version numbering system for dashboards. If a +// dashboard is below this schemaVersion, it is necessary for the frontend +// typescript dashboard migration logic to first run and get it past this +// number, after which Thema can take over. +const HandoffSchemaVersion = 36 + // Lineage returns the Thema lineage representing Grafana dashboards. The // lineage is the canonical specification of the current datasource schema, all // prior schema versions, and the mappings that allow migration between schema @@ -80,7 +89,7 @@ type model struct { SchemaVersion int `json:"schemaVersion"` Panels []interface{} `json:"panels"` - //// + // // Uid string `json:"uid"` // OrgId int64 `json:"orgId"` diff --git a/pkg/schema/load/load_test.go b/pkg/schema/load/load_test.go index 65d7655bac5..d9f96849cc0 100644 --- a/pkg/schema/load/load_test.go +++ b/pkg/schema/load/load_test.go @@ -19,6 +19,7 @@ import ( "cuelang.org/go/cue/errors" "cuelang.org/go/cue/load" cuejson "cuelang.org/go/pkg/encoding/json" + "github.com/grafana/grafana/pkg/coremodel/dashboard" "github.com/grafana/grafana/pkg/schema" "github.com/laher/mergefs" "github.com/stretchr/testify/require" @@ -48,7 +49,6 @@ var doTestAgainstDevenv = func(sch schema.VersionedCueSchema, validdir string, f b, err := os.Open(path) require.NoError(t, err, "failed to open dashboard file") - // Only try to validate dashboards with schemaVersion >= 30 jtree := make(map[string]interface{}) byt, err := io.ReadAll(b) if err != nil { @@ -59,9 +59,9 @@ var doTestAgainstDevenv = func(sch schema.VersionedCueSchema, validdir string, f t.Logf("no schemaVersion in %s", path) return nil } else { - if !(oldschemav.(float64) > 32) { + if !(oldschemav.(float64) > dashboard.HandoffSchemaVersion-1) { if testing.Verbose() { - t.Logf("schemaVersion is %v, older than 33, skipping %s", oldschemav, path) + t.Logf("schemaVersion is %v, older than %v, skipping %s", oldschemav, dashboard.HandoffSchemaVersion-1, path) } return nil } diff --git a/scripts/stripnulls.sh b/scripts/stripnulls.sh index 90a873ed50b..d64ff2fb8c9 100755 --- a/scripts/stripnulls.sh +++ b/scripts/stripnulls.sh @@ -7,7 +7,7 @@ SED=$(command -v gsed) SED=${SED:-"sed"} -FILES=$(grep -rl '"schemaVersion": 3[34]' devenv) +FILES=$(grep -rl '"schemaVersion": 3[3456]' devenv) set -e set -x for DASH in ${FILES}; do echo "${DASH}"; grep -v 'null,$' "${DASH}" > "${DASH}-nulless"; mv "${DASH}-nulless" "${DASH}"; done From 516c8b60ee0b706b14c5c503b7c7c80cab472e76 Mon Sep 17 00:00:00 2001 From: Joe Blubaugh Date: Mon, 18 Apr 2022 10:42:45 +0800 Subject: [PATCH 31/40] Unified Alerting: Stable order for state history annotations (#47674) This change sorts the State History list returned by the backend by the id in addition to the timeEnd and time fields by which it is already sorted. This results in a stable view of the State History table. Fixes #45873 Signed-off-by: Joe Blubaugh --- .../components/rules/StateHistory.test.tsx | 40 +++++++++++++++++++ .../unified/components/rules/StateHistory.tsx | 32 ++++++++++++++- 2 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 public/app/features/alerting/unified/components/rules/StateHistory.test.tsx diff --git a/public/app/features/alerting/unified/components/rules/StateHistory.test.tsx b/public/app/features/alerting/unified/components/rules/StateHistory.test.tsx new file mode 100644 index 00000000000..31d0bc9e188 --- /dev/null +++ b/public/app/features/alerting/unified/components/rules/StateHistory.test.tsx @@ -0,0 +1,40 @@ +import { StateHistoryItem } from 'app/types/unified-alerting'; +import { sortStateHistory } from './StateHistory'; + +describe(sortStateHistory, () => { + describe('should stably sort', () => { + describe('when timeEnd is different', () => { + it('should not sort by rule id', () => { + let data: StateHistoryItem[] = [ + { timeEnd: 23, time: 22, id: 1 } as StateHistoryItem, + { timeEnd: 22, time: 21, id: 3 } as StateHistoryItem, + { timeEnd: 22, time: 22, id: 2 } as StateHistoryItem, + { timeEnd: 24, id: 3 } as StateHistoryItem, + ]; + + data.sort(sortStateHistory); + expect(data[0].timeEnd).toBe(24); + expect(data[1].timeEnd).toBe(23); + expect(data[2].time).toBe(22); + expect(data[3].id).toBe(3); + }); + }); + + describe('when only the rule id is different', () => { + it('should sort by rule id', () => { + let data: StateHistoryItem[] = [ + { timeEnd: 23, time: 22, id: 1 } as StateHistoryItem, + { timeEnd: 23, time: 22, id: 3 } as StateHistoryItem, + { timeEnd: 23, time: 22, id: 2 } as StateHistoryItem, + { timeEnd: 23, time: 22, id: 6 } as StateHistoryItem, + ]; + + data.sort(sortStateHistory); + expect(data[0].id).toBe(6); + expect(data[1].id).toBe(3); + expect(data[2].id).toBe(2); + expect(data[3].id).toBe(1); + }); + }); + }); +}); diff --git a/public/app/features/alerting/unified/components/rules/StateHistory.tsx b/public/app/features/alerting/unified/components/rules/StateHistory.tsx index f7050eb198c..75d17e883fe 100644 --- a/public/app/features/alerting/unified/components/rules/StateHistory.tsx +++ b/public/app/features/alerting/unified/components/rules/StateHistory.tsx @@ -24,6 +24,32 @@ interface RuleStateHistoryProps { alertId: string; } +function sortStateHistory(a: StateHistoryItem, b: StateHistoryItem): number { + const compareDesc = (a: number, b: number): number => { + // Larger numbers first. + if (a > b) { + return -1; + } + + if (b > a) { + return 1; + } + return 0; + }; + + const endNeq = compareDesc(a.timeEnd, b.timeEnd); + if (endNeq) { + return endNeq; + } + + const timeNeq = compareDesc(a.time, b.time); + if (timeNeq) { + return timeNeq; + } + + return compareDesc(a.id, b.id); +} + const StateHistory: FC = ({ alertId }) => { const { loading, error, result = [] } = useManagedAlertStateHistory(alertId); @@ -42,6 +68,7 @@ const StateHistory: FC = ({ alertId }) => { ]; const items: StateHistoryRow[] = result + .sort(sortStateHistory) .reduce((acc: StateHistoryRowItem[], item, index) => { acc.push({ id: String(item.id), @@ -123,4 +150,7 @@ function hasMatchingPrecedingState(index: number, items: StateHistoryItem[]): bo return previousHistoryItem.newState === currentHistoryItem.prevState; } -export { StateHistory }; +export { + StateHistory, + sortStateHistory, // exported for testing. +}; From 858a1bd24e58bcb2627d58949f94574bef34395e Mon Sep 17 00:00:00 2001 From: Adela Almasan <88068998+adela-almasan@users.noreply.github.com> Date: Mon, 18 Apr 2022 09:19:20 -0500 Subject: [PATCH 32/40] Geomap: Add color scale in legend (#47803) Added color scale in geomap legend --- .../components/ColorScale}/ColorScale.tsx | 22 ++++--- .../app/plugins/panel/geomap/GeomapPanel.tsx | 20 ++++++- .../geomap/layers/data/MarkersLegend.tsx | 57 +++++++++++++------ .../panel/geomap/layers/data/markersLayer.tsx | 13 +++-- public/app/plugins/panel/geomap/types.ts | 3 + .../panel/heatmap-new/HeatmapPanel.tsx | 10 +++- .../app/plugins/panel/heatmap-new/module.tsx | 2 +- 7 files changed, 92 insertions(+), 35 deletions(-) rename public/app/{plugins/panel/heatmap-new => core/components/ColorScale}/ColorScale.tsx (89%) diff --git a/public/app/plugins/panel/heatmap-new/ColorScale.tsx b/public/app/core/components/ColorScale/ColorScale.tsx similarity index 89% rename from public/app/plugins/panel/heatmap-new/ColorScale.tsx rename to public/app/core/components/ColorScale/ColorScale.tsx index e21f231e94f..31080de26bd 100644 --- a/public/app/plugins/panel/heatmap-new/ColorScale.tsx +++ b/public/app/core/components/ColorScale/ColorScale.tsx @@ -11,6 +11,7 @@ type Props = { // Show a value as string -- when not defined, the raw values will not be shown display?: (v: number) => string; hoverValue?: number; + useStopsPercentage?: boolean; }; type HoverState = { @@ -19,8 +20,9 @@ type HoverState = { }; const LEFT_OFFSET = 2; +const GRADIENT_STOPS = 10; -export const ColorScale = ({ colorPalette, min, max, display, hoverValue }: Props) => { +export const ColorScale = ({ colorPalette, min, max, display, hoverValue, useStopsPercentage }: Props) => { const [colors, setColors] = useState([]); const [scaleHover, setScaleHover] = useState({ isShown: false, value: 0 }); const [percent, setPercent] = useState(null); @@ -29,8 +31,8 @@ export const ColorScale = ({ colorPalette, min, max, display, hoverValue }: Prop const styles = getStyles(theme, colors); useEffect(() => { - setColors(getGradientStops({ colorArray: colorPalette })); - }, [colorPalette]); + setColors(getGradientStops({ colorArray: colorPalette, stops: GRADIENT_STOPS, useStopsPercentage })); + }, [colorPalette, useStopsPercentage]); const onScaleMouseMove = (event: React.MouseEvent) => { const divOffset = event.nativeEvent.offsetX; @@ -79,9 +81,17 @@ export const ColorScale = ({ colorPalette, min, max, display, hoverValue }: Prop ); }; -const getGradientStops = ({ colorArray, stops = 10 }: { colorArray: string[]; stops?: number }): string[] => { +const getGradientStops = ({ + colorArray, + stops, + useStopsPercentage = true, +}: { + colorArray: string[]; + stops: number; + useStopsPercentage?: boolean; +}): string[] => { const colorCount = colorArray.length; - if (colorCount <= 20) { + if (useStopsPercentage && colorCount <= 20) { const incr = (1 / colorCount) * 100; let per = 0; const stops: string[] = []; @@ -114,8 +124,6 @@ const getStyles = (theme: GrafanaTheme2, colors: string[]) => ({ scaleWrapper: css` width: 100%; max-width: 300px; - margin-left: 25px; - padding: 10px 0; font-size: 11px; opacity: 1; `, diff --git a/public/app/plugins/panel/geomap/GeomapPanel.tsx b/public/app/plugins/panel/geomap/GeomapPanel.tsx index c49c6a37b73..420a3125a9c 100644 --- a/public/app/plugins/panel/geomap/GeomapPanel.tsx +++ b/public/app/plugins/panel/geomap/GeomapPanel.tsx @@ -33,11 +33,12 @@ import { DebugOverlay } from './components/DebugOverlay'; import { getGlobalStyles } from './globalStyles'; import { Global } from '@emotion/react'; import { GeomapHoverPayload, GeomapLayerHover } from './event'; -import { Subscription } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; import { PanelEditExitedEvent } from 'app/types/events'; import { defaultMarkersConfig, MARKERS_LAYER_ID } from './layers/data/markersLayer'; import { cloneDeep } from 'lodash'; import { GeomapTooltip } from './GeomapTooltip'; +import { FeatureLike } from 'ol/Feature'; // Allows multiple panels to share the same view instance let sharedView: View | undefined = undefined; @@ -313,7 +314,7 @@ export class GeomapPanel extends Component { this.props.eventBus.publish(new DataHoverClearEvent()); }); - // Notify the the panel editor + // Notify the panel editor if (this.panelContext.onInstanceStateChange) { this.panelContext.onInstanceStateChange({ map: this.map, @@ -371,6 +372,7 @@ export class GeomapPanel extends Component { this.map.forEachFeatureAtPixel( pixel, (feature, layer, geo) => { + const s: MapLayerState = (layer as any).__state; //match hover layer to layer in layers //check if the layer show tooltip is enabled //then also pass the list of tooltip fields if exists @@ -382,9 +384,12 @@ export class GeomapPanel extends Component { hoverPayload.data = ttip.data = frame as DataFrame; hoverPayload.rowIndex = ttip.rowIndex = props['rowIndex']; } + + if (s?.mouseEvents) { + s.mouseEvents.next(feature); + } } - const s: MapLayerState = (layer as any).__state; if (s) { let h = layerLookup.get(s); if (!h) { @@ -406,6 +411,14 @@ export class GeomapPanel extends Component { this.props.eventBus.publish(this.hoverEvent); this.setState({ ttip: { ...hoverPayload } }); + + if (!layers.length) { + // clear mouse events + this.layers.forEach((layer) => { + layer.mouseEvents.next(undefined); + }); + } + return layers.length ? true : false; }; @@ -508,6 +521,7 @@ export class GeomapPanel extends Component { options, layer, handler, + mouseEvents: new Subject(), getName: () => UID, diff --git a/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx b/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx index b403b1fb805..b1e982b68f4 100644 --- a/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx +++ b/public/app/plugins/panel/geomap/layers/data/MarkersLegend.tsx @@ -1,6 +1,6 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Label, stylesFactory, useTheme2, VizLegendItem } from '@grafana/ui'; -import { formattedValueToString, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data'; +import { DataFrame, formattedValueToString, getFieldColorModeForField, GrafanaTheme2 } from '@grafana/data'; import { css } from '@emotion/css'; import { config } from 'app/core/config'; import { DimensionSupplier } from 'app/features/dimensions'; @@ -8,26 +8,50 @@ import { getThresholdItems } from 'app/plugins/panel/state-timeline/utils'; import { getMinMaxAndDelta } from '@grafana/data/src/field/scale'; import SVG from 'react-inlinesvg'; import { StyleConfigState } from '../../style/types'; +import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; +import { useObservable } from 'react-use'; +import { of } from 'rxjs'; +import BaseLayer from 'ol/layer/Base'; +import { MapLayerState } from '../../types'; export interface MarkersLegendProps { size?: DimensionSupplier; layerName?: string; styleConfig?: StyleConfigState; + layer?: BaseLayer; } export function MarkersLegend(props: MarkersLegendProps) { - const { layerName, styleConfig } = props; + const { layerName, styleConfig, layer } = props; const theme = useTheme2(); const style = getStyles(theme); + const hoverEvent = useObservable(((layer as any)?.__state as MapLayerState)?.mouseEvents ?? of(undefined)); + + const colorField = styleConfig?.dims?.color?.field; + const hoverValue = useMemo(() => { + if (!colorField || !hoverEvent) { + return undefined; + } + + const props = hoverEvent.getProperties(); + const frame = props.frame as DataFrame; + + if (!frame) { + return undefined; + } + + const rowIndex = props.rowIndex as number; + return colorField.values.get(rowIndex); + }, [hoverEvent, colorField]); + if (!styleConfig) { return <>; } + const { color, opacity} = styleConfig?.base ?? {}; const symbol = styleConfig?.config.symbol?.fixed; - const colorField = styleConfig.dims?.color?.field; - if (color && symbol && !colorField) { return (
@@ -48,7 +72,6 @@ export function MarkersLegend(props: MarkersLegendProps) { return <>; } - const fmt = (v: any) => `${formattedValueToString(colorField.display!(v))}`; const colorMode = getFieldColorModeForField(colorField); if (colorMode.isContinuous && colorMode.getColors) { @@ -65,15 +88,15 @@ export function MarkersLegend(props: MarkersLegendProps) { // ] // }) + const display = colorField.display ? (v: number) => formattedValueToString(colorField.display!(v)) : (v: number) => `${v}`; return ( <> - -
c).join(', ')}` }} - > -
{fmt(colorRange.min)}
-
{fmt(colorRange.max)}
+
+ + +
+
+
); @@ -132,11 +155,13 @@ const getStyles = stylesFactory((theme: GrafanaTheme2) => ({ margin: auto; margin-right: 4px; `, - gradientContainer: css` + colorScaleWrapper: css` min-width: 200px; - display: flex; - justify-content: space-between; font-size: ${theme.typography.bodySmall.fontSize}; padding: ${theme.spacing(0, 0.5)}; `, + labelsWrapper: css` + display: flex; + justify-content: space-between; + ` })); diff --git a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx index 64dcb32ab0d..8641c09ce0b 100644 --- a/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx +++ b/public/app/plugins/panel/geomap/layers/data/markersLayer.tsx @@ -67,12 +67,6 @@ export const markersLayer: MapLayerRegistryItem = { ...options?.config, }; - const legendProps = new ReplaySubject(1); - let legend: ReactNode = null; - if (config.showLegend) { - legend = ; - } - const style = await getStyleConfigState(config.style); const location = await getLocationMatchers(options.location); const source = new FrameVectorSource(location); @@ -80,6 +74,12 @@ export const markersLayer: MapLayerRegistryItem = { source, }); + const legendProps = new ReplaySubject(1); + let legend: ReactNode = null; + if (config.showLegend) { + legend = ; + } + if (!style.fields) { // Set a global style vectorLayer.setStyle(style.maker(style.base)); @@ -142,6 +142,7 @@ export const markersLayer: MapLayerRegistryItem = { styleConfig: style, size: style.dims?.size, layerName: options.name, + layer: vectorLayer, }); } diff --git a/public/app/plugins/panel/geomap/types.ts b/public/app/plugins/panel/geomap/types.ts index 9944c5b5ac9..575ba4f333d 100644 --- a/public/app/plugins/panel/geomap/types.ts +++ b/public/app/plugins/panel/geomap/types.ts @@ -5,6 +5,8 @@ import BaseLayer from 'ol/layer/Base'; import Units from 'ol/proj/Units'; import { StyleConfig } from './style/types'; import { MapCenterID } from './view'; +import { Subject } from 'rxjs'; +import { FeatureLike } from 'ol/Feature'; export interface ControlsOptions { // Zoom (upper left) @@ -80,4 +82,5 @@ export interface MapLayerState extends LayerElement { layer: BaseLayer; // the openlayers instance onChange: (cfg: MapLayerOptions) => void; isBasemap?: boolean; + mouseEvents: Subject; } diff --git a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx index a76ece81a90..490f66842ff 100644 --- a/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx +++ b/public/app/plugins/panel/heatmap-new/HeatmapPanel.tsx @@ -18,7 +18,7 @@ import { quantizeScheme } from './palettes'; import { HeatmapHoverEvent, prepConfig } from './utils'; import { HeatmapHoverView } from './HeatmapHoverView'; import { CloseButton } from 'app/core/components/CloseButton/CloseButton'; -import { ColorScale } from './ColorScale'; +import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; interface HeatmapPanelProps extends PanelProps {} @@ -113,7 +113,9 @@ export const HeatmapPanel: React.FC = ({ return ( - +
+ +
); }; @@ -164,4 +166,8 @@ const getStyles = (theme: GrafanaTheme2) => ({ closeButtonSpacer: css` margin-bottom: 15px; `, + colorScaleWrapper: css` + margin-left: 25px; + padding: 10px 0; + `, }); diff --git a/public/app/plugins/panel/heatmap-new/module.tsx b/public/app/plugins/panel/heatmap-new/module.tsx index 3e9afc9e063..d425a25217d 100644 --- a/public/app/plugins/panel/heatmap-new/module.tsx +++ b/public/app/plugins/panel/heatmap-new/module.tsx @@ -15,7 +15,7 @@ import { heatmapChangedHandler } from './migrations'; import { addHeatmapCalculationOptions } from 'app/features/transformers/calculateHeatmap/editor/helper'; import { colorSchemes, quantizeScheme } from './palettes'; import { config } from '@grafana/runtime'; -import { ColorScale } from './ColorScale'; +import { ColorScale } from 'app/core/components/ColorScale/ColorScale'; export const plugin = new PanelPlugin(HeatmapPanel) .useFieldConfig() From 4f26129aa4cca09589296225aae949231fc23b78 Mon Sep 17 00:00:00 2001 From: Shirley <4163034+fridgepoet@users.noreply.github.com> Date: Mon, 18 Apr 2022 20:05:10 +0200 Subject: [PATCH 33/40] CloudWatch: Run query on blur in logs query field (#47454) * Cloudwatch: add test to ensure RunQuery is fired onBlur of LogQuery Also remove the `onBlur` prop for the `QueryField` in `LogsQueryField` since `QueryField` is configured to re-run queries `onBlur` by default. Co-authored-by: Sarah Zinger Co-authored-by: Isabella Siu Co-authored-by: Adam Simpson --- .betterer.results | 2 +- .../__mocks__/CloudWatchDataSource.ts | 2 ++ .../cloudwatch/components/LogsQueryEditor.tsx | 1 - .../components/LogsQueryField.test.tsx | 25 +++++++++++++++++++ .../cloudwatch/components/LogsQueryField.tsx | 2 +- 5 files changed, 29 insertions(+), 3 deletions(-) diff --git a/.betterer.results b/.betterer.results index e0cea5bc339..c90b157dd4a 100644 --- a/.betterer.results +++ b/.betterer.results @@ -290,7 +290,7 @@ exports[`no enzyme tests`] = { "public/app/plugins/datasource/cloudwatch/components/ConfigEditor.test.tsx:2974837543": [ [1, 19, 13, "RegExp match", "2409514259"] ], - "public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:132770839": [ + "public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx:3888529428": [ [1, 19, 13, "RegExp match", "2409514259"] ], "public/app/plugins/datasource/elasticsearch/configuration/ConfigEditor.test.tsx:1089831034": [ diff --git a/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts b/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts index 5479778b689..e5701c7898b 100644 --- a/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts +++ b/public/app/plugins/datasource/cloudwatch/__mocks__/CloudWatchDataSource.ts @@ -41,6 +41,8 @@ export function setupMockedDataSource({ data = [], variables }: { data?: any; va }, } as any ); + datasource.getVariables = () => ['test']; + datasource.getRegions = () => Promise.resolve([]); const fetchMock = jest.fn().mockReturnValue(of({ data })); setBackendSrv({ fetch: fetchMock } as any); diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx index 034c120450d..8691e3ec8ed 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryEditor.tsx @@ -41,7 +41,6 @@ export const CloudWatchLogsQueryEditor = memo(function CloudWatchLogsQueryEditor exploreId={exploreId} datasource={datasource} query={query} - onBlur={() => {}} onChange={onChange} onRunQuery={onRunQuery} history={[]} diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx index 5572214d07d..ef60536b5d5 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.test.tsx @@ -6,12 +6,37 @@ import { DescribeLogGroupsRequest } from '../types'; import { SelectableValue } from '@grafana/data'; // eslint-disable-next-line lodash/import-scope import _, { DebouncedFunc } from 'lodash'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; +import { setupMockedDataSource } from '../__mocks__/CloudWatchDataSource'; jest .spyOn(_, 'debounce') .mockImplementation((func: (...args: any) => any, wait?: number) => func as DebouncedFunc); describe('CloudWatchLogsQueryField', () => { + it('runs onRunQuery on blur of Log Groups', async () => { + const onRunQuery = jest.fn(); + const ds = setupMockedDataSource(); + + render( + {}} + /> + ); + + const multiSelect = screen.getByLabelText('Log Groups'); + await act(async () => { + fireEvent.blur(multiSelect); + }); + expect(onRunQuery).toHaveBeenCalled(); + }); + it('updates upstream query log groups on region change', async () => { const onChange = jest.fn(); const wrapper = shallow( diff --git a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx index 0bd033b018b..005e2aed266 100644 --- a/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/LogsQueryField.tsx @@ -321,6 +321,7 @@ export class CloudWatchLogsQueryField extends React.PureComponent { this.setCustomLogGroups(v); }} + onBlur={this.props.onRunQuery} className={containerClass} closeMenuOnSelect={false} isClearable={true} @@ -344,7 +345,6 @@ export class CloudWatchLogsQueryField extends React.PureComponent Date: Mon, 18 Apr 2022 15:19:01 -0400 Subject: [PATCH 34/40] Chore: Remove remaining x from user (#47854) --- pkg/services/sqlstore/user.go | 2 +- pkg/services/sqlstore/user_test.go | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/services/sqlstore/user.go b/pkg/services/sqlstore/user.go index d119422ce1c..2a029912a37 100644 --- a/pkg/services/sqlstore/user.go +++ b/pkg/services/sqlstore/user.go @@ -489,7 +489,7 @@ func (ss *SQLStore) GetUserOrgList(ctx context.Context, query *models.GetUserOrg query.Result = make([]*models.UserOrgDTO, 0) sess := dbSess.Table("org_user") sess.Join("INNER", "org", "org_user.org_id=org.id") - sess.Join("INNER", x.Dialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user"))) + sess.Join("INNER", ss.Dialect.Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", ss.Dialect.Quote("user"))) sess.Where("org_user.user_id=?", query.UserId) sess.Where(notServiceAccountFilter(ss)) sess.Cols("org.name", "org_user.role", "org_user.org_id") diff --git a/pkg/services/sqlstore/user_test.go b/pkg/services/sqlstore/user_test.go index 76182fbc2cd..e2340de0ab6 100644 --- a/pkg/services/sqlstore/user_test.go +++ b/pkg/services/sqlstore/user_test.go @@ -255,7 +255,7 @@ func TestUserDataAccess(t *testing.T) { require.Nil(t, err) query1 := &models.GetOrgUsersQuery{OrgId: users[0].OrgId} - err = GetOrgUsersForTest(query1) + err = ss.GetOrgUsersForTest(context.Background(), query1) require.Nil(t, err) require.Len(t, query1.Result, 1) @@ -341,7 +341,7 @@ func TestUserDataAccess(t *testing.T) { // delete connected org users and permissions query2 := &models.GetOrgUsersQuery{OrgId: users[0].OrgId} - err = GetOrgUsersForTest(query2) + err = ss.GetOrgUsersForTest(context.Background(), query2) require.Nil(t, err) require.Len(t, query2.Result, 1) @@ -507,15 +507,17 @@ func TestUserDataAccess(t *testing.T) { }) } -func GetOrgUsersForTest(query *models.GetOrgUsersQuery) error { - query.Result = make([]*models.OrgUserDTO, 0) - sess := x.Table("org_user") - sess.Join("LEFT ", x.Dialect().Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", x.Dialect().Quote("user"))) - sess.Where("org_user.org_id=?", query.OrgId) - sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role") +func (ss *SQLStore) GetOrgUsersForTest(ctx context.Context, query *models.GetOrgUsersQuery) error { + return ss.WithDbSession(ctx, func(dbSess *DBSession) error { + query.Result = make([]*models.OrgUserDTO, 0) + sess := dbSess.Table("org_user") + sess.Join("LEFT ", ss.Dialect.Quote("user"), fmt.Sprintf("org_user.user_id=%s.id", ss.Dialect.Quote("user"))) + sess.Where("org_user.org_id=?", query.OrgId) + sess.Cols("org_user.org_id", "org_user.user_id", "user.email", "user.login", "org_user.role") - err := sess.Find(&query.Result) - return err + err := sess.Find(&query.Result) + return err + }) } func createFiveTestUsers(t *testing.T, sqlStore *SQLStore, fn func(i int) *models.CreateUserCommand) []models.User { From 7d5cb170c6f5b4352a4e3390644458dedf7132b1 Mon Sep 17 00:00:00 2001 From: Joe Blubaugh Date: Tue, 19 Apr 2022 16:05:28 +0800 Subject: [PATCH 35/40] Alerting: Sort StateHistoryItem after fetch instead of on render. (#47842) PR #47674 attempted to sort a read-only managed async array. This change moves the sort logic to the fetch code so sort happens once on fetch, to a mutable array, rather than trying on each render for an immutable array. Signed-off-by: Joe Blubaugh --- .../alerting/unified/api/annotations.test.ts | 51 +++++++++++++++++-- .../alerting/unified/api/annotations.ts | 36 +++++++++++-- .../components/rules/StateHistory.test.tsx | 40 --------------- .../unified/components/rules/StateHistory.tsx | 32 +----------- 4 files changed, 82 insertions(+), 77 deletions(-) delete mode 100644 public/app/features/alerting/unified/components/rules/StateHistory.test.tsx diff --git a/public/app/features/alerting/unified/api/annotations.test.ts b/public/app/features/alerting/unified/api/annotations.test.ts index ca52b08ca65..183c103f58c 100644 --- a/public/app/features/alerting/unified/api/annotations.test.ts +++ b/public/app/features/alerting/unified/api/annotations.test.ts @@ -1,10 +1,17 @@ import '@grafana/runtime'; -import { fetchAnnotations } from './annotations'; +import { fetchAnnotations, sortStateHistory } from './annotations'; +import { StateHistoryItem } from 'app/types/unified-alerting'; -const get = jest.fn(); +const get = jest.fn(() => { + return new Promise((resolve) => { + resolve(undefined); + }); +}); jest.mock('@grafana/runtime', () => ({ - getBackendSrv: () => ({ get }), + getBackendSrv: () => ({ + get, + }), })); describe('annotations', () => { @@ -16,3 +23,41 @@ describe('annotations', () => { expect(get).toBeCalledWith('/api/annotations', { alertId: ALERT_ID }); }); }); + +describe(sortStateHistory, () => { + describe('should stably sort', () => { + describe('when timeEnd is different', () => { + it('should not sort by rule id', () => { + let data: StateHistoryItem[] = [ + { timeEnd: 23, time: 22, id: 1 } as StateHistoryItem, + { timeEnd: 22, time: 21, id: 3 } as StateHistoryItem, + { timeEnd: 22, time: 22, id: 2 } as StateHistoryItem, + { timeEnd: 24, id: 3 } as StateHistoryItem, + ]; + + data.sort(sortStateHistory); + expect(data[0].timeEnd).toBe(24); + expect(data[1].timeEnd).toBe(23); + expect(data[2].time).toBe(22); + expect(data[3].id).toBe(3); + }); + }); + + describe('when only the rule id is different', () => { + it('should sort by rule id', () => { + let data: StateHistoryItem[] = [ + { timeEnd: 23, time: 22, id: 1 } as StateHistoryItem, + { timeEnd: 23, time: 22, id: 3 } as StateHistoryItem, + { timeEnd: 23, time: 22, id: 2 } as StateHistoryItem, + { timeEnd: 23, time: 22, id: 6 } as StateHistoryItem, + ]; + + data.sort(sortStateHistory); + expect(data[0].id).toBe(6); + expect(data[1].id).toBe(3); + expect(data[2].id).toBe(2); + expect(data[3].id).toBe(1); + }); + }); + }); +}); diff --git a/public/app/features/alerting/unified/api/annotations.ts b/public/app/features/alerting/unified/api/annotations.ts index 2df5ee2cacd..39195dc1f48 100644 --- a/public/app/features/alerting/unified/api/annotations.ts +++ b/public/app/features/alerting/unified/api/annotations.ts @@ -2,7 +2,37 @@ import { getBackendSrv } from '@grafana/runtime'; import { StateHistoryItem } from 'app/types/unified-alerting'; export function fetchAnnotations(alertId: string): Promise { - return getBackendSrv().get('/api/annotations', { - alertId, - }); + return getBackendSrv() + .get('/api/annotations', { + alertId, + }) + .then((result) => { + return result?.sort(sortStateHistory); + }); +} + +export function sortStateHistory(a: StateHistoryItem, b: StateHistoryItem): number { + const compareDesc = (a: number, b: number): number => { + // Larger numbers first. + if (a > b) { + return -1; + } + + if (b > a) { + return 1; + } + return 0; + }; + + const endNeq = compareDesc(a.timeEnd, b.timeEnd); + if (endNeq) { + return endNeq; + } + + const timeNeq = compareDesc(a.time, b.time); + if (timeNeq) { + return timeNeq; + } + + return compareDesc(a.id, b.id); } diff --git a/public/app/features/alerting/unified/components/rules/StateHistory.test.tsx b/public/app/features/alerting/unified/components/rules/StateHistory.test.tsx deleted file mode 100644 index 31d0bc9e188..00000000000 --- a/public/app/features/alerting/unified/components/rules/StateHistory.test.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { StateHistoryItem } from 'app/types/unified-alerting'; -import { sortStateHistory } from './StateHistory'; - -describe(sortStateHistory, () => { - describe('should stably sort', () => { - describe('when timeEnd is different', () => { - it('should not sort by rule id', () => { - let data: StateHistoryItem[] = [ - { timeEnd: 23, time: 22, id: 1 } as StateHistoryItem, - { timeEnd: 22, time: 21, id: 3 } as StateHistoryItem, - { timeEnd: 22, time: 22, id: 2 } as StateHistoryItem, - { timeEnd: 24, id: 3 } as StateHistoryItem, - ]; - - data.sort(sortStateHistory); - expect(data[0].timeEnd).toBe(24); - expect(data[1].timeEnd).toBe(23); - expect(data[2].time).toBe(22); - expect(data[3].id).toBe(3); - }); - }); - - describe('when only the rule id is different', () => { - it('should sort by rule id', () => { - let data: StateHistoryItem[] = [ - { timeEnd: 23, time: 22, id: 1 } as StateHistoryItem, - { timeEnd: 23, time: 22, id: 3 } as StateHistoryItem, - { timeEnd: 23, time: 22, id: 2 } as StateHistoryItem, - { timeEnd: 23, time: 22, id: 6 } as StateHistoryItem, - ]; - - data.sort(sortStateHistory); - expect(data[0].id).toBe(6); - expect(data[1].id).toBe(3); - expect(data[2].id).toBe(2); - expect(data[3].id).toBe(1); - }); - }); - }); -}); diff --git a/public/app/features/alerting/unified/components/rules/StateHistory.tsx b/public/app/features/alerting/unified/components/rules/StateHistory.tsx index 75d17e883fe..f7050eb198c 100644 --- a/public/app/features/alerting/unified/components/rules/StateHistory.tsx +++ b/public/app/features/alerting/unified/components/rules/StateHistory.tsx @@ -24,32 +24,6 @@ interface RuleStateHistoryProps { alertId: string; } -function sortStateHistory(a: StateHistoryItem, b: StateHistoryItem): number { - const compareDesc = (a: number, b: number): number => { - // Larger numbers first. - if (a > b) { - return -1; - } - - if (b > a) { - return 1; - } - return 0; - }; - - const endNeq = compareDesc(a.timeEnd, b.timeEnd); - if (endNeq) { - return endNeq; - } - - const timeNeq = compareDesc(a.time, b.time); - if (timeNeq) { - return timeNeq; - } - - return compareDesc(a.id, b.id); -} - const StateHistory: FC = ({ alertId }) => { const { loading, error, result = [] } = useManagedAlertStateHistory(alertId); @@ -68,7 +42,6 @@ const StateHistory: FC = ({ alertId }) => { ]; const items: StateHistoryRow[] = result - .sort(sortStateHistory) .reduce((acc: StateHistoryRowItem[], item, index) => { acc.push({ id: String(item.id), @@ -150,7 +123,4 @@ function hasMatchingPrecedingState(index: number, items: StateHistoryItem[]): bo return previousHistoryItem.newState === currentHistoryItem.prevState; } -export { - StateHistory, - sortStateHistory, // exported for testing. -}; +export { StateHistory }; From 378ce4f685729b873fb580c6abaab854d1089e81 Mon Sep 17 00:00:00 2001 From: Andrej Ocenas Date: Tue, 19 Apr 2022 11:25:28 +0200 Subject: [PATCH 36/40] Prometheus: Add documentation for the query builder (#47324) * Update documentation for the query builder * Review feedback * Update docs and add pictures --- docs/sources/datasources/prometheus.md | 116 +++++++++++++++++-------- 1 file changed, 80 insertions(+), 36 deletions(-) diff --git a/docs/sources/datasources/prometheus.md b/docs/sources/datasources/prometheus.md index 0af9f91187c..97bc899128f 100644 --- a/docs/sources/datasources/prometheus.md +++ b/docs/sources/datasources/prometheus.md @@ -38,56 +38,37 @@ To access Prometheus settings, hover your mouse over the **Configuration** (gear ## Prometheus query editor -Below you can find information and options for Prometheus query editor in dashboard and in Explore. +Prometheus query editor is separated into 3 distinct modes that you can switch between. See docs for each section below. -### Query editor in dashboards +![Editor toolbar](/static/img/docs/prometheus/header-8-5.png 'Editor toolbar') -Open a graph in edit mode by clicking the title > Edit (or by pressing `e` key while hovering over panel). +At the top of the editor there is `Run query` button that will run the query and `Explain | Builder | Code` tabs to switch between the editor modes. If the query editor is in Builder mode there are additional elements explained in the Builder section. -{{< figure src="/static/img/docs/v45/prometheus_query_editor_still.png" - animated-gif="/static/img/docs/v45/prometheus_query_editor.gif" >}} +Each mode is synchronized with the other modes, so you can switch between them without losing your work, although there are some limitations. Some more complex queries are not yet supported in the builder mode. If you try to switch from `Code` to `Builder` with such query, editor will show a popup explaining that you can lose some parts of the query, and you can decide if you still want to continue to `Builder` mode or not. -| Name | Description | -| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `Query expression` | Prometheus query expression. For more information, refer to the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). | -| `Legend format` | Controls the name of the time series, using name or pattern. For example, `{{hostname}}` is replaced by the label value for the label `hostname`. | -| `Step` | Use 'Minimum' or 'Maximum' step mode to set the lower or upper bounds respectively on the interval between data points. For example, set "minimum 1h" to hint that measurements are not frequent (taken hourly). Use the 'Exact' step mode to set a precise interval between data points. `$__interval` and `$__rate_interval` are supported. | -| `Resolution` | `1/1` sets both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) such that each pixel corresponds to one data point. For better performance, you can pick lower resolutions. `1/2` only retrieves a data point for every other pixel, and `1/10` retrieves one data point per 10 pixels. Both _Min time interval_ and _Step_ limit the final value of `$__interval` and `step`. | -| `Metric lookup` | Search for metric names in this input field. | -| `Format as` | You can switch between `Table` `Time series` or `Heatmap` options. The `Table` option works only in the Table panel. `Heatmap` displays metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. | -| `Instant` | Perform an "instant" query to return only the latest value that Prometheus has scraped for the requested time series. Instant queries can return results much faster than normal range queries. Use them to look up label sets. | -| `Min time interval` | This value multiplied by the denominator from the _Resolution_ setting sets a lower limit to both the `$__interval` variable and the [`step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries). Defaults to _Scrape interval_ as specified in the data source options. | -| `Exemplars` | Run and show exemplars in the graph. | +### Code mode -> **Note:** Grafana modifies the request dates for queries to align them with the dynamically calculated step. This ensures consistent display of metrics data, but it can result in a small gap of data at the right edge of a graph. +![Code mode](/static/img/docs/prometheus/code-mode-8-5.png 'Code mode') -#### Instant queries in dashboards +Code mode allows you to write raw queries in a textual editor. It implements advanced autocomplete features and syntax highlighting to help with writing complex queries. In addition, it also contains `Metrics browser` to further aid with writing queries (see more docs below). -The Prometheus data source allows you to run "instant" queries, which query only the latest value. -You can visualize the results in a table panel to see all available labels of a timeseries. +For more information about Prometheus query language, refer to the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). -Instant query results are made up only of one data point per series but can be shown in the graph panel with the help of [series overrides]({{< relref "../visualizations/graph-panel.md#series-overrides" >}}). -To show them in the graph as a latest value point, add a series override and select `Points > true`. -To show a horizontal line across the whole graph, add a series override and select `Transform > constant`. +#### Autocomplete -> Support for constant series overrides is available from Grafana v6.4 +![Autocomplete](/static/img/docs/prometheus/autocomplete-8-5.png 'Autocomplete') -### Query editor in Explore +Autocomplete kicks automatically in appropriate times during typing. Use `ctrl/cmd + space` to trigger autocomplete manually when needed. Autocomplete can suggest both static functions, aggregations and keywords but also dynamic items like metrics and labels. Autocomplete dropdown also shows documentation for the suggested items, either static one or dynamic metric documentation where available. -| Name | Description | -| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `Query expression` | Prometheus query expression, check out the [Prometheus documentation](http://prometheus.io/docs/querying/basics/). | -| `Step` | [`Step` parameter of Prometheus range queries](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries). Time units can be used here, for example: 5s, 1m, 3h, 1d, 1y. Default unit if no unit specified is `s` (seconds). | -| `Query type` | `Range`, `Instant`, or `Both`. When running **Range query**, the result of the query is displayed in graph and table. Instant query returns only the latest value that Prometheus has scraped for the requested time series and it is displayed in the table. When **Both** is selected, both instant query and range query is run. Result of range query is displayed in graph and the result of instant query is displayed in the table. | -| `Exemplars` | Run and show exemplars in the graph. | +In [Explore]({{< relref "../explore/_index.md" >}}) use `shift + enter` to run the query. -### Metrics browser +#### Metrics browser The metrics browser allows you to quickly find metrics and select relevant labels to build basic queries. When you open the browser you will see all available metrics and labels. If supported by your Prometheus instance, each metric will show its HELP and TYPE as a tooltip. -{{< figure src="/static/img/docs/v8/prometheus_metrics_browser.png" class="docs-image--no-shadow" max-width="800px" caption="Screenshot of the metrics browser for Prometheus" >}} +![Metrics browser](/static/img/docs/prometheus/metric-browser-8-5.png 'Metrics browser') When you select a metric, the browser narrows down the available labels to show only the ones applicable to the metric. You can then select one or more labels for which the available label values are shown in lists in the bottom section. @@ -95,14 +76,77 @@ Select one or more values for each label to tighten your query scope. > **Note:** If you do not remember a metric name to start with, you can also select a few labels first, to narrow down the list and then find relevant label values. -All lists in the metrics browser have a search field above them to quickly filter for metrics or labels that match a certain string. The values section only has one search field. Its filtering applies to all labels to help you find values across labels once they have been selected, for example, among your labels `app`, `job`, `job_name` only one might with the value you are looking for. +All lists in the metrics browser have a search field above them to quickly filter for metrics or labels that match a certain string. The values section only has one search field. It's filtering applies to all labels to help you find values across labels once they have been selected, for example, among your labels `app`, `job`, `job_name` only one might with the value you are looking for. Once you are satisfied with your query, click "Use query" to run the query. The button "Use as rate query" adds a `rate(...)[$__interval]` around your query to help write queries for counter metrics. The "Validate selector" button will check with Prometheus how many time series are available for that selector. -#### Limitations +#### Options -The metrics browser has a hard limit of 10,000 labels (keys) and 50,000 label values (including metric names). If your Prometheus instance returns more results, the browser will continue functioning. However, the result sets will be cut off above those maximum limits. +![Options](/static/img/docs/prometheus/options-8-5.png 'Options') + +| Name | Description | +| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `Legend` | Controls the name of the time series. Use predefined format or use custom format.
`Auto` - only includes unique labels.
`Verbose` - includes all labels.
`Custom` - select will change to text input. Use use tamplating to select which labels will be included. For example, `{{hostname}}` is replaced by the label value for the label `hostname`. Clear the input and click outside the input to go back to select mode. | +| `Min step` | Set the lower bounds on the interval between data points. For example, set "1h" to hint that measurements are not frequent (taken hourly). `$__interval` and `$__rate_interval` are supported. | +| `Format` | You can switch between `Table` `Time series` or `Heatmap` options. The `Table` option works only in the Table panel. `Heatmap` displays metrics of the Histogram type on a Heatmap panel. Under the hood, it converts cumulative histograms to regular ones and sorts series by the bucket bound. | +| `Type` | `Range` - Query returning a Range vector, a set of time series containing a range of data points over time for each time series.
`Instant` - Perform an "instant" query to return only the latest value that Prometheus has scraped for the requested time series. Instant queries can return results much faster than normal range queries. Use them to look up label sets. Instant query results are made up only of one data point per series but can be shown in the graph panel in a dashboard with the help of [series overrides]({{< relref "../visualizations/graph-panel.md#series-overrides" >}}). To show them in the graph as a latest value point, add a series override and select `Points > true`. To show a horizontal line across the whole graph, add a series override and select `Transform > constant`.
`Both` - Available only in Explore. Runs both range and instant query | +| `Exemplars` | If on, run exemplars query with the regular query and show exemplars in the graph. | + +> **Note:** Grafana modifies the request dates for queries to align them with the dynamically calculated step. This ensures consistent display of metrics data, but it can result in a small gap of data at the right edge of a graph. + +### Builder mode + +#### Toolbar + +In addition to `Run query` button and mode switcher, in builder mode additional elements are available: + +| Name | Description | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------- | +| Query patterns | A list of useful operation patterns that can be used to quickly add multiple operations to your query to achieve a specific goal. | +| Raw query | Toggle to show raw query generated by the builder that will be sent to Prometheus instance. | + +#### Metric and labels + +![Metric and labels](/static/img/docs/prometheus/metric-select-8-5.png 'Metric and labels') + +Select a specific metric name from the dropdown list. List of available metrics is fetched from the Prometheus server based on selected time rage. Write into the select when the dropdown is open to search and filter the list. + +Select desired labels and their values from the dropdown list. When metric is selected, available labels and their values are fetched from the server. Use the `+` button to add more labels. Use the `x` button to remove a label. + +#### Operations + +![Operations](/static/img/docs/prometheus/operations-8-5.gif 'Operations') + +Use the `+ Operations` button to add operation to your query. Operations are grouped into sections for easier navigation. When the operations dropdown is open, write into the search input to search and filter operations list. + +Operations in a query are shown as boxes in the operations section. Each has a header with a name and additional action buttons. Hover over the operation header to show the action buttons. Click the `v` button to quickly replace the operation with different one of the same type. Click the `info` button to open operations' description tooltip. Click the `x` button to remove the operation. + +Operation can have additional parameters under the operation header. See the operation description or Prometheus docs for more details about each operation. + +Some operations make sense only in specific order, if adding an operation would result in nonsensical query, operation will be added to the correct place. To order operations manually drag operation box by the operation name and drop in appropriate place. + +##### Hints + +![Hint](/static/img/docs/prometheus/hint-8-5.gif 'Hint') + +In same cases the query editor can detect which operations would be most appropriate for a selected metric. In such cases it will show a hint next to the `+ Operations` button. Click on the hint to add the operations to your query. + +#### Raw query + +![Raw query](/static/img/docs/prometheus/raw-query-8-5.gif 'Raw query') + +This section is shown only if the `Raw query` switch from the query editor top toolbar is set to `on`. It shows the raw query that will be created and executed by the query editor. + +#### Options + +Same set of option is available as in the `Code` mode. See the [Code mode options]({{< relref "#options" >}}) for details. + +### Explain mode + +![Explain mode](/static/img/docs/prometheus/explain-8-5.png 'Explain mode') + +Explain mode helps with understanding the query. It shows a step by step explanation of all query parts and the operations. ## Templating From bb5f77703ca8682ec6261cc127dc515436816da0 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Tue, 19 Apr 2022 10:31:59 +0100 Subject: [PATCH 37/40] prevent horizontal scrollbar on firefox during expand animation (#47758) --- public/app/core/components/NavBar/Next/NavBarMenu.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/app/core/components/NavBar/Next/NavBarMenu.tsx b/public/app/core/components/NavBar/Next/NavBarMenu.tsx index 224e32ff66e..00e40a6e5f1 100644 --- a/public/app/core/components/NavBar/Next/NavBarMenu.tsx +++ b/public/app/core/components/NavBar/Next/NavBarMenu.tsx @@ -147,6 +147,10 @@ const getAnimStyles = (theme: GrafanaTheme2, animationDuration: number) => { const overlayTransition = { ...commonTransition, transitionProperty: 'background-color, box-shadow, width', + // this is needed to prevent a horizontal scrollbar during the animation on firefox + '.scrollbar-view': { + overflow: 'hidden !important', + }, }; const backdropTransition = { From 4570615afc7c2e7964aa3b8cf15e1120ee5949eb Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Tue, 19 Apr 2022 15:08:19 +0200 Subject: [PATCH 38/40] Alerting: Display alert instances instead of alert rules when creating silence (#47396) * modify matchers util for instances * filter alerts * change label to include instances * re add empty case --- .../silences/MatchedSilencedRules.tsx | 52 +++++++++++-------- .../alerting/unified/utils/matchers.test.ts | 40 +++++++------- .../alerting/unified/utils/matchers.ts | 27 +++++----- 3 files changed, 66 insertions(+), 53 deletions(-) diff --git a/public/app/features/alerting/unified/components/silences/MatchedSilencedRules.tsx b/public/app/features/alerting/unified/components/silences/MatchedSilencedRules.tsx index e09479f3d2a..d464202f3a4 100644 --- a/public/app/features/alerting/unified/components/silences/MatchedSilencedRules.tsx +++ b/public/app/features/alerting/unified/components/silences/MatchedSilencedRules.tsx @@ -3,21 +3,22 @@ import { useFormContext } from 'react-hook-form'; import { useDebounce } from 'react-use'; import { useDispatch } from 'react-redux'; import { css } from '@emotion/css'; -import { GrafanaTheme2 } from '@grafana/data'; +import { dateTime, GrafanaTheme2 } from '@grafana/data'; import { Badge, useStyles2 } from '@grafana/ui'; import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; -import { RuleState } from '../rules/RuleState'; import { useCombinedRuleNamespaces } from '../../hooks/useCombinedRuleNamespaces'; -import { Annotation } from '../../utils/constants'; -import { findAlertRulesWithMatchers } from '../../utils/matchers'; +import { findAlertInstancesWithMatchers } from '../../utils/matchers'; import { fetchAllPromAndRulerRulesAction } from '../../state/actions'; -import { CombinedRule } from 'app/types/unified-alerting'; +import { Alert, AlertingRule } from 'app/types/unified-alerting'; import { MatcherFieldValue, SilenceFormFields } from '../../types/silence-form'; +import { isAlertingRule } from '../../utils/rules'; +import { AlertStateTag } from '../rules/AlertStateTag'; +import { AlertLabels } from '../AlertLabels'; type MatchedRulesTableItemProps = DynamicTableItemProps<{ - matchedRule: CombinedRule; + matchedInstance: Alert; }>; -type MatchedRulesTableColumnProps = DynamicTableColumnProps<{ matchedRule: CombinedRule }>; +type MatchedRulesTableColumnProps = DynamicTableColumnProps<{ matchedInstance: Alert }>; export const MatchedSilencedRules = () => { const [matchedAlertRules, setMatchedAlertRules] = useState([]); @@ -35,12 +36,15 @@ export const MatchedSilencedRules = () => { const combinedNamespaces = useCombinedRuleNamespaces(); useDebounce( () => { - const matchedRules = combinedNamespaces.flatMap((namespace) => { + const matchedInstances = combinedNamespaces.flatMap((namespace) => { return namespace.groups.flatMap((group) => { - return findAlertRulesWithMatchers(group.rules, matchers); + return group.rules + .map((combinedRule) => combinedRule.promRule) + .filter((rule): rule is AlertingRule => isAlertingRule(rule)) + .flatMap((rule) => findAlertInstancesWithMatchers(rule.alerts ?? [], matchers)); }); }); - setMatchedAlertRules(matchedRules); + setMatchedAlertRules(matchedInstances); }, 500, [combinedNamespaces, matchers] @@ -49,7 +53,7 @@ export const MatchedSilencedRules = () => { return (

- Affected alerts + Affected alert instances {matchedAlertRules.length > 0 ? ( ) : null} @@ -75,24 +79,30 @@ function useColumns(): MatchedRulesTableColumnProps[] { { id: 'state', label: 'State', - renderCell: function renderStateTag({ data: { matchedRule } }) { - return ; + renderCell: function renderStateTag({ data: { matchedInstance } }) { + return ; }, size: '160px', }, { - id: 'name', - label: 'Name', - renderCell: function renderName({ data: { matchedRule } }) { - return matchedRule.name; + id: 'labels', + label: 'Labels', + renderCell: function renderName({ data: { matchedInstance } }) { + return ; }, size: '250px', }, { - id: 'summary', - label: 'Summary', - renderCell: function renderSummary({ data: { matchedRule } }) { - return matchedRule.annotations[Annotation.summary] ?? ''; + id: 'created', + label: 'Created', + renderCell: function renderSummary({ data: { matchedInstance } }) { + return ( + <> + {matchedInstance.activeAt.startsWith('0001') + ? '-' + : dateTime(matchedInstance.activeAt).format('YYYY-MM-DD HH:mm:ss')} + + ); }, size: '400px', }, diff --git a/public/app/features/alerting/unified/utils/matchers.test.ts b/public/app/features/alerting/unified/utils/matchers.test.ts index 33ddec0bb14..cedddfe5e55 100644 --- a/public/app/features/alerting/unified/utils/matchers.test.ts +++ b/public/app/features/alerting/unified/utils/matchers.test.ts @@ -1,6 +1,6 @@ import { MatcherOperator } from 'app/plugins/datasource/alertmanager/types'; -import { getMatcherQueryParams, findAlertRulesWithMatchers, parseQueryParamMatchers } from './matchers'; -import { mockCombinedRule } from '../mocks'; +import { getMatcherQueryParams, findAlertInstancesWithMatchers, parseQueryParamMatchers } from './matchers'; +import { mockPromAlert } from '../mocks'; describe('Unified Alerting matchers', () => { describe('getMatcherQueryParams tests', () => { @@ -39,42 +39,42 @@ describe('Unified Alerting matchers', () => { describe('matchLabelsToMatchers', () => { it('should match for equal', () => { const matchers = [{ name: 'foo', value: 'bar', operator: MatcherOperator.equal }]; - const rules = [mockCombinedRule({ labels: { foo: 'bar' } }), mockCombinedRule({ labels: { foo: 'baz' } })]; - const matchedRules = findAlertRulesWithMatchers(rules, matchers); + const alerts = [mockPromAlert({ labels: { foo: 'bar' } }), mockPromAlert({ labels: { foo: 'baz' } })]; + const matchedAlerts = findAlertInstancesWithMatchers(alerts, matchers); - expect(matchedRules).toHaveLength(1); + expect(matchedAlerts).toHaveLength(1); }); it('should match for not equal', () => { const matchers = [{ name: 'foo', value: 'bar', operator: MatcherOperator.notEqual }]; - const rules = [mockCombinedRule({ labels: { foo: 'bar' } }), mockCombinedRule({ labels: { foo: 'baz' } })]; + const alerts = [mockPromAlert({ labels: { foo: 'bar' } }), mockPromAlert({ labels: { foo: 'baz' } })]; - const matchedRules = findAlertRulesWithMatchers(rules, matchers); - expect(matchedRules).toHaveLength(1); + const matchedAlerts = findAlertInstancesWithMatchers(alerts, matchers); + expect(matchedAlerts).toHaveLength(1); }); it('should match for regex', () => { const matchers = [{ name: 'foo', value: 'bar', operator: MatcherOperator.regex }]; - const rules = [ - mockCombinedRule({ labels: { foo: 'bar' } }), - mockCombinedRule({ labels: { foo: 'baz' } }), - mockCombinedRule({ labels: { foo: 'bas' } }), + const alerts = [ + mockPromAlert({ labels: { foo: 'bar' } }), + mockPromAlert({ labels: { foo: 'baz' } }), + mockPromAlert({ labels: { foo: 'bas' } }), ]; - const matchedRules = findAlertRulesWithMatchers(rules, matchers); - expect(matchedRules).toHaveLength(1); + const matchedAlerts = findAlertInstancesWithMatchers(alerts, matchers); + expect(matchedAlerts).toHaveLength(1); }); it('should not match regex', () => { const matchers = [{ name: 'foo', value: 'bar', operator: MatcherOperator.notRegex }]; - const rules = [ - mockCombinedRule({ labels: { foo: 'bar' } }), - mockCombinedRule({ labels: { foo: 'baz' } }), - mockCombinedRule({ labels: { foo: 'bas' } }), + const alerts = [ + mockPromAlert({ labels: { foo: 'bar' } }), + mockPromAlert({ labels: { foo: 'baz' } }), + mockPromAlert({ labels: { foo: 'bas' } }), ]; - const matchedRules = findAlertRulesWithMatchers(rules, matchers); - expect(matchedRules).toHaveLength(2); + const matchedAlerts = findAlertInstancesWithMatchers(alerts, matchers); + expect(matchedAlerts).toHaveLength(2); }); }); }); diff --git a/public/app/features/alerting/unified/utils/matchers.ts b/public/app/features/alerting/unified/utils/matchers.ts index 6979f8764bf..619a370b40d 100644 --- a/public/app/features/alerting/unified/utils/matchers.ts +++ b/public/app/features/alerting/unified/utils/matchers.ts @@ -3,7 +3,7 @@ import { Labels } from '@grafana/data'; import { parseMatcher } from './alertmanager'; import { uniqBy } from 'lodash'; import { MatcherFieldValue } from '../types/silence-form'; -import { CombinedRule } from 'app/types/unified-alerting'; +import { Alert } from 'app/types/unified-alerting'; // Parses a list of entries like like "['foo=bar', 'baz=~bad*']" into SilenceMatcher[] export function parseQueryParamMatchers(matcherPairs: string[]): Matcher[] { @@ -27,16 +27,19 @@ export const getMatcherQueryParams = (labels: Labels) => { return matcherUrlParams; }; -interface MatchedRule { +interface MatchedInstance { id: string; data: { - matchedRule: CombinedRule; + matchedInstance: Alert; }; } -export const findAlertRulesWithMatchers = (rules: CombinedRule[], matchers: MatcherFieldValue[]): MatchedRule[] => { - const hasMatcher = (rule: CombinedRule, matcher: MatcherFieldValue) => { - return Object.entries(rule.labels).some(([key, value]) => { +export const findAlertInstancesWithMatchers = ( + instances: Alert[], + matchers: MatcherFieldValue[] +): MatchedInstance[] => { + const hasMatcher = (instance: Alert, matcher: MatcherFieldValue) => { + return Object.entries(instance.labels).some(([key, value]) => { if (!matcher.name || !matcher.value) { return false; } @@ -56,13 +59,13 @@ export const findAlertRulesWithMatchers = (rules: CombinedRule[], matchers: Matc }); }; - const filteredRules = rules.filter((rule) => { - return matchers.every((matcher) => hasMatcher(rule, matcher)); + const filteredInstances = instances.filter((instance) => { + return matchers.every((matcher) => hasMatcher(instance, matcher)); }); - const mappedRules = filteredRules.map((rule) => ({ - id: `${rule.namespace}-${rule.name}`, - data: { matchedRule: rule }, + const mappedInstances = filteredInstances.map((instance) => ({ + id: `${instance.activeAt}-${instance.value}`, + data: { matchedInstance: instance }, })); - return mappedRules; + return mappedInstances; }; From 2aa6f4d17b77023739fe119a62f6a41508fe05e2 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Tue, 19 Apr 2022 15:29:27 +0200 Subject: [PATCH 39/40] Loki: Fix showing of default visual query only if no expression (#47764) * AutoSizeInput: Fix updating of value * Update * Revert "Update" This reverts commit a3b527e0c68934d1965a3bea3715196e45822d19. * Revert "AutoSizeInput: Fix updating of value" This reverts commit d4b4ae0a10ea48c70a6ca5abfc73df7f47c90a18. * Fix showing of default visual query only if no expression * Update public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx * Update public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx * Update * Update --- .../components/LokiQueryBuilderContainer.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx index 3a428319d78..bc5e93b3c38 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx @@ -26,11 +26,15 @@ export interface State { export function LokiQueryBuilderContainer(props: Props) { const { query, onChange, onRunQuery, datasource } = props; const [state, dispatch] = useReducer(stateSlice.reducer, { - expr: '', - visQuery: { - labels: [], - operations: [{ id: '__line_contains', params: [''] }], - }, + expr: query.expr, + // Use initial visual query only if query.expr is empty string + visQuery: + query.expr === '' + ? { + labels: [], + operations: [{ id: '__line_contains', params: [''] }], + } + : undefined, }); // Only rebuild visual query if expr changes from outside @@ -62,7 +66,7 @@ export function LokiQueryBuilderContainer(props: Props) { } const stateSlice = createSlice({ - name: 'prom-builder-container', + name: 'loki-builder-container', initialState: { expr: '' } as State, reducers: { visualQueryChange: (state, action: PayloadAction<{ visQuery: LokiVisualQuery; expr: string }>) => { From a068712ac21ff07a2a6abf18e8c97db921e25df2 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Tue, 19 Apr 2022 15:29:40 +0200 Subject: [PATCH 40/40] Loki: Updates to raw query and run query button (#47874) * Loki: Create toggle-able raw query * Update name --- .../components/LokiQueryBuilderContainer.tsx | 2 +- .../LokiQueryEditorSelector.test.tsx | 40 ++++++------ .../components/LokiQueryEditorSelector.tsx | 65 ++++++++++--------- public/app/plugins/datasource/loki/types.ts | 2 + 4 files changed, 57 insertions(+), 52 deletions(-) diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx index bc5e93b3c38..32390a99fa3 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryBuilderContainer.tsx @@ -60,7 +60,7 @@ export function LokiQueryBuilderContainer(props: Props) { onChange={onVisQueryChange} onRunQuery={onRunQuery} /> - + {query.rawQuery && } ); } diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx index c655ffcac96..23c9dae50fb 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.test.tsx @@ -82,29 +82,29 @@ describe('LokiQueryEditorSelector', () => { }); }); - // it('Can enable preview', async () => { - // const { onChange } = renderWithMode(QueryEditorMode.Builder); - // expect(screen.queryByLabelText('selector')).not.toBeInTheDocument(); + it('Can enable raw query', async () => { + const { onChange } = renderWithMode(QueryEditorMode.Builder); + expect(screen.queryByLabelText('selector')).not.toBeInTheDocument(); - // screen.getByLabelText('Preview').click(); + screen.getByLabelText('Raw query').click(); - // expect(onChange).toBeCalledWith({ - // refId: 'A', - // expr: defaultQuery.expr, - // range: true, - // editorMode: QueryEditorMode.Builder, - // editorPreview: true, - // }); - // }); + expect(onChange).toBeCalledWith({ + refId: 'A', + expr: defaultQuery.expr, + queryType: 'range', + editorMode: QueryEditorMode.Builder, + rawQuery: true, + }); + }); - // it('Should show preview', async () => { - // renderWithProps({ - // editorPreview: true, - // editorMode: QueryEditorMode.Builder, - // expr: 'my_metric', - // }); - // expect(screen.getByLabelText('selector').textContent).toBe('my_metric'); - // }); + it('Should show raw query', async () => { + renderWithProps({ + rawQuery: true, + editorMode: QueryEditorMode.Builder, + expr: '{job="grafana"}', + }); + expect(screen.getByLabelText('selector').textContent).toBe('{job="grafana"}'); + }); it('changes to code mode', async () => { const { onChange } = renderWithMode(QueryEditorMode.Builder); diff --git a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx index 58f0de41fe9..a405ef7b602 100644 --- a/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx +++ b/public/app/plugins/datasource/loki/querybuilder/components/LokiQueryEditorSelector.tsx @@ -1,10 +1,9 @@ -import { css } from '@emotion/css'; -import { GrafanaTheme2, LoadingState } from '@grafana/data'; +import { LoadingState } from '@grafana/data'; import { EditorHeader, EditorRows, FlexItem, InlineSelect, Space } from '@grafana/experimental'; -import { Button, useStyles2, ConfirmModal } from '@grafana/ui'; +import { Button, ConfirmModal } from '@grafana/ui'; import { QueryEditorModeToggle } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryEditorModeToggle'; import { QueryEditorMode } from 'app/plugins/datasource/prometheus/querybuilder/shared/types'; -import React, { useCallback, useState } from 'react'; +import React, { SyntheticEvent, useCallback, useEffect, useState } from 'react'; import { LokiQueryEditorProps } from '../../components/types'; import { lokiQueryModeller } from '../LokiQueryModeller'; import { getQueryWithDefaults } from '../state'; @@ -13,12 +12,15 @@ import { LokiQueryBuilderExplained } from './LokiQueryBuilderExplained'; import { LokiQueryBuilderOptions } from './LokiQueryBuilderOptions'; import { LokiQueryCodeEditor } from './LokiQueryCodeEditor'; import { buildVisualQueryFromString } from '../parsing'; +import { QueryHeaderSwitch } from 'app/plugins/datasource/prometheus/querybuilder/shared/QueryHeaderSwitch'; +import { LokiQuery } from '../../types'; export const LokiQueryEditorSelector = React.memo((props) => { const { onChange, onRunQuery, data } = props; - const styles = useStyles2(getStyles); - const query = getQueryWithDefaults(props.query); const [parseModalOpen, setParseModalOpen] = useState(false); + const [dataIsStale, setDataIsStale] = useState(false); + + const query = getQueryWithDefaults(props.query); const onEditorModeChange = useCallback( (newMetricEditorMode: QueryEditorMode) => { @@ -36,6 +38,20 @@ export const LokiQueryEditorSelector = React.memo((props) [onChange, query] ); + useEffect(() => { + setDataIsStale(false); + }, [data]); + + const onChangeInternal = (query: LokiQuery) => { + setDataIsStale(true); + onChange(query); + }; + + const onQueryPreviewChange = (event: SyntheticEvent) => { + const isEnabled = event.currentTarget.checked; + onChange({ ...query, rawQuery: isEnabled }); + }; + // If no expr (ie new query) then default to builder const editorMode = query.editorMode ?? (query.expr ? QueryEditorMode.Code : QueryEditorMode.Builder); return ( @@ -52,18 +68,6 @@ export const LokiQueryEditorSelector = React.memo((props) onDismiss={() => setParseModalOpen(false)} /> - - ((props) }} options={lokiQueryModeller.getQueryPatterns().map((x) => ({ label: x.name, value: x }))} /> + + + @@ -87,7 +102,7 @@ export const LokiQueryEditorSelector = React.memo((props) )} @@ -101,15 +116,3 @@ export const LokiQueryEditorSelector = React.memo((props) }); LokiQueryEditorSelector.displayName = 'LokiQueryEditorSelector'; - -const getStyles = (theme: GrafanaTheme2) => { - return { - runQuery: css({ - color: theme.colors.text.secondary, - }), - switchLabel: css({ - color: theme.colors.text.secondary, - fontSize: theme.typography.bodySmall.fontSize, - }), - }; -}; diff --git a/public/app/plugins/datasource/loki/types.ts b/public/app/plugins/datasource/loki/types.ts index f1e91c3b279..1563669632c 100644 --- a/public/app/plugins/datasource/loki/types.ts +++ b/public/app/plugins/datasource/loki/types.ts @@ -42,6 +42,8 @@ export interface LokiQuery extends DataQuery { /* @deprecated now use queryType */ instant?: boolean; editorMode?: QueryEditorMode; + /** Controls if the raw query text is shown */ + rawQuery?: boolean; } export interface LokiOptions extends DataSourceJsonData {