Skip to content
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
81 changes: 79 additions & 2 deletions backend/src/entities/shared-jobs/shared-jobs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export class SharedJobsService {
countryCode: new Set<string>(),
email: new Set<string>(),
url: new Set<string>(),
rgbColor: new Set<string>(),
hexColor: new Set<string>(),
hslColor: new Set<string>(),
};

for (const column of columnsArray) {
Expand Down Expand Up @@ -95,7 +98,15 @@ export class SharedJobsService {
case sampleValues.every((value) => this.isValueURL(value)):
widgetColumnNames.url.add(column);
break;

case sampleValues.every((value) => this.isValueRgbColor(value)):
widgetColumnNames.rgbColor.add(column);
break;
case sampleValues.every((value) => this.isValueHexColor(value)):
widgetColumnNames.hexColor.add(column);
break;
case sampleValues.every((value) => this.isHslColor(value)):
widgetColumnNames.hslColor.add(column);
break;
default:
break;
}
Expand All @@ -106,8 +117,20 @@ export class SharedJobsService {
const countryCodeColumns = Array.from(widgetColumnNames.countryCode);
const emailColumns = Array.from(widgetColumnNames.email);
const urlColumns = Array.from(widgetColumnNames.url);
const rgbColorColumns = Array.from(widgetColumnNames.rgbColor);
const hexColorColumns = Array.from(widgetColumnNames.hexColor);
const hslColorColumns = Array.from(widgetColumnNames.hslColor);

const allColumns = [telephoneColumns, uuidColumns, countryCodeColumns, emailColumns, urlColumns];
const allColumns = [
telephoneColumns,
uuidColumns,
countryCodeColumns,
emailColumns,
urlColumns,
rgbColorColumns,
hexColorColumns,
hslColorColumns,
];
if (allColumns.every((columns) => columns.length === 0)) {
return;
}
Expand Down Expand Up @@ -172,13 +195,67 @@ export class SharedJobsService {
await this._dbContext.tableWidgetsRepository.save(newWidget);
}
}
if (rgbColorColumns.length) {
for (const column of rgbColorColumns) {
const newWidget = new TableWidgetEntity();
newWidget.field_name = column;
newWidget.widget_type = WidgetTypeEnum.Color;
newWidget.widget_options =
'// Optional: Specify output format for color values\n// Supported formats: "hex", "hex_hash" (default), "rgb", "hsl"\n// Example configuration:\n\n{\n "format": "rgb" // Will display colors as "#FF5733"\n}\n\n// Format options:\n// - "hex": Display as "FF5733" (no hash)\n// - "hex_hash": Display as "#FF5733" (default)\n// - "rgb": Display as "rgb(255, 87, 51)"\n// - "hsl": Display as "hsl(9, 100%, 60%)"';
newWidget.settings = savedTableSettings;
await this._dbContext.tableWidgetsRepository.save(newWidget);
}
}
if (hexColorColumns.length) {
for (const column of hexColorColumns) {
const newWidget = new TableWidgetEntity();
newWidget.field_name = column;
newWidget.widget_type = WidgetTypeEnum.Color;
newWidget.widget_options =
'// Optional: Specify output format for color values\n// Supported formats: "hex", "hex_hash" (default), "rgb", "hsl"\n// Example configuration:\n\n{\n "format": "hex_hash" // Will display colors as "#FF5733"\n}\n\n// Format options:\n// - "hex": Display as "FF5733" (no hash)\n// - "hex_hash": Display as "#FF5733" (default)\n// - "rgb": Display as "rgb(255, 87, 51)"\n// - "hsl": Display as "hsl(9, 100%, 60%)"';
newWidget.settings = savedTableSettings;
await this._dbContext.tableWidgetsRepository.save(newWidget);
}
}
if (hslColorColumns.length) {
for (const column of hslColorColumns) {
const newWidget = new TableWidgetEntity();
newWidget.field_name = column;
newWidget.widget_type = WidgetTypeEnum.Color;
newWidget.widget_options =
'// Optional: Specify output format for color values\n// Supported formats: "hex", "hex_hash" (default), "rgb", "hsl"\n// Example configuration:\n\n{\n "format": "hsl" // Will display colors as "#FF5733"\n}\n\n// Format options:\n// - "hex": Display as "FF5733" (no hash)\n// - "hex_hash": Display as "#FF5733" (default)\n// - "rgb": Display as "rgb(255, 87, 51)"\n// - "hsl": Display as "hsl(9, 100%, 60%)"';
newWidget.settings = savedTableSettings;
await this._dbContext.tableWidgetsRepository.save(newWidget);
}
}
}
}

