diff --git a/plugins/catalog-backend/migrations/20200805163904_location_update_log_duplication_fix.js b/plugins/catalog-backend/migrations/20200805163904_location_update_log_duplication_fix.js new file mode 100644 index 0000000000..aa59b54ac8 --- /dev/null +++ b/plugins/catalog-backend/migrations/20200805163904_location_update_log_duplication_fix.js @@ -0,0 +1,75 @@ +/* + * Copyright 2020 Spotify AB + * + * 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. + */ + +// @ts-check + +/** + * @param {import('knex')} knex + */ +exports.up = function up(knex) { + return knex.schema + .dropTable('location_update_log') + .createTable('location_update_log', table => { + table.bigIncrements('id').primary(); // instead of uuid, so we can MAX it + table.enum('status', ['success', 'fail']).notNullable(); + table.dateTime('created_at').defaultTo(knex.fn.now()).notNullable(); + table.string('message'); + table + .uuid('location_id') + .references('id') + .inTable('locations') + .onUpdate('CASCADE') + .onDelete('CASCADE'); + table.string('entity_name').nullable(); + }).raw(` + DROP VIEW location_update_log_latest; + `).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(id) AS MAXID + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.id = t2.MAXID + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; + `); +}; + +/** + * @param {import('knex')} knex + */ +exports.down = function down(knex) { + return knex.schema.raw(` + DROP VIEW location_update_log_latest; + `).raw(` + CREATE VIEW location_update_log_latest AS + SELECT t1.* FROM location_update_log t1 + JOIN + ( + SELECT location_id, MAX(created_at) AS MAXDATE + FROM location_update_log + GROUP BY location_id + ) t2 + ON t1.location_id = t2.location_id + AND t1.created_at = t2.MAXDATE + GROUP BY t1.location_id, t1.id + ORDER BY created_at DESC; + `); +}; diff --git a/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts b/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts index 8c7897fa2c..679e5b43c1 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseLocationsCatalog.test.ts @@ -39,4 +39,33 @@ describe('DatabaseLocationsCatalog', () => { expect.objectContaining({ data: location }), ]); }); + + it('does not return duplicates of rows because of logs', async () => { + const location1 = { + id: 'dd12620d-0436-422f-93bd-929aa0788123', + type: 'valid_type', + target: 'valid_target1', + }; + const location2 = { + id: '1a89c479-1a33-4f27-8927-6090ba488c42', + type: 'valid_type', + target: 'valid_target2', + }; + await expect(catalog.addLocation(location1)).resolves.toEqual(location1); + await expect(catalog.addLocation(location2)).resolves.toEqual(location2); + await expect( + catalog.logUpdateSuccess(location1.id), + ).resolves.toBeUndefined(); + await expect( + catalog.logUpdateSuccess(location1.id), + ).resolves.toBeUndefined(); + const locations = await catalog.locations(); + expect(locations.length).toBe(2); + expect(locations).toEqual( + expect.arrayContaining([ + expect.objectContaining({ data: location1 }), + expect.objectContaining({ data: location2 }), + ]), + ); + }); }); diff --git a/plugins/catalog-backend/src/database/CommonDatabase.ts b/plugins/catalog-backend/src/database/CommonDatabase.ts index c860f52d1e..0c56ca4370 100644 --- a/plugins/catalog-backend/src/database/CommonDatabase.ts +++ b/plugins/catalog-backend/src/database/CommonDatabase.ts @@ -29,7 +29,6 @@ import { } from '@backstage/catalog-model'; import Knex from 'knex'; import lodash from 'lodash'; -import { v4 as uuidv4 } from 'uuid'; import type { Logger } from 'winston'; import { buildEntitySearch } from './search'; import type { @@ -347,7 +346,6 @@ export class CommonDatabase implements Database { return this.database( 'location_update_log', ).insert({ - id: uuidv4(), status, location_id: locationId, entity_name: entityName,