Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Align charts.updatedAt and chart_configs.updatedAt #3962

Merged
merged 6 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions adminSiteServer/apiRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -434,19 +434,23 @@ const updateExistingChart = async (
if (!chartConfigId)
throw new JsonError(`No chart config found for id ${chartId}`, 404)

const now = new Date()

// update configs
await db.knexRaw(
knex,
`-- sql
UPDATE chart_configs
SET
patch=?,
full=?
full=?,
updatedAt=?
WHERE id = ?
`,
[
serializeChartConfig(patchConfig),
fullConfigStringified,
now,
chartConfigId.configId,
]
)
Expand All @@ -456,10 +460,10 @@ const updateExistingChart = async (
knex,
`-- sql
UPDATE charts
SET isInheritanceEnabled=?, lastEditedAt=?, lastEditedByUserId=?
SET isInheritanceEnabled=?, updatedAt=?, lastEditedAt=?, lastEditedByUserId=?
WHERE id = ?
`,
[shouldInherit, new Date(), user.id, chartId]
[shouldInherit, now, now, user.id, chartId]
)

// We need to get the full config and the md5 hash from the database instead of
Expand Down Expand Up @@ -1643,6 +1647,8 @@ deleteRouteWithRWTransaction(
// no-op if the variable doesn't have an ETL config
if (!variable.etl) return { success: true }

const now = new Date()

// remove reference in the variables table
await db.knexRaw(
trx,
Expand All @@ -1669,6 +1675,7 @@ deleteRouteWithRWTransaction(
await updateExistingFullConfig(trx, {
configId: variable.admin.configId,
config: variable.admin.patchConfig,
updatedAt: now,
})
}

Expand All @@ -1678,6 +1685,7 @@ deleteRouteWithRWTransaction(
variableId,
{
patchConfigAdmin: variable.admin?.patchConfig,
updatedAt: now,
}
)

Expand Down Expand Up @@ -1734,6 +1742,8 @@ deleteRouteWithRWTransaction(
// no-op if the variable doesn't have an admin-authored config
if (!variable.admin) return { success: true }

const now = new Date()

// remove reference in the variables table
await db.knexRaw(
trx,
Expand Down Expand Up @@ -1761,6 +1771,7 @@ deleteRouteWithRWTransaction(
variableId,
{
patchConfigETL: variable.etl?.patchConfig,
updatedAt: now,
}
)

Expand Down
40 changes: 40 additions & 0 deletions adminSiteServer/app.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -725,4 +725,44 @@ describe("OwidAdminApp: indicator-level chart configs", () => {
)
expect(fullConfig).not.toHaveProperty("note")
})

it("should update timestamps on chart update", async () => {
// make sure the database is in a clean state
const chartCount = await getCountForTable(ChartsTableName)
expect(chartCount).toBe(0)
const chartConfigsCount = await getCountForTable(ChartConfigsTableName)
expect(chartConfigsCount).toBe(0)

// make a request to create a chart
const response = await makeRequestAgainstAdminApi({
method: "POST",
path: "/charts",
body: JSON.stringify(testChartConfig),
})
const chartId = response.chartId

// helper functions to get the updatedAt timestamp of the chart and its config
const chartUpdatedAt = async (): Promise<Date> =>
(await testKnexInstance!(ChartsTableName).first()).updatedAt
const configUpdatedAt = async (): Promise<Date> =>
(await testKnexInstance!(ChartConfigsTableName).first()).updatedAt

// verify that both updatedAt timestamps are null initially
expect(await chartUpdatedAt()).toBeNull()
expect(await configUpdatedAt()).toBeNull()

// update the chart
await makeRequestAgainstAdminApi({
method: "PUT",
path: `/charts/${chartId}`,
body: JSON.stringify({ ...testChartConfig, title: "New title" }),
})

// verify that the updatedAt timestamps are the same
const chartAfterUpdate = await chartUpdatedAt()
const configAfterUpdate = await configUpdatedAt()
expect(chartAfterUpdate).not.toBeNull()
expect(configAfterUpdate).not.toBeNull()
expect(chartAfterUpdate).toEqual(configAfterUpdate)
})
})
34 changes: 34 additions & 0 deletions db/migration/1726480222201-AlignUpdatedAtOfCharts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { MigrationInterface, QueryRunner } from "typeorm"

export class AlignUpdatedAtOfCharts1726480222201 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
UPDATE chart_configs cf
INNER JOIN charts c ON c.configId = cf.id
SET cf.updatedAt = LEAST(cf.updatedAt, c.updatedAt)
WHERE c.updatedAt IS NOT NULL;
`)

await queryRunner.query(`
ALTER TABLE charts
MODIFY updatedAt datetime DEFAULT NULL;
`)
await queryRunner.query(`
ALTER TABLE chart_configs
MODIFY updatedAt datetime DEFAULT NULL;
`)
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE charts
MODIFY updatedAt datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP;
`)
await queryRunner.query(`
ALTER TABLE chart_configs
MODIFY updatedAt datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP;
`)

// We can't automatically revert the data changes
}
}
31 changes: 20 additions & 11 deletions db/model/ChartConfigs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,18 @@ import {

import * as db from "../db.js"

interface ConfigWithId {
configId: DbInsertChartConfig["id"]
config: GrapherInterface
}

export async function updateExistingConfigPair(
knex: db.KnexReadWriteTransaction,
{
configId,
patchConfig,
fullConfig,
updatedAt,
}: {
configId: DbInsertChartConfig["id"]
patchConfig: GrapherInterface
fullConfig: GrapherInterface
updatedAt: Date
}
): Promise<void> {
await db.knexRaw(
Expand All @@ -29,27 +26,35 @@ export async function updateExistingConfigPair(
UPDATE chart_configs
SET
patch = ?,
full = ?
full = ?,
updatedAt = ?
WHERE id = ?
`,
[
serializeChartConfig(patchConfig),
serializeChartConfig(fullConfig),
updatedAt,
configId,
]
)
}

type ConfigId = DbInsertChartConfig["id"]
interface UpdateFields {
config: GrapherInterface
updatedAt: Date
}

export async function updateExistingPatchConfig(
knex: db.KnexReadWriteTransaction,
params: ConfigWithId
params: UpdateFields & { configId: ConfigId }
): Promise<void> {
await updateExistingConfig(knex, { ...params, column: "patch" })
}

export async function updateExistingFullConfig(
knex: db.KnexReadWriteTransaction,
params: ConfigWithId
params: UpdateFields & { configId: ConfigId }
): Promise<void> {
await updateExistingConfig(knex, { ...params, column: "full" })
}
Expand All @@ -60,17 +65,21 @@ async function updateExistingConfig(
column,
configId,
config,
}: ConfigWithId & {
updatedAt,
}: UpdateFields & {
configId: ConfigId
column: "patch" | "full"
}
): Promise<void> {
await db.knexRaw(
knex,
`-- sql
UPDATE chart_configs
SET ?? = ?
SET
?? = ?,
updatedAt = ?
WHERE id = ?
`,
[column, serializeChartConfig(config), configId]
[column, serializeChartConfig(config), updatedAt, configId]
)
}
20 changes: 18 additions & 2 deletions db/model/Variable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,9 +251,11 @@ export async function updateAllChartsThatInheritFromIndicator(
trx: db.KnexReadWriteTransaction,
variableId: number,
{
updatedAt,
patchConfigETL,
patchConfigAdmin,
}: {
updatedAt: Date
patchConfigETL?: GrapherInterface
patchConfigAdmin?: GrapherInterface
}
Expand All @@ -262,6 +264,7 @@ export async function updateAllChartsThatInheritFromIndicator(
trx,
variableId
)

for (const chart of inheritingCharts) {
const fullConfig = mergeGrapherConfigs(
defaultGrapherConfig,
Expand All @@ -274,12 +277,16 @@ export async function updateAllChartsThatInheritFromIndicator(
`-- sql
UPDATE chart_configs cc
JOIN charts c ON c.configId = cc.id
SET cc.full = ?
SET
cc.full = ?,
cc.updatedAt = ?,
c.updatedAt = ?
WHERE c.id = ?
`,
[JSON.stringify(fullConfig), chart.chartId]
[JSON.stringify(fullConfig), updatedAt, updatedAt, chart.chartId]
)
}

// let the caller know if any charts were updated
return inheritingCharts
}
Expand All @@ -299,11 +306,14 @@ export async function updateGrapherConfigETLOfVariable(
variableId,
})

const now = new Date()

if (variable.etl) {
await updateExistingConfigPair(trx, {
configId: variable.etl.configId,
patchConfig: configETL,
fullConfig: configETL,
updatedAt: now,
})
} else {
await insertNewGrapherConfigForVariable(trx, {
Expand All @@ -323,6 +333,7 @@ export async function updateGrapherConfigETLOfVariable(
await updateExistingFullConfig(trx, {
configId: variable.admin.configId,
config: fullConfig,
updatedAt: now,
})
}

Expand All @@ -332,6 +343,7 @@ export async function updateGrapherConfigETLOfVariable(
{
patchConfigETL: configETL,
patchConfigAdmin: variable.admin?.patchConfig,
updatedAt: now,
}
)

Expand Down Expand Up @@ -366,11 +378,14 @@ export async function updateGrapherConfigAdminOfVariable(
patchConfigAdmin
)

const now = new Date()

if (variable.admin) {
await updateExistingConfigPair(trx, {
configId: variable.admin.configId,
patchConfig: patchConfigAdmin,
fullConfig: fullConfigAdmin,
updatedAt: now,
})
} else {
await insertNewGrapherConfigForVariable(trx, {
Expand All @@ -387,6 +402,7 @@ export async function updateGrapherConfigAdminOfVariable(
{
patchConfigETL: variable.etl?.patchConfig ?? {},
patchConfigAdmin: patchConfigAdmin,
updatedAt: now,
}
)

Expand Down
27 changes: 2 additions & 25 deletions db/tests/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
UsersTableName,
DbInsertChartConfig,
} from "@ourworldindata/types"
import { cleanTestDb, sleep } from "./testHelpers.js"
import { cleanTestDb } from "./testHelpers.js"

let knexInstance: Knex<any, unknown[]> | undefined = undefined

Expand Down Expand Up @@ -62,7 +62,7 @@ test("it can query a user created in fixture via TypeORM", async () => {
expect(user.email).toBe("[email protected]")
})

test("timestamps are automatically created and updated", async () => {
test("createdAt timestamp is automatically created", async () => {
await knexReadWriteTransaction(
async (trx) => {
const user = await knexInstance!
Expand Down Expand Up @@ -95,29 +95,6 @@ test("timestamps are automatically created and updated", async () => {
if (created) {
expect(created.createdAt).not.toBeNull()
expect(created.updatedAt).toBeNull()
await sleep(1000, undefined)
await trx
.table(ChartsTableName)
.where({ id: chartId })
.update({ isIndexable: true })
const updated = await knexRawFirst<DbPlainChart>(
trx,
"select * from charts where id = ?",
[chartId]
)
expect(updated).not.toBeNull()
if (updated) {
expect(updated.createdAt).not.toBeNull()
expect(updated.updatedAt).not.toBeNull()
expect(
updated.updatedAt!.getTime() -
updated.createdAt.getTime()
).toBeGreaterThan(800)
expect(
updated.updatedAt!.getTime() -
updated.createdAt.getTime()
).toBeLessThanOrEqual(2000)
}
}
},
TransactionCloseMode.KeepOpen,
Expand Down