This is an automated email from the ASF dual-hosted git repository. elizabeth pushed a commit to branch 2.1 in repository https://gitbox.apache.org/repos/asf/superset.git
commit 8405ec4ae7aefcce20777afce643a5a7eb0b1199 Author: Ville Brofeldt <[email protected]> AuthorDate: Mon Jun 26 19:13:55 2023 +0300 fix(sqllab): normalize changedOn timestamp (#24513) --- .../superset-ui-core/src/time-format/index.ts | 3 ++ .../src/time-format/utils/denormalizeTimestamp.ts} | 16 ++++---- .../src/time-format/utils/normalizeTimestamp.ts} | 16 ++++---- .../time-format/utils/denormalizeTimestamp.test.ts | 43 ++++++++++++++++++++++ .../preset-chart-xy/src/Line/createMetadata.ts | 2 + .../SqlLab/components/SouthPane/SouthPane.test.jsx | 9 +++-- superset-frontend/src/SqlLab/fixtures.ts | 9 ++++- superset-frontend/src/SqlLab/reducers/sqlLab.js | 8 ++-- .../src/SqlLab/reducers/sqlLab.test.js | 22 +++++++++-- 9 files changed, 98 insertions(+), 30 deletions(-) diff --git a/superset-frontend/packages/superset-ui-core/src/time-format/index.ts b/superset-frontend/packages/superset-ui-core/src/time-format/index.ts index 48ac1a6803..53f23f3643 100644 --- a/superset-frontend/packages/superset-ui-core/src/time-format/index.ts +++ b/superset-frontend/packages/superset-ui-core/src/time-format/index.ts @@ -36,4 +36,7 @@ export { default as smartDateFormatter } from './formatters/smartDate'; export { default as smartDateDetailedFormatter } from './formatters/smartDateDetailed'; export { default as smartDateVerboseFormatter } from './formatters/smartDateVerbose'; +export { default as normalizeTimestamp } from './utils/normalizeTimestamp'; +export { default as denormalizeTimestamp } from './utils/denormalizeTimestamp'; + export * from './types'; diff --git a/superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts b/superset-frontend/packages/superset-ui-core/src/time-format/utils/denormalizeTimestamp.ts similarity index 74% copy from superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts copy to superset-frontend/packages/superset-ui-core/src/time-format/utils/denormalizeTimestamp.ts index 1567210cf7..3a69667ce1 100644 --- a/superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts +++ b/superset-frontend/packages/superset-ui-core/src/time-format/utils/denormalizeTimestamp.ts @@ -17,14 +17,12 @@ * under the License. */ -import { t, ChartMetadata } from '@superset-ui/core'; -import thumbnail from './images/thumbnail.png'; +import { TS_REGEX } from './normalizeTimestamp'; -export default function createMetadata(useLegacyApi = false) { - return new ChartMetadata({ - description: '', - name: t('Line Chart'), - thumbnail, - useLegacyApi, - }); +export default function normalizeTimestamp(value: string): string { + const match = value.match(TS_REGEX); + if (match) { + return `${match[1]}T${match[2]}`; + } + return value; } diff --git a/superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts b/superset-frontend/packages/superset-ui-core/src/time-format/utils/normalizeTimestamp.ts similarity index 74% copy from superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts copy to superset-frontend/packages/superset-ui-core/src/time-format/utils/normalizeTimestamp.ts index 1567210cf7..8e5201f238 100644 --- a/superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts +++ b/superset-frontend/packages/superset-ui-core/src/time-format/utils/normalizeTimestamp.ts @@ -17,14 +17,12 @@ * under the License. */ -import { t, ChartMetadata } from '@superset-ui/core'; -import thumbnail from './images/thumbnail.png'; +export const TS_REGEX = /(\d{4}-\d{2}-\d{2})[\sT](\d{2}:\d{2}:\d{2}\.?\d*).*/; -export default function createMetadata(useLegacyApi = false) { - return new ChartMetadata({ - description: '', - name: t('Line Chart'), - thumbnail, - useLegacyApi, - }); +export default function normalizeTimestamp(value: string): string { + const match = value.match(TS_REGEX); + if (match) { + return `${match[1]}T${match[2]}Z`; + } + return value; } diff --git a/superset-frontend/packages/superset-ui-core/test/time-format/utils/denormalizeTimestamp.test.ts b/superset-frontend/packages/superset-ui-core/test/time-format/utils/denormalizeTimestamp.test.ts new file mode 100644 index 0000000000..27838c393a --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/test/time-format/utils/denormalizeTimestamp.test.ts @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +import denormalizeTimestamp from '../../../src/time-format/utils/denormalizeTimestamp'; + +test('denormalizeTimestamp should normalize typical timestamps', () => { + expect(denormalizeTimestamp('2023-03-11 08:26:52.695 UTC')).toEqual( + '2023-03-11T08:26:52.695', + ); + expect( + denormalizeTimestamp('2023-03-11 08:26:52.695 Europe/Helsinki'), + ).toEqual('2023-03-11T08:26:52.695'); + expect(denormalizeTimestamp('2023-03-11T08:26:52.695 UTC')).toEqual( + '2023-03-11T08:26:52.695', + ); + expect(denormalizeTimestamp('2023-03-11T08:26:52.695')).toEqual( + '2023-03-11T08:26:52.695', + ); + expect(denormalizeTimestamp('2023-03-11 08:26:52')).toEqual( + '2023-03-11T08:26:52', + ); +}); + +test('denormalizeTimestamp should return unmatched timestamps as-is', () => { + expect(denormalizeTimestamp('abcd')).toEqual('abcd'); + expect(denormalizeTimestamp('03/11/2023')).toEqual('03/11/2023'); +}); diff --git a/superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts b/superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts index 1567210cf7..a1554206e9 100644 --- a/superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts +++ b/superset-frontend/plugins/preset-chart-xy/src/Line/createMetadata.ts @@ -20,6 +20,8 @@ import { t, ChartMetadata } from '@superset-ui/core'; import thumbnail from './images/thumbnail.png'; +export const TS_REGEX = /(\d{4}-\d{2}-\d{2})[\sT](\d{2}:\d{2}:\d{2}\.?\d*).*/; + export default function createMetadata(useLegacyApi = false) { return new ChartMetadata({ description: '', diff --git a/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.jsx b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.jsx index 519e729c41..276d8eea66 100644 --- a/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.jsx +++ b/superset-frontend/src/SqlLab/components/SouthPane/SouthPane.test.jsx @@ -24,6 +24,7 @@ import SouthPane from 'src/SqlLab/components/SouthPane'; import '@testing-library/jest-dom/extend-expect'; import { STATUS_OPTIONS } from 'src/SqlLab/constants'; import { initialState, table, defaultQueryEditor } from 'src/SqlLab/fixtures'; +import { denormalizeTimestamp } from '@superset-ui/core'; const mockedProps = { queryEditorId: defaultQueryEditor.id, @@ -61,7 +62,7 @@ const store = mockStore({ queries: { LCly_kkIN: { cached: false, - changedOn: Date.now(), + changed_on: denormalizeTimestamp(new Date().toISOString()), db: 'main', dbId: 1, id: 'LCly_kkIN', @@ -71,7 +72,7 @@ const store = mockStore({ }, lXJa7F9_r: { cached: false, - changedOn: 1559238500401, + changed_on: denormalizeTimestamp(new Date(1559238500401).toISOString()), db: 'main', dbId: 1, id: 'lXJa7F9_r', @@ -80,7 +81,7 @@ const store = mockStore({ }, '2g2_iRFMl': { cached: false, - changedOn: 1559238506925, + changed_on: denormalizeTimestamp(new Date(1559238506925).toISOString()), db: 'main', dbId: 1, id: '2g2_iRFMl', @@ -89,7 +90,7 @@ const store = mockStore({ }, erWdqEWPm: { cached: false, - changedOn: 1559238516395, + changed_on: denormalizeTimestamp(new Date(1559238516395).toISOString()), db: 'main', dbId: 1, id: 'erWdqEWPm', diff --git a/superset-frontend/src/SqlLab/fixtures.ts b/superset-frontend/src/SqlLab/fixtures.ts index 456a83a3fa..27b582cbd0 100644 --- a/superset-frontend/src/SqlLab/fixtures.ts +++ b/superset-frontend/src/SqlLab/fixtures.ts @@ -19,7 +19,12 @@ import sinon from 'sinon'; import * as actions from 'src/SqlLab/actions/sqlLab'; import { ColumnKeyTypeType } from 'src/SqlLab/components/ColumnElement'; -import { DatasourceType, QueryResponse, QueryState } from '@superset-ui/core'; +import { + DatasourceType, + denormalizeTimestamp, + QueryResponse, + QueryState, +} from '@superset-ui/core'; import { ISaveableDatasource } from 'src/SqlLab/components/SaveDatasetModal'; export const mockedActions = sinon.stub({ ...actions }); @@ -721,7 +726,7 @@ export const mockdatasets = [...new Array(3)].map((_, i) => ({ kind: i === 0 ? 'virtual' : 'physical', // ensure there is 1 virtual changed_by_url: 'changed_by_url', changed_by: 'user', - changed_on: new Date().toISOString(), + changed_on: denormalizeTimestamp(new Date().toISOString()), database_name: `db ${i}`, explore_url: `/explore/?datasource_type=table&datasource_id=${i}`, id: i, diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.js b/superset-frontend/src/SqlLab/reducers/sqlLab.js index e3bb196fbc..db0332f8c9 100644 --- a/superset-frontend/src/SqlLab/reducers/sqlLab.js +++ b/superset-frontend/src/SqlLab/reducers/sqlLab.js @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { QueryState, t } from '@superset-ui/core'; +import { normalizeTimestamp, QueryState, t } from '@superset-ui/core'; import getInitialState from './getInitialState'; import * as actions from '../actions/sqlLab'; import { now } from '../../utils/dates'; @@ -729,8 +729,10 @@ export default function sqlLabReducer(state = {}, action) { (state.queries[id].state !== QueryState.STOPPED && state.queries[id].state !== QueryState.FAILED) ) { - if (changedQuery.changedOn > queriesLastUpdate) { - queriesLastUpdate = changedQuery.changedOn; + const changedOn = normalizeTimestamp(changedQuery.changed_on); + const timestamp = Date.parse(changedOn); + if (timestamp > queriesLastUpdate) { + queriesLastUpdate = timestamp; } const prevState = state.queries[id]?.state; const currentState = changedQuery.state; diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.test.js b/superset-frontend/src/SqlLab/reducers/sqlLab.test.js index dd4c0be4b4..a5468f6898 100644 --- a/superset-frontend/src/SqlLab/reducers/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/reducers/sqlLab.test.js @@ -18,7 +18,6 @@ */ import sqlLabReducer from 'src/SqlLab/reducers/sqlLab'; import * as actions from 'src/SqlLab/actions/sqlLab'; -import { now } from 'src/utils/dates'; import { table, initialState as mockState } from '../fixtures'; const initialState = mockState.sqlLab; @@ -272,6 +271,8 @@ describe('sqlLabReducer', () => { }); }); describe('Run Query', () => { + const DENORMALIZED_CHANGED_ON = '2023-06-26T07:53:05.439'; + const CHANGED_ON_TIMESTAMP = 1687765985439; let newState; let query; beforeEach(() => { @@ -279,7 +280,8 @@ describe('sqlLabReducer', () => { query = { id: 'abcd', progress: 0, - startDttm: now(), + changed_on: DENORMALIZED_CHANGED_ON, + startDttm: CHANGED_ON_TIMESTAMP, state: 'running', cached: false, sqlEditorId: 'dfsadfs', @@ -291,7 +293,8 @@ describe('sqlLabReducer', () => { query: { id: 'abcd', progress: 0, - startDttm: now(), + changed_on: DENORMALIZED_CHANGED_ON, + startDttm: CHANGED_ON_TIMESTAMP, state: 'running', cached: false, sqlEditorId: 'dfsadfs', @@ -327,6 +330,19 @@ describe('sqlLabReducer', () => { newState = sqlLabReducer(newState, removeQueryAction); expect(Object.keys(newState.queries)).toHaveLength(0); }); + it('should refresh queries when polling returns new results', () => { + newState = sqlLabReducer( + { + ...newState, + queries: { abcd: {} }, + }, + actions.refreshQueries({ + abcd: query, + }), + ); + expect(newState.queries.abcd.changed_on).toBe(DENORMALIZED_CHANGED_ON); + expect(newState.queriesLastUpdate).toBe(CHANGED_ON_TIMESTAMP); + }); it('should refresh queries when polling returns empty', () => { newState = sqlLabReducer(newState, actions.refreshQueries({})); });
