Compare commits

..
7 changed files with 96 additions and 72 deletions
+38
View File
@@ -9,6 +9,7 @@ import {
Inject,
NotFoundException,
Param,
Patch,
Post,
Put,
Query,
@@ -51,6 +52,7 @@ import {
IUpdateDataRequest,
TriggerCatalogReq,
TriggerCatalogRes,
UpdateColumnsMetadataRequest,
} from './dtos';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
import { Language } from 'src/decorators/language.decorator';
@@ -448,6 +450,42 @@ export class CatalogController {
return { columns_metadata };
}
@Patch('data-asset/:id/columns-metadata')
@RequireSomePermission(
PERMISSIONS_GROUPS.CATALOG.permissions.UPDATE,
PERMISSIONS_GROUPS.CATALOG.permissions.DATA_MANAGER,
)
async updateColumnsMetadata(
@User() user: RequestUser,
@Language() language: LanguageEnum,
@Param('id') id: string,
@Body(new ValidationPipe()) body: UpdateColumnsMetadataRequest,
): Promise<{ success: boolean }> {
const { customer_name, customer_id, user_id, username } = user;
this.logger.info(`/catalog - update columns metadata`, {
user_id,
customer_name,
columns_count: body.columns.length,
});
const metadata = PackTheMetadata({
customer_name,
customer_id,
user_id,
username,
language,
});
await this.catalogService.updateColumnsDescriptions(
id,
body.columns,
metadata,
);
return { success: true };
}
@Get('data-asset/:id/preview')
@RequireSomePermission(
PERMISSIONS_GROUPS.CATALOG.permissions.GET,
+12
View File
@@ -467,6 +467,16 @@ class CatalogService implements OnModuleInit {
return result;
}
async updateColumnsDescriptions(
id: string,
columns: { column_name: string; description: string }[],
metadata: Metadata,
) {
await lastValueFrom(
this.catalogWriteService.UpdateColumnDescriptions({ id, columns }, metadata),
);
}
async createDataDocs(body: CreateDataDocsDTO, metadata: Metadata) {
if (body.asset_type === 'table' || body.asset_type === 'view') {
return this.createDataDocsViaNimbus(body);
@@ -832,6 +842,8 @@ class CatalogService implements OnModuleInit {
data_asset_id: table_metadata_id.toString(),
customer_name: customer_name,
data_asset_type: 'dataset',
column_metadata: [],
data_preview: '',
},
],
},
+24 -1
View File
@@ -1,10 +1,13 @@
import { ApiProperty, ApiPropertyOptional, PickType } from '@nestjs/swagger';
import {
ArrayNotEmpty,
IsArray,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { CreateDataAssetRequest } from '@dadosfera/protospack-v2/dist/lib/Catalog/interfaces/messages';
export enum DataAssetShareType {
@@ -247,6 +250,26 @@ export class IUpdateCertificationStatusRequest {
certification_status: CertificationStatus;
}
export class ColumnDescriptionDto {
@ApiProperty()
@IsString()
@IsNotEmpty()
column_name: string;
@ApiProperty()
@IsString()
description: string;
}
export class UpdateColumnsMetadataRequest {
@ApiProperty({ type: [ColumnDescriptionDto] })
@IsArray()
@ArrayNotEmpty()
@ValidateNested({ each: true })
@Type(() => ColumnDescriptionDto)
columns: ColumnDescriptionDto[];
}
export class ICreateDataAsset implements CreateDataAssetRequest {
@ApiProperty()
display_name: string;
@@ -65,7 +65,7 @@ describe('ConnectionTestService catalog cache', () => {
});
});
it('maps cached columns and derives references from the engine allowlist', async () => {
it('maps cached columns to the existing table metadata contract', async () => {
connectionsApiService.proxy.mockResolvedValue({
columns: [
{
@@ -73,16 +73,6 @@ describe('ConnectionTestService catalog cache', () => {
data_type: 'bigint',
is_primary_key: true,
},
{
column_name: 'name',
data_type: 'varchar',
is_primary_key: false,
},
],
});
platformApiService.proxy.mockResolvedValue({
allowed_datatypes: [
{ engine: 'postgresql', allowed_datatypes: ['bigint', 'timestamp'] },
],
});
@@ -102,10 +92,13 @@ describe('ConnectionTestService catalog cache', () => {
{
table_name: 'customers',
columns: [
{ name: 'id', type: 'bigint', is_primary_key: true },
{ name: 'name', type: 'varchar', is_primary_key: false },
{
name: 'id',
type: 'bigint',
is_primary_key: true,
},
],
references: [{ name: 'id', type: 'bigint', is_primary_key: true }],
references: [],
},
],
});
@@ -114,39 +107,6 @@ describe('ConnectionTestService catalog cache', () => {
'/connection_catalog/config-id/schemas/public/tables/customers/columns',
user,
);
expect(platformApiService.proxy).toHaveBeenCalledWith(
'GET',
'/jobs/jdbc/configs/allowed_datatypes',
user,
);
});
it('returns empty references when the allowlist call fails', async () => {
connectionsApiService.proxy.mockResolvedValue({
columns: [{ column_name: 'id', data_type: 'bigint', is_primary_key: true }],
});
platformApiService.proxy.mockRejectedValue(new Error('platform down'));
await expect(
service.getTableMetadata(
{
connection_id: 'config-id',
plugin: 'postgresql',
schema: 'public',
table_list: ['customers'],
},
user,
),
).resolves.toEqual({
operation_result: true,
tables_metadata: [
{
table_name: 'customers',
columns: [{ name: 'id', type: 'bigint', is_primary_key: true }],
references: [],
},
],
});
});
it('submits a catalog refresh without holding the request open', async () => {
@@ -187,20 +187,6 @@ export class ConnectionTestService {
body: GetTableMetadataReq,
user: RequestUser,
): Promise<GetTableMetadataRes> {
// Columns eligible as the incremental reference field are the ones whose
// data type is allowed for this engine (e.g. int/date/timestamp). The
// allowlist is owned by the platform API, keyed by engine === plugin.
const allowedByEngine = await this.platformApiService
.proxy('GET', '/jobs/jdbc/configs/allowed_datatypes', user)
.catch(() => null);
const allowedDataTypes: string[] =
allowedByEngine?.allowed_datatypes?.find(
(datatypes) => datatypes.engine === body.plugin,
)?.allowed_datatypes ?? [];
const allowedSet = new Set(
allowedDataTypes.map((type) => type.toLowerCase()),
);
const tables_metadata = await Promise.all(
body.table_list.map(async (table_name) => {
const result = await this.connectionsApiService.proxy(
@@ -210,18 +196,14 @@ export class ConnectionTestService {
`/tables/${encodeURIComponent(table_name)}/columns`,
user,
);
const columns = result.columns.map((column) => ({
name: column.column_name,
type: column.data_type,
is_primary_key: column.is_primary_key,
}));
const references = columns.filter((column) =>
allowedSet.has(String(column.type).toLowerCase()),
);
return {
table_name,
columns,
references,
columns: result.columns.map((column) => ({
name: column.column_name,
type: column.data_type,
is_primary_key: column.is_primary_key,
})),
references: [],
};
}),
);
+8
View File
@@ -315,6 +315,14 @@ export function EnrichErrorCode(code: string) {
'Tente realizar a ação novamente. Caso o erro persista, entre em contato com o suporte',
code,
};
case ErrorCodes.CATALOG.COLUMN_NOT_FOUND:
return {
statusCode: HttpStatus.NOT_FOUND,
error: 'Coluna não encontrada',
message:
'Uma ou mais colunas informadas não existem neste ativo. Verifique os nomes e tente novamente.',
code,
};
case ErrorCodes.CATALOG.PREVIEW_TOO_BIG:
return {
statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
+1
View File
@@ -74,6 +74,7 @@ const CATALOG = {
DATA_ASSET_NOT_FOUND: 'CATALOG.DATA_ASSET_NOT_FOUND',
PREVIEW_TOO_BIG: 'CATALOG.PREVIEW_TOO_BIG',
METADATA_TOO_BIG: 'CATALOG.METADATA_TOO_BIG',
COLUMN_NOT_FOUND: 'CATALOG.COLUMN_NOT_FOUND',
};
const IDENTITY_PROVIDER = {
INVALID_RESPONSE: 'IDENTITY_PROVIDER.INVALID_RESPONSE',