mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 09:28:31 +00:00
commit
0a006b5150
14 changed files with 92 additions and 48 deletions
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
3.0.0-ce-beta.1
|
||||
3.0.0-ce
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
3.0.0-ce-beta.1
|
||||
3.0.0-ce
|
||||
|
|
|
|||
|
|
@ -156,7 +156,15 @@ const useAppData = (appId, moduleId, mode = 'edit', { environmentId, versionId }
|
|||
let editorEnvironment = result.editorEnvironment;
|
||||
const editorEnvironmentId = result.editing_version?.current_environment_id;
|
||||
if (isPreviewForVersion) {
|
||||
const rawDataQueries = appData?.data_queries;
|
||||
const rawEditingVersionDataQueries = appData?.editing_version?.data_queries;
|
||||
appData = convertAllKeysToSnakeCase(appData);
|
||||
|
||||
appData.data_queries = rawDataQueries;
|
||||
if (appData.editing_version && rawEditingVersionDataQueries) {
|
||||
appData.editing_version.data_queries = rawEditingVersionDataQueries;
|
||||
}
|
||||
|
||||
editorEnvironment = {
|
||||
id: environmentId,
|
||||
name: queryParams.env,
|
||||
|
|
|
|||
|
|
@ -164,7 +164,7 @@ export const Multiselect = function Multiselect({
|
|||
<label
|
||||
style={{ marginRight: label ? '1rem' : '', marginBottom: 0 }}
|
||||
className={`form-label py-1 ${darkMode ? 'text-light' : 'text-secondary'}`}
|
||||
data-cy={`multiselect-label-${componentName.toLowerCase()}`}
|
||||
data-cy={`multiselect-label-${componentName?.toLowerCase()}`}
|
||||
>
|
||||
{label}
|
||||
</label>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState, useRef } from 'react';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { CustomSelect } from './CustomSelect';
|
||||
import { getAvatar, decodeEntities } from '@/_helpers/utils';
|
||||
|
|
@ -29,14 +29,21 @@ export const OrganizationList = function () {
|
|||
fetchOrganizations();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const switchOrganization = (id) => {
|
||||
const newTabRef = useRef(false);
|
||||
const switchOrganization = (id, newTab = false) => {
|
||||
newTabRef.current = newTab;
|
||||
const organization = organizationList.find((org) => org.id === id);
|
||||
if (![id, organization.slug].includes(getWorkspaceIdOrSlugFromURL())) {
|
||||
const newPath = appendWorkspaceId(organization.slug || id, location.pathname, true);
|
||||
window.open(newPath, '_blank');
|
||||
newTab ? window.open(newPath, '_blank') : (window.location = newPath);
|
||||
}
|
||||
};
|
||||
const handleOnChange = (id) => {
|
||||
if (!newTabRef.current) {
|
||||
switchOrganization(id, false);
|
||||
}
|
||||
newTabRef.current = false;
|
||||
};
|
||||
|
||||
const options = organizationList
|
||||
.map((org) => ({
|
||||
|
|
@ -81,7 +88,7 @@ export const OrganizationList = function () {
|
|||
<div
|
||||
className="current-org-indicator"
|
||||
data-cy="current-org-indicator"
|
||||
onClick={() => switchOrganization(org.id)}
|
||||
onClick={() => switchOrganization(org.id, true)}
|
||||
>
|
||||
<SolidIcon name="newtab" fill="var(--icon-strong)" width="16" className="add-new-workspace-icon" />
|
||||
</div>
|
||||
|
|
@ -103,7 +110,7 @@ export const OrganizationList = function () {
|
|||
isLoading={isGettingOrganizations}
|
||||
options={options}
|
||||
value={current_organization_id}
|
||||
onChange={(id) => switchOrganization(id)}
|
||||
onChange={handleOnChange}
|
||||
className={`tj-org-select ${darkMode && 'dark-theme'}`}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@
|
|||
.datasource-list-container {
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 64px);
|
||||
padding-left: 20px;
|
||||
|
||||
.datasource-list {
|
||||
width: 976px;
|
||||
|
|
|
|||
|
|
@ -3656,6 +3656,9 @@ input:focus-visible {
|
|||
}
|
||||
|
||||
.main-wrapper.theme-dark {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
min-width: 100%;
|
||||
background-color: #2b394b;
|
||||
}
|
||||
|
||||
|
|
@ -7796,7 +7799,11 @@ tbody {
|
|||
.audit-log-nav-item {
|
||||
bottom: 40px;
|
||||
}
|
||||
|
||||
.workspace-content-wrapper{
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.workspace-content-wrapper,
|
||||
.database-page-content-wrap {
|
||||
background-color: var(--page-default);
|
||||
|
|
@ -10560,6 +10567,7 @@ tbody {
|
|||
.manage-groups-body {
|
||||
padding: 12px 12px 10px 12px;
|
||||
font-size: 12px;
|
||||
overflow-y: auto;
|
||||
// overflow-y: auto;
|
||||
height: calc(100vh - 300px);
|
||||
|
||||
|
|
|
|||
|
|
@ -196,7 +196,6 @@ module.exports = {
|
|||
COMMENT_FEATURE_ENABLE: process.env.COMMENT_FEATURE_ENABLE ?? true,
|
||||
ENABLE_MULTIPLAYER_EDITING: true,
|
||||
ENABLE_MARKETPLACE_DEV_MODE: process.env.ENABLE_MARKETPLACE_DEV_MODE,
|
||||
TJDB_SQL_MODE_DISABLE: process.env.TJDB_SQL_MODE_DISABLE ?? false,
|
||||
TOOLJET_MARKETPLACE_URL:
|
||||
process.env.TOOLJET_MARKETPLACE_URL || 'https://tooljet-plugins-production.s3.us-east-2.amazonaws.com',
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -5,14 +5,6 @@
|
|||
"type": "database",
|
||||
"defaults": {},
|
||||
"properties": {
|
||||
"index": {
|
||||
"label": "Index",
|
||||
"key": "index",
|
||||
"type": "text",
|
||||
"description": "Enter the index name (e.g., example-index)",
|
||||
"placeholder": "example-index",
|
||||
"height": "36px"
|
||||
},
|
||||
"operation": {
|
||||
"label": "Operation",
|
||||
"key": "operation",
|
||||
|
|
|
|||
|
|
@ -82,8 +82,7 @@ export const sanitizeHeaders = (
|
|||
queryOptions: any,
|
||||
hasDataSource = true
|
||||
): { [k: string]: string } => {
|
||||
const cleanHeaders = (headers) =>
|
||||
headers.filter(([_, v]) => !isEmpty(v)).map(([k, v]) => [k.trim().toLowerCase(), v]);
|
||||
const cleanHeaders = (headers) => headers.filter(([k, _]) => k !== '').map(([k, v]) => [k.trim(), v]);
|
||||
|
||||
const _queryHeaders = cleanHeaders(queryOptions.headers || []);
|
||||
const queryHeaders = Object.fromEntries(_queryHeaders);
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
3.0.0-ce-beta.1
|
||||
3.0.0-ce
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class PopulateManualConnectionTypeForOldPostgresDs1731283187529 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`
|
||||
UPDATE data_source_options
|
||||
SET options = jsonb_set(
|
||||
COALESCE(options::jsonb, '{}'),
|
||||
'{connection_type}',
|
||||
'{"value": "manual", "encrypted": false}'::jsonb
|
||||
)
|
||||
WHERE data_source_id IN (
|
||||
SELECT ds.id
|
||||
FROM data_sources ds
|
||||
WHERE ds.kind = 'postgresql'
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM jsonb_object_keys(COALESCE(options::jsonb, '{}')) AS keys
|
||||
WHERE keys = 'connection_type'
|
||||
);
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||
}
|
||||
|
|
@ -573,7 +573,6 @@ export class DataQueriesService {
|
|||
finalResult += str.slice(lastIndex);
|
||||
return finalResult;
|
||||
}
|
||||
|
||||
async parseQueryOptions(
|
||||
object: any,
|
||||
options: object,
|
||||
|
|
@ -581,10 +580,8 @@ export class DataQueriesService {
|
|||
environmentId?: string
|
||||
): Promise<object> {
|
||||
const stack: any[] = [{ obj: object, key: null, parent: null }];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const { obj, key, parent } = stack.pop();
|
||||
|
||||
// Case 1: Object
|
||||
if (typeof obj === 'object' && obj !== null && !Array.isArray(obj)) {
|
||||
Object.keys(obj).forEach((k) => {
|
||||
|
|
@ -592,7 +589,6 @@ export class DataQueriesService {
|
|||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Case 2: Array
|
||||
if (Array.isArray(obj)) {
|
||||
obj.forEach((element, index) => {
|
||||
|
|
@ -600,52 +596,54 @@ export class DataQueriesService {
|
|||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Case 3: String
|
||||
if (typeof obj === 'string') {
|
||||
let resolvedValue = obj.replace(/\n/g, ' ');
|
||||
|
||||
// a: Handle strings with both {{ }} and %%
|
||||
if (resolvedValue.includes('{{') && resolvedValue.includes('}}') && resolvedValue.includes('%%')) {
|
||||
if (
|
||||
typeof resolvedValue === 'string' &&
|
||||
resolvedValue.includes('{{') &&
|
||||
resolvedValue.includes('}}') &&
|
||||
resolvedValue?.includes('%%')
|
||||
) {
|
||||
let resolvedVar = options[resolvedValue];
|
||||
|
||||
// find all server variables in the string
|
||||
if (resolvedValue.includes(`server.`)) {
|
||||
if (typeof resolvedValue === 'string' && resolvedValue.includes(`server.`)) {
|
||||
let serverVariables: string[] = resolvedValue.match(/server.(.*?)%%/g) || [];
|
||||
|
||||
// Map each variable by replacing '%%' and 'server.'
|
||||
serverVariables = serverVariables?.map((variable) => {
|
||||
return variable.replace('%%', '').replace('server.', '');
|
||||
});
|
||||
|
||||
const resolvedOrgVar: string[] = [];
|
||||
|
||||
for (const variable of serverVariables || []) {
|
||||
const resolvedVariable = await this.resolveVariable(variable, organization_id);
|
||||
resolvedOrgVar.push(resolvedVariable);
|
||||
}
|
||||
|
||||
serverVariables?.forEach((i) => {
|
||||
resolvedVar = resolvedVar.replace('HiddenEnvironmentVariable', resolvedOrgVar[i]);
|
||||
});
|
||||
}
|
||||
|
||||
if (parent && key !== null) {
|
||||
parent[key] = resolvedVar;
|
||||
}
|
||||
}
|
||||
|
||||
// b: Handle {{constants.}} or {{secrets.}}
|
||||
if (resolvedValue?.includes('{{constants.') || resolvedValue.includes('{{secrets.')) {
|
||||
if (
|
||||
(typeof resolvedValue === 'string' && resolvedValue.includes('{{constants.')) ||
|
||||
resolvedValue.includes('{{secrets.')
|
||||
) {
|
||||
const resolvingConstant = await this.resolveConstants(resolvedValue, organization_id, environmentId);
|
||||
resolvedValue = resolvingConstant;
|
||||
if (parent && key !== null) {
|
||||
parent[key] = resolvedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// c: Replace all occurrences of {{ }} variables
|
||||
if (resolvedValue?.match(/\{\{(.*?)\}\}/g)?.length > 0) {
|
||||
if (
|
||||
typeof resolvedValue === 'string' &&
|
||||
resolvedValue?.match(/\{\{(.*?)\}\}/g)?.length > 0 &&
|
||||
!(resolvedValue.startsWith('{{') && resolvedValue.endsWith('}}'))
|
||||
) {
|
||||
const variables = resolvedValue.match(/\{\{(.*?)\}\}/g);
|
||||
|
||||
for (const variable of variables || []) {
|
||||
|
|
@ -655,18 +653,25 @@ export class DataQueriesService {
|
|||
// Ensure parent is a non-empty array before attempting to access its first element
|
||||
if (Array.isArray(parent) && parent.length > 0) {
|
||||
// Assign replacement value based on the first item in the parent array
|
||||
replacement = replacement[parent[0]];
|
||||
replacement = replacement[parent[0]] || replacement;
|
||||
}
|
||||
}
|
||||
resolvedValue = resolvedValue.replace(variable, replacement);
|
||||
// Check type of replacement and assign accordingly
|
||||
if (typeof replacement === 'string' || typeof replacement === 'number') {
|
||||
// If replacement is a string, perform the replace
|
||||
resolvedValue = resolvedValue.replace(variable, String(replacement));
|
||||
} else {
|
||||
// If replacement is an object or an array, assign the whole value to resolvedValue
|
||||
resolvedValue = resolvedValue.replace(variable, JSON.stringify(replacement));
|
||||
}
|
||||
}
|
||||
if (parent && key !== null) {
|
||||
parent[key] = resolvedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// d: Simple variable replacement for single {{variable}}
|
||||
if (
|
||||
typeof resolvedValue === 'string' &&
|
||||
resolvedValue.startsWith('{{') &&
|
||||
resolvedValue.endsWith('}}') &&
|
||||
(resolvedValue.match(/{{/g) || [])?.length === 1
|
||||
|
|
@ -676,9 +681,9 @@ export class DataQueriesService {
|
|||
parent[key] = resolvedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// e: Handle strings with %%
|
||||
if (
|
||||
typeof resolvedValue === 'string' &&
|
||||
resolvedValue.startsWith('%%') &&
|
||||
resolvedValue.endsWith('%%') &&
|
||||
(resolvedValue.match(/%%/g) || [])?.length === 2
|
||||
|
|
@ -692,9 +697,9 @@ export class DataQueriesService {
|
|||
parent[key] = resolvedValue;
|
||||
}
|
||||
}
|
||||
|
||||
// f: Replace all %% variables
|
||||
const variables = resolvedValue?.match(/%%(.*?)%%/g);
|
||||
//disallow strings with spaces in between '%%' eg. '%% hghgh hg %%'
|
||||
const variables = typeof resolvedValue === 'string' && resolvedValue?.match(/%%(?:client|server)\.[^\s%%]+%%/g);
|
||||
if (variables?.length > 0) {
|
||||
for (const variable of variables) {
|
||||
if (variable.includes(`server.`)) {
|
||||
|
|
@ -710,7 +715,6 @@ export class DataQueriesService {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -845,7 +845,7 @@ export class OrganizationsService {
|
|||
...(slug && { slug }),
|
||||
},
|
||||
});
|
||||
if (result) throw new ConflictException(`${name ? 'Name' : 'Slug'} must be unique`);
|
||||
if (result) throw new ConflictException(`Workspace ${name ? 'name' : 'slug'} already exists`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue