ToolJet/server/src/controllers/data_queries.controller.ts
Johnson Cherian 55cdc7a0b5
Query manager revamp (#6680)
* global store init

* Moved query data to new component

* Removed unwanted code

* Removed data queries prop drilling

* Moved query state out of editor

* Added unsafe to componentWillReceiveProps

* Selected first query when the version is changed

* Fixed bug on renaming query

* Fixed issue on dark theme

* Fixed running query on page load in viewer

* Query manager refactor init

* Added global data source in store

* Disabled devtools on production

* Fixed bug on selecting query after deletion

* Reset store when editor is loaded

* Moved query manager to functional component

* Fixed conflict issues

* Fixed infinite loop on tooljetDB

* Set the store name and updated devtools logic

* Fixed issue on displaying draft query from data sources

* Updated comments on the store

* Fixed bug on changing data source and creating query from data source

* Fixed bug on showing unsaved changes popup

* Fixed issue on showing confirmation modal everytime without any changes

* feat: autosave data query functionality

* feat: show publish button only when the status in draft state

* Fixed issues on query renaming

* feat: removed discard popup for data query create/edit widget

* stye: reduced autosave api call timeout and added draft tag

* feat: added minor style changes

* feat: fixed issues with restapi plugin, removed unused api calls

* fix: fixed issue that breaks restapi creation

* fix: reload selected query details after update query

* perf: reduced debounce time for data query update apis

* feat: removed full reloading of query list on query renaming

* feat: duplicate data query feature added

* Fixed issue on creating restAPI query

* fix: fixed issue in transforming response from update queyr api

* fix: refresh selected query details when the selected query is updated

* fix: rename query on click enter

* fix: full refresh of query list on update

* fix: style changes

* fix: subscribing to state to autsave

* feat: updated the query manager styles to new design

* feat: revamped the querypane header buttons

* fix: fixed the padding for query panel maximize button

* feat: updated search box style

* refactor: moved function to render data source icon to its own component

* fix: fixed querymanager widget breaking issue

* merged with feat/query-manager-autosave

* refactor: removed unused consoles

* refactor: removed unused consoles

* refactor: removed unused consoles

* fix: removed commented code

* fix: removed unused code

* refactor: removed unused comments

* fix: show change datasource select only if valid ds available

* Update frontend/src/Editor/Inspector/EventManager.jsx

Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com>

* Update frontend/src/Editor/QueryManager/Components/DataSourceLister.jsx

Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com>

* Update frontend/src/Editor/QueryManager/Components/DataSourceLister.jsx

Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com>

* Update frontend/src/Editor/QueryManager/Components/QueryManagerBody.jsx

Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com>

* feat: modify behaviour of search icon in query panel

* fix: fixed theme color mismatch in query manager

* refactor: remove dead code

* refactor: updated theme for data source listner

* fix: theming in filter and sort popup

* refactor: remove unused variables

* fix: removed draftQuery logic from query manager

* refactor: removed unused varibales

* Update frontend/src/Editor/QueryManager/QueryEditors/Restapi/TabParams.jsx

Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com>

* Update frontend/src/Editor/QueryPanel/QueryCard.jsx

Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com>

* feat: diable preview for draft queries

* fix: added tooltip for query panel button

* fix: fixed issues in saving query manager events

* fix: moved query save subscriber to QuerPanel component

* feat: converted query run api to save and run

* fix: made varibale an optional param in updateDataQuery dto

* refactor: cleanup update dataquery status api response

* refactor: moved query status to constants file

* feat: prompt for queryname when creating new query

* fix: store new queryname in state on create query pageload

* fix: fixed alignment of Tooljet db component form

* fix: correct translation and format file

* refactor: removed consoles

* merge: merge appbuilder-1.2

* style: updated rename input/button UX

* style: revamped dataquery create widget styles

* style: revamped data source selector styles

* fix: removed code added for debugging

* style: updated data query filter design

* style: Add prop to control visibility of clear button in search box

* style: implement new style for query filter

* merge appbuilder-1.2 to feat/query-manager-sort-filter

* refactor: remove unintended file change

* fix: set default value for method in respapi

* style: updated copilot info popup style

* style: updated quer panel header icons

* style: updated button styles

* style: fixed query manager button styles

* style: smoothened query preview modal view

* fix: correct import for some funs

* fix: fixed minor UX bugs

* style: fixed styling of REST api GDS

* style: fixed styleing of sort and filter popup

* style: improved data queries sort filter UI/UX

* fix: remove click listner when overlay is closed

* fix: moved component declaration out of parent component

* fix: set selected datasource for default sources

* fix: filter DS based on saerch in create dropdown

* fix: restrict draft query running to preview mode

* fix: query renamed on input change in create screen

* fix: set name to state as soon as user renames query

* fix: make query notification message consistent

* style: correct s3 bucket plugin layout config

* fix: fixed issues with cloning of Static DS queries

* fix: made change so that newly created query is reflected immediatly

* style: updated spacing for query manager components

* fix: hide rename input when no query selected

* fix: check bothe selected query and DS before rendering query manager

* fix: set isSaving to true only for api calls in querymanager

* fix: added success message form in qm

* fix: filter out draft queries from viewer on running

* fix: fixed inconsistent gutter for runpy and runjs editors

* fix: reload dataqueris on LDS deletion

* fix: redesigned filter/sort popup

* fix: fixed issue that resets filter on search

* fix: fixed query manager breaking on plugin select

* fix: diable json preview for text output

* fix: reset to filter and sort main menu on close filter popup

* refactor: rename varibales

* stye: redesigned query create panel

* feat: revert data query status column from backend

* style: redesign query picker section

* refactor: removed dead code

* style: querypanel expand/collapse btn style

* style: add query select and query filter popup style redesign

* style: updated filter popup style

* feat: removed draft query checks everywhere

* style: empty dataqueries style changed

* style: updated query selector popup and rest options styles

* style: removed 100% height to query option remove btn

* feat: added the query runnable status check

* style: updated query manager footer style

* feat: changed DS filter from kind to DS ID

* style: minor ui tweaks in filter popup

* style: disable DS filter if no DQs created

* style: minor ui change

* fix: rerender filter popup post DS api call. fixed rest api copy feature

* fix: add local DS to filter popup

* refactor: removed dead code/comments

* add new row is crashing when no data is fed to table (#7102)

* fix: fixed condition that blocked GDS run on load

* fix: revert name back to og name if update fails in rename query

* feat: added tooltip for show query btn

* fix: added click interaction for pill btn as well

* fix: minor UI tweaks to make UX better

* style: fixed the styling of filter popup

* style: minor UI tweaks in query filter popup

* fix: fixed minor css issue in ds picker

* style: wrap overflowing text in queryname

* fix: update updated_at after query update api call success

* fix: update remove the caller query from event query dropdown

* style: minor ui spacing tweaks

* fix: fix issue that cuased app crash when tjdb opened

* fix: fixed update row styles

* fix: fixed info popup dark theme bg

* fix: fixed headers styling according to general QM styles

* style: fixed stripe QM UI

* fix: added tooltip for quernames

* feat: add tooltip for select ds options

* added consoles to debug debugger issue

* fix: fixed :active style of ds select dropdown in QM

* fix: fixed DS kind name in data source selector in QM

* fix: fixed border color mismatch for ds select dd

* fix: change tooltip msg for maximize/minize QM

* Fix automation for query manager revamp. (#7223)

* Add data-cy to support modified specs

* Fix event handler

* Fix RunPy and RunJS specs

* Fix event handler label

* Fix basic components spec

* Fix basic components failure

* Fix tabel spec failure.

* Fix runjs and runpy actions

* Fix table column options

* Add data-cy

* version: version updated to 2.13.0

* Version bump

---------

Co-authored-by: Kavin Venkatachalam <kavin.saratha@gmail.com>
Co-authored-by: Kavin Venkatachalam <50441969+kavinvenkatachalam@users.noreply.github.com>
Co-authored-by: Manish Kushare <37823141+manishkushare@users.noreply.github.com>
Co-authored-by: Midhun Kumar E <midhun752@gmail.com>
2023-08-09 18:01:48 +05:30

293 lines
9.4 KiB
TypeScript

import {
Controller,
Get,
Param,
Body,
Post,
Patch,
Delete,
Query,
UseGuards,
ForbiddenException,
BadRequestException,
Put,
} from '@nestjs/common';
import { JwtAuthGuard } from '../../src/modules/auth/jwt-auth.guard';
import { decamelizeKeys } from 'humps';
import { DataQueriesService } from '../../src/services/data_queries.service';
import { DataSourcesService } from '../../src/services/data_sources.service';
import { QueryAuthGuard } from 'src/modules/auth/query-auth.guard';
import { AppsAbilityFactory } from 'src/modules/casl/abilities/apps-ability.factory';
import { AppsService } from '@services/apps.service';
import { CreateDataQueryDto, UpdateDataQueryDto } from '@dto/data-query.dto';
import { User } from 'src/decorators/user.decorator';
import { decode } from 'js-base64';
import { dbTransactionWrap } from 'src/helpers/utils.helper';
import { EntityManager } from 'typeorm';
import { DataSource } from 'src/entities/data_source.entity';
import { DataSourceScopes, DataSourceTypes } from 'src/helpers/data_source.constants';
import { App } from 'src/entities/app.entity';
import { isEmpty } from 'class-validator';
@Controller('data_queries')
export class DataQueriesController {
constructor(
private appsService: AppsService,
private dataQueriesService: DataQueriesService,
private dataSourcesService: DataSourcesService,
private appsAbilityFactory: AppsAbilityFactory
) {}
@UseGuards(JwtAuthGuard)
@Get()
async index(@User() user, @Query() query) {
const app = await this.appsService.findAppFromVersion(query.app_version_id);
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
if (!ability.can('getQueries', app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
const queries = await this.dataQueriesService.all(query);
const seralizedQueries = [];
// serialize
for (const query of queries) {
if (query.dataSource.type === DataSourceTypes.STATIC) {
delete query['dataSourceId'];
}
delete query['dataSource'];
const decamelizedQuery = decamelizeKeys(query);
decamelizedQuery['options'] = query.options;
if (query.plugin) {
decamelizedQuery['plugin'].manifest_file.data = JSON.parse(
decode(query.plugin.manifestFile.data.toString('utf8'))
);
decamelizedQuery['plugin'].icon_file.data = query.plugin.iconFile.data.toString('utf8');
}
seralizedQueries.push(decamelizedQuery);
}
const response = { data_queries: seralizedQueries };
return response;
}
@UseGuards(JwtAuthGuard)
@Post()
async create(@User() user, @Body() dataQueryDto: CreateDataQueryDto): Promise<object> {
const {
kind,
name,
options,
data_source_id: dataSourceId,
plugin_id: pluginId,
app_version_id: appVersionId,
} = dataQueryDto;
let dataSource: DataSource;
let app: App;
if (!dataSourceId && !(kind === 'restapi' || kind === 'runjs' || kind === 'tooljetdb' || kind === 'runpy')) {
throw new BadRequestException();
}
return dbTransactionWrap(async (manager: EntityManager) => {
if (!dataSourceId && (kind === 'restapi' || kind === 'runjs' || kind === 'tooljetdb' || kind === 'runpy')) {
dataSource = await this.dataSourcesService.findDefaultDataSource(
kind,
appVersionId,
pluginId,
user.organizationId,
manager
);
}
dataSource = await this.dataSourcesService.findOne(dataSource?.id || dataSourceId, manager);
if (dataSource.scope === DataSourceScopes.GLOBAL) {
app = await this.appsService.findAppFromVersion(appVersionId);
} else {
app = await this.dataSourcesService.findApp(dataSource?.id || dataSourceId, manager);
}
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
if (!ability.can('createQuery', app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
// todo: pass the whole dto instead of indv. values
const dataQuery = await this.dataQueriesService.create(
name,
options,
dataSource?.id || dataSourceId,
appVersionId,
manager
);
const decamelizedQuery = decamelizeKeys({ ...dataQuery, kind });
decamelizedQuery['options'] = dataQuery.options;
return decamelizedQuery;
});
}
@UseGuards(JwtAuthGuard)
@Patch(':id')
async update(@User() user, @Param('id') dataQueryId, @Body() updateDataQueryDto: UpdateDataQueryDto) {
const { name, options } = updateDataQueryDto;
const dataQuery = await this.dataQueriesService.findOne(dataQueryId);
const ability = await this.appsAbilityFactory.appsActions(user, dataQuery.app.id);
if (!ability.can('updateQuery', dataQuery.app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
const result = await this.dataQueriesService.update(dataQueryId, name, options);
const decamelizedQuery = decamelizeKeys({ ...dataQuery, ...result });
decamelizedQuery['options'] = result.options;
return decamelizedQuery;
}
@UseGuards(JwtAuthGuard)
@Delete(':id')
async delete(@User() user, @Param('id') dataQueryId) {
const dataQuery = await this.dataQueriesService.findOne(dataQueryId);
const ability = await this.appsAbilityFactory.appsActions(user, dataQuery.app.id);
if (!ability.can('deleteQuery', dataQuery.app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
const result = await this.dataQueriesService.delete(dataQueryId);
return decamelizeKeys(result);
}
@UseGuards(QueryAuthGuard)
@Post([':id/run/:environmentId', ':id/run'])
async runQuery(
@User() user,
@Param('id') dataQueryId,
@Param('environmentId') environmentId,
@Body() updateDataQueryDto: UpdateDataQueryDto
) {
const { options, resolvedOptions } = updateDataQueryDto;
const dataQuery = await this.dataQueriesService.findOne(dataQueryId);
if (user) {
const ability = await this.appsAbilityFactory.appsActions(user, dataQuery.app.id);
if (!ability.can('runQuery', dataQuery.app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
if (ability.can('updateQuery', dataQuery.app) && !isEmpty(options)) {
await this.dataQueriesService.update(dataQueryId, dataQuery.name, options);
dataQuery['options'] = options;
}
}
let result = {};
try {
result = await this.dataQueriesService.runQuery(user, dataQuery, resolvedOptions, environmentId);
} catch (error) {
if (error.constructor.name === 'QueryError') {
result = {
status: 'failed',
message: error.message,
description: error.description,
data: error.data,
};
} else {
console.log(error);
result = {
status: 'failed',
message: 'Internal server error',
description: error.message,
data: {},
};
}
}
return result;
}
@UseGuards(JwtAuthGuard)
@Post(['/preview/:environmentId', '/preview'])
async previewQuery(
@User() user,
@Body() updateDataQueryDto: UpdateDataQueryDto,
@Param('environmentId') environmentId
) {
const { options, query, app_version_id: appVersionId } = updateDataQueryDto;
const app = await this.appsService.findAppFromVersion(appVersionId);
if (!(query['data_source_id'] || appVersionId || environmentId)) {
throw new BadRequestException('Data source id or app version id or environment id is mandatory');
}
const kind = query ? query['kind'] : null;
const dataQueryEntity = {
...query,
app,
dataSource: query['data_source_id']
? await this.dataSourcesService.findOne(query['data_source_id'])
: await this.dataSourcesService.findDefaultDataSourceByKind(kind, appVersionId),
};
const ability = await this.appsAbilityFactory.appsActions(user, app.id);
if (!ability.can('previewQuery', app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
let result = {};
try {
result = await this.dataQueriesService.runQuery(user, dataQueryEntity, options, environmentId);
} catch (error) {
if (error.constructor.name === 'QueryError') {
result = {
status: 'failed',
message: error.message,
description: error.description,
data: error.data,
};
} else {
console.log(error);
result = {
status: 'failed',
message: 'Internal server error',
description: error.message,
data: {},
};
}
}
return result;
}
@UseGuards(JwtAuthGuard)
@Put(':id/data_source')
async changeQueryDataSource(@User() user, @Param('id') queryId, @Body() updateDataQueryDto: UpdateDataQueryDto) {
const { data_source_id: dataSourceId } = updateDataQueryDto;
const dataQuery = await this.dataQueriesService.findOne(queryId);
const ability = await this.appsAbilityFactory.appsActions(user, dataQuery.app.id);
if (!ability.can('updateQuery', dataQuery.app)) {
throw new ForbiddenException('you do not have permissions to perform this action');
}
await this.dataQueriesService.changeQueryDataSource(queryId, dataSourceId);
return;
}
}