import chalk from 'chalk'; import { getGlobalConfig, getProjectPath, loadVariables } from './cluserHelper'; import { findTaskAssociation } from './taskAssociationHelper'; import { IEntitySearchResult } from './@types/IEntitySearchResult'; import { createVariable, readVariableKey, readVariableValue, } from './variablesHelper'; import fg from 'fast-glob'; import fs from 'fs'; import { parse, stringify } from 'yaml'; import { Dictionary } from 'tsyringe/dist/typings/types'; import { IEntityInfo } from './@types/IEntityInfo'; import path from 'path'; import { findEntityById } from './entityHelper'; import inquirer from 'inquirer'; import { normalizeName } from './profileGeneratorHelper'; import { v4 as uuid } from 'uuid'; export async function findTableById( tableId: string, ): Promise { var variables = loadVariables(); const tableIdKey = readVariableKey(tableId, '', variables); const cfg = getGlobalConfig(); const tableCachePath = cfg.tableCachesPath; const tableCaches = parse( fs.readFileSync(tableCachePath, 'utf8'), ) as Dictionary; if (!tableCaches) { console.error( `${chalk.red('✗')} Table caches not found in ${chalk.green(tableCachePath)}`, ); console.error( `${chalk.red('✗')} Please run ${chalk.green('hexasync profile set-context ')} to update the cache again.`, ); return null; } if (!tableCaches[tableIdKey] || !tableCaches[tableIdKey].length) { // table not found in cache console.error( `${chalk.red('✗')} Table with key ${chalk.green(tableIdKey)} not found in cache.`, ); console.error( `${chalk.red('✗')} Please run ${chalk.green('hexasync profile set-context ')} to update the cache again.`, ); return null; } var foundEntity: IEntitySearchResult = null as any; for (const cachedEntity of tableCaches[tableIdKey].sort( (a, b) => a.index - b.index, )) { const entityContent = ( parse(fs.readFileSync(cachedEntity.filePath, 'utf8'))?.tables || [] ).find((t: any) => { return t.id === tableIdKey; }); if (!entityContent || !Object.keys(entityContent).length) { console.error( `${chalk.red('✗')} Table with key ${chalk.green(tableIdKey)} not found in ${chalk.green(cachedEntity.filePath)}.`, ); continue; } if (!cachedEntity.importTypes.includes('override')) { console.log(`\n${chalk.green('✓')} Table found in ${chalk.gray((cachedEntity.filePath as any).replace(cfg.currentProject, ''))} - id: ${chalk.gray(readVariableValue(entityContent.id, '', variables))} (${chalk.green(entityContent.id)})`); foundEntity = { filePath: cachedEntity.filePath as any, entityType: 'table', entityId: entityContent.id, entityName: entityContent.name, entityDescription: entityContent.description, content: entityContent, }; } else { console.log(`\n${chalk.green('✓')} Overrided Table found in ${chalk.gray((cachedEntity.filePath as any).replace(cfg.currentProject, ''))} - id ${chalk.gray(readVariableValue(entityContent.id, '', variables))} (${chalk.green(entityContent.id)})`); foundEntity = { ...foundEntity, override: { filePath: cachedEntity.filePath as any, content: entityContent, }, }; } } return foundEntity; } export async function findTableByEntityId( entityId: string, entityType: 'task' | 'report' | 'schema', ): Promise { var tableId = ''; switch (entityType) { case 'task': var taskAssociation = findTaskAssociation(entityId); tableId = taskAssociation?.table?.id; if (!taskAssociation?.table?.id) { console.error( `${chalk.red('✗')} Table not found for task ${chalk.green(entityId)}`, ); return null; } break; case 'report': var report = await findEntityById(entityId, 'report'); tableId = report?.content?.tableId || ''; break; case 'schema': var schema = await findEntityById(entityId, 'schema'); tableId = schema?.content?.tableId || ''; break; default: console.error( `${chalk.red('✗')} Entity type ${chalk.green(entityType)} not supported.`, ); break; } if (!tableId) { console.error( `${chalk.red('✗')} Table not found for ${chalk.green(entityType)} ${chalk.green(readVariableKey(entityId))} (${chalk.gray(readVariableValue(entityId))})`, ); return null; } return findTableById(tableId); } export function setTableColumnsIndexGroup( table: IEntitySearchResult, columns: string[], indexGroup: 'KEYS' | 'TRACK_FOR_CHANGES' | 'SOURCE_ID', ) { var childTableContent = table.override?.content ? { ...table.override.content } : { ...table.content }; var originalTableContent = { ...table.content }; var hasChanged = false; if (!table.override?.content) { for (var key of Object.keys(originalTableContent.columns)) { originalTableContent.columns[key] = originalTableContent.columns[key] || {}; originalTableContent.columns[key].indexGroups = originalTableContent.columns[key].indexGroups || []; if (!columns.includes(key)) { if ( originalTableContent.columns[key].indexGroups.includes(indexGroup) ) { hasChanged = true; console.log( `${chalk.red('✗')} Removing IndexGroup ${chalk.green(indexGroup)} from column ${chalk.green(key)}`, ); } originalTableContent.columns[key].indexGroups = originalTableContent.columns[key].indexGroups.filter( (g) => g !== indexGroup, ); } else { if ( !originalTableContent.columns[key].indexGroups.includes(indexGroup) ) { hasChanged = true; console.log( `${chalk.green('✓')} Adding IndexGroup ${chalk.green(indexGroup)} to column ${chalk.green(key)}`, ); originalTableContent.columns[key].indexGroups.push(indexGroup); } } } if (!hasChanged) { console.log(`${chalk.green('✓')} Nothing has changed...`); } return { ...originalTableContent, filePath: table.filePath }; } else { var allColumns = [ ...Object.keys(childTableContent.columns), ...Object.keys(originalTableContent.columns), ]; allColumns = [...new Set(allColumns)]; for (var key of allColumns) { childTableContent.columns[key] = childTableContent.columns[key] || {}; childTableContent.columns[key].indexGroups = childTableContent.columns[key].indexGroups || []; if (!columns.includes(key)) { if ( childTableContent.columns[key].indexGroups.includes(indexGroup) || originalTableContent.columns[key]?.indexGroups?.includes(indexGroup) ) { hasChanged = true; console.log( `${chalk.red('✗')} Removing IndexGroup ${chalk.green(indexGroup)} from column ${chalk.green(key)}`, ); childTableContent.columns[key].indexGroups = childTableContent.columns[key].indexGroups.filter( (g) => g !== indexGroup, ); childTableContent.columns[key].indexGroups.push( `!!_remove_(${indexGroup})`, ); } } else { if (!childTableContent.columns[key].indexGroups.includes(indexGroup)) { hasChanged = true; console.log( `${chalk.green('✓')} Adding IndexGroup ${chalk.green(indexGroup)} to column ${chalk.green(key)}`, ); childTableContent.columns[key].indexGroups = childTableContent.columns[key].indexGroups.filter( (g) => g !== `!!_remove_(${indexGroup})`, ); childTableContent.columns[key].indexGroups.push(indexGroup); } } } if (!hasChanged) { console.log(`${chalk.green('✓')} Nothing has changed...`); } return { ...childTableContent, filePath: table.override.filePath }; } } export function selectTableColumnsVisibility( table: IEntitySearchResult, columns: string[], ) { var tableContent = table.override?.content ? { ...table.override.content } : { ...table.content }; var hasChanged = false; var allColumns = [...Object.keys(tableContent.columns), ...columns]; allColumns = [...new Set(allColumns)]; for (var i = 0; i < allColumns.length; i++) { var key = allColumns[i]; var visibility = columns.find((c) => c === key) ? 'Visible' : 0; if (visibility === 0 && tableContent.columns[key].visibility !== 0) { hasChanged = true; console.log(`${chalk.red('✗')} Hiding column ${chalk.green(key)}`); } else if ( visibility === 'Visible' && tableContent.columns[key].visibility === 0 ) { hasChanged = true; console.log(`${chalk.green('✓')} Showing column ${chalk.green(key)}`); } tableContent.columns[key] = tableContent.columns[key] || {}; tableContent.columns[key].visibility = columns.find((c) => c === key) ? 1 : 0; } if (!hasChanged) { console.log(`${chalk.green('✓')} Nothing has changed...`); } return { ...tableContent, filePath: table.override?.content ? table.override.filePath : table.filePath, }; } export function setTableColumnsPosition( table: IEntitySearchResult, positions: Record, ) { var tableContent = table.override?.content ? { ...table.override.content } : { ...table.content }; var hasChanged = false; for (var key of Object.keys(tableContent.columns)) { tableContent.columns[key] = tableContent.columns[key] || {}; var newPosition = positions[key]; var oldPosition = tableContent.columns[key].position; if (newPosition !== undefined && oldPosition !== newPosition) { hasChanged = true; console.log( `${chalk.green('✓')} Setting position ${chalk.yellow(String(newPosition))} for column ${chalk.green(key)}`, ); tableContent.columns[key].position = newPosition; } else if (newPosition === undefined && oldPosition !== undefined) { hasChanged = true; console.log( `${chalk.red('✗')} Removing position from column ${chalk.green(key)}`, ); delete tableContent.columns[key].position; } } if (!hasChanged) { console.log(`${chalk.green('✓')} Nothing has changed...`); } return { ...tableContent, filePath: table.override?.content ? table.override.filePath : table.filePath, }; } export interface IIndexGroupTemplate { type: | 'COMMON' | 'UNIQUE' | 'HASH' | 'PRIMARY' | 'SOURCE_ID' | 'TRACK_FOR_CHANGES' | 'KEY'; includeColumns: string[]; } export interface ITableColumnTemplate { name?: string; description?: string; visibility: 'Visible' | 'Hidden'; dataType: 'String' | 'Numeric' | 'DateTime' | 'Boolean'; // DataType.String dbType: string; indexGroups?: string[]; // IndexGroups indexGroupOptions?: Dictionary<{ index: number; indexGroupOrder: 'ASC' | 'DESC'; }>; defaultValue?: string; // DefaultValue width?: number; // Width position?: number; // Position } export interface ITableTemplate { id: string; name: string; description: string; columns: Dictionary; indexGroups: Dictionary; } export async function createTable( tableName: string, tableNamePrefix: string = '', ): Promise<{ filePath: string; content: ITableTemplate } | null> { const entityType = 'table'; const entityTypeDisplayName = normalizeName(entityType); if (!tableName) { let { name } = await inquirer.prompt([ { type: 'input', name: 'name', message: `ⓘ Enter the ${entityTypeDisplayName.displayName} name:`, validate: (input) => input.trim() !== '' || chalk.red(`✗ ${entityTypeDisplayName.displayName} name is required.`), }, ]); if (!name) { console.error( chalk.red(`✗ ${entityTypeDisplayName.displayName} name is required.`), ); return null; } tableName = name; } var normalizedTableName = normalizeName(tableName); var normalizedTableNamePrefix = normalizeName(tableNamePrefix); var baseNamePrefix = normalizedTableNamePrefix.underscoreDisplayName ? `${normalizedTableNamePrefix.underscoreDisplayName}__` : ''; var fileName = `${baseNamePrefix}${normalizedTableName.underscoreDisplayName}_${entityTypeDisplayName.underscoreDisplayName}.yaml`; var filePath = path.join(getProjectPath(), 'objects', fileName); if (fs.existsSync(filePath)) { console.error( chalk.red( `✗ ${entityTypeDisplayName.displayName} already exists at ${chalk.white(filePath)}`, ), ); return null; } var tableTemplate: ITableTemplate = { id: createVariable( `${baseNamePrefix}${normalizedTableName.underscoreDisplayName}_${entityTypeDisplayName.underscoreDisplayName}_Id`, uuid(), ), name: `${tableNamePrefix} ${normalizedTableName.displayName} ${entityTypeDisplayName.displayName}`, description: '', columns: {}, indexGroups: {}, }; // 4. Write the task to file fs.writeFileSync( filePath, stringify( { tables: [tableTemplate] }, { keepSourceTokens: true, lineWidth: 0 }, ), ); console.log( chalk.green( `✓ ${entityTypeDisplayName.displayName} generated successfully at ${chalk.gray(filePath)}`, ), ); return { filePath: filePath, content: tableTemplate, }; }