diff --git a/pkg/services/cloudmigration/model.go b/pkg/services/cloudmigration/model.go index d77e2fc7492..109a9f3bd0a 100644 --- a/pkg/services/cloudmigration/model.go +++ b/pkg/services/cloudmigration/model.go @@ -143,6 +143,7 @@ type Base64HGInstance struct { // dtos for cms api +// swagger:enum MigrateDataType type MigrateDataType string const ( @@ -162,6 +163,7 @@ type MigrateDataRequestItemDTO struct { Data interface{} `json:"data"` } +// swagger:enum ItemStatus type ItemStatus string const ( @@ -175,8 +177,11 @@ type MigrateDataResponseDTO struct { } type MigrateDataResponseItemDTO struct { - Type MigrateDataType `json:"type"` - RefID string `json:"refId"` - Status ItemStatus `json:"status"` - Error string `json:"error,omitempty"` + // required:true + Type MigrateDataType `json:"type"` + // required:true + RefID string `json:"refId"` + // required:true + Status ItemStatus `json:"status"` + Error string `json:"error,omitempty"` } diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index fa2a2efff5c..3f7b9bb95f0 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -2776,6 +2776,14 @@ } } }, + "CloudMigrationRequest": { + "type": "object", + "properties": { + "authToken": { + "type": "string" + } + } + }, "CloudMigrationResponse": { "type": "object", "properties": { @@ -4539,9 +4547,6 @@ } } }, - "ItemStatus": { - "type": "string" - }, "JSONWebKey": { "description": "JSONWebKey represents a public or private key in JWK format. It can be\nmarshaled into JSON and unmarshaled from JSON.", "type": "object", @@ -4896,6 +4901,11 @@ }, "MigrateDataResponseItemDTO": { "type": "object", + "required": [ + "type", + "refId", + "status" + ], "properties": { "error": { "type": "string" @@ -4904,7 +4914,19 @@ "type": "string" }, "status": { - "$ref": "#/definitions/ItemStatus" + "type": "string", + "enum": [ + "OK", + "ERROR" + ] + }, + "type": { + "type": "string", + "enum": [ + "DASHBOARD", + "DATASOURCE", + "FOLDER" + ] } } }, diff --git a/public/api-merged.json b/public/api-merged.json index bef6be380cd..2bea2e006eb 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -15499,9 +15499,6 @@ } } }, - "ItemStatus": { - "type": "string" - }, "JSONWebKey": { "description": "JSONWebKey represents a public or private key in JWK format. It can be\nmarshaled into JSON and unmarshaled from JSON.", "type": "object", @@ -15954,6 +15951,11 @@ }, "MigrateDataResponseItemDTO": { "type": "object", + "required": [ + "type", + "refId", + "status" + ], "properties": { "error": { "type": "string" @@ -15962,16 +15964,22 @@ "type": "string" }, "status": { - "$ref": "#/definitions/ItemStatus" + "type": "string", + "enum": [ + "OK", + "ERROR" + ] }, "type": { - "$ref": "#/definitions/MigrateDataType" + "type": "string", + "enum": [ + "DASHBOARD", + "DATASOURCE", + "FOLDER" + ] } } }, - "MigrateDataType": { - "type": "string" - }, "MoveFolderCommand": { "description": "MoveFolderCommand captures the information required by the folder service\nto move a folder.", "type": "object", diff --git a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts index 19de279b1df..84a8bbafab1 100644 --- a/public/app/features/migrate-to-cloud/api/endpoints.gen.ts +++ b/public/app/features/migrate-to-cloud/api/endpoints.gen.ts @@ -93,13 +93,11 @@ export type ErrorResponseBody = { export type CloudMigrationRequest = { authToken?: string; }; -export type ItemStatus = string; -export type MigrateDataType = string; export type MigrateDataResponseItemDto = { error?: string; - refId?: string; - status?: ItemStatus; - type?: MigrateDataType; + refId: string; + status: 'OK' | 'ERROR'; + type: 'DASHBOARD' | 'DATASOURCE' | 'FOLDER'; }; export type MigrateDataResponseDto = { id?: number; diff --git a/public/app/features/migrate-to-cloud/api/index.ts b/public/app/features/migrate-to-cloud/api/index.ts index ee3bd1aa218..84a94cac4f9 100644 --- a/public/app/features/migrate-to-cloud/api/index.ts +++ b/public/app/features/migrate-to-cloud/api/index.ts @@ -1,4 +1,6 @@ export * from './endpoints.gen'; +import { BaseQueryFn, QueryDefinition } from '@reduxjs/toolkit/dist/query'; + import { generatedAPI } from './endpoints.gen'; export const cloudMigrationAPI = generatedAPI.enhanceEndpoints({ @@ -35,5 +37,24 @@ export const cloudMigrationAPI = generatedAPI.enhanceEndpoints({ runCloudMigration: { invalidatesTags: ['cloud-migration-run'], }, + + getDashboardByUid(endpoint) { + suppressErrorsOnQuery(endpoint); + }, }, }); + +function suppressErrorsOnQuery( + endpoint: QueryDefinition +) { + if (!endpoint.query) { + return; + } + + const originalQuery = endpoint.query; + endpoint.query = (...args) => { + const baseQuery = originalQuery(...args); + baseQuery.showErrorAlert = false; + return baseQuery; + }; +} diff --git a/public/app/features/migrate-to-cloud/fixtures/migrationItems.ts b/public/app/features/migrate-to-cloud/fixtures/migrationItems.ts new file mode 100644 index 00000000000..52323a766b4 --- /dev/null +++ b/public/app/features/migrate-to-cloud/fixtures/migrationItems.ts @@ -0,0 +1,31 @@ +import { Chance } from 'chance'; + +import { MigrateDataResponseItemDto } from '../api'; + +export function wellFormedDatasourceMigrationItem( + seed = 1, + partial: Partial = {} +): MigrateDataResponseItemDto { + const random = Chance(seed); + + return { + type: 'DATASOURCE', + refId: random.guid(), + status: random.pickone(['OK', 'ERROR']), + ...partial, + }; +} + +export function wellFormedDashboardMigrationItem( + seed = 1, + partial: Partial = {} +): MigrateDataResponseItemDto { + const random = Chance(seed); + + return { + type: 'DASHBOARD', + refId: random.guid(), + status: random.pickone(['OK', 'ERROR']), + ...partial, + }; +} diff --git a/public/app/features/migrate-to-cloud/fixtures/mswAPI.ts b/public/app/features/migrate-to-cloud/fixtures/mswAPI.ts index d8b9748e525..fe687e9782f 100644 --- a/public/app/features/migrate-to-cloud/fixtures/mswAPI.ts +++ b/public/app/features/migrate-to-cloud/fixtures/mswAPI.ts @@ -4,12 +4,30 @@ import { SetupServer, setupServer } from 'msw/node'; export function registerAPIHandlers(): SetupServer { const server = setupServer( // TODO - http.get('/api/cloudmigration/status', () => { + http.get('/api/dashboards/uid/:uid', ({ request, params }) => { + if (params.uid === 'dashboard-404') { + return HttpResponse.json( + { + message: 'Dashboard not found', + }, + { + status: 404, + } + ); + } + return HttpResponse.json({ - enabled: false, + dashboard: { + title: 'My Dashboard', + }, + meta: { + folderTitle: 'Dashboards', + }, }); }) ); + server.listen(); + return server; } diff --git a/public/app/features/migrate-to-cloud/fixtures/others.ts b/public/app/features/migrate-to-cloud/fixtures/others.ts new file mode 100644 index 00000000000..7599166bf73 --- /dev/null +++ b/public/app/features/migrate-to-cloud/fixtures/others.ts @@ -0,0 +1,44 @@ +import { Chance } from 'chance'; + +import { DataSourceInstanceSettings, PluginType } from '@grafana/data'; + +export function wellFormedDatasource( + seed = 1, + custom: Partial = {} +): DataSourceInstanceSettings { + const random = Chance(seed); + + return { + id: random.integer(), + uid: random.guid(), + type: random.word(), + name: random.sentence({ words: 3 }), + readOnly: false, + jsonData: {}, + meta: { + id: random.word(), + name: random.word(), + type: PluginType.datasource, + module: random.word(), + baseUrl: random.url(), + + info: { + author: { + name: random.name(), + }, + description: random.sentence({ words: 5 }), + + links: [], + logos: { + large: random.url(), + small: random.url(), + }, + screenshots: [], + updated: random.date().toISOString(), + version: '1.0.0', + }, + }, + access: 'direct', + ...custom, + }; +} diff --git a/public/app/features/migrate-to-cloud/onprem/NameCell.tsx b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx new file mode 100644 index 00000000000..30e0d30ff4c --- /dev/null +++ b/public/app/features/migrate-to-cloud/onprem/NameCell.tsx @@ -0,0 +1,143 @@ +import { css } from '@emotion/css'; +import React, { useMemo } from 'react'; +import Skeleton from 'react-loading-skeleton'; + +import { DataSourceInstanceSettings } from '@grafana/data'; +import { config } from '@grafana/runtime'; +import { CellProps, Stack, Text, Icon, useStyles2 } from '@grafana/ui'; +import { getSvgSize } from '@grafana/ui/src/components/Icon/utils'; +import { Trans } from 'app/core/internationalization'; + +import { useGetDashboardByUidQuery, MigrateDataResponseItemDto } from '../api'; + +export function NameCell(props: CellProps) { + const data = props.row.original; + + return ( + + + + + {data.type === 'DATASOURCE' ? : } + + + ); +} + +function getDashboardTitle(dashboardData: object) { + if ('title' in dashboardData && typeof dashboardData.title === 'string') { + return dashboardData.title; + } + + return undefined; +} + +function DatasourceInfo({ data }: { data: MigrateDataResponseItemDto }) { + const datasourceUID = data.refId; + const datasource = useDatasource(datasourceUID); + + if (!datasource) { + return ( + <> + + + Data source {{ datasourceUID }} + + + + Unknown data source + + + ); + } + + return ( + <> + {datasource.name} + {datasource.type} + + ); +} + +function DashboardInfo({ data }: { data: MigrateDataResponseItemDto }) { + const dashboardUID = data.refId; + // TODO: really, the API should return this directly + const { data: dashboardData, isError } = useGetDashboardByUidQuery({ + uid: dashboardUID, + }); + + const dashboardName = useMemo(() => { + return (dashboardData?.dashboard && getDashboardTitle(dashboardData.dashboard)) ?? dashboardUID; + }, [dashboardData, dashboardUID]); + + if (isError) { + // Not translated because this is only temporary until the data comes through in the MigrationRun API + return ( + <> + Unable to load dashboard + Dashboard {dashboardUID} + + ); + } + + if (!dashboardData) { + return ; + } + + return ( + <> + {dashboardName} + {dashboardData.meta?.folderTitle ?? 'Dashboards'} + + ); +} + +function InfoSkeleton() { + return ( + <> + + + + ); +} + +function ResourceIcon({ resource }: { resource: MigrateDataResponseItemDto }) { + const styles = useStyles2(getIconStyles); + const datasource = useDatasource(resource.type === 'DATASOURCE' ? resource.refId : undefined); + + if (resource.type === 'DASHBOARD') { + return ; + } + + if (resource.type === 'DATASOURCE' && datasource?.meta?.info?.logos?.small) { + return ; + } else if (resource.type === 'DATASOURCE') { + return ; + } + + return undefined; +} + +function getIconStyles() { + return { + icon: css({ + display: 'block', + width: getSvgSize('xl'), + height: getSvgSize('xl'), + }), + }; +} + +function useDatasource(datasourceUID: string | undefined): DataSourceInstanceSettings | undefined { + const datasource = useMemo(() => { + if (!datasourceUID) { + return undefined; + } + + return ( + config.datasources[datasourceUID] || Object.values(config.datasources).find((ds) => ds.uid === datasourceUID) + ); + }, [datasourceUID]); + + return datasource; +} diff --git a/public/app/features/migrate-to-cloud/onprem/Page.tsx b/public/app/features/migrate-to-cloud/onprem/Page.tsx index 907c171b8f7..ab9406cd491 100644 --- a/public/app/features/migrate-to-cloud/onprem/Page.tsx +++ b/public/app/features/migrate-to-cloud/onprem/Page.tsx @@ -1,19 +1,16 @@ import { skipToken } from '@reduxjs/toolkit/query/react'; -import React, { useCallback, useMemo } from 'react'; +import React, { useCallback } from 'react'; -import { config } from '@grafana/runtime'; import { Alert, Box, Button, Stack } from '@grafana/ui'; import { Trans, t } from 'app/core/internationalization'; import { - MigrateDataResponseDto, useDeleteCloudMigrationMutation, useGetCloudMigrationRunListQuery, useGetCloudMigrationRunQuery, useGetMigrationListQuery, useRunCloudMigrationMutation, } from '../api'; -import { MigrationResourceDTOMock } from '../mockAPI'; import { EmptyState } from './EmptyState/EmptyState'; import { MigrationInfo } from './MigrationInfo'; @@ -79,7 +76,7 @@ export const Page = () => { lastMigrationRun.isFetching || disconnectResult.isLoading; - const resources = useFixResources(lastMigrationRun.data); + const resources = lastMigrationRun.data?.items; const handleDisconnect = useCallback(() => { if (migrationDestination.data?.id) { @@ -178,61 +175,3 @@ export const Page = () => { ); }; - -// converts API status to our expected/mocked status -function convertStatus(status: string) { - switch (status) { - case 'OK': - return 'migrated'; - case 'ERROR': - return 'failed'; - case 'failed': - return 'failed'; - default: - return 'failed'; - } -} - -function useFixResources(data: MigrateDataResponseDto | undefined) { - return useMemo(() => { - if (!data?.items) { - return undefined; - } - - const betterResources: MigrationResourceDTOMock[] = data.items.flatMap((item) => { - if (item.type === 'DATASOURCE') { - const datasourceConfig = Object.values(config.datasources).find((v) => v.uid === item.refId); - - return { - type: 'datasource', - uid: item.refId ?? '', - status: convertStatus(item.status ?? ''), - statusMessage: item.error, - resource: { - uid: item.refId ?? '', - name: datasourceConfig?.name ?? 'Unknown data source', - type: datasourceConfig?.meta?.name ?? 'Unknown type', - icon: datasourceConfig?.meta?.info?.logos?.small, - }, - }; - } - - if (item.type === 'DASHBOARD') { - return { - type: 'dashboard', - uid: item.refId ?? '', - status: convertStatus(item.status ?? ''), - statusMessage: item.error, - resource: { - uid: item.refId ?? '', - name: item.refId ?? 'Unknown dashboard', - }, - }; - } - - return []; - }); - - return betterResources; - }, [data]); -} diff --git a/public/app/features/migrate-to-cloud/onprem/ResourcesTable.test.tsx b/public/app/features/migrate-to-cloud/onprem/ResourcesTable.test.tsx new file mode 100644 index 00000000000..f4a91f473fa --- /dev/null +++ b/public/app/features/migrate-to-cloud/onprem/ResourcesTable.test.tsx @@ -0,0 +1,132 @@ +import 'whatwg-fetch'; // fetch polyfill +import { render as rtlRender, screen } from '@testing-library/react'; +import { SetupServer } from 'msw/lib/node'; +import React from 'react'; +import { TestProvider } from 'test/helpers/TestProvider'; + +import { setBackendSrv, config } from '@grafana/runtime'; +import { backendSrv } from 'app/core/services/backend_srv'; + +import { wellFormedDashboardMigrationItem, wellFormedDatasourceMigrationItem } from '../fixtures/migrationItems'; +import { registerAPIHandlers } from '../fixtures/mswAPI'; +import { wellFormedDatasource } from '../fixtures/others'; + +import { ResourcesTable } from './ResourcesTable'; + +setBackendSrv(backendSrv); + +function render(...[ui, options]: Parameters) { + rtlRender({ui}, options); +} + +describe('ResourcesTable', () => { + let server: SetupServer; + let originalDatasources: (typeof config)['datasources']; + + const datasourceA = wellFormedDatasource(1, { + uid: 'datasource-a-uid', + name: 'Datasource A', + }); + + beforeAll(() => { + server = registerAPIHandlers(); + originalDatasources = config.datasources; + + config.datasources = { + ...config.datasources, + [datasourceA.name]: datasourceA, + }; + }); + + afterAll(() => { + server.close(); + config.datasources = originalDatasources; + }); + + it('renders data sources', async () => { + const resources = [ + wellFormedDatasourceMigrationItem(1, { + refId: datasourceA.uid, + }), + ]; + + render(); + + expect(screen.getByText('Datasource A')).toBeInTheDocument(); + }); + + it('renders data sources when their data is missing', () => { + const item = wellFormedDatasourceMigrationItem(2); + const resources = [item]; + + render(); + + expect(screen.getByText(`Data source ${item.refId}`)).toBeInTheDocument(); + expect(screen.getByText(`Unknown data source`)).toBeInTheDocument(); + }); + + it('renders dashboards', async () => { + const resources = [wellFormedDashboardMigrationItem(1)]; + + render(); + + expect(await screen.findByText('My Dashboard')).toBeInTheDocument(); + }); + + it('renders dashboards when their data is missing', async () => { + const resources = [ + wellFormedDashboardMigrationItem(2, { + refId: 'dashboard-404', + }), + ]; + + render(); + + expect(await screen.findByText('Unable to load dashboard')).toBeInTheDocument(); + expect(await screen.findByText('Dashboard dashboard-404')).toBeInTheDocument(); + }); + + it('renders the success status correctly', () => { + const resources = [ + wellFormedDatasourceMigrationItem(1, { + refId: datasourceA.uid, + status: 'OK', + }), + ]; + + render(); + + expect(screen.getByText('Uploaded to cloud')).toBeInTheDocument(); + }); + + it('renders the success error correctly', () => { + const resources = [ + wellFormedDatasourceMigrationItem(1, { + refId: datasourceA.uid, + status: 'ERROR', + }), + ]; + + render(); + + expect(screen.getByText('Error')).toBeInTheDocument(); + }); + + it("shows a details button when there's an error description", () => { + const resources = [ + wellFormedDatasourceMigrationItem(1, { + refId: datasourceA.uid, + status: 'ERROR', + error: 'Some error', + }), + ]; + + render(); + + expect( + screen.getByRole('button', { + name: 'Details', + }) + ).toBeInTheDocument(); + }); +}); diff --git a/public/app/features/migrate-to-cloud/onprem/ResourcesTable.tsx b/public/app/features/migrate-to-cloud/onprem/ResourcesTable.tsx index 1835047b1b8..1657e721503 100644 --- a/public/app/features/migrate-to-cloud/onprem/ResourcesTable.tsx +++ b/public/app/features/migrate-to-cloud/onprem/ResourcesTable.tsx @@ -1,16 +1,15 @@ -import { css } from '@emotion/css'; -import React, { useMemo } from 'react'; -import Skeleton from 'react-loading-skeleton'; +import React from 'react'; -import { InteractiveTable, CellProps, Stack, Text, Icon, useStyles2, Button } from '@grafana/ui'; -import { getSvgSize } from '@grafana/ui/src/components/Icon/utils'; -import { t } from 'app/core/internationalization'; +import { InteractiveTable } from '@grafana/ui'; -import { useGetDashboardByUidQuery } from '../api'; -import { MigrationResourceDTOMock, MigrationResourceDashboard, MigrationResourceDatasource } from '../mockAPI'; +import { MigrateDataResponseItemDto } from '../api'; + +import { NameCell } from './NameCell'; +import { StatusCell } from './StatusCell'; +import { TypeCell } from './TypeCell'; interface ResourcesTableProps { - resources: MigrationResourceDTOMock[]; + resources: MigrateDataResponseItemDto[]; } const columns = [ @@ -20,134 +19,5 @@ const columns = [ ]; export function ResourcesTable({ resources }: ResourcesTableProps) { - return r.uid} pageSize={15} />; -} - -function NameCell(props: CellProps) { - const data = props.row.original; - - return ( - - - - - {data.type === 'datasource' ? : } - - - ); -} - -function getDashboardTitle(dashboardData: object) { - if ('title' in dashboardData && typeof dashboardData.title === 'string') { - return dashboardData.title; - } - - return undefined; -} - -function DatasourceInfo({ data }: { data: MigrationResourceDatasource }) { - return ( - <> - {data.resource.name} - {data.resource.type} - - ); -} - -// TODO: really, the API should return this directly -function DashboardInfo({ data }: { data: MigrationResourceDashboard }) { - const { data: dashboardData } = useGetDashboardByUidQuery({ - uid: data.resource.uid, - }); - - const dashboardName = useMemo(() => { - return (dashboardData?.dashboard && getDashboardTitle(dashboardData.dashboard)) ?? data.resource.uid; - }, [dashboardData, data.resource.uid]); - - if (!dashboardData) { - return ( - <> - - - - - - - - ); - } - - return ( - <> - {dashboardName} - {dashboardData.meta?.folderTitle ?? 'Dashboards'} - - ); -} - -function TypeCell(props: CellProps) { - const { type } = props.row.original; - - if (type === 'datasource') { - return t('migrate-to-cloud.resource-type.datasource', 'Data source'); - } - - if (type === 'dashboard') { - return t('migrate-to-cloud.resource-type.dashboard', 'Dashboard'); - } - - return t('migrate-to-cloud.resource-type.unknown', 'Unknown'); -} - -function StatusCell(props: CellProps) { - const { status, statusMessage } = props.row.original; - - if (status === 'not-migrated') { - return {t('migrate-to-cloud.resource-status.not-migrated', 'Not yet uploaded')}; - } else if (status === 'migrating') { - return {t('migrate-to-cloud.resource-status.migrating', 'Uploading...')}; - } else if (status === 'migrated') { - return {t('migrate-to-cloud.resource-status.migrated', 'Uploaded to cloud')}; - } else if (status === 'failed') { - return ( - - {t('migrate-to-cloud.resource-status.failed', 'Error')} - - {statusMessage && ( - // TODO: trigger a proper modal, probably from the parent, on click - - )} - - ); - } - - return {t('migrate-to-cloud.resource-status.unknown', 'Unknown')}; -} - -function ResourceIcon({ resource }: { resource: MigrationResourceDTOMock }) { - const styles = useStyles2(getIconStyles); - - if (resource.type === 'dashboard') { - return ; - } - - if (resource.type === 'datasource' && resource.resource.icon) { - return ; - } else if (resource.type === 'datasource') { - return ; - } - - return undefined; -} - -function getIconStyles() { - return { - icon: css({ - display: 'block', - width: getSvgSize('xl'), - height: getSvgSize('xl'), - }), - }; + return r.refId} pageSize={15} />; } diff --git a/public/app/features/migrate-to-cloud/onprem/StatusCell.tsx b/public/app/features/migrate-to-cloud/onprem/StatusCell.tsx new file mode 100644 index 00000000000..644ea9729f2 --- /dev/null +++ b/public/app/features/migrate-to-cloud/onprem/StatusCell.tsx @@ -0,0 +1,33 @@ +import React from 'react'; + +import { CellProps, Text, Stack, Button } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +import { MigrateDataResponseItemDto } from '../api'; + +export function StatusCell(props: CellProps) { + const { status, error } = props.row.original; + + // Keep these here to preserve the translations + // t('migrate-to-cloud.resource-status.not-migrated', 'Not yet uploaded') + // t('migrate-to-cloud.resource-status.migrating', 'Uploading...') + + if (status === 'OK') { + return {t('migrate-to-cloud.resource-status.migrated', 'Uploaded to cloud')}; + } else if (status === 'ERROR') { + return ( + + {t('migrate-to-cloud.resource-status.failed', 'Error')} + + {error && ( + // TODO: trigger a proper modal, probably from the parent, on click + + )} + + ); + } + + return {t('migrate-to-cloud.resource-status.unknown', 'Unknown')}; +} diff --git a/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx b/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx new file mode 100644 index 00000000000..769183d1bd7 --- /dev/null +++ b/public/app/features/migrate-to-cloud/onprem/TypeCell.tsx @@ -0,0 +1,19 @@ +import { CellProps } from '@grafana/ui'; +import { t } from 'app/core/internationalization'; + +import { MigrateDataResponseItemDto } from '../api'; + +export function TypeCell(props: CellProps) { + const { type } = props.row.original; + + switch (type) { + case 'DATASOURCE': + return t('migrate-to-cloud.resource-type.datasource', 'Data source'); + case 'DASHBOARD': + return t('migrate-to-cloud.resource-type.dashboard', 'Dashboard'); + case 'FOLDER': + return t('migrate-to-cloud.resource-type.folder', 'Folder'); + default: + return t('migrate-to-cloud.resource-type.unknown', 'Unknown'); + } +} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index d5456911eac..b037d9e8b35 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -786,9 +786,14 @@ "not-migrated": "Not yet uploaded", "unknown": "Unknown" }, + "resource-table": { + "unknown-datasource-title": "Data source {{datasourceUID}}", + "unknown-datasource-type": "Unknown data source" + }, "resource-type": { "dashboard": "Dashboard", "datasource": "Data source", + "folder": "Folder", "unknown": "Unknown" }, "summary": { diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index c0220152110..fe9435e5c32 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -786,9 +786,14 @@ "not-migrated": "Ńőŧ yęŧ ūpľőäđęđ", "unknown": "Ůʼnĸʼnőŵʼn" }, + "resource-table": { + "unknown-datasource-title": "Đäŧä şőūřčę {{datasourceUID}}", + "unknown-datasource-type": "Ůʼnĸʼnőŵʼn đäŧä şőūřčę" + }, "resource-type": { "dashboard": "Đäşĥþőäřđ", "datasource": "Đäŧä şőūřčę", + "folder": "Főľđęř", "unknown": "Ůʼnĸʼnőŵʼn" }, "summary": { diff --git a/public/openapi3.json b/public/openapi3.json index c9aad593102..243e97eac9b 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -6277,9 +6277,6 @@ }, "type": "object" }, - "ItemStatus": { - "type": "string" - }, "JSONWebKey": { "description": "JSONWebKey represents a public or private key in JWK format. It can be\nmarshaled into JSON and unmarshaled from JSON.", "properties": { @@ -6739,17 +6736,28 @@ "type": "string" }, "status": { - "$ref": "#/components/schemas/ItemStatus" + "enum": [ + "OK", + "ERROR" + ], + "type": "string" }, "type": { - "$ref": "#/components/schemas/MigrateDataType" + "enum": [ + "DASHBOARD", + "DATASOURCE", + "FOLDER" + ], + "type": "string" } }, + "required": [ + "type", + "refId", + "status" + ], "type": "object" }, - "MigrateDataType": { - "type": "string" - }, "MoveFolderCommand": { "description": "MoveFolderCommand captures the information required by the folder service\nto move a folder.", "properties": {