diff --git a/.betterer.results b/.betterer.results index e0aca642544..ca0b5eeff6f 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3953,9 +3953,6 @@ exports[`better eslint`] = { "public/app/plugins/panel/table/TableCellOptionEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], - "public/app/plugins/panel/table/cells/AutoCellOptionsEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] - ], "public/app/plugins/panel/table/cells/BarGaugeCellOptionsEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] @@ -3963,52 +3960,27 @@ exports[`better eslint`] = { "public/app/plugins/panel/table/cells/ColorBackgroundCellOptionsEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "2"] + [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] ], "public/app/plugins/panel/table/cells/ImageCellOptionsEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] ], + "public/app/plugins/panel/table/cells/MarkdownCellOptionsEditor.tsx:5381": [ + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] + ], "public/app/plugins/panel/table/cells/SparklineCellOptionsEditor.tsx:5381": [ [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] ], + "public/app/plugins/panel/table/cells/TextWrapOptionsEditor.tsx:5381": [ + [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] + ], "public/app/plugins/panel/table/migrations.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"], [0, 0, 0, "Unexpected any. Specify a different type.", "1"], [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"] ], - "public/app/plugins/panel/table/table-new/TableCellOptionEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] - ], - "public/app/plugins/panel/table/table-new/cells/BarGaugeCellOptionsEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] - ], - "public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"], - [0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "2"] - ], - "public/app/plugins/panel/table/table-new/cells/ImageCellOptionsEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"], - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "1"] - ], - "public/app/plugins/panel/table/table-new/cells/MarkdownCellOptionsEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] - ], - "public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] - ], - "public/app/plugins/panel/table/table-new/cells/TextWrapOptionsEditor.tsx:5381": [ - [0, 0, 0, "Add noMargin prop to Field components to remove built-in margins. Use layout components like Stack or Grid with the gap prop instead for consistent spacing.", "0"] - ], - "public/app/plugins/panel/table/table-new/migrations.ts:5381": [ - [0, 0, 0, "Unexpected any. Specify a different type.", "0"], - [0, 0, 0, "Unexpected any. Specify a different type.", "1"], - [0, 0, 0, "Unexpected any. Specify a different type.", "2"], - [0, 0, 0, "Unexpected any. Specify a different type.", "3"] - ], "public/app/plugins/panel/text/textPanelMigrationHandler.ts:5381": [ [0, 0, 0, "Unexpected any. Specify a different type.", "0"] ], diff --git a/apps/dashboard/pkg/migration/testdata/output/v24.table-angular.json b/apps/dashboard/pkg/migration/testdata/output/v24.table-angular.json index f9ccbcd0dc4..028723f153e 100644 --- a/apps/dashboard/pkg/migration/testdata/output/v24.table-angular.json +++ b/apps/dashboard/pkg/migration/testdata/output/v24.table-angular.json @@ -1331,4 +1331,4 @@ ], "refresh": "", "schemaVersion": 41 -} \ No newline at end of file +} diff --git a/devenv/dev-dashboards/panel-table/table_kitchen_sink.json b/devenv/dev-dashboards/panel-table/table_kitchen_sink.json index 8ff35adb06d..b961173d362 100644 --- a/devenv/dev-dashboards/panel-table/table_kitchen_sink.json +++ b/devenv/dev-dashboards/panel-table/table_kitchen_sink.json @@ -37,8 +37,7 @@ "wrapText": false }, "filterable": true, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "fieldMinMax": true, "links": [], @@ -557,8 +556,7 @@ "type": "color-background" }, "filterable": true, - "inspect": true, - "wrapHeaderText": false + "inspect": true }, "fieldMinMax": true, "links": [], @@ -664,8 +662,7 @@ "type": "auto" }, "inspect": false, - "minWidth": 50, - "wrapHeaderText": false + "minWidth": 50 }, "mappings": [], "thresholds": { @@ -1012,8 +1009,7 @@ "cellOptions": { "type": "auto" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "fieldMinMax": true, "mappings": [], @@ -1161,8 +1157,7 @@ "cellOptions": { "type": "auto" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "fieldMinMax": true, "mappings": [], @@ -1309,8 +1304,7 @@ "cellOptions": { "type": "auto" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "fieldMinMax": true, "mappings": [], @@ -1457,8 +1451,7 @@ "cellOptions": { "type": "auto" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "fieldMinMax": true, "mappings": [], @@ -1605,8 +1598,7 @@ "cellOptions": { "type": "auto" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "fieldMinMax": true, "mappings": [], @@ -1788,8 +1780,7 @@ "cellOptions": { "type": "auto" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "mappings": [], "thresholds": { diff --git a/devenv/dev-dashboards/panel-table/table_markdown.json b/devenv/dev-dashboards/panel-table/table_markdown.json index 06f3d7c310c..eb4a570d75e 100644 --- a/devenv/dev-dashboards/panel-table/table_markdown.json +++ b/devenv/dev-dashboards/panel-table/table_markdown.json @@ -36,8 +36,7 @@ "cellOptions": { "type": "auto" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "mappings": [], "thresholds": { diff --git a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json index d36609b1678..3337c794279 100644 --- a/devenv/dev-dashboards/panel-table/table_sparkline_cell.json +++ b/devenv/dev-dashboards/panel-table/table_sparkline_cell.json @@ -36,8 +36,7 @@ "cellOptions": { "type": "sparkline" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "mappings": [], "thresholds": { @@ -113,8 +112,7 @@ "drawStyle": "bars", "type": "sparkline" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "mappings": [], "thresholds": { @@ -191,8 +189,7 @@ "drawStyle": "points", "type": "sparkline" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "mappings": [], "thresholds": { @@ -269,8 +266,7 @@ "drawStyle": "line", "type": "sparkline" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "mappings": [], "min": 0, @@ -349,8 +345,7 @@ "drawStyle": "line", "type": "sparkline" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "mappings": [], "max": 100, @@ -430,8 +425,7 @@ "lineInterpolation": "linear", "type": "sparkline" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "mappings": [], "max": 100, @@ -509,8 +503,7 @@ "cellOptions": { "type": "auto" }, - "inspect": false, - "wrapHeaderText": false + "inspect": false }, "mappings": [], "thresholds": { diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index c7ec80a9662..cc9297ded83 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -98,7 +98,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `canvasPanelPanZoom` | Allow pan and zoom in canvas panel | | `regressionTransformation` | Enables regression analysis transformation | | `alertingSaveStateCompressed` | Enables the compressed protobuf-based alert state storage | -| `tableNextGen` | Allows access to the new react-data-grid based table component. | | `enableSCIM` | Enables SCIM support for user and group management | | `elasticsearchCrossClusterSearch` | Enables cross cluster search in the Elasticsearch datasource | | `alertRuleRestore` | Enables the alert rule restore feature | diff --git a/e2e-playwright/dashboards-suite/dashboard-live-streaming.spec.ts b/e2e-playwright/dashboards-suite/dashboard-live-streaming.spec.ts index c8917dfae47..07aacd5b3fb 100644 --- a/e2e-playwright/dashboards-suite/dashboard-live-streaming.spec.ts +++ b/e2e-playwright/dashboards-suite/dashboard-live-streaming.spec.ts @@ -4,7 +4,6 @@ import testDashboard from '../dashboards/DashboardLiveTest.json'; test.use({ featureToggles: { - tableNextGen: true, kubernetesDashboards: process.env.KUBERNETES_DASHBOARDS === 'true', }, }); diff --git a/e2e-playwright/dashboards-suite/dashboard-time-zone.spec.ts b/e2e-playwright/dashboards-suite/dashboard-time-zone.spec.ts index 3dc86271227..271e2f42b1f 100644 --- a/e2e-playwright/dashboards-suite/dashboard-time-zone.spec.ts +++ b/e2e-playwright/dashboards-suite/dashboard-time-zone.spec.ts @@ -51,7 +51,7 @@ test.describe( .getByGrafanaSelector(selectors.components.Panels.Panel.title(title)) .getByRole('row') .nth(1) - .getByRole('cell') + .getByRole('gridcell') .first(); const time = await timeCell.textContent(); if (time) { @@ -79,7 +79,7 @@ test.describe( .getByGrafanaSelector(selectors.components.Panels.Panel.title(title)) .getByRole('row') .nth(1) - .getByRole('cell') + .getByRole('gridcell') .first(); await expect(async () => { const inUtc = timesInUtc[title]; diff --git a/e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts b/e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts index dc625ea4adc..cc05bf71b5d 100644 --- a/e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts +++ b/e2e-playwright/panels-suite/geomap-spatial-operations-transform.spec.ts @@ -2,12 +2,6 @@ import { test, expect } from '@grafana/plugin-e2e'; const DASHBOARD_ID = 'P2jR04WVk'; -test.use({ - featureToggles: { - tableNextGen: true, - }, -}); - test.describe( 'Panels test: Geomap spatial operations', { diff --git a/e2e-playwright/panels-suite/panelEdit_base.spec.ts b/e2e-playwright/panels-suite/panelEdit_base.spec.ts index da98e8089f5..9e12c3b7860 100644 --- a/e2e-playwright/panels-suite/panelEdit_base.spec.ts +++ b/e2e-playwright/panels-suite/panelEdit_base.spec.ts @@ -2,12 +2,6 @@ import { test, expect } from '@grafana/plugin-e2e'; const PANEL_UNDER_TEST = 'Lines 500 data points'; -test.use({ - featureToggles: { - tableNextGen: true, - }, -}); - test.describe( 'Panels test: Panel edit base', { diff --git a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts index fc24bfd6832..5765ada1e18 100644 --- a/e2e-playwright/panels-suite/table-kitchenSink.spec.ts +++ b/e2e-playwright/panels-suite/table-kitchenSink.spec.ts @@ -4,7 +4,7 @@ import { test, expect, E2ESelectorGroups } from '@grafana/plugin-e2e'; const DASHBOARD_UID = 'dcb9f5e9-8066-4397-889e-864b99555dbb'; -test.use({ viewport: { width: 2000, height: 1080 }, featureToggles: { tableNextGen: true } }); +test.use({ viewport: { width: 2000, height: 1080 } }); // helper utils const waitForTableLoad = async (loc: Page | Locator) => { diff --git a/e2e-playwright/panels-suite/table-markdown.spec.ts b/e2e-playwright/panels-suite/table-markdown.spec.ts index b2e127fc92c..68068271fe6 100644 --- a/e2e-playwright/panels-suite/table-markdown.spec.ts +++ b/e2e-playwright/panels-suite/table-markdown.spec.ts @@ -2,9 +2,6 @@ import { test, expect } from '@grafana/plugin-e2e'; test.use({ viewport: { width: 1280, height: 1080 }, - featureToggles: { - tableNextGen: true, - }, }); test.describe( diff --git a/e2e-playwright/panels-suite/table-sparkline.spec.ts b/e2e-playwright/panels-suite/table-sparkline.spec.ts index d2ac814e2df..fa2ed681f1d 100644 --- a/e2e-playwright/panels-suite/table-sparkline.spec.ts +++ b/e2e-playwright/panels-suite/table-sparkline.spec.ts @@ -1,6 +1,6 @@ import { test, expect } from '@grafana/plugin-e2e'; -test.use({ viewport: { width: 1280, height: 1080 }, featureToggles: { tableNextGen: true } }); +test.use({ viewport: { width: 1280, height: 1080 } }); test.describe('Panels test: Table - Sparkline', { tag: ['@panels', '@table'] }, () => { test('Tests sparkline tables are successfully rendered', async ({ gotoDashboardPage, selectors, page }) => { diff --git a/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelDataAssertion.spec.ts b/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelDataAssertion.spec.ts index 0133a3e3712..e1fef705a88 100644 --- a/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelDataAssertion.spec.ts +++ b/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelDataAssertion.spec.ts @@ -38,7 +38,7 @@ test.describe( formatExpectError('Could not locate header elements in table panel') ).toContainText(['col1', 'col2']); await expect( - panelEditPage.panel.data, + panelEditPage.panel.locator.getByRole('gridcell'), formatExpectError('Could not locate headers in table panel') ).toContainText(['val1', 'val2', 'val3', 'val4']); }); @@ -58,7 +58,7 @@ test.describe( formatExpectError('Could not locate header elements in table panel') ).toContainText(['col1', 'col2']); await expect( - panelEditPage.panel.data, + panelEditPage.panel.locator.getByRole('gridcell'), formatExpectError('Could not locate data elements in table panel') ).toContainText(['val1', 'val2', 'val3', 'val4']); }); diff --git a/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelEditPage.spec.ts b/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelEditPage.spec.ts index 577a758d97e..6ea57641a7f 100644 --- a/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelEditPage.spec.ts +++ b/e2e-playwright/plugin-e2e/plugin-e2e-api-tests/as-admin-user/panelEditPage.spec.ts @@ -11,12 +11,6 @@ const STANDARD_OTIONS_CATEGORY = 'Standard options'; const DISPLAY_NAME_LABEL = 'Display name'; const REACT_TABLE_DASHBOARD = { uid: 'U_bZIMRMk' }; -test.use({ - featureToggles: { - tableNextGen: true, - }, -}); - test.describe( 'plugin-e2e-api-tests admin', { diff --git a/e2e-playwright/various-suite/loki-table-explore-to-dash.spec.ts b/e2e-playwright/various-suite/loki-table-explore-to-dash.spec.ts index 8495d43c07c..4b2e3876154 100644 --- a/e2e-playwright/various-suite/loki-table-explore-to-dash.spec.ts +++ b/e2e-playwright/various-suite/loki-table-explore-to-dash.spec.ts @@ -190,7 +190,7 @@ test.describe( await expect(panel).toBeVisible(); // Check the table cells in the panel - const panelCells = panel.locator('[role="table"] [role="cell"]'); + const panelCells = panel.locator('[role="gridcell"]'); // Should have 3 columns await expect(panelCells).toHaveCount(3); @@ -198,7 +198,7 @@ test.describe( await expect(page.getByText('"wave":-0.5877852522916832')).toBeVisible(); // Column has correct value of "targetLabelValue" - await expect(panel.locator('[role="table"] [role="cell"]').filter({ hasText: 'targetLabelValue' })).toBeVisible(); + await expect(panel.locator('[role="gridcell"]').filter({ hasText: 'targetLabelValue' })).toBeVisible(); }); } ); diff --git a/e2e-playwright/various-suite/visualization-suggestions.spec.ts b/e2e-playwright/various-suite/visualization-suggestions.spec.ts index 38c6f30d298..57ccfccc475 100644 --- a/e2e-playwright/various-suite/visualization-suggestions.spec.ts +++ b/e2e-playwright/various-suite/visualization-suggestions.spec.ts @@ -1,11 +1,5 @@ import { test, expect } from '@grafana/plugin-e2e'; -test.use({ - featureToggles: { - tableNextGen: true, - }, -}); - test.describe( 'Visualization suggestions', { diff --git a/e2e/old-arch/dashboards-suite/dashboard-live-streaming.spec.ts b/e2e/old-arch/dashboards-suite/dashboard-live-streaming.spec.ts deleted file mode 100644 index 5798b669f6f..00000000000 --- a/e2e/old-arch/dashboards-suite/dashboard-live-streaming.spec.ts +++ /dev/null @@ -1,16 +0,0 @@ -import testDashboard from '../dashboards/DashboardLiveTest.json'; -import { e2e } from '../utils'; - -describe('Dashboard Live streaming support', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - e2e.flows.importDashboard(testDashboard, 1000); - }); - - it('Should receive streaming data', () => { - e2e.flows.openDashboard({ uid: 'live-e2e-test', queryParams: { '__feature.tableNextGen': false } }); - cy.wait(1000); - e2e.components.Panels.Panel.title('Live').should('exist'); - e2e.components.Panels.Visualization.Table.body().find('[role="row"]').should('have.length.at.least', 5); - }); -}); diff --git a/e2e/old-arch/dashboards-suite/dashboard-time-zone.spec.ts b/e2e/old-arch/dashboards-suite/dashboard-time-zone.spec.ts deleted file mode 100644 index ce6d9950234..00000000000 --- a/e2e/old-arch/dashboards-suite/dashboard-time-zone.spec.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { - addDays, - addHours, - differenceInCalendarDays, - differenceInMinutes, - format, - isBefore, - parseISO, - toDate, -} from 'date-fns'; - -import { e2e } from '../utils'; - -describe('Dashboard time zone support', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it.skip('Tests dashboard time zone scenarios', () => { - e2e.flows.openDashboard({ uid: '5SdHCasdf' }); - - const fromTimeZone = 'UTC'; - const toTimeZone = 'America/Chicago'; - const offset = offsetBetweenTimeZones(toTimeZone, fromTimeZone); - - const panelsToCheck = [ - 'Random walk series', - 'Millisecond res x-axis and tooltip', - '2 yaxis and axis labels', - 'Stacking value ontop of nulls', - 'Null between points', - 'Legend Table No Scroll Visible', - ]; - - const timesInUtc: Record = {}; - - for (const title of panelsToCheck) { - e2e.components.Panels.Panel.title(title) - .should('be.visible') - .within(() => { - e2e.components.Panels.Visualization.Graph.xAxis.labels().should('be.visible'); - e2e.components.Panels.Visualization.Graph.xAxis - .labels() - .last() - .should((element) => { - timesInUtc[title] = element.text(); - }); - }); - } - - e2e.components.PageToolbar.item('Dashboard settings').click(); - - e2e.components.TimeZonePicker.containerV2() - .should('be.visible') - .within(() => { - e2e.components.Select.singleValue().should('have.text', 'Coordinated Universal Time'); - e2e.components.Select.input().should('be.visible').click(); - }); - - e2e.components.Select.option().should('be.visible').contains(toTimeZone).click(); - - // click to go back to the dashboard. - e2e.pages.Dashboard.Settings.Actions.close().click(); - e2e.components.RefreshPicker.runButtonV2().should('be.visible').click(); - - for (const title of panelsToCheck) { - e2e.components.Panels.Panel.title(title) - .should('be.visible') - .within(() => { - e2e.components.Panels.Visualization.Graph.xAxis.labels().should('be.visible'); - e2e.components.Panels.Visualization.Graph.xAxis - .labels() - .last() - .should((element) => { - const inUtc = timesInUtc[title]; - const inTz = element.text(); - const isCorrect = isTimeCorrect(inUtc, inTz, offset); - expect(isCorrect).to.be.equal(true); - }); - }); - } - }); - - it('Tests relative timezone support and overrides', () => { - // Open dashboard - e2e.flows.openDashboard({ - uid: 'd41dbaa2-a39e-4536-ab2b-caca52f1a9c8', - }); - - cy.intercept('/api/ds/query*').as('dataQuery'); - - // Switch to Browser timezone - e2e.flows.setTimeRange({ - from: 'now-6h', - to: 'now', - zone: 'Browser', - }); - // Need to wait for 2 calls as there's 2 panels - cy.wait(['@dataQuery', '@dataQuery']); - - e2e.components.Panels.Panel.title('Panel with relative time override') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - // Today so far, still in Browser timezone - e2e.flows.setTimeRange({ - from: 'now/d', - to: 'now', - }); - // Need to wait for 2 calls as there's 2 panels - cy.wait(['@dataQuery', '@dataQuery']); - - e2e.components.Panels.Panel.title('Panel with relative time override') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - e2e.components.Panels.Panel.title('Panel in timezone') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - // Test UTC timezone - e2e.flows.setTimeRange({ - from: 'now-6h', - to: 'now', - zone: 'Coordinated Universal Time', - }); - // Need to wait for 2 calls as there's 2 panels - cy.wait(['@dataQuery', '@dataQuery']); - - e2e.components.Panels.Panel.title('Panel with relative time override') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - // Today so far, still in UTC timezone - e2e.flows.setTimeRange({ - from: 'now/d', - to: 'now', - }); - // Need to wait for 2 calls as there's 2 panels - cy.wait(['@dataQuery', '@dataQuery']); - - e2e.components.Panels.Panel.title('Panel with relative time override') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - e2e.components.Panels.Panel.title('Panel in timezone') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - // Test Tokyo timezone - e2e.flows.setTimeRange({ - from: 'now-6h', - to: 'now', - zone: 'Asia/Tokyo', - }); - // Need to wait for 2 calls as there's 2 panels - cy.wait(['@dataQuery', '@dataQuery']); - - e2e.components.Panels.Panel.title('Panel with relative time override') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - // Today so far, still in Tokyo timezone - e2e.flows.setTimeRange({ - from: 'now/d', - to: 'now', - }); - // Need to wait for 2 calls as there's 2 panels - cy.wait(['@dataQuery', '@dataQuery']); - - e2e.components.Panels.Panel.title('Panel with relative time override') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - e2e.components.Panels.Panel.title('Panel in timezone') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - // Test LA timezone - e2e.flows.setTimeRange({ - from: 'now-6h', - to: 'now', - zone: 'America/Los Angeles', - }); - // Need to wait for 2 calls as there's 2 panels - cy.wait(['@dataQuery', '@dataQuery']); - - e2e.components.Panels.Panel.title('Panel with relative time override') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - // Today so far, still in LA timezone - e2e.flows.setTimeRange({ - from: 'now/d', - to: 'now', - }); - // Need to wait for 2 calls as there's 2 panels - cy.wait(['@dataQuery', '@dataQuery']); - - e2e.components.Panels.Panel.title('Panel with relative time override') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - - e2e.components.Panels.Panel.title('Panel in timezone') - .should('be.visible') - .within(() => { - cy.contains('[role="row"]', '00:00:00').should('be.visible'); - }); - }); -}); - -const isTimeCorrect = (inUtc: string, inTz: string, offset: number): boolean => { - if (inUtc === inTz) { - // we need to catch issues when timezone isn't changed for some reason like https://github.com/grafana/grafana/issues/35504 - return false; - } - - const reference = format(new Date(), 'yyyy-LL-dd'); - - const utcDate = toDate(parseISO(`${reference} ${inUtc}`)); - const utcDateWithOffset = addHours(toDate(parseISO(`${reference} ${inUtc}`)), offset); - const dayDifference = differenceInCalendarDays(utcDate, utcDateWithOffset); // if the utcDate +/- offset is the day before/after then we need to adjust reference - const dayOffset = isBefore(utcDateWithOffset, utcDate) ? dayDifference * -1 : dayDifference; - const tzDate = addDays(toDate(parseISO(`${reference} ${inTz}`)), dayOffset); // adjust tzDate with any dayOffset - const diff = Math.abs(differenceInMinutes(utcDate, tzDate)); // use Math.abs if tzDate is in future - - return diff <= Math.abs(offset * 60); -}; - -const offsetBetweenTimeZones = (timeZone1: string, timeZone2: string, when: Date = new Date()): number => { - const t1 = convertDateToAnotherTimeZone(when, timeZone1); - const t2 = convertDateToAnotherTimeZone(when, timeZone2); - return (t1.getTime() - t2.getTime()) / (1000 * 60 * 60); -}; - -const convertDateToAnotherTimeZone = (date: Date, timeZone: string): Date => { - const dateString = date.toLocaleString('en-US', { - timeZone: timeZone, - }); - return new Date(dateString); -}; diff --git a/e2e/old-arch/panels-suite/geomap-spatial-operations-transform.spec.ts b/e2e/old-arch/panels-suite/geomap-spatial-operations-transform.spec.ts deleted file mode 100644 index 7c8d3d4f197..00000000000 --- a/e2e/old-arch/panels-suite/geomap-spatial-operations-transform.spec.ts +++ /dev/null @@ -1,98 +0,0 @@ -import { e2e } from '../utils'; - -const DASHBOARD_ID = 'P2jR04WVk'; - -describe.skip('Geomap spatial operations', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it.skip('Tests location auto option', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); - e2e.components.Tab.title('Transform data').should('be.visible').click(); - e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); - - e2e.components.TransformTab.newTransform('Spatial operations').scrollIntoView().should('be.visible').click(); - e2e.components.Transforms.SpatialOperations.actionLabel().type('Prepare spatial field{enter}'); - e2e.components.Transforms.SpatialOperations.locationLabel().should('be.visible'); - e2e.components.Transforms.SpatialOperations.locationLabel().type('Auto{enter}'); - - e2e.components.PanelEditor.toggleTableView().click({ force: true }); - e2e.components.Panels.Visualization.Table.header() - .should('be.visible') - .within(() => { - cy.contains('Point').should('be.visible'); - }); - }); - - it('Tests location coords option', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); - e2e.components.Tab.title('Transform data').should('be.visible').click(); - e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); - - e2e.components.TransformTab.newTransform('Spatial operations').scrollIntoView().should('be.visible').click(); - e2e.components.Transforms.SpatialOperations.actionLabel().type('Prepare spatial field{enter}'); - e2e.components.Transforms.SpatialOperations.locationLabel().should('be.visible'); - e2e.components.Transforms.SpatialOperations.locationLabel().type('Coords{enter}'); - - e2e.components.Transforms.SpatialOperations.location.coords.latitudeFieldLabel().should('be.visible'); - e2e.components.Transforms.SpatialOperations.location.coords.latitudeFieldLabel().type('Lat{enter}'); - - e2e.components.Transforms.SpatialOperations.location.coords.longitudeFieldLabel().should('be.visible'); - e2e.components.Transforms.SpatialOperations.location.coords.longitudeFieldLabel().type('Lng{enter}'); - - e2e.components.PanelEditor.toggleTableView().click({ force: true }); - e2e.components.Panels.Visualization.Table.header() - .should('be.visible') - .within(() => { - cy.contains('Point').should('be.visible'); - }); - }); - - it('Tests geoshash field column appears in table view', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); - e2e.components.Tab.title('Transform data').should('be.visible').click(); - e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); - - e2e.components.TransformTab.newTransform('Spatial operations').scrollIntoView().should('be.visible').click(); - e2e.components.Transforms.SpatialOperations.actionLabel().type('Prepare spatial field{enter}'); - e2e.components.Transforms.SpatialOperations.locationLabel().should('be.visible'); - e2e.components.Transforms.SpatialOperations.locationLabel().type('Geohash{enter}'); - - e2e.components.Transforms.SpatialOperations.location.geohash - .geohashFieldLabel() - .should('be.visible') - .type('State{enter}'); - - e2e.components.PanelEditor.toggleTableView().click({ force: true }); - e2e.components.Panels.Visualization.Table.header() - .should('be.visible') - .within(() => { - cy.contains('State 1').should('be.visible'); - }); - }); - - it('Tests location lookup option', () => { - e2e.flows.openDashboard({ uid: DASHBOARD_ID, queryParams: { '__feature.tableNextGen': false, editPanel: 1 } }); - e2e.components.Tab.title('Transform data').should('be.visible').click(); - e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible').click(); - - e2e.components.TransformTab.newTransform('Spatial operations').scrollIntoView().should('be.visible').click(); - e2e.components.Transforms.SpatialOperations.actionLabel().type('Prepare spatial field{enter}'); - e2e.components.Transforms.SpatialOperations.locationLabel().should('be.visible'); - e2e.components.Transforms.SpatialOperations.locationLabel().type('Lookup{enter}'); - - e2e.components.Transforms.SpatialOperations.location.lookup.lookupFieldLabel().should('be.visible'); - e2e.components.Transforms.SpatialOperations.location.lookup.lookupFieldLabel().type('State{enter}'); - - e2e.components.Transforms.SpatialOperations.location.lookup.gazetteerFieldLabel().should('be.visible'); - e2e.components.Transforms.SpatialOperations.location.lookup.gazetteerFieldLabel().type('USA States{enter}'); - - e2e.components.PanelEditor.toggleTableView().click({ force: true }); - e2e.components.Panels.Visualization.Table.header() - .should('be.visible') - .within(() => { - cy.contains('Geometry').should('be.visible'); - }); - }); -}); diff --git a/e2e/old-arch/panels-suite/panelEdit_base.spec.ts b/e2e/old-arch/panels-suite/panelEdit_base.spec.ts deleted file mode 100644 index 8691382f90f..00000000000 --- a/e2e/old-arch/panels-suite/panelEdit_base.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { e2e } from '../utils'; - -const PANEL_UNDER_TEST = 'Lines 500 data points'; - -describe('Panel edit tests', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Tests various Panel edit scenarios', () => { - cy.intercept({ - pathname: '/api/ds/query', - }).as('query'); - e2e.flows.openDashboard({ uid: 'TkZXxlNG3', queryParams: { '__feature.tableNextGen': false } }); - cy.wait('@query'); - - e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST); - - // New panel editor opens when navigating from Panel menu - e2e.components.PanelEditor.General.content().should('be.visible'); - - // Queries tab is rendered and open by default - e2e.components.PanelEditor.DataPane.content() - .should('be.visible') - .within(() => { - e2e.components.Tab.title('Query').should('be.visible'); - // data should be the active tab - e2e.components.Tab.active().within((li: JQuery) => { - expect(li.text()).equals('Query1'); // there's already a query so therefore Query + 1 - }); - e2e.components.QueryTab.content().should('be.visible'); - e2e.components.TransformTab.content().should('not.exist'); - e2e.components.AlertTab.content().should('not.exist'); - e2e.components.PanelAlertTabContent.content().should('not.exist'); - - // Bottom pane tabs - // Can change to Transform tab - e2e.components.Tab.title('Transform data').should('be.visible').click(); - e2e.components.Tab.active().within((li: JQuery) => { - expect(li.text()).equals('Transform data0'); // there's no transform so therefore Transform + 0 - }); - e2e.components.Transforms.addTransformationButton().scrollIntoView().should('be.visible'); - e2e.components.QueryTab.content().should('not.exist'); - e2e.components.AlertTab.content().should('not.exist'); - e2e.components.PanelAlertTabContent.content().should('not.exist'); - - // Can change to Alerts tab (graph panel is the default vis so the alerts tab should be rendered) - e2e.components.Tab.title('Alert').should('be.visible').click(); - e2e.components.Tab.active().should('have.text', 'Alert0'); // there's no alert so therefore Alert + 0 - - // Needs to be disabled until Grafana EE turns unified alerting on by default - // e2e.components.AlertTab.content().should('not.exist'); - - e2e.components.QueryTab.content().should('not.exist'); - e2e.components.TransformTab.content().should('not.exist'); - - // Needs to be disabled until Grafana EE turns unified alerting on by default - // e2e.components.PanelAlertTabContent.content().should('exist'); - // e2e.components.PanelAlertTabContent.content().should('be.visible'); - - e2e.components.Tab.title('Query').should('be.visible').click(); - }); - - // Panel sidebar is rendered open by default - e2e.components.PanelEditor.OptionsPane.content().should('be.visible'); - - // close options pane - e2e.components.PanelEditor.toggleVizOptions().click(); - e2e.components.PanelEditor.OptionsPane.content().should('not.exist'); - - // open options pane - e2e.components.PanelEditor.toggleVizOptions().should('be.visible').click(); - e2e.components.PanelEditor.OptionsPane.content().should('be.visible'); - - // Check that Time series is chosen - e2e.components.PanelEditor.toggleVizPicker().click(); - e2e.components.PluginVisualization.item('Time series').should('be.visible'); - e2e.components.PluginVisualization.current().should((e) => expect(e).to.contain('Time series')); - - // Check that table view works - e2e.components.Panels.Panel.loadingBar().should('not.exist'); - e2e.components.PanelEditor.toggleTableView().click({ force: true }); - e2e.components.Panels.Visualization.Table.header() - .should('be.visible') - .within(() => { - cy.contains('A-series').should('be.visible'); - }); - - // Change to Text panel - e2e.components.PluginVisualization.item('Text').scrollIntoView().should('be.visible').click(); - e2e.components.PanelEditor.toggleVizPicker().should((e) => expect(e).to.contain('Text')); - - // Data pane should not be rendered - e2e.components.PanelEditor.DataPane.content().should('not.exist'); - - // Change to Table panel - e2e.components.PanelEditor.toggleVizPicker().click(); - e2e.components.PluginVisualization.item('Table').scrollIntoView().should('be.visible').click(); - e2e.components.PanelEditor.toggleVizPicker().should((e) => expect(e).to.contain('Table')); - - // Data pane should be rendered - e2e.components.PanelEditor.DataPane.content().should('be.visible'); - - // Field & Overrides tabs (need to switch to React based vis, i.e. Table) - e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Show table header').should('be.visible'); - e2e.components.PanelEditor.OptionsPane.fieldLabel('Table Column width').should('be.visible'); - }); -}); diff --git a/e2e/old-arch/various-suite/visualization-suggestions.spec.ts b/e2e/old-arch/various-suite/visualization-suggestions.spec.ts deleted file mode 100644 index c5dfb03ef93..00000000000 --- a/e2e/old-arch/various-suite/visualization-suggestions.spec.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { e2e } from '../utils'; - -describe('Visualization suggestions', () => { - beforeEach(() => { - e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD')); - }); - - it('Should be shown and clickable', () => { - e2e.flows.openDashboard({ uid: 'aBXrJ0R7z', queryParams: { '__feature.tableNextGen': false, editPanel: 9 } }); - - // Try visualization suggestions - e2e.components.PanelEditor.toggleVizPicker().click(); - e2e.components.RadioButton.container().filter(':contains("Suggestions")').click(); - - // Verify we see suggestions - e2e.components.VisualizationPreview.card('Line chart').should('be.visible'); - - // Verify search works - cy.get('[placeholder="Search for..."]').type('Table'); - // Should no longer see line chart - e2e.components.VisualizationPreview.card('Line chart').should('not.exist'); - - // Select a visualisation - e2e.components.VisualizationPreview.card('Table').click(); - e2e.components.Panels.Visualization.Table.header().should('be.visible'); - }); -}); diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 3f3ccc676d7..57074b39883 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -616,10 +616,6 @@ export interface FeatureToggles { */ newFiltersUI?: boolean; /** - * Allows access to the new react-data-grid based table component. - */ - tableNextGen?: boolean; - /** * Uses Prometheus rules as the primary source of truth for ruler-enabled data sources */ alertingPrometheusRulesPrimary?: boolean; diff --git a/packages/grafana-schema/src/common/table.cue b/packages/grafana-schema/src/common/table.cue index 27229571a19..0d2b369329b 100644 --- a/packages/grafana-schema/src/common/table.cue +++ b/packages/grafana-schema/src/common/table.cue @@ -119,7 +119,7 @@ TableCellTooltipOptions: { // The name of the field to get the tooltip content from field: string // placement of the tooltip - placement?: TableCellTooltipPlacement & (*"auto" | _) + placement?: TableCellTooltipPlacement } // Field options for each field within a table (e.g 10, "The String", 64.20, etc.) diff --git a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts index fe36790fe55..1aa6ed61a7e 100644 --- a/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts +++ b/packages/grafana-schema/src/raw/composable/table/panelcfg/x/TablePanelCfg_types.gen.ts @@ -25,6 +25,12 @@ export interface Options { * Represents the index of the selected frame */ frameIndex: number; + /** + * Defines the number of columns to freeze on the left side of the table + */ + frozenColumns?: { + left?: number; + }; /** * Controls whether the panel should show the header */ diff --git a/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx b/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx index d0c9a479042..df29e9b44ed 100644 --- a/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx +++ b/packages/grafana-ui/src/components/Table/DataLinksActionsTooltip.tsx @@ -82,7 +82,7 @@ export const DataLinksActionsTooltip = ({ links, actions, value, coords, onToolt return ( <> - {/* TODO: we can remove `value` from this component when tableNextGen is fully rolled out */} + {/* TODO: we can remove `value` from this component when TableRT is fully deprecated */} {value}
await import(/* webpackChunkName: "graphitePlugin" */ 'app/plugins/datasource/graphite/module'); const cloudwatchPlugin = async () => @@ -56,13 +54,7 @@ const stateTimelinePanel = async () => await import(/* webpackChunkName: "stateTimelinePanel" */ 'app/plugins/panel/state-timeline/module'); const statusHistoryPanel = async () => await import(/* webpackChunkName: "statusHistoryPanel" */ 'app/plugins/panel/status-history/module'); -const tablePanel = async () => { - if (config.featureToggles.tableNextGen) { - return await import(/* webpackChunkName: "tableNewPanel" */ 'app/plugins/panel/table/table-new/module'); - } else { - return await import(/* webpackChunkName: "tablePanel" */ 'app/plugins/panel/table/module'); - } -}; +const tablePanel = async () => await import(/* webpackChunkName: "tablePanel" */ 'app/plugins/panel/table/module'); const textPanel = async () => await import(/* webpackChunkName: "textPanel" */ 'app/plugins/panel/text/module'); const timeseriesPanel = async () => await import(/* webpackChunkName: "timeseriesPanel" */ 'app/plugins/panel/timeseries/module'); diff --git a/public/app/plugins/panel/table/PaginationEditor.tsx b/public/app/plugins/panel/table/PaginationEditor.tsx index 0bfa20b565b..ccd9451ee3f 100644 --- a/public/app/plugins/panel/table/PaginationEditor.tsx +++ b/public/app/plugins/panel/table/PaginationEditor.tsx @@ -1,15 +1,19 @@ import * as React from 'react'; import { StandardEditorProps } from '@grafana/data'; +import { selectors } from '@grafana/e2e-selectors'; import { Switch } from '@grafana/ui'; -export function PaginationEditor({ onChange, value, context }: StandardEditorProps) { +export function PaginationEditor({ onChange, value }: StandardEditorProps) { const changeValue = (event: React.FormEvent | undefined) => { - if (event?.currentTarget.checked) { - context.options.footer.show = false; - } onChange(event?.currentTarget.checked); }; - return ; + return ( + + ); } diff --git a/public/app/plugins/panel/table/TableCellOptionEditor.tsx b/public/app/plugins/panel/table/TableCellOptionEditor.tsx index c0fd5e0e5ea..b70723695cb 100644 --- a/public/app/plugins/panel/table/TableCellOptionEditor.tsx +++ b/public/app/plugins/panel/table/TableCellOptionEditor.tsx @@ -2,15 +2,17 @@ import { css } from '@emotion/css'; import { merge } from 'lodash'; import { useState } from 'react'; -import { GrafanaTheme2, SelectableValue } from '@grafana/data'; -import { TableCellOptions } from '@grafana/schema'; -import { Field, Select, TableCellDisplayMode, useStyles2 } from '@grafana/ui'; +import { GrafanaTheme2 } from '@grafana/data'; +import { t } from '@grafana/i18n'; +import { TableCellOptions, TableWrapTextOptions } from '@grafana/schema'; +import { Combobox, ComboboxOption, Field, TableCellDisplayMode, useStyles2 } from '@grafana/ui'; -import { AutoCellOptionsEditor } from './cells/AutoCellOptionsEditor'; import { BarGaugeCellOptionsEditor } from './cells/BarGaugeCellOptionsEditor'; import { ColorBackgroundCellOptionsEditor } from './cells/ColorBackgroundCellOptionsEditor'; import { ImageCellOptionsEditor } from './cells/ImageCellOptionsEditor'; +import { MarkdownCellOptionsEditor } from './cells/MarkdownCellOptionsEditor'; import { SparklineCellOptionsEditor } from './cells/SparklineCellOptionsEditor'; +import { TextWrapOptionsEditor } from './cells/TextWrapOptionsEditor'; // The props that any cell type editor are expected // to handle. In this case the generic type should @@ -25,18 +27,48 @@ interface Props { onChange: (v: TableCellOptions) => void; } +const TEXT_WRAP_CELL_TYPES = new Set([ + TableCellDisplayMode.Auto, + TableCellDisplayMode.Sparkline, + TableCellDisplayMode.ColorText, + TableCellDisplayMode.ColorBackground, + TableCellDisplayMode.DataLinks, + TableCellDisplayMode.Pill, +]); + +function isTextWrapCellType(value: TableCellOptions): value is TableCellOptions & TableWrapTextOptions { + return TEXT_WRAP_CELL_TYPES.has(value.type); +} + export const TableCellOptionEditor = ({ value, onChange }: Props) => { const cellType = value.type; const styles = useStyles2(getStyles); - const currentMode = cellDisplayModeOptions.find((o) => o.value!.type === cellType)!; + const cellDisplayModeOptions: Array> = [ + { value: TableCellDisplayMode.Auto, label: t('table.cell-types.auto', 'Auto') }, + { value: TableCellDisplayMode.ColorText, label: t('table.cell-types.color-text', 'Colored text') }, + { + value: TableCellDisplayMode.ColorBackground, + label: t('table.cell-types.color-background', 'Colored background'), + }, + { value: TableCellDisplayMode.DataLinks, label: t('table.cell-types.data-links', 'Data links') }, + { value: TableCellDisplayMode.Gauge, label: t('table.cell-types.gauge', 'Gauge') }, + { value: TableCellDisplayMode.Sparkline, label: t('table.cell-types.sparkline', 'Sparkline') }, + { value: TableCellDisplayMode.JSONView, label: t('table.cell-types.json', 'JSON View') }, + { value: TableCellDisplayMode.Pill, label: t('table.cell-types.pill', 'Pill') }, + { value: TableCellDisplayMode.Markdown, label: t('table.cell-types.markdown', 'Markdown + HTML') }, + { value: TableCellDisplayMode.Image, label: t('table.cell-types.image', 'Image') }, + { value: TableCellDisplayMode.Actions, label: t('table.cell-types.actions', 'Actions') }, + ]; + const currentMode = cellDisplayModeOptions.find((o) => o.value === cellType)!; + let [settingCache, setSettingCache] = useState>({}); // Update display mode on change - const onCellTypeChange = (v: SelectableValue) => { - if (v.value !== undefined) { + const onCellTypeChange = (v: ComboboxOption) => { + if (v !== null) { // Set the new type of cell starting // with default settings - value = v.value; + value = { type: v.value }; // When changing cell type see if there were previously stored // settings and merge those with the changed value @@ -60,11 +92,9 @@ export const TableCellOptionEditor = ({ value, onChange }: Props) => { return (
- -
-
- ); -} - -function getCurrentFrameIndex(frames: DataFrame[], options: Options) { - return options.frameIndex > 0 && options.frameIndex < frames.length ? options.frameIndex : 0; -} - -// placeholder function; assuming the values are already interpolated -const replaceVars: InterpolateFunction = (value: string) => value; - -const getCellActions = ( - dataFrame: DataFrame, - field: Field, - rowIndex: number, - replaceVariables: InterpolateFunction | undefined -): Array> => { - const numActions = field.config.actions?.length ?? 0; - - if (numActions > 0) { - const actions = getActions( - dataFrame, - field, - field.state!.scopedVars!, - replaceVariables ?? replaceVars, - field.config.actions ?? [], - { valueRowIndex: rowIndex } - ); - - if (actions.length === 1) { - return actions; - } else { - const actionsOut: Array> = []; - const actionLookup = new Set(); - - actions.forEach((action) => { - const key = action.title; - - if (!actionLookup.has(key)) { - actionsOut.push(action); - actionLookup.add(key); - } - }); - - return actionsOut; - } - } - - return []; -}; - -const tableStyles = { - wrapper: css({ - display: 'flex', - flexDirection: 'column', - justifyContent: 'space-between', - height: '100%', - }), - selectWrapper: css({ - padding: '8px 8px 0px 8px', - }), -}; diff --git a/public/app/plugins/panel/table/table-new/__snapshots__/migrations.test.ts.snap b/public/app/plugins/panel/table/table-new/__snapshots__/migrations.test.ts.snap deleted file mode 100644 index e51e742e7d5..00000000000 --- a/public/app/plugins/panel/table/table-new/__snapshots__/migrations.test.ts.snap +++ /dev/null @@ -1,82 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Table Migrations migrates transform out to core transforms 1`] = ` -{ - "fieldConfig": { - "defaults": { - "custom": {}, - }, - "overrides": [], - }, - "transformations": [ - { - "id": "seriesToColumns", - "options": { - "reducers": [], - }, - }, - ], -} -`; - -exports[`Table Migrations migrates transform out to core transforms 2`] = ` -{ - "fieldConfig": { - "defaults": { - "custom": {}, - }, - "overrides": [], - }, - "transformations": [ - { - "id": "seriesToRows", - "options": { - "reducers": [], - }, - }, - ], -} -`; - -exports[`Table Migrations migrates transform out to core transforms 3`] = ` -{ - "fieldConfig": { - "defaults": { - "custom": {}, - }, - "overrides": [], - }, - "transformations": [ - { - "id": "reduce", - "options": { - "includeTimeField": false, - "reducers": [ - "mean", - "max", - "lastNotNull", - ], - }, - }, - ], -} -`; - -exports[`Table Migrations migrates transform out to core transforms 4`] = ` -{ - "fieldConfig": { - "defaults": { - "custom": {}, - }, - "overrides": [], - }, - "transformations": [ - { - "id": "merge", - "options": { - "reducers": [], - }, - }, - ], -} -`; diff --git a/public/app/plugins/panel/table/table-new/cells/BarGaugeCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/BarGaugeCellOptionsEditor.tsx deleted file mode 100644 index 4e29eaf9360..00000000000 --- a/public/app/plugins/panel/table/table-new/cells/BarGaugeCellOptionsEditor.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { SelectableValue } from '@grafana/data'; -import { t } from '@grafana/i18n'; -import { BarGaugeDisplayMode, BarGaugeValueMode, TableBarGaugeCellOptions } from '@grafana/schema'; -import { Field, RadioButtonGroup, Stack } from '@grafana/ui'; - -import { TableCellEditorProps } from '../TableCellOptionEditor'; - -type Props = TableCellEditorProps; - -export function BarGaugeCellOptionsEditor({ cellOptions, onChange }: Props) { - // Set the display mode on change - - const onCellOptionsChange = (v: BarGaugeDisplayMode) => { - cellOptions.mode = v; - onChange(cellOptions); - }; - - const onValueModeChange = (v: BarGaugeValueMode) => { - cellOptions.valueDisplayMode = v; - onChange(cellOptions); - }; - - return ( - - - - - - - - - ); -} - -const barGaugeOpts: SelectableValue[] = [ - { value: BarGaugeDisplayMode.Basic, label: 'Basic' }, - { value: BarGaugeDisplayMode.Gradient, label: 'Gradient' }, - { value: BarGaugeDisplayMode.Lcd, label: 'Retro LCD' }, -]; - -const valueModes: SelectableValue[] = [ - { value: BarGaugeValueMode.Color, label: 'Value color' }, - { value: BarGaugeValueMode.Text, label: 'Text color' }, - { value: BarGaugeValueMode.Hidden, label: 'Hidden' }, -]; diff --git a/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx deleted file mode 100644 index 33d85539ec4..00000000000 --- a/public/app/plugins/panel/table/table-new/cells/ColorBackgroundCellOptionsEditor.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { SelectableValue } from '@grafana/data'; -import { selectors } from '@grafana/e2e-selectors'; -import { t } from '@grafana/i18n'; -import { TableCellBackgroundDisplayMode, TableColoredBackgroundCellOptions } from '@grafana/schema'; -import { Field, RadioButtonGroup, Switch } from '@grafana/ui'; - -import { TableCellEditorProps } from '../TableCellOptionEditor'; - -import { TextWrapOptionsEditor } from './TextWrapOptionsEditor'; - -const colorBackgroundOpts: Array> = [ - { value: TableCellBackgroundDisplayMode.Basic, label: 'Basic' }, - { value: TableCellBackgroundDisplayMode.Gradient, label: 'Gradient' }, -]; -export const ColorBackgroundCellOptionsEditor = ({ - cellOptions, - onChange, -}: TableCellEditorProps) => { - // Set the display mode on change - const onCellOptionsChange = (v: TableCellBackgroundDisplayMode) => { - cellOptions.mode = v; - onChange(cellOptions); - }; - const onColorRowChange = () => { - cellOptions.applyToRow = !cellOptions.applyToRow; - onChange(cellOptions); - }; - - return ( - <> - - - - - - - - - { - cellOptions.wrapText = updatedCellOptions.wrapText; - onChange(cellOptions); - }} - /> - - ); -}; diff --git a/public/app/plugins/panel/table/table-new/cells/ImageCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/ImageCellOptionsEditor.tsx deleted file mode 100644 index 75233f4fefe..00000000000 --- a/public/app/plugins/panel/table/table-new/cells/ImageCellOptionsEditor.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { FormEvent } from 'react'; - -import { t } from '@grafana/i18n'; -import { TableImageCellOptions } from '@grafana/schema'; -import { Field, Input } from '@grafana/ui'; - -import { TableCellEditorProps } from '../TableCellOptionEditor'; - -export const ImageCellOptionsEditor = ({ cellOptions, onChange }: TableCellEditorProps) => { - const onAltChange = (e: FormEvent) => { - cellOptions.alt = e.currentTarget.value; - onChange(cellOptions); - }; - - const onTitleChange = (e: FormEvent) => { - cellOptions.title = e.currentTarget.value; - onChange(cellOptions); - }; - - return ( - <> - - - - - - - - - ); -}; diff --git a/public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx b/public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx deleted file mode 100644 index 69ad1c0f0b5..00000000000 --- a/public/app/plugins/panel/table/table-new/cells/SparklineCellOptionsEditor.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { css } from '@emotion/css'; -import { useMemo } from 'react'; - -import { createFieldConfigRegistry, SetFieldConfigOptionsArgs } from '@grafana/data'; -import { GraphFieldConfig, TableSparklineCellOptions } from '@grafana/schema'; -import { Field, useStyles2, Stack } from '@grafana/ui'; -import { defaultSparklineCellConfig } from '@grafana/ui/internal'; - -import { getGraphFieldConfig } from '../../../timeseries/config'; -import { TableCellEditorProps } from '../TableCellOptionEditor'; - -type OptionKey = keyof TableSparklineCellOptions; - -const optionIds: Array = [ - 'hideValue', - 'drawStyle', - 'lineInterpolation', - 'barAlignment', - 'lineWidth', - 'fillOpacity', - 'gradientMode', - 'lineStyle', - 'spanNulls', - 'showPoints', - 'pointSize', -]; - -function getChartCellConfig(cfg: GraphFieldConfig): SetFieldConfigOptionsArgs { - const graphFieldConfig = getGraphFieldConfig(cfg); - return { - ...graphFieldConfig, - useCustomConfig: (builder) => { - graphFieldConfig.useCustomConfig?.(builder); - builder.addBooleanSwitch({ - path: 'hideValue', - name: 'Hide value', - }); - }, - }; -} - -export const SparklineCellOptionsEditor = (props: TableCellEditorProps) => { - const { cellOptions, onChange } = props; - - const registry = useMemo(() => { - const config = getChartCellConfig(defaultSparklineCellConfig); - return createFieldConfigRegistry(config, 'ChartCell'); - }, []); - - const style = useStyles2(getStyles); - - const values = { ...defaultSparklineCellConfig, ...cellOptions }; - - return ( - - {registry.list(optionIds.map((id) => `custom.${id}`)).map((item) => { - if (item.showIf && !item.showIf(values)) { - return null; - } - const Editor = item.editor; - const path = item.path; - - return ( - - onChange({ ...cellOptions, [path]: val })} - value={(isOptionKey(path, values) ? values[path] : undefined) ?? item.defaultValue} - item={item} - context={{ data: [] }} - /> - - ); - })} - - ); -}; - -// jumping through hoops to avoid using "any" -function isOptionKey(key: string, options: TableSparklineCellOptions): key is OptionKey { - return key in options; -} - -const getStyles = () => ({ - field: css({ - width: '100%', - - // @TODO don't show "scheme" option for custom gradient mode. - // it needs thresholds to work, which are not supported - // for area chart cell right now - "[title='Use color scheme to define gradient']": { - display: 'none', - }, - }), -}); diff --git a/public/app/plugins/panel/table/table-new/img/icn-table-panel.svg b/public/app/plugins/panel/table/table-new/img/icn-table-panel.svg deleted file mode 100644 index 21846a8d38f..00000000000 --- a/public/app/plugins/panel/table/table-new/img/icn-table-panel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/public/app/plugins/panel/table/table-new/migrations.test.ts b/public/app/plugins/panel/table/table-new/migrations.test.ts deleted file mode 100644 index a40120c8857..00000000000 --- a/public/app/plugins/panel/table/table-new/migrations.test.ts +++ /dev/null @@ -1,364 +0,0 @@ -import { createDataFrame, FieldType, PanelModel } from '@grafana/data'; - -import { migrateFromParentRowIndexToNestedFrames, tablePanelChangedHandler } from './migrations'; - -describe('Table Migrations', () => { - it('migrates transform out to core transforms', () => { - const toColumns = { - angular: { - columns: [], - styles: [], - transform: 'timeseries_to_columns', - options: {}, - }, - }; - const toRows = { - angular: { - columns: [], - styles: [], - transform: 'timeseries_to_rows', - options: {}, - }, - }; - const aggregations = { - angular: { - columns: [ - { - text: 'Avg', - value: 'avg', - $$hashKey: 'object:82', - }, - { - text: 'Max', - value: 'max', - $$hashKey: 'object:83', - }, - { - text: 'Current', - value: 'current', - $$hashKey: 'object:84', - }, - ], - styles: [], - transform: 'timeseries_aggregations', - options: {}, - }, - }; - const table = { - angular: { - columns: [], - styles: [], - transform: 'table', - options: {}, - }, - }; - - const columnsPanel = {} as PanelModel; - tablePanelChangedHandler(columnsPanel, 'table-old', toColumns); - expect(columnsPanel).toMatchSnapshot(); - const rowsPanel = {} as PanelModel; - tablePanelChangedHandler(rowsPanel, 'table-old', toRows); - expect(rowsPanel).toMatchSnapshot(); - const aggregationsPanel = {} as PanelModel; - tablePanelChangedHandler(aggregationsPanel, 'table-old', aggregations); - expect(aggregationsPanel).toMatchSnapshot(); - const tablePanel = {} as PanelModel; - tablePanelChangedHandler(tablePanel, 'table-old', table); - expect(tablePanel).toMatchSnapshot(); - }); - - it('migrates styles to field config overrides and defaults', () => { - const oldStyles = { - angular: { - columns: [], - styles: [ - { - alias: 'Time', - align: 'auto', - dateFormat: 'YYYY-MM-DD HH:mm:ss', - pattern: 'Time', - type: 'date', - $$hashKey: 'object:195', - }, - { - alias: '', - align: 'left', - colorMode: 'cell', - colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'], - dateFormat: 'YYYY-MM-DD HH:mm:ss', - decimals: 2, - mappingType: 1, - pattern: 'ColorCell', - thresholds: ['5', '10'], - type: 'number', - unit: 'currencyUSD', - $$hashKey: 'object:196', - }, - { - alias: '', - align: 'auto', - colorMode: 'value', - colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'], - dateFormat: 'YYYY-MM-DD HH:mm:ss', - decimals: 2, - link: true, - linkTargetBlank: true, - linkTooltip: '', - linkUrl: 'http://www.grafana.com', - mappingType: 1, - pattern: 'ColorValue', - thresholds: ['5', '10'], - type: 'number', - unit: 'Bps', - $$hashKey: 'object:197', - }, - { - unit: 'short', - type: 'number', - alias: '', - decimals: 2, - colors: ['rgba(245, 54, 54, 0.9)', 'rgba(237, 129, 40, 0.89)', 'rgba(50, 172, 45, 0.97)'], - colorMode: null, - pattern: '/.*/', - thresholds: [], - align: 'right', - }, - ], - }, - }; - - const panel = {} as PanelModel; - tablePanelChangedHandler(panel, 'table-old', oldStyles); - expect(panel).toMatchInlineSnapshot(` - { - "fieldConfig": { - "defaults": { - "custom": { - "align": "right", - }, - "decimals": 2, - "displayName": "", - "unit": "short", - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Time", - }, - "properties": [ - { - "id": "displayName", - "value": "Time", - }, - { - "id": "unit", - "value": "time: YYYY-MM-DD HH:mm:ss", - }, - { - "id": "custom.align", - "value": null, - }, - ], - }, - { - "matcher": { - "id": "byName", - "options": "ColorCell", - }, - "properties": [ - { - "id": "unit", - "value": "currencyUSD", - }, - { - "id": "decimals", - "value": 2, - }, - { - "id": "custom.cellOptions", - "value": { - "type": "color-background", - }, - }, - { - "id": "custom.align", - "value": "left", - }, - { - "id": "thresholds", - "value": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(245, 54, 54, 0.9)", - "value": -Infinity, - }, - { - "color": "rgba(237, 129, 40, 0.89)", - "value": 5, - }, - { - "color": "rgba(50, 172, 45, 0.97)", - "value": 10, - }, - ], - }, - }, - ], - }, - { - "matcher": { - "id": "byName", - "options": "ColorValue", - }, - "properties": [ - { - "id": "unit", - "value": "Bps", - }, - { - "id": "decimals", - "value": 2, - }, - { - "id": "links", - "value": [ - { - "targetBlank": true, - "title": "", - "url": "http://www.grafana.com", - }, - ], - }, - { - "id": "custom.cellOptions", - "value": { - "type": "color-text", - }, - }, - { - "id": "custom.align", - "value": null, - }, - { - "id": "thresholds", - "value": { - "mode": "absolute", - "steps": [ - { - "color": "rgba(245, 54, 54, 0.9)", - "value": -Infinity, - }, - { - "color": "rgba(237, 129, 40, 0.89)", - "value": 5, - }, - { - "color": "rgba(50, 172, 45, 0.97)", - "value": 10, - }, - ], - }, - }, - ], - }, - ], - }, - "transformations": [], - } - `); - }); - - it('migrates hidden fields to override', () => { - const oldStyles = { - angular: { - columns: [], - styles: [ - { - dateFormat: 'YYYY-MM-DD HH:mm:ss', - pattern: 'time', - type: 'hidden', - }, - ], - }, - }; - - const panel = {} as PanelModel; - tablePanelChangedHandler(panel, 'table-old', oldStyles); - expect(panel.fieldConfig.overrides).toEqual([ - { - matcher: { - id: 'byName', - options: 'time', - }, - properties: [ - { - id: 'custom.hidden', - value: true, - }, - ], - }, - ]); - }); - - it('migrates DataFrame[] from format using meta.custom.parentRowIndex to format using FieldType.nestedFrames', () => { - const mainFrame = (refId: string) => { - return createDataFrame({ - refId, - fields: [ - { - name: 'field', - type: FieldType.string, - config: {}, - values: ['a', 'b', 'c'], - }, - ], - meta: { - preferredVisualisationType: 'table', - }, - }); - }; - - const subFrame = (index: number) => { - return createDataFrame({ - refId: 'B', - fields: [ - { - name: `field_${index}`, - type: FieldType.string, - config: {}, - values: [`${index}_subA`, 'subB', 'subC'], - }, - ], - meta: { - preferredVisualisationType: 'table', - custom: { - parentRowIndex: index, - }, - }, - }); - }; - - const oldFormat = [mainFrame('A'), mainFrame('B'), subFrame(0), subFrame(1)]; - const newFormat = migrateFromParentRowIndexToNestedFrames(oldFormat); - expect(newFormat.length).toBe(2); - expect(newFormat[0].refId).toBe('A'); - expect(newFormat[1].refId).toBe('B'); - expect(newFormat[0].fields.length).toBe(1); - expect(newFormat[1].fields.length).toBe(2); - expect(newFormat[0].fields[0].name).toBe('field'); - expect(newFormat[1].fields[0].name).toBe('field'); - expect(newFormat[1].fields[1].name).toBe('nested'); - expect(newFormat[1].fields[1].type).toBe(FieldType.nestedFrames); - expect(newFormat[1].fields[1].values.length).toBe(2); - expect(newFormat[1].fields[1].values[0][0].refId).toBe('B'); - expect(newFormat[1].fields[1].values[1][0].refId).toBe('B'); - expect(newFormat[1].fields[1].values[0][0].length).toBe(3); - expect(newFormat[1].fields[1].values[0][0].length).toBe(3); - expect(newFormat[1].fields[1].values[0][0].fields[0].name).toBe('field_0'); - expect(newFormat[1].fields[1].values[1][0].fields[0].name).toBe('field_1'); - expect(newFormat[1].fields[1].values[0][0].fields[0].values[0]).toBe('0_subA'); - expect(newFormat[1].fields[1].values[1][0].fields[0].values[0]).toBe('1_subA'); - }); -}); diff --git a/public/app/plugins/panel/table/table-new/migrations.ts b/public/app/plugins/panel/table/table-new/migrations.ts deleted file mode 100644 index aa4c635682c..00000000000 --- a/public/app/plugins/panel/table/table-new/migrations.ts +++ /dev/null @@ -1,299 +0,0 @@ -import { omitBy, isNil, isNumber, defaultTo, groupBy } from 'lodash'; - -import { - PanelModel, - FieldMatcherID, - ConfigOverrideRule, - ThresholdsMode, - ThresholdsConfig, - FieldConfig, - DataFrame, - FieldType, -} from '@grafana/data'; -import { ReduceTransformerOptions } from '@grafana/data/internal'; - -import { Options } from './panelcfg.gen'; - -/** - * At 7.0, the `table` panel was swapped from an angular implementation to a react one. - * The models do not match, so this process will delegate to the old implementation when - * a saved table configuration exists. - */ -export const tableMigrationHandler = (panel: PanelModel): Partial => { - // Table was saved as an angular table, lets just swap to the 'table-old' panel - if (!panel.pluginVersion && 'columns' in panel) { - console.log('Was angular table', panel); - } - - // Nothing changed - return panel.options; -}; - -const transformsMap = { - timeseries_to_rows: 'seriesToRows', - timeseries_to_columns: 'seriesToColumns', - timeseries_aggregations: 'reduce', - table: 'merge', -}; - -const columnsMap = { - avg: 'mean', - min: 'min', - max: 'max', - total: 'sum', - current: 'lastNotNull', - count: 'count', -}; - -const colorModeMap = { - cell: 'color-background', - row: 'color-background', - value: 'color-text', -}; - -type Transformations = keyof typeof transformsMap; - -type Transformation = { - id: string; - options: ReduceTransformerOptions; -}; - -type Columns = keyof typeof columnsMap; - -type Column = { - value: Columns; - text: string; -}; - -type ColorModes = keyof typeof colorModeMap; - -const generateThresholds = (thresholds: string[], colors: string[]) => { - return [-Infinity, ...thresholds].map((threshold, idx) => ({ - color: colors[idx], - value: isNumber(threshold) ? threshold : parseInt(threshold, 10), - })); -}; - -const migrateTransformations = ( - panel: PanelModel>, - oldOpts: { columns: any; transform: Transformations } -) => { - const transformations: Transformation[] = panel.transformations ?? []; - if (Object.keys(transformsMap).includes(oldOpts.transform)) { - const opts: ReduceTransformerOptions = { - reducers: [], - }; - if (oldOpts.transform === 'timeseries_aggregations') { - opts.includeTimeField = false; - opts.reducers = oldOpts.columns.map((column: Column) => columnsMap[column.value]); - } - transformations.push({ - id: transformsMap[oldOpts.transform], - options: opts, - }); - } - return transformations; -}; - -type Style = { - unit: string; - type: string; - alias: string; - decimals: number; - colors: string[]; - colorMode: ColorModes; - pattern: string; - thresholds: string[]; - align?: string; - dateFormat: string; - link: boolean; - linkTargetBlank?: boolean; - linkTooltip?: string; - linkUrl?: string; -}; - -const migrateTableStyleToOverride = (style: Style) => { - const fieldMatcherId = /^\/.*\/$/.test(style.pattern) ? FieldMatcherID.byRegexp : FieldMatcherID.byName; - const override: ConfigOverrideRule = { - matcher: { - id: fieldMatcherId, - options: style.pattern, - }, - properties: [], - }; - - if (style.alias) { - override.properties.push({ - id: 'displayName', - value: style.alias, - }); - } - - if (style.unit) { - override.properties.push({ - id: 'unit', - value: style.unit, - }); - } - - if (style.decimals) { - override.properties.push({ - id: 'decimals', - value: style.decimals, - }); - } - - if (style.type === 'date') { - override.properties.push({ - id: 'unit', - value: `time: ${style.dateFormat}`, - }); - } - - if (style.type === 'hidden') { - override.properties.push({ - id: 'custom.hidden', - value: true, - }); - } - - if (style.link) { - override.properties.push({ - id: 'links', - value: [ - { - title: defaultTo(style.linkTooltip, ''), - url: defaultTo(style.linkUrl, ''), - targetBlank: defaultTo(style.linkTargetBlank, false), - }, - ], - }); - } - - if (style.colorMode) { - override.properties.push({ - id: 'custom.cellOptions', - value: { - type: colorModeMap[style.colorMode], - }, - }); - } - - if (style.align) { - override.properties.push({ - id: 'custom.align', - value: style.align === 'auto' ? null : style.align, - }); - } - - if (style.thresholds?.length) { - override.properties.push({ - id: 'thresholds', - value: { - mode: ThresholdsMode.Absolute, - steps: generateThresholds(style.thresholds, style.colors), - }, - }); - } - - return override; -}; - -const migrateDefaults = (prevDefaults: Style) => { - let defaults: FieldConfig = { - custom: {}, - }; - if (prevDefaults) { - defaults = omitBy( - { - unit: prevDefaults.unit, - decimals: prevDefaults.decimals, - displayName: prevDefaults.alias, - custom: { - align: prevDefaults.align === 'auto' ? null : prevDefaults.align, - }, - }, - isNil - ); - - if (prevDefaults.thresholds.length) { - const thresholds: ThresholdsConfig = { - mode: ThresholdsMode.Absolute, - steps: generateThresholds(prevDefaults.thresholds, prevDefaults.colors), - }; - defaults.thresholds = thresholds; - } - - if (prevDefaults.colorMode) { - defaults.custom.cellOptions = { - type: colorModeMap[prevDefaults.colorMode], - }; - } - } - return defaults; -}; - -/** - * This is called when the panel changes from another panel - */ -export const tablePanelChangedHandler = ( - panel: PanelModel>, - prevPluginId: string, - prevOptions: any -) => { - // Changing from angular table panel - if (prevPluginId === 'table-old' && prevOptions.angular) { - const oldOpts = prevOptions.angular; - const transformations = migrateTransformations(panel, oldOpts); - const prevDefaults = oldOpts.styles.find((style: any) => style.pattern === '/.*/'); - const defaults = migrateDefaults(prevDefaults); - const overrides = oldOpts.styles.filter((style: any) => style.pattern !== '/.*/').map(migrateTableStyleToOverride); - - panel.transformations = transformations; - panel.fieldConfig = { - defaults, - overrides, - }; - } - - return {}; -}; - -const getMainFrames = (frames: DataFrame[] | null) => { - return frames?.filter((df) => df.meta?.custom?.parentRowIndex === undefined) || [frames?.[0]]; -}; - -/** - * In 9.3 meta.custom.parentRowIndex was introduced to support sub-tables. - * In 10.2 meta.custom.parentRowIndex was deprecated in favor of FieldType.nestedFrames, which supports multiple nested frames. - * Migrate DataFrame[] from using meta.custom.parentRowIndex to using FieldType.nestedFrames - */ -export const migrateFromParentRowIndexToNestedFrames = (frames: DataFrame[] | null) => { - const migratedFrames: DataFrame[] = []; - const mainFrames = getMainFrames(frames).filter( - (frame: DataFrame | undefined): frame is DataFrame => !!frame && frame.length !== 0 - ); - - mainFrames?.forEach((frame) => { - const subFrames = frames?.filter((df) => frame.refId === df.refId && df.meta?.custom?.parentRowIndex !== undefined); - const subFramesGrouped = groupBy(subFrames, (frame: DataFrame) => frame.meta?.custom?.parentRowIndex); - const subFramesByIndex = Object.keys(subFramesGrouped).map((key) => subFramesGrouped[key]); - const migratedFrame = { ...frame }; - - if (subFrames && subFrames.length > 0) { - migratedFrame.fields.push({ - name: 'nested', - type: FieldType.nestedFrames, - config: {}, - values: subFramesByIndex, - }); - } - migratedFrames.push(migratedFrame); - }); - - return migratedFrames; -}; - -export const hasDeprecatedParentRowIndex = (frames: DataFrame[] | null) => { - return frames?.some((df) => df.meta?.custom?.parentRowIndex !== undefined); -}; diff --git a/public/app/plugins/panel/table/table-new/module.tsx b/public/app/plugins/panel/table/table-new/module.tsx deleted file mode 100644 index 0e17f6c1072..00000000000 --- a/public/app/plugins/panel/table/table-new/module.tsx +++ /dev/null @@ -1,258 +0,0 @@ -import { - FieldOverrideContext, - FieldType, - getFieldDisplayName, - PanelPlugin, - ReducerID, - standardEditorsRegistry, - identityOverrideProcessor, - FieldConfigProperty, -} from '@grafana/data'; -import { t } from '@grafana/i18n'; -import { - TableCellOptions, - TableCellDisplayMode, - defaultTableFieldOptions, - TableCellHeight, - TableCellTooltipPlacement, -} from '@grafana/schema'; - -import { PaginationEditor } from './PaginationEditor'; -import { TableCellOptionEditor } from './TableCellOptionEditor'; -import { TablePanel } from './TablePanel'; -import { tableMigrationHandler, tablePanelChangedHandler } from './migrations'; -import { Options, defaultOptions, FieldConfig } from './panelcfg.gen'; -import { TableSuggestionsSupplier } from './suggestions'; - -export const plugin = new PanelPlugin(TablePanel) - .setPanelChangeHandler(tablePanelChangedHandler) - .setMigrationHandler(tableMigrationHandler) - .useFieldConfig({ - standardOptions: { - [FieldConfigProperty.Actions]: { - hideFromDefaults: false, - }, - }, - useCustomConfig: (builder) => { - const category = [t('table-new.category-table', 'Table')]; - const cellCategory = [t('table-new.category-cell-options', 'Cell options')]; - builder - .addNumberInput({ - path: 'minWidth', - name: t('table-new.name-min-column-width', 'Minimum column width'), - category, - description: t('table-new.description-min-column-width', 'The minimum width for column auto resizing'), - settings: { - placeholder: '150', - min: 50, - max: 500, - }, - shouldApply: () => true, - defaultValue: defaultTableFieldOptions.minWidth, - }) - .addNumberInput({ - path: 'width', - name: t('table-new.name-column-width', 'Column width'), - category, - settings: { - placeholder: t('table-new.placeholder-column-width', 'auto'), - min: 20, - }, - shouldApply: () => true, - defaultValue: defaultTableFieldOptions.width, - }) - .addRadio({ - path: 'align', - name: t('table-new.name-column-alignment', 'Column alignment'), - category, - settings: { - options: [ - { label: t('table-new.column-alignment-options.label-auto', 'Auto'), value: 'auto' }, - { label: t('table-new.column-alignment-options.label-left', 'Left'), value: 'left' }, - { label: t('table-new.column-alignment-options.label-center', 'Center'), value: 'center' }, - { label: t('table-new.column-alignment-options.label-right', 'Right'), value: 'right' }, - ], - }, - defaultValue: defaultTableFieldOptions.align, - }) - .addCustomEditor({ - id: 'cellOptions', - path: 'cellOptions', - name: t('table-new.name-cell-type', 'Cell type'), - editor: TableCellOptionEditor, - override: TableCellOptionEditor, - defaultValue: defaultTableFieldOptions.cellOptions, - process: identityOverrideProcessor, - category: cellCategory, - shouldApply: () => true, - }) - .addBooleanSwitch({ - path: 'inspect', - name: t('table-new.name-cell-value-inspect', 'Cell value inspect'), - description: t('table-new.description-cell-value-inspect', 'Enable cell value inspection in a modal window'), - defaultValue: false, - category: cellCategory, - showIf: (cfg) => { - return ( - cfg.cellOptions.type === TableCellDisplayMode.Auto || - cfg.cellOptions.type === TableCellDisplayMode.JSONView || - cfg.cellOptions.type === TableCellDisplayMode.ColorText || - cfg.cellOptions.type === TableCellDisplayMode.ColorBackground - ); - }, - }) - .addBooleanSwitch({ - path: 'filterable', - name: t('table-new.name-column-filter', 'Column filter'), - category, - description: t('table-new.description-column-filter', 'Enables/disables field filters in table'), - defaultValue: defaultTableFieldOptions.filterable, - }) - .addBooleanSwitch({ - path: 'wrapHeaderText', - name: t('table.name-wrap-header-text', 'Wrap header text'), - description: t('table.description-wrap-header-text', 'Enables text wrapping for column headers'), - category, - defaultValue: defaultTableFieldOptions.wrapHeaderText, - }) - .addBooleanSwitch({ - path: 'hidden', - name: t('table-new.name-hide-in-table', 'Hide in table'), - category, - defaultValue: undefined, - hideFromDefaults: true, - }) - .addFieldNamePicker({ - path: 'tooltip.field', - name: t('table-new.name-tooltip-from-field', 'Tooltip from field'), - description: t( - 'table-new.description-tooltip-from-field', - 'Render a cell from a field (hidden or visible) in a tooltip' - ), - category: cellCategory, - }) - .addSelect({ - path: 'tooltip.placement', - name: t('table-new.name-tooltip-placement', 'Tooltip placement'), - category: cellCategory, - settings: { - options: [ - { - label: t('table-new.tooltip-placement-options.label-auto', 'Auto'), - value: TableCellTooltipPlacement.Auto, - }, - { - label: t('table-new.tooltip-placement-options.label-top', 'Top'), - value: TableCellTooltipPlacement.Top, - }, - { - label: t('table-new.tooltip-placement-options.label-right', 'Right'), - value: TableCellTooltipPlacement.Right, - }, - { - label: t('table-new.tooltip-placement-options.label-bottom', 'Bottom'), - value: TableCellTooltipPlacement.Bottom, - }, - { - label: t('table-new.tooltip-placement-options.label-left', 'Left'), - value: TableCellTooltipPlacement.Left, - }, - ], - }, - defaultValue: 'auto', - showIf: (cfg) => cfg.tooltip?.field !== undefined, - }); - }, - }) - .setPanelOptions((builder) => { - const footerCategory = [t('table-new.category-table-footer', 'Table footer')]; - const category = [t('table-new.category-table', 'Table')]; - builder - .addBooleanSwitch({ - path: 'showHeader', - name: t('table-new.name-show-table-header', 'Show table header'), - category, - defaultValue: defaultOptions.showHeader, - }) - .addNumberInput({ - path: 'frozenColumns.left', - name: t('table-new.name-frozen-columns', 'Frozen columns'), - description: t('table-new.description-frozen-columns', 'Columns are frozen from the left side of the table'), - settings: { - placeholder: 'none', - }, - category, - }) - .addRadio({ - path: 'cellHeight', - name: t('table-new.name-cell-height', 'Cell height'), - category, - defaultValue: defaultOptions.cellHeight, - settings: { - options: [ - { value: TableCellHeight.Sm, label: t('table-new.cell-height-options.label-small', 'Small') }, - { value: TableCellHeight.Md, label: t('table-new.cell-height-options.label-medium', 'Medium') }, - { value: TableCellHeight.Lg, label: t('table-new.cell-height-options.label-large', 'Large') }, - ], - }, - }) - .addBooleanSwitch({ - path: 'footer.show', - category: footerCategory, - name: t('table-new.name-show-table-footer', 'Show table footer'), - defaultValue: defaultOptions.footer?.show, - }) - .addCustomEditor({ - id: 'footer.reducer', - category: footerCategory, - path: 'footer.reducer', - name: t('table-new.name-calculation', 'Calculation'), - description: t('table-new.description-calculation', 'Choose a reducer function / calculation'), - editor: standardEditorsRegistry.get('stats-picker').editor, - defaultValue: [ReducerID.sum], - showIf: (cfg) => cfg.footer?.show, - }) - .addBooleanSwitch({ - path: 'footer.countRows', - category: footerCategory, - name: t('table-new.name-count-rows', 'Count rows'), - description: t('table-new.description-count-rows', 'Display a single count for all data rows'), - defaultValue: defaultOptions.footer?.countRows, - showIf: (cfg) => cfg.footer?.reducer?.length === 1 && cfg.footer?.reducer[0] === ReducerID.count, - }) - .addMultiSelect({ - path: 'footer.fields', - category: footerCategory, - name: t('table-new.name-fields', 'Fields'), - description: t('table-new.description-fields', 'Select the fields that should be calculated'), - settings: { - allowCustomValue: false, - options: [], - placeholder: t('table-new.placeholder-fields', 'All Numeric Fields'), - getOptions: async (context: FieldOverrideContext) => { - const options = []; - if (context && context.data && context.data.length > 0) { - const frame = context.data[0]; - for (const field of frame.fields) { - if (field.type === FieldType.number) { - const name = getFieldDisplayName(field, frame, context.data); - const value = field.name; - options.push({ value, label: name }); - } - } - } - return options; - }, - }, - defaultValue: '', - showIf: (cfg) => cfg.footer?.show && !(cfg.footer?.countRows && cfg.footer?.reducer.includes(ReducerID.count)), - }) - .addCustomEditor({ - id: 'footer.enablePagination', - path: 'footer.enablePagination', - name: t('table-new.name-enable-pagination', 'Enable pagination'), - category, - editor: PaginationEditor, - }); - }) - .setSuggestionsSupplier(new TableSuggestionsSupplier()); diff --git a/public/app/plugins/panel/table/table-new/panelcfg.cue b/public/app/plugins/panel/table/table-new/panelcfg.cue deleted file mode 100644 index 1bfbd053cf2..00000000000 --- a/public/app/plugins/panel/table/table-new/panelcfg.cue +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2021 Grafana Labs -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package grafanaplugin - -import ( - ui "github.com/grafana/grafana/packages/grafana-schema/src/common" -) - -composableKinds: PanelCfg: { - maturity: "experimental" - lineage: { - schemas: [{ - version: [0, 0] - schema: { - Options: { - // Represents the index of the selected frame - frameIndex: number | *0 - // Controls whether the panel should show the header - showHeader: bool | *true - // Controls whether the header should show icons for the column types - showTypeIcons?: bool | *false - // Used to control row sorting - sortBy?: [...ui.TableSortByFieldState] - // Controls footer options - footer?: ui.TableFooterOptions | *{ - // Controls whether the footer should be shown - show: false - // Controls whether the footer should show the total number of rows on Count calculation - countRows: false - // Represents the selected calculations - reducer: [] - } - // Controls the height of the rows - cellHeight?: ui.TableCellHeight & (*"sm" | _) - // Defines the number of columns to freeze on the left side of the table - frozenColumns?: { - left?: number | *0 - } - } @cuetsy(kind="interface") - FieldConfig: { - ui.TableFieldOptions - } @cuetsy(kind="interface") - } - }] - lenses: [] - } -} diff --git a/public/app/plugins/panel/table/table-new/panelcfg.gen.ts b/public/app/plugins/panel/table/table-new/panelcfg.gen.ts deleted file mode 100644 index bed8165671a..00000000000 --- a/public/app/plugins/panel/table/table-new/panelcfg.gen.ts +++ /dev/null @@ -1,68 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// public/app/plugins/gen.go -// Using jennies: -// TSTypesJenny -// PluginTsTypesJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -import * as ui from '@grafana/schema'; - -export interface Options { - /** - * Controls the height of the rows - */ - cellHeight?: ui.TableCellHeight; - /** - * Controls footer options - */ - footer?: ui.TableFooterOptions; - /** - * Represents the index of the selected frame - */ - frameIndex: number; - /** - * number of columns on the left side of the table that should be frozen - */ - frozenColumns?: { - left?: number; - }; - /** - * Controls whether the panel should show the header - */ - showHeader: boolean; - /** - * Controls whether the header should show icons for the column types - */ - showTypeIcons?: boolean; - /** - * Used to control row sorting - */ - sortBy?: Array; -} - -export const defaultOptions: Partial = { - cellHeight: ui.TableCellHeight.Sm, - footer: { - /** - * Controls whether the footer should be shown - */ - show: false, - /** - * Controls whether the footer should show the total number of rows on Count calculation - */ - countRows: false, - /** - * Represents the selected calculations - */ - reducer: [], - }, - frameIndex: 0, - showHeader: true, - showTypeIcons: false, - sortBy: [], -}; - -export interface FieldConfig extends ui.TableFieldOptions {} diff --git a/public/app/plugins/panel/table/table-new/plugin.json b/public/app/plugins/panel/table/table-new/plugin.json deleted file mode 100644 index 36fb974d930..00000000000 --- a/public/app/plugins/panel/table/table-new/plugin.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "type": "panel", - "name": "Table", - "id": "table", - "state": "beta", - - "info": { - "description": "Supports many column styles", - "author": { - "name": "Grafana Labs", - "url": "https://grafana.com" - }, - "logos": { - "small": "img/icn-table-panel.svg", - "large": "img/icn-table-panel.svg" - }, - "links": [ - { "name": "Raise issue", "url": "https://github.com/grafana/grafana/issues/new" }, - { - "name": "Documentation", - "url": "https://grafana.com/docs/grafana/latest/panels-visualizations/visualizations/table/" - } - ] - } -} diff --git a/public/app/plugins/panel/table/table-new/suggestions.ts b/public/app/plugins/panel/table/table-new/suggestions.ts deleted file mode 100644 index bcfead0fe31..00000000000 --- a/public/app/plugins/panel/table/table-new/suggestions.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { VisualizationSuggestionsBuilder } from '@grafana/data'; -import { TableFieldOptions } from '@grafana/schema'; -import icnTablePanelSvg from 'app/plugins/panel/table/img/icn-table-panel.svg'; -import { SuggestionName } from 'app/types/suggestions'; - -import { Options } from './panelcfg.gen'; - -export class TableSuggestionsSupplier { - getSuggestionsForData(builder: VisualizationSuggestionsBuilder) { - const list = builder.getListAppender({ - name: SuggestionName.Table, - pluginId: 'table', - options: {}, - fieldConfig: { - defaults: { - custom: {}, - }, - overrides: [], - }, - cardOptions: { - previewModifier: (s) => { - s.fieldConfig!.defaults.custom!.minWidth = 50; - }, - }, - }); - - // If there are not data suggest table anyway but use icon instead of real preview - if (builder.dataSummary.fieldCount === 0) { - list.append({ - cardOptions: { - imgSrc: icnTablePanelSvg, - }, - }); - } else { - list.append({}); - } - } -} diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 2706ec22fce..278716c8265 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -12708,10 +12708,6 @@ "login": "Login" }, "table": { - "auto-cell-options-editor": { - "description-wrap-text": "If selected text will be wrapped to the width of text in the configured column", - "label-wrap-text": "Wrap text" - }, "bar-gauge-cell-options-editor": { "label-gauge-display-mode": "Gauge display mode", "label-value-display": "Value display" @@ -12739,13 +12735,8 @@ }, "color-background-cell-options-editor": { "description-apply-to-entire-row": "If selected the entire row will be colored as this cell would be.", - "description-wrap-text": "If selected text will be wrapped to the width of text in the configured column", - "label": { - "text-alpha": "Alpha" - }, "label-apply-to-entire-row": "Apply to entire row", - "label-background-display-mode": "Background display mode", - "wrap-text": "Wrap text" + "label-background-display-mode": "Background display mode" }, "column-alignment-options": { "label-auto": "Auto", @@ -12763,7 +12754,9 @@ "description-column-filter": "Enables/disables field filters in table", "description-count-rows": "Display a single count for all data rows", "description-fields": "Select the fields that should be calculated", + "description-frozen-columns": "Columns are frozen from the left side of the table", "description-min-column-width": "The minimum width for column auto resizing", + "description-tooltip-from-field": "Render a cell from a field (hidden or visible) in a tooltip", "description-wrap-header-text": "Enables text wrapping for column headers", "image-cell-options-editor": { "description-alt-text": "Alternative text that will be displayed if an image can't be displayed or for users who use a screen reader", @@ -12789,50 +12782,6 @@ "name-column-filter": "Column filter", "name-column-width": "Column width", "name-count-rows": "Count rows", - "name-enable-paginations": "Enable pagination", - "name-fields": "Fields", - "name-hide-in-table": "Hide in table", - "name-min-column-width": "Minimum column width", - "name-show-table-footer": "Show table footer", - "name-show-table-header": "Show table header", - "name-wrap-header-text": "Wrap header text", - "placeholder-column-width": "auto", - "placeholder-fields": "All Numeric Fields", - "text-wrap-options": { - "label-wrap-text": "Wrap text" - } - }, - "table-new": { - "category-cell-options": "Cell options", - "category-table": "Table", - "category-table-footer": "Table footer", - "cell-height-options": { - "label-large": "Large", - "label-medium": "Medium", - "label-small": "Small" - }, - "column-alignment-options": { - "label-auto": "Auto", - "label-center": "Center", - "label-left": "Left", - "label-right": "Right" - }, - "description-calculation": "Choose a reducer function / calculation", - "description-cell-value-inspect": "Enable cell value inspection in a modal window", - "description-column-filter": "Enables/disables field filters in table", - "description-count-rows": "Display a single count for all data rows", - "description-fields": "Select the fields that should be calculated", - "description-frozen-columns": "Columns are frozen from the left side of the table", - "description-min-column-width": "The minimum width for column auto resizing", - "description-tooltip-from-field": "Render a cell from a field (hidden or visible) in a tooltip", - "name-calculation": "Calculation", - "name-cell-height": "Cell height", - "name-cell-type": "Cell type", - "name-cell-value-inspect": "Cell value inspect", - "name-column-alignment": "Column alignment", - "name-column-filter": "Column filter", - "name-column-width": "Column width", - "name-count-rows": "Count rows", "name-enable-pagination": "Enable pagination", "name-fields": "Fields", "name-frozen-columns": "Frozen columns", @@ -12842,8 +12791,12 @@ "name-show-table-header": "Show table header", "name-tooltip-from-field": "Tooltip from field", "name-tooltip-placement": "Tooltip placement", + "name-wrap-header-text": "Wrap header text", "placeholder-column-width": "auto", "placeholder-fields": "All Numeric Fields", + "text-wrap-options": { + "label-wrap-text": "Wrap text" + }, "tooltip-placement-options": { "label-auto": "Auto", "label-bottom": "Bottom",