From d627cd2cb46d9708f497374481c3ac4c61e9ff7c Mon Sep 17 00:00:00 2001 From: Adam Krol Date: Tue, 5 Mar 2024 12:37:11 +0100 Subject: [PATCH 1/9] layout endpoints in swagger --- src/swagger/dashboard-swagger.json | 183 +++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) diff --git a/src/swagger/dashboard-swagger.json b/src/swagger/dashboard-swagger.json index cb37c46..96fbc7c 100644 --- a/src/swagger/dashboard-swagger.json +++ b/src/swagger/dashboard-swagger.json @@ -422,6 +422,136 @@ } } } + }, + "/api/dashboard/{dashboardId}/layout": { + "post": { + "tags": ["Dashboard"], + "summary": "Add a layout item to a specific dashboard", + "description": "Endpoint for adding a layout item to a specific dashboard by its ID.", + "parameters": [ + { + "name": "dashboardId", + "in": "path", + "description": "Unique ID of the dashboard", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "layoutItemData", + "description": "Layout item data to add", + "required": true, + "schema": { + "$ref": "#/definitions/LayoutItemInput" + } + } + ], + "responses": { + "201": { + "description": "Layout item added to dashboard" + }, + "500": { + "description": "Internal server error" + } + } + }, + "get": { + "tags": ["Dashboard"], + "summary": "Get layout items from a specific dashboard", + "description": "Endpoint for retrieving all layout items from a specific dashboard by its ID.", + "parameters": [ + { + "name": "dashboardId", + "in": "path", + "description": "Unique ID of the dashboard", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "List of layout items retrieved", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/LayoutItem" + } + } + }, + "500": { + "description": "Internal server error" + } + } + } + }, + "/api/dashboard/{dashboardId}/layout/{layoutItemId}": { + "put": { + "tags": ["Dashboard"], + "summary": "Update a layout item in a specific dashboard", + "description": "Endpoint for updating a layout item in a specific dashboard by its ID.", + "parameters": [ + { + "name": "dashboardId", + "in": "path", + "description": "Unique ID of the dashboard", + "required": true, + "type": "string" + }, + { + "name": "layoutItemId", + "in": "path", + "description": "Unique ID of the layout item to update", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "layoutItemData", + "description": "Updated layout item data", + "required": true, + "schema": { + "$ref": "#/definitions/LayoutItemInput" + } + } + ], + "responses": { + "200": { + "description": "Layout item updated in dashboard" + }, + "500": { + "description": "Internal server error" + } + } + }, + "delete": { + "tags": ["Dashboard"], + "summary": "Remove a layout item from a specific dashboard", + "description": "Endpoint for removing a layout item from a specific dashboard by its ID.", + "parameters": [ + { + "name": "dashboardId", + "in": "path", + "description": "Unique ID of the dashboard", + "required": true, + "type": "string" + }, + { + "name": "layoutItemId", + "in": "path", + "description": "Unique ID of the layout item to remove", + "required": true, + "type": "string" + } + ], + "responses": { + "202": { + "description": "Layout item removed from dashboard" + }, + "500": { + "description": "Internal server error" + } + } + } } }, "definitions": { @@ -657,6 +787,59 @@ "enum": ["date_picker", "select", "multiselect", "checkbox", "radio"] } } + }, + "LayoutItem": { + "type": "object", + "required": ["elementId", "x", "y", "w", "h"], + "properties": { + "elementId": { + "type": "string" + }, + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "w": { + "type": "number" + }, + "h": { + "type": "number" + }, + "static": { + "type": "boolean" + } + } + }, + "LayoutItemInput": { + "type": "object", + "properties": { + "elementId": { + "type": "string", + "description": "Unique ID of the element associated with this layout item (if applicable)" + }, + "x": { + "type": "number", + "description": "The x coordinate of the layout item on the dashboard grid" + }, + "y": { + "type": "number", + "description": "The y coordinate of the layout item on the dashboard grid" + }, + "w": { + "type": "number", + "description": "The width of the layout item on the dashboard grid" + }, + "h": { + "type": "number", + "description": "The height of the layout item on the dashboard grid" + }, + "static": { + "type": "boolean", + "description": "Indicates whether the layout item is static (not movable or resizable)" + } + } } } } From 605675dc2035a64a3e96431820987b9f57718476 Mon Sep 17 00:00:00 2001 From: Adam Krol Date: Wed, 6 Mar 2024 13:28:32 +0100 Subject: [PATCH 2/9] elements can be specified while dashboard is created --- .../dashboard/dashboard.controller.ts | 1 - .../dashboard/dashboard.interface.ts | 1 + src/components/dashboard/dashboard.service.ts | 31 +++++++++++++------ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/components/dashboard/dashboard.controller.ts b/src/components/dashboard/dashboard.controller.ts index 0e9cef0..2ccd044 100644 --- a/src/components/dashboard/dashboard.controller.ts +++ b/src/components/dashboard/dashboard.controller.ts @@ -54,7 +54,6 @@ const createDashboard = async (req: Request, res: Response) => { res.status(httpStatus.INTERNAL_SERVER_ERROR).send({ message: err.message }); } }; -// const getAllDashboards = async (req: Request, res: Response) => { try { diff --git a/src/components/dashboard/dashboard.interface.ts b/src/components/dashboard/dashboard.interface.ts index 86a9e71..b1118c2 100644 --- a/src/components/dashboard/dashboard.interface.ts +++ b/src/components/dashboard/dashboard.interface.ts @@ -38,6 +38,7 @@ interface IWriteDashboard extends Document { title?: string; theme?: ITheme; layout?: ILayoutItem[]; + elements?: IDashboardElement[]; } export { IDashboard, ILayoutItem, ITheme, IWriteDashboard }; diff --git a/src/components/dashboard/dashboard.service.ts b/src/components/dashboard/dashboard.service.ts index 9608a04..d3249bc 100644 --- a/src/components/dashboard/dashboard.service.ts +++ b/src/components/dashboard/dashboard.service.ts @@ -13,15 +13,28 @@ import { IDashboardElement } from '@components/dashboard/dashboardElement/dashbo import { IDashboardFilter } from './dashboardFilter/dashboardFilter.interface'; const create = async (dashboardData: IWriteDashboard): Promise => { - logger.info(`creating dashboard with data ${JSON.stringify(dashboardData)}`); - const newDashboard = await DashboardModel.create({ - elements: [], - layout: [], - filters: [], - ...dashboardData, - }); - logger.info(`Dashboard created: %O`, newDashboard); - return newDashboard; + logger.info(`Creating dashboard with data: ${JSON.stringify(dashboardData)}`); + + try { + const elementIds = await Promise.all( + dashboardData.elements.map(async (element) => { + const createdElement = + await dashboardElementService.createDashboardElement(element); + return createdElement._id; + }), + ); + + const newDashboard = await DashboardModel.create({ + ...dashboardData, + elements: elementIds, + }); + + logger.info(`Dashboard created successfully: %O`, newDashboard); + return newDashboard; + } catch (error) { + logger.error(`Error creating dashboard: %O`, error); + throw error; + } }; const read = async ( From 1a236f91370dfb3d527fa5f5752b39d54605ba29 Mon Sep 17 00:00:00 2001 From: rrozek Date: Wed, 6 Mar 2024 23:53:03 +0100 Subject: [PATCH 3/9] handle dashboard update properly (still without filters) --- src/components/dashboard/dashboard.model.ts | 2 +- src/components/dashboard/dashboard.service.ts | 64 +++++++++++++++++-- 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/src/components/dashboard/dashboard.model.ts b/src/components/dashboard/dashboard.model.ts index 925c43d..0742de5 100644 --- a/src/components/dashboard/dashboard.model.ts +++ b/src/components/dashboard/dashboard.model.ts @@ -32,7 +32,7 @@ const themeSchema = new mongoose.Schema({ const dashboardSchema = new mongoose.Schema({ title: { type: String, required: true }, elements: [{ type: mongoose.Schema.Types.ObjectId, ref: 'DashboardElement' }], - filters: [{type: mongoose.Schema.Types.ObjectId, ref: 'DashboardFilter'}], + filters: [{ type: mongoose.Schema.Types.ObjectId, ref: 'DashboardFilter' }], layout: [layoutSchema], theme: themeSchema, }); diff --git a/src/components/dashboard/dashboard.service.ts b/src/components/dashboard/dashboard.service.ts index d3249bc..51ee8fa 100644 --- a/src/components/dashboard/dashboard.service.ts +++ b/src/components/dashboard/dashboard.service.ts @@ -84,18 +84,68 @@ const getAll = async (): Promise => { const update = async ( dashboardId: string, - dashboard: IWriteDashboard, + dashboardData: IWriteDashboard, ): Promise => { - logger.debug('log dashboard', dashboard); - const updatedDashboard = await DashboardModel.findOneAndUpdate( - { _id: dashboardId }, - dashboard, + // Read existing dashboard data from the database + const existingDashboard = await read(dashboardId); + + if (!existingDashboard) { + throw new Error('Dashboard not found'); + } + + const updatedElementsIds = await Promise.all( + dashboardData.elements.map(async (element) => { + logger.info(`${element.id}`); + const existingElement = existingDashboard.elements.find( + (el) => el.id === element.id, + ); + + if (existingElement) { + await dashboardElementService.updateDashboardElement( + existingElement._id, + element, + ); + return existingElement._id; // Return the ID of existing element + } else { + const newElement = await dashboardElementService.createDashboardElement( + element, + ); + return newElement._id; // Return the ID of newly created element + } + }), + ); + + // Remove missing elements + const removedElementsIds = existingDashboard.elements + .filter( + (element) => + !dashboardData.elements.some( + (reqElement) => reqElement.id === element.id, + ), + ) + .map((element) => element._id); + + await Promise.all( + removedElementsIds.map(async (elementId) => { + await dashboardElementService.deleteDashboardElement(elementId); + return elementId; // Return the ID of removed element + }), + ); + + logger.info(`removedElementsIds ${removedElementsIds}`); + logger.info(`updatedElementsIds ${updatedElementsIds}`); + // Update the dashboard + const updatedDashboard = await DashboardModel.findByIdAndUpdate( + dashboardId, + { + ...dashboardData, + elements: updatedElementsIds, + }, { new: true }, ); - logger.debug(`log updatedDashboard', ${updatedDashboard}`); if (!updatedDashboard) { - throw new Error('Dashboard not found'); + throw new Error('Failed to update dashboard'); } logger.debug(`Dashboard updated: %O`, updatedDashboard); From a31cdd5622ac0098d18232796a9e1a12413984c2 Mon Sep 17 00:00:00 2001 From: rrozek Date: Mon, 18 Mar 2024 17:48:03 +0100 Subject: [PATCH 4/9] fix database info and groupby operation --- .../db-api/databaseInfo.controller.ts | 26 +++++++++++++------ src/components/db-api/databaseInfo.router.ts | 8 +++--- .../db-api/databaseInfo.validation.ts | 6 +++-- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/components/db-api/databaseInfo.controller.ts b/src/components/db-api/databaseInfo.controller.ts index 42b8242..0cd1139 100644 --- a/src/components/db-api/databaseInfo.controller.ts +++ b/src/components/db-api/databaseInfo.controller.ts @@ -20,7 +20,10 @@ export const getAllDatabases = async (req: Request, res: Response) => { export const getAllSchemas = async (req: Request, res: Response) => { try { - const response = await axios.get(`${API_BASE_URL}/schemas`); + const { dbname } = req.params; + const response = await axios.get( + `${API_BASE_URL}/database/${encodeURIComponent(dbname)}/schemas`, + ); res.status(httpStatus.OK).send({ data: response.data }); } catch (error) { console.error('Error fetching schemas:', error); @@ -32,7 +35,10 @@ export const getAllSchemas = async (req: Request, res: Response) => { export const getAllTables = async (req: Request, res: Response) => { try { - const response = await axios.get(`${API_BASE_URL}/tables`); + const { dbname } = req.params; + const response = await axios.get( + `${API_BASE_URL}/database/${encodeURIComponent(dbname)}/tables`, + ); res.status(httpStatus.OK).send({ data: response.data }); } catch (error) { console.error('Error fetching tables:', error); @@ -44,11 +50,13 @@ export const getAllTables = async (req: Request, res: Response) => { export const getTableColumns = async (req: Request, res: Response) => { try { - const { schemaName, tableName } = req.params; + const { dbname, schema, table } = req.params; const response = await axios.get( - `${API_BASE_URL}/tables/${encodeURIComponent( - schemaName, - )}/${encodeURIComponent(tableName)}/columns`, + `${API_BASE_URL}/database/${encodeURIComponent( + dbname, + )}/tables/${encodeURIComponent(schema)}/${encodeURIComponent( + table, + )}/columns`, ); const processedColumns = response.data.map((column) => { @@ -89,7 +97,7 @@ export const getTableColumns = async (req: Request, res: Response) => { export const generateChartData = async (req: Request, res: Response) => { try { - const { schema, table } = req.params; + const { dbname, schema, table } = req.params; const { dimension, measures, differential, filters } = req.body; logger.debug('measures', measures); @@ -107,7 +115,9 @@ export const generateChartData = async (req: Request, res: Response) => { parsedFilters, }); - const url = `${API_BASE_URL}/group-by-operation/${schema}/${table}`; + const url = `${API_BASE_URL}/group-by-operation/${encodeURIComponent( + dbname, + )}/${encodeURIComponent(schema)}/${encodeURIComponent(table)}`; const response = await axios.post(url, payload); const mappedData = chartDataGenerator.formatChartDataResponse( diff --git a/src/components/db-api/databaseInfo.router.ts b/src/components/db-api/databaseInfo.router.ts index f8a53f4..0ddfee7 100644 --- a/src/components/db-api/databaseInfo.router.ts +++ b/src/components/db-api/databaseInfo.router.ts @@ -15,15 +15,15 @@ import { const router: Router = Router(); router.get('/database-info/databases', getAllDatabases); -router.get('/database-info/schemas', getAllSchemas); -router.get('/database-info/tables', getAllTables); +router.get('/database-info/:dbname/schemas', getAllSchemas); +router.get('/database-info/:dbname/tables', getAllTables); router.get( - '/database-info/tables/:schemaName/:tableName/columns', + '/database-info/:dbname/tables/:schema/:table/columns', validate(getTableColumnsValidation), getTableColumns, ); router.post( - '/database-info/generate-chart-data/:schema/:table', + '/database-info/generate-chart-data/:dbname/:schema/:table', validate(generateChartDataValidation), generateChartData, ); diff --git a/src/components/db-api/databaseInfo.validation.ts b/src/components/db-api/databaseInfo.validation.ts index 5759241..53c7225 100644 --- a/src/components/db-api/databaseInfo.validation.ts +++ b/src/components/db-api/databaseInfo.validation.ts @@ -3,8 +3,9 @@ import { ValidSchema } from '../../middleware/joiValidate'; export const getTableColumnsValidation: ValidSchema = { params: Joi.object({ - schemaName: Joi.string().required(), - tableName: Joi.string().required(), + dbname: Joi.string().required(), + schema: Joi.string().required(), + table: Joi.string().required(), }), }; @@ -28,6 +29,7 @@ const filterColumnSchema = Joi.object({ export const generateChartDataValidation: ValidSchema = { params: Joi.object({ + dbname: Joi.string().required(), schema: Joi.string().required(), table: Joi.string().required(), }), From 2d4c8df6cc8d681d9be686a16129e89d1ee8d9c7 Mon Sep 17 00:00:00 2001 From: rrozek Date: Wed, 20 Mar 2024 12:42:05 +0100 Subject: [PATCH 5/9] fix basicQuery payload --- .../dashboardElement/dashboardElement.interface.ts | 7 ++++++- .../dashboard/dashboardElement/dashboardElement.model.ts | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/components/dashboard/dashboardElement/dashboardElement.interface.ts b/src/components/dashboard/dashboardElement/dashboardElement.interface.ts index a6c07fc..2054b30 100644 --- a/src/components/dashboard/dashboardElement/dashboardElement.interface.ts +++ b/src/components/dashboard/dashboardElement/dashboardElement.interface.ts @@ -29,11 +29,16 @@ export interface IDashboardElementVis extends IDashboardElement { | 'pieChart'; } +export interface IDashboardElementBasicQueryMeasure { + columnName: string; + operator: string; +} + export interface IDashboardElementBasicQuery extends IDashboardElementVis { type: 'basicQuery'; dimension: string; differential?: string; - measures: string[]; + measures: IDashboardElementBasicQueryMeasure[]; } export interface IDashboardElementCustomQuery extends IDashboardElementVis { diff --git a/src/components/dashboard/dashboardElement/dashboardElement.model.ts b/src/components/dashboard/dashboardElement/dashboardElement.model.ts index 8a60ad4..dd588d0 100644 --- a/src/components/dashboard/dashboardElement/dashboardElement.model.ts +++ b/src/components/dashboard/dashboardElement/dashboardElement.model.ts @@ -30,7 +30,12 @@ const dashboardElementBasicQuerySchema = new mongoose.Schema({ dimension: { type: String }, differential: { type: String }, - measures: [{ type: String }], + measures: [ + { + columnName: { type: String }, + operator: { type: String }, + }, + ], visType: { type: String, required: true }, }); From 6babba83e183220ec0a6a540460ec55357767564 Mon Sep 17 00:00:00 2001 From: rrozek Date: Wed, 20 Mar 2024 15:00:04 +0100 Subject: [PATCH 6/9] add missing basicQuery properties --- .../dashboard/dashboardElement/dashboardElement.interface.ts | 3 +++ .../dashboard/dashboardElement/dashboardElement.model.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/components/dashboard/dashboardElement/dashboardElement.interface.ts b/src/components/dashboard/dashboardElement/dashboardElement.interface.ts index 2054b30..e5c380c 100644 --- a/src/components/dashboard/dashboardElement/dashboardElement.interface.ts +++ b/src/components/dashboard/dashboardElement/dashboardElement.interface.ts @@ -36,6 +36,9 @@ export interface IDashboardElementBasicQueryMeasure { export interface IDashboardElementBasicQuery extends IDashboardElementVis { type: 'basicQuery'; + dbname: string; + schema: string; + table: string; dimension: string; differential?: string; measures: IDashboardElementBasicQueryMeasure[]; diff --git a/src/components/dashboard/dashboardElement/dashboardElement.model.ts b/src/components/dashboard/dashboardElement/dashboardElement.model.ts index dd588d0..79798ec 100644 --- a/src/components/dashboard/dashboardElement/dashboardElement.model.ts +++ b/src/components/dashboard/dashboardElement/dashboardElement.model.ts @@ -28,6 +28,9 @@ const dashboardElementButtonSchema = const dashboardElementBasicQuerySchema = new mongoose.Schema({ + dbname: { type: String }, + schema: { type: String }, + table: { type: String }, dimension: { type: String }, differential: { type: String }, measures: [ From ce114e3fa3a9fc3fe0e28c50862ae457d85ea1d4 Mon Sep 17 00:00:00 2001 From: rrozek Date: Wed, 20 Mar 2024 17:35:17 +0100 Subject: [PATCH 7/9] dockerignore --- .dockerignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.dockerignore b/.dockerignore index 644fb82..db939a9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,7 @@ -**/node_modules/ -**/dist +node_modules +dist .git npm-debug.log .coverage .coverage.* -.aws \ No newline at end of file +.aws From 73065fe4417f00b8253e921a8e1d43c29039647f Mon Sep 17 00:00:00 2001 From: rrozek Date: Wed, 20 Mar 2024 17:47:04 +0100 Subject: [PATCH 8/9] update dockerfile --- Dockerfile | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/Dockerfile b/Dockerfile index 22cae1d..652ae16 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,13 @@ -# First stage: Build the application -FROM node:18-slim as builder +FROM node:18-alpine +RUN mkdir -p /usr/src/node-app && chown -R node:node /usr/src/node-app +WORKDIR /usr/src/node-app +USER node -WORKDIR /app +COPY --chown=node:node package*.json ./ -# Copy package.json and package-lock.json files first -COPY package*.json ./ - -# Install dependencies RUN npm install - -COPY . . - -# Build application +COPY --chown=node:node . . RUN npm run build - -# Second stage: Setup the production image -FROM node:16-alpine - -WORKDIR /app - -# Copy built assets from the builder stage to production image -COPY --from=builder /app/dist ./dist -COPY --from=builder /app/package.json ./package.json -COPY --from=builder /app/node_modules ./node_modules - -# Run the server in production mode -CMD npm run server:prod - EXPOSE 8080 +CMD ["npm", "run", "server:prod"] + From 152f3baf02f0456caa92d3a2b66f10b4332df015 Mon Sep 17 00:00:00 2001 From: rrozek Date: Thu, 21 Mar 2024 15:31:10 +0100 Subject: [PATCH 9/9] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0000b0d..7e9fbdb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dashboard-creator-server", - "version": "1.0.1", + "version": "2.0.0", "description": "", "main": "src/server.ts", "engines": {