Merge pull request #6552 from ToolJet/merge-back/2.6.1

Merge back/2.6.1
This commit is contained in:
Sherfin Shamsudeen 2023-05-26 14:01:48 +05:30 committed by GitHub
commit d24744e0cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 166 additions and 58 deletions

View file

@ -1 +1 @@
2.6.0 2.6.1

View file

@ -7,6 +7,7 @@ PG_HOST=__required__
PG_PASS=__required__ PG_PASS=__required__
PG_DB=tooljet_prod PG_DB=tooljet_prod
ORM_LOGGING=true ORM_LOGGING=true
NODE_ENV=production
DEPLOYMENT_PLATFORM=ec2 DEPLOYMENT_PLATFORM=ec2
# ToolJet Database # ToolJet Database

View file

@ -89,7 +89,9 @@ COPY --from=builder /app/server/dist ./app/server/dist
# Define non-sudo user # Define non-sudo user
RUN useradd --create-home appuser \ RUN useradd --create-home appuser \
&& chown -R appuser:appuser /app && chown -R appuser:0 /app \
&& chmod u+x /app \
&& chmod -R g=u /app
USER appuser USER appuser
WORKDIR /app WORKDIR /app

View file

@ -4,6 +4,7 @@ FROM tooljet/tooljet-ce:latest
COPY --from=postgrest/postgrest:v10.1.1.20221215 /bin/postgrest /bin COPY --from=postgrest/postgrest:v10.1.1.20221215 /bin/postgrest /bin
# Install Postgres # Install Postgres
USER root
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN echo "deb http://deb.debian.org/debian" RUN echo "deb http://deb.debian.org/debian"
@ -33,6 +34,7 @@ RUN echo "[supervisord] \n" \
# ENV defaults # ENV defaults
ENV TOOLJET_HOST=http://localhost \ ENV TOOLJET_HOST=http://localhost \
PORT=80 \ PORT=80 \
NODE_ENV=production \
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \ LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
SECRET_KEY_BASE=replace_with_secret_key_base \ SECRET_KEY_BASE=replace_with_secret_key_base \
PG_DB=tooljet_production \ PG_DB=tooljet_production \
@ -49,6 +51,7 @@ ENV TOOLJET_HOST=http://localhost \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \ PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
ORM_LOGGING=true \ ORM_LOGGING=true \
DEPLOYMENT_PLATFORM=docker:local \ DEPLOYMENT_PLATFORM=docker:local \
HOME=/home/appuser \
TERM=xterm TERM=xterm
# Prepare DB and start application # Prepare DB and start application

View file