private isValueTelephoneNumber(value: unknown): boolean {
return ValidationHelper.isValidPhoneNumber(String(value));
}

private isValueRgbColor(value: unknown): boolean {
if (typeof value !== 'string') {
return false;
}
return ValidationHelper.isValidRgbColor(value);
}

private isValueHexColor(value: unknown): boolean {
if (typeof value !== 'string') {
return false;
}
return ValidationHelper.isValidHexColor(value);
}

private isHslColor(value: unknown): boolean {
if (typeof value !== 'string') {
return false;
}
return ValidationHelper.isValidHslColor(value);
}

private isValueUUID(value: unknown): boolean {
if (typeof value !== 'string') {
return false;
Expand Down
12 changes: 12 additions & 0 deletions backend/src/helpers/validators/validation-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ export class ValidationHelper {
return validator.isMobilePhone(phoneNumber, 'any', { strictMode: false });
}

public static isValidRgbColor(rgbColor: string): boolean {
return validator.isRgbColor(rgbColor);
}

public static isValidHexColor(hexColor: string): boolean {
return validator.isHexColor(hexColor);
}

public static isValidHslColor(hslColor: string): boolean {
return validator.isHSL(hslColor);
}

public static isValidJSON(jsonString: string): boolean {
if (typeof jsonString !== 'string') {
return false;
Expand Down
93 changes: 48 additions & 45 deletions backend/test/ava-tests/saas-tests/table-widgets-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,57 +97,60 @@ test.serial(`${currentTest} should return empty array, table widgets not created
t.is(getTableWidgetsRO.length, 2);
});

test.serial(`${currentTest} should return automatically created widgets array, if table contains specific data`, async (t) => {
const connectionToTestDB = getTestData(mockFactory).connectionToPostgres;
const firstUserToken = (await registerUserAndReturnUserInfo(app)).token;
const { testTableName } = await createTestTable(connectionToTestDB, undefined, undefined, false, true);
const createdConnection = await request(app.getHttpServer())
.post('/connection')
.send(connectionToTestDB)
.set('Cookie', firstUserToken)
.set('masterpwd', 'ahalaimahalai')
.set('Content-Type', 'application/json')
.set('Accept', 'application/json');
test.serial(
`${currentTest} should return automatically created widgets array, if table contains specific data`,
async (t) => {
const connectionToTestDB = getTestData(mockFactory).connectionToPostgres;
const firstUserToken = (await registerUserAndReturnUserInfo(app)).token;
const { testTableName } = await createTestTable(connectionToTestDB, undefined, undefined, false, true);
const createdConnection = await request(app.getHttpServer())
.post('/connection')
.send(connectionToTestDB)
.set('Cookie', firstUserToken)
.set('masterpwd', 'ahalaimahalai')
.set('Content-Type', 'application/json')
.set('Accept', 'application/json');

t.is(createdConnection.status, 201);
const connectionId = JSON.parse(createdConnection.text).id;
t.is(createdConnection.status, 201);
const connectionId = JSON.parse(createdConnection.text).id;

// wait 3 seconds to let the system create widgets automatically in the background
// wait 3 seconds to let the system create widgets automatically in the background

await new Promise((resolve) => setTimeout(resolve, 3000));
await new Promise((resolve) => setTimeout(resolve, 3000));

const getTableWidgets = await request(app.getHttpServer())
.get(`/widgets/${connectionId}?tableName=${testTableName}`)
.set('Content-Type', 'application/json')
.set('Cookie', firstUserToken)
.set('Accept', 'application/json');
const getTableWidgets = await request(app.getHttpServer())
.get(`/widgets/${connectionId}?tableName=${testTableName}`)
.set('Content-Type', 'application/json')
.set('Cookie', firstUserToken)
.set('Accept', 'application/json');

const foundRows = await request(app.getHttpServer())
.get(`/table/rows/${connectionId}?tableName=${testTableName}`)
.set('Cookie', firstUserToken)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json');
t.is(foundRows.status, 200);
const foundRowsRO = JSON.parse(foundRows.text);
console.log('🚀 ~ foundRowsRO:', foundRowsRO);
const foundRows = await request(app.getHttpServer())
.get(`/table/rows/${connectionId}?tableName=${testTableName}`)
.set('Cookie', firstUserToken)
.set('Content-Type', 'application/json')
.set('Accept', 'application/json');
t.is(foundRows.status, 200);
const foundRowsRO = JSON.parse(foundRows.text);

const getTableWidgetsRO = JSON.parse(getTableWidgets.text);
t.is(typeof getTableWidgetsRO, 'object');
t.is(getTableWidgets.status, 200);
console.log('🚀 ~ getTableWidgetsRO:', getTableWidgetsRO);
t.is(getTableWidgetsRO.length, 5);
const phoneWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.Phone);
t.truthy(phoneWidget);
const uuidWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.UUID);
t.truthy(uuidWidget);
const countryCodeWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.Country);
t.truthy(countryCodeWidget);
const emailWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.String);
t.truthy(emailWidget);
t.is(emailWidget.widget_params.includes('"validate": "isEmail"'), true);
const urlWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.URL);
t.truthy(urlWidget);
});
const getTableWidgetsRO = JSON.parse(getTableWidgets.text);
t.is(typeof getTableWidgetsRO, 'object');
t.is(getTableWidgets.status, 200);
t.is(getTableWidgetsRO.length, 8);
const phoneWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.Phone);
t.truthy(phoneWidget);
const uuidWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.UUID);
t.truthy(uuidWidget);
const countryCodeWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.Country);
t.truthy(countryCodeWidget);
const emailWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.String);
t.truthy(emailWidget);
t.is(emailWidget.widget_params.includes('"validate": "isEmail"'), true);
const urlWidget = getTableWidgetsRO.find((w) => w.widget_type === WidgetTypeEnum.URL);
t.truthy(urlWidget);
const foundColorWIdgets = getTableWidgetsRO.filter((w) => w.widget_type === WidgetTypeEnum.Color);
t.is(foundColorWIdgets.length, 3);
},
);

test.serial(`${currentTest} should return array of table widgets for table`, async (t) => {
const { token } = await registerUserAndReturnUserInfo(app);
Expand Down
17 changes: 14 additions & 3 deletions backend/test/utils/create-test-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,23 @@ export async function createTestTable(
});
}

// telephone
// uuid
// countryCode
// telephoneColumns,
// uuidColumns,
// countryCodeColumns,
// urlColumns,
// rgbColorColumns,
// hexColorColumns,
// hslColorColumns,
// email columns already exists as some of the test columns
if (withWidgetsData) {
await Knex.schema.table(testTableName, function (table) {
table.string('telephone');
table.string('uuid');
table.string('countryCode');
table.string('url');
table.string('rgbColor');
table.string('hexColor');
table.string('hslColor');
});
}

Expand All @@ -87,6 +95,9 @@ export async function createTestTable(
widgetsDataObject.uuid = faker.string.uuid();
widgetsDataObject.countryCode = faker.location.countryCode();
widgetsDataObject.url = faker.internet.url();
widgetsDataObject.rgbColor = faker.color.rgb();
widgetsDataObject.hexColor = faker.color.rgb({ format: 'hex', casing: 'lower' });
widgetsDataObject.hslColor = faker.color.hsl({ format: 'css' });
}
if (i === 0 || i === testEntitiesSeedsCount - 21 || i === testEntitiesSeedsCount - 5) {
await Knex(testTableName).insert({
Expand Down
Loading