@ -175,7 +175,7 @@ export function CodeHinter({
const getPreview = () => { const getPreview = () => {
if (!enablePreview) return; if (!enablePreview) return;
const customResolvables = getCustomResolvables(); const customResolvables = getCustomResolvables();
const [preview, error] = resolveReferences(currentValue, realState, null, customResolvables, true); const [preview, error] = resolveReferences(currentValue, realState, null, customResolvables, true, true);
const themeCls = darkMode ? 'bg-dark py-1' : 'bg-light py-1'; const themeCls = darkMode ? 'bg-dark py-1' : 'bg-light py-1';
if (error) { if (error) {

View file

@ -4,6 +4,7 @@ import { CodeHinter } from '../../../CodeBuilder/CodeHinter';
export const ProgramaticallyHandleToggleSwitch = ({ export const ProgramaticallyHandleToggleSwitch = ({
currentState, currentState,
darkMode, darkMode,
// eslint-disable-next-line no-unused-vars
label, label,
index, index,
callbackFunction, callbackFunction,
@ -18,28 +19,29 @@ export const ProgramaticallyHandleToggleSwitch = ({
const param = { name: property }; const param = { name: property };
const definition = { value, fxActive: props.fxActive }; const definition = { value, fxActive: props.fxActive };
const initialValue = definition?.value ?? `{{false}}`; const initialValue = definition?.value ?? `{{false}}`;
const options = {}; const options = {};
return ( return (
<CodeHinter <div className={`mb-2 field ${options.className}`} onClick={(e) => e.stopPropagation()}>
enablePreview={true} <CodeHinter
currentState={currentState} enablePreview={true}
initialValue={initialValue} currentState={currentState}
mode={options.mode} initialValue={initialValue}
theme={darkMode ? 'monokai' : options.theme} mode={options.mode}
lineWrapping={true} theme={darkMode ? 'monokai' : options.theme}
onChange={(value) => callbackFunction(index, property, value)} lineWrapping={true}
componentName={`widget/${component.name}::${label}`} onChange={(value) => callbackFunction(index, property, value)}
type={paramMeta.type} componentName={`widget/${component?.component?.name}::${param.name}`}
paramName={param.name} type={paramMeta.type}
paramLabel={paramMeta.displayName} paramName={param.name}
fieldMeta={paramMeta} paramLabel={paramMeta.displayName}
onFxPress={(active) => { fieldMeta={paramMeta}
callbackFunction(index, 'fxActive', active); onFxPress={(active) => {
}} callbackFunction(index, 'fxActive', active);
fxActive={props?.fxActive ?? false} }}
component={component} fxActive={props?.fxActive ?? false}
className="codehinter-default-input" component={component.component}
/> className={options.className}
/>
</div>
); );
}; };

View file

@ -777,10 +777,19 @@ export function getQueryVariables(options, state) {
switch (optionsType) { switch (optionsType) {
case 'string': { case 'string': {
options = options.replace(/\n/g, ' '); options = options.replace(/\n/g, ' ');
const dynamicVariables = getDynamicVariables(options) || []; // check if {{var}} and %%var%% are present in the string
dynamicVariables.forEach((variable) => {
queryVariables[variable] = resolveReferences(variable, state); if (options.includes('{{') && options.includes('%%')) {
}); const vars = resolveReferences(options, state);
console.log('queryVariables', { options, vars });
queryVariables[options] = vars;
} else {
const dynamicVariables = getDynamicVariables(options) || [];
dynamicVariables.forEach((variable) => {
queryVariables[variable] = resolveReferences(variable, state);
});
}
break; break;
} }
@ -800,6 +809,7 @@ export function getQueryVariables(options, state) {
default: default:
break; break;
} }
return queryVariables; return queryVariables;
} }

View file

@ -91,8 +91,62 @@ function resolveCode(code, state, customObjects = {}, withError = false, reserve
if (withError) return [result, error]; if (withError) return [result, error];
return result; return result;
} }
export function resolveString(str, state, customObjects, reservedKeyword, withError, forPreviewBox) {
let resolvedStr = str;
export function resolveReferences(object, state, defaultValue, customObjects = {}, withError = false) { // Resolve {{object}}
const codeRegex = /(\{\{.+?\}\})/g;
const codeMatches = resolvedStr.match(codeRegex);
if (codeMatches) {
codeMatches.forEach((codeMatch) => {
const code = codeMatch.replace('{{', '').replace('}}', '');
if (reservedKeyword.includes(code)) {
resolvedStr = resolvedStr.replace(codeMatch, '');
} else {
const resolvedCode = resolveCode(code, state, customObjects, withError, reservedKeyword, true);
if (forPreviewBox) {
resolvedStr = resolvedStr.replace(codeMatch, resolvedCode[0]);
} else {
resolvedStr = resolvedStr.replace(codeMatch, resolvedCode);
}
}
});
}
// Resolve %%object%%
const serverRegex = /(%%.+?%%)/g;
const serverMatches = resolvedStr.match(serverRegex);
if (serverMatches) {
serverMatches.forEach((serverMatch) => {
const code = serverMatch.replace(/%%/g, '');
if (code.includes('server.') && !/^server\.[A-Za-z0-9]+$/.test(code)) {
resolvedStr = resolvedStr.replace(serverMatch, '');
} else {
const resolvedCode = resolveCode(code, state, customObjects, withError, reservedKeyword, false);
if (forPreviewBox) {
resolvedStr = resolvedStr.replace(serverMatch, resolvedCode[0]);
} else {
resolvedStr = resolvedStr.replace(serverMatch, resolvedCode);
}
}
});
}
return resolvedStr;
}
export function resolveReferences(
object,
state,
defaultValue,
customObjects = {},
withError = false,
forPreviewBox = false
) {
if (object === '{{{}}}') return ''; if (object === '{{{}}}') return '';
const reservedKeyword = ['app']; //Keywords that slows down the app const reservedKeyword = ['app']; //Keywords that slows down the app
object = _.clone(object); object = _.clone(object);
@ -100,6 +154,10 @@ export function resolveReferences(object, state, defaultValue, customObjects = {
let error; let error;
switch (objectType) { switch (objectType) {
case 'string': { case 'string': {
if (object.includes('{{') && object.includes('}}') && object.includes('%%') && object.includes('%%')) {
object = resolveString(object, state, customObjects, reservedKeyword, withError, forPreviewBox);
}
if (object.startsWith('{{') && object.endsWith('}}')) { if (object.startsWith('{{') && object.endsWith('}}')) {
const code = object.replace('{{', '').replace('}}', ''); const code = object.replace('{{', '').replace('}}', '');

View file

@ -1 +1 @@
2.6.0 2.6.1

View file

@ -39,8 +39,6 @@ import { GroupPermissionsModule } from './modules/group_permissions/group_permis
import { TooljetDbModule } from './modules/tooljet_db/tooljet_db.module'; import { TooljetDbModule } from './modules/tooljet_db/tooljet_db.module';
import { PluginsModule } from './modules/plugins/plugins.module'; import { PluginsModule } from './modules/plugins/plugins.module';
import { CopilotModule } from './modules/copilot/copilot.module'; import { CopilotModule } from './modules/copilot/copilot.module';
import * as path from 'path';
import * as fs from 'fs';
import { AppEnvironmentsModule } from './modules/app_environments/app_environments.module'; import { AppEnvironmentsModule } from './modules/app_environments/app_environments.module';
import { RequestContextModule } from './modules/request_context/request-context.module'; import { RequestContextModule } from './modules/request_context/request-context.module';
import { ScheduleModule } from '@nestjs/schedule'; import { ScheduleModule } from '@nestjs/schedule';
@ -101,31 +99,7 @@ const imports = [
CopilotModule, CopilotModule,
]; ];
if (process.env.SERVE_CLIENT !== 'false') { if (process.env.SERVE_CLIENT !== 'false' && process.env.NODE_ENV === 'production') {
const filesToReplaceAssetPath = ['index.html', 'runtime.js', 'main.js'];
for (const fileName of filesToReplaceAssetPath) {
const file = join(__dirname, '../../../', 'frontend/build', fileName);
let newValue = process.env.SUB_PATH;
if (process.env.SUB_PATH === undefined) {
newValue = fileName === 'index.html' ? '/' : '';
}
fs.readFile(file, 'utf8', function (err, data) {
if (err) {
return console.log(err);
}
const result = data
.replace(/__REPLACE_SUB_PATH__\/api/g, path.join(newValue, '/api'))
.replace(/__REPLACE_SUB_PATH__/g, newValue);
fs.writeFile(file, result, 'utf8', function (err) {
if (err) return console.log(err);
});
});
}
imports.unshift( imports.unshift(
ServeStaticModule.forRoot({ ServeStaticModule.forRoot({
// Have to remove trailing slash of SUB_PATH. // Have to remove trailing slash of SUB_PATH.

View file

@ -17,6 +17,28 @@ const fs = require('fs');
globalThis.TOOLJET_VERSION = fs.readFileSync('./.version', 'utf8').trim(); globalThis.TOOLJET_VERSION = fs.readFileSync('./.version', 'utf8').trim();
function replaceSubpathPlaceHoldersInStaticAssets() {
const filesToReplaceAssetPath = ['index.html', 'runtime.js', 'main.js'];
for (const fileName of filesToReplaceAssetPath) {
const file = join(__dirname, '../../../', 'frontend/build', fileName);
let newValue = process.env.SUB_PATH;
if (process.env.SUB_PATH === undefined) {
newValue = fileName === 'index.html' ? '/' : '';
}
const data = fs.readFileSync(file, { encoding: 'utf8' });
const result = data
.replace(/__REPLACE_SUB_PATH__\/api/g, join(newValue, '/api'))
.replace(/__REPLACE_SUB_PATH__/g, newValue);
fs.writeFileSync(file, result, { encoding: 'utf8' });
}
}
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule, { const app = await NestFactory.create<NestExpressApplication>(AppModule, {
bufferLogs: true, bufferLogs: true,
@ -97,9 +119,13 @@ async function bootstrap() {
defaultVersion: VERSION_NEUTRAL, defaultVersion: VERSION_NEUTRAL,
}); });
const listen_addr = process.env.LISTEN_ADDR || "::"; const listen_addr = process.env.LISTEN_ADDR || '::';
const port = parseInt(process.env.PORT) || 3000; const port = parseInt(process.env.PORT) || 3000;
if (process.env.SERVE_CLIENT !== 'false' && process.env.NODE_ENV === 'production') {
replaceSubpathPlaceHoldersInStaticAssets();
}
await app.listen(port, listen_addr, function () { await app.listen(port, listen_addr, function () {
const tooljetHost = configService.get<string>('TOOLJET_HOST'); const tooljetHost = configService.get<string>('TOOLJET_HOST');
console.log(`Ready to use at ${tooljetHost} 🚀`); console.log(`Ready to use at ${tooljetHost} 🚀`);

View file

@ -409,6 +409,38 @@ export class DataQueriesService {
return object; return object;
} else if (typeof object === 'string') { } else if (typeof object === 'string') {
object = object.replace(/\n/g, ' '); object = object.replace(/\n/g, ' ');
//if object has {{}} and %%%% then resolve %% in a single string
if (object.includes('{{') && object.includes('}}') && object.includes('%%') && object.includes('%%')) {
let resolvedvar = options[object];
if (object.includes(`server.`)) {
// find all server variables in the string
const serverVariables = object.match(/server.(.*?)%%/g);
serverVariables?.map((variable) => {
return variable
.match(/server.(.*?)%%/g)[0]
.replace('%%', '')
.replace('server.', '');
});
const resolvedOrgVar = [];
for (const variable of serverVariables) {
const resolvedVariable = await this.resolveVariable(variable, organization_id);
resolvedOrgVar.push(resolvedVariable);
}
//replace the HiddenEnvironmentVariable with the resolved value
for (let i = 0; i < serverVariables.length; i++) {
resolvedvar = resolvedvar.replace('HiddenEnvironmentVariable', resolvedOrgVar[i]);
}
}
return resolvedvar;
}
if (object.startsWith('{{') && object.endsWith('}}') && (object.match(/{{/g) || []).length === 1) { if (object.startsWith('{{') && object.endsWith('}}') && (object.match(/{{/g) || []).length === 1) {
object = options[object]; object = options[object];
return object; return object;