mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 17:08:34 +00:00
Merge pull request #7534 from ToolJet/rebase-dev/platformv5
Merge v2.18.0 to develop
This commit is contained in:
commit
607b01baa1
28 changed files with 176 additions and 278 deletions
4
.github/workflows/packer-build.yml
vendored
4
.github/workflows/packer-build.yml
vendored
|
|
@ -1,8 +1,8 @@
|
|||
name: AWS AMI build using Packer config
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
# release:
|
||||
# types: [published]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
|
|
|||
|
|
@ -68,130 +68,7 @@ jobs:
|
|||
|
||||
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
try-tooljet-image-build:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build-tooljet-ce-image
|
||||
if: ${{ needs.build-tooljet-ce-image.result == 'success' }}
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: main
|
||||
|
||||
# Create Docker Buildx builder with platform configuration
|
||||
- name: Set up Docker Buildx
|
||||
run: |
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
|
||||
docker buildx use mybuilder
|
||||
|
||||
- name: Set DOCKER_CLI_EXPERIMENTAL
|
||||
run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV
|
||||
|
||||
- name: use mybuilder buildx
|
||||
run: docker buildx use mybuilder
|
||||
|
||||
- name: Docker Login
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Check if Docker image is present
|
||||
id: check-image-presence
|
||||
run: |
|
||||
response=$(curl -s "https://hub.docker.com/v2/repositories/tooljet/tooljet-ce/tags/${{ github.event.release.tag_name }}")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
echo "Failed to fetch JSON response. Stopping workflow execution."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $response == *"tag '${{ github.event.release.tag_name }}' not found"* ]]; then
|
||||
echo "Docker image tag '${{ github.event.release.tag_name }}' not present."
|
||||
exit 1
|
||||
else
|
||||
echo "Docker image tag '${{ github.event.release.tag_name }}' is present."
|
||||
fi
|
||||
|
||||
- name: Build and Push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/try-tooljet.Dockerfile
|
||||
push: true
|
||||
tags: tooljet/try:${{ github.event.release.tag_name }},tooljet/try:latest
|
||||
platforms: linux/amd64
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Send Slack Notification
|
||||
run: |
|
||||
if [[ "${{ job.status }}" == "success" ]]; then
|
||||
message="Job '${{ env.JOB_NAME }}' succeeded! tooljet/try:${{ github.event.release.tag_name }}"
|
||||
else
|
||||
message="Job '${{ env.JOB_NAME }}' failed! tooljet/try:${{ github.event.release.tag_name }}"
|
||||
fi
|
||||
|
||||
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
building-tooljet-server-ce-image:
|
||||
runs-on: ubuntu-latest
|
||||
if: "${{ github.event.release }}"
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: refs/heads/main
|
||||
|
||||
# Create Docker Buildx builder with platform configuration
|
||||
- name: Set up Docker Buildx
|
||||
run: |
|
||||
mkdir -p ~/.docker/cli-plugins
|
||||
curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
|
||||
chmod a+x ~/.docker/cli-plugins/docker-buildx
|
||||
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
|
||||
docker buildx use mybuilder
|
||||
|
||||
- name: Set DOCKER_CLI_EXPERIMENTAL
|
||||
run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV
|
||||
|
||||
- name: use mybuilder buildx
|
||||
run: docker buildx use mybuilder
|
||||
|
||||
- name: Docker Login
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build and Push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: docker/server.Dockerfile
|
||||
push: true
|
||||
tags: tooljet/tooljet-server-ce:${{ github.event.release.tag_name }},tooljet/tooljet-server-ce:latest
|
||||
platforms: linux/amd64
|
||||
env:
|
||||
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
||||
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Send Slack Notification
|
||||
run: |
|
||||
if [[ "${{ job.status }}" == "success" ]]; then
|
||||
message="Job '${{ env.JOB_NAME }}' succeeded! tooljet/tooljet-server-ce:${{ github.event.release.tag_name }}"
|
||||
else
|
||||
message="Job '${{ env.JOB_NAME }}' failed! tooljet/tooljet-server-ce:${{ github.event.release.tag_name }}"
|
||||
fi
|
||||
|
||||
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
#Below code helps to trigger the workflow separately
|
||||
# #Below code helps to trigger the workflow separately
|
||||
|
||||
tooljet-ce:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
|
|||
2
.version
2
.version
|
|
@ -1 +1 @@
|
|||
2.17.3
|
||||
2.18.0
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
export const verifyElemtsNoGds = (option) => {
|
||||
cy.get('[data-cy="label-select-datasource"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
"Connect to a datasource"
|
||||
"Connect to a Data Source"
|
||||
);
|
||||
cy.get('[data-cy="querymanager-description"]').verifyVisibleElement(
|
||||
"contain.text",
|
||||
"Select a datasource to start creating a new query. To know more about queries in ToolJet, you can read our"
|
||||
"Select a Data Source to start creating a new query. To know more about queries in ToolJet, you can read our"
|
||||
);
|
||||
cy.get('[data-cy="querymanager-doc-link"]').verifyVisibleElement(
|
||||
"have.text",
|
||||
|
|
@ -47,4 +47,4 @@ export const verifyElemtsNoGds = (option) => {
|
|||
);
|
||||
};
|
||||
|
||||
export const verifyElemtsWithGds = (option) => {};
|
||||
export const verifyElemtsWithGds = (option) => { };
|
||||
|
|
|
|||
|
|
@ -19,5 +19,5 @@ variable "ami_groups" {
|
|||
|
||||
variable "ami_regions" {
|
||||
type = list(string)
|
||||
default = ["us-west-1", "us-east-1", "us-east-2", "eu-west-2", "eu-central-1", "ap-northeast-1", "ap-southeast-1", "ap-northeast-3", "ap-south-1", "ap-northeast-2", "ap-southeast-2", "ca-central-1", "eu-west-1", "eu-north-1", "sa-east-1", "ap-east-1"]
|
||||
default = ["us-west-1", "us-east-1", "us-east-2", "eu-west-2", "eu-central-1", "ap-northeast-1", "ap-southeast-1","ap-northeast-3", "ap-south-1", "ap-northeast-2", "ap-southeast-2", "ca-central-1", "eu-west-1", "eu-north-1", "sa-east-1", "ap-east-1"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
2.17.3
|
||||
2.17.5
|
||||
|
|
|
|||
|
|
@ -1,51 +1,4 @@
|
|||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<rect x="104.923" y="191.732" style="fill:#F4B2B0;" width="302.163" height="304.524"/>
|
||||
<g>
|
||||
<path style="fill:#B3404A;" d="M180.066,413.377c-8.69,0-15.738-7.047-15.738-15.738V296.918c0-8.69,7.047-15.738,15.738-15.738
|
||||
s15.738,7.047,15.738,15.738v100.721C195.803,406.329,188.756,413.377,180.066,413.377z"/>
|
||||
<path style="fill:#B3404A;" d="M256,413.377c-8.69,0-15.738-7.047-15.738-15.738V296.918c0-8.69,7.047-15.738,15.738-15.738
|
||||
c8.69,0,15.738,7.047,15.738,15.738v100.721C271.738,406.329,264.69,413.377,256,413.377z"/>
|
||||
<path style="fill:#B3404A;" d="M331.934,413.377c-8.69,0-15.738-7.047-15.738-15.738V296.918c0-8.69,7.047-15.738,15.738-15.738
|
||||
s15.738,7.047,15.738,15.738v100.721C347.672,406.329,340.625,413.377,331.934,413.377z"/>
|
||||
<path style="fill:#B3404A;" d="M395.935,73.706c-8.69,0-15.738,7.047-15.738,15.738s7.047,15.738,15.738,15.738
|
||||
c18.295,0,33.18,14.885,33.18,33.18v37.64H407.08H104.92H82.886v-37.64c0-18.295,14.885-33.18,33.18-33.18h163.541
|
||||
c8.69,0,15.738-7.047,15.738-15.738s-7.047-15.738-15.738-15.738h-92.852v-42.23h138.492v57.968c0,8.69,7.047,15.738,15.738,15.738
|
||||
s15.738-7.047,15.738-15.738V15.738c0-8.69-7.047-15.738-15.738-15.738H171.017c-8.69,0-15.738,7.047-15.738,15.738v57.968h-39.214
|
||||
c-35.651,0-64.655,29.005-64.655,64.655v53.377c0,8.69,7.047,15.738,15.738,15.738h22.034v288.786
|
||||
c0,8.69,7.047,15.738,15.738,15.738h302.16c8.69,0,15.738-7.047,15.738-15.738V207.476h22.034c8.69,0,15.738-7.047,15.738-15.738
|
||||
v-53.377C460.59,102.71,431.585,73.706,395.935,73.706z M391.342,480.525H120.658V207.476h270.685V480.525z"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12" fill="none">
|
||||
<path d="M4.70313 1.4453C4.8886 1.1671 5.20083 1 5.53518 1H6.46482C6.79917 1 7.1114 1.1671 7.29687 1.4453L7.75 2.125H9.625C9.83211 2.125 10 2.29289 10 2.5C10 2.70711 9.83211 2.875 9.625 2.875H2.375C2.16789 2.875 2 2.70711 2 2.5C2 2.29289 2.16789 2.125 2.375 2.125H4.25L4.70313 1.4453Z" fill="#E54D2E"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.5 11H4.5C3.39543 11 2.5 10.1046 2.5 9V3.5H9.5V9C9.5 10.1046 8.60457 11 7.5 11ZM5 5.125C5.20711 5.125 5.375 5.29289 5.375 5.5V9C5.375 9.20711 5.20711 9.375 5 9.375C4.79289 9.375 4.625 9.20711 4.625 9L4.625 5.5C4.625 5.29289 4.79289 5.125 5 5.125ZM7 5.125C7.20711 5.125 7.375 5.29289 7.375 5.5V9C7.375 9.20711 7.20711 9.375 7 9.375C6.79289 9.375 6.625 9.20711 6.625 9V5.5C6.625 5.29289 6.79289 5.125 7 5.125Z" fill="#E54D2E"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 889 B |
|
|
@ -38,7 +38,7 @@
|
|||
"query": "Query",
|
||||
"requestBody": "Request Body",
|
||||
"page": "Page",
|
||||
"searchItem": "Search item",
|
||||
"searchItem": "Search apps in this workspace",
|
||||
"searchComponents": "Search components"
|
||||
},
|
||||
"errorBoundary": "Something went wrong.",
|
||||
|
|
@ -721,7 +721,7 @@
|
|||
"addColumn": "Add column",
|
||||
"addNewColumn": "Add new column",
|
||||
"noActionMessage": "This table doesn't have any action buttons",
|
||||
"horizontalAlignment":"horizontal alignment"
|
||||
"horizontalAlignment": "horizontal alignment"
|
||||
},
|
||||
"Button": {
|
||||
"displayName": "Button",
|
||||
|
|
|
|||
|
|
@ -125,6 +125,26 @@ class AppComponent extends React.Component {
|
|||
this.fetchMetadata();
|
||||
setInterval(this.fetchMetadata, 1000 * 60 * 60 * 1);
|
||||
}
|
||||
// check if its getting routed from editor
|
||||
checkPreviousRoute = (route) => {
|
||||
if (route.includes('/apps')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
// Check if the current location is the dashboard (homepage)
|
||||
if (
|
||||
this.props.location.pathname === `/${getWorkspaceIdFromURL()}` &&
|
||||
prevProps.location.pathname !== `/${getWorkspaceIdFromURL()}` &&
|
||||
this.checkPreviousRoute(prevProps.location.pathname) &&
|
||||
prevProps.location.pathname !== `/:workspaceId`
|
||||
) {
|
||||
// Reload the page for clearing already set intervals
|
||||
window.location.reload();
|
||||
}
|
||||
}
|
||||
|
||||
isThisWorkspaceLoginPage = (justLoginPage = false) => {
|
||||
const subpath = window?.public_config?.SUB_PATH ? stripTrailingSlash(window?.public_config?.SUB_PATH) : null;
|
||||
|
|
|
|||
|
|
@ -10,9 +10,10 @@ export const EditVersion = ({
|
|||
setAppVersions,
|
||||
setShowEditAppVersion,
|
||||
showEditAppVersion,
|
||||
editingVersion,
|
||||
appVersions,
|
||||
}) => {
|
||||
const [isEditingVersion, setIsEditingVersion] = useState(false);
|
||||
const editingVersion = appVersions?.find((version) => version.id === editingVersionId);
|
||||
const [versionName, setVersionName] = useState(editingVersion?.name || '');
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ export default function EditorHeader({
|
|||
updatePresence(initialPresence);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [currentUser]);
|
||||
const handleLogoClick = () => {
|
||||
// Force a reload for clearing interval triggers
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="header" style={{ width: '100%' }}>
|
||||
|
|
@ -66,7 +70,7 @@ export default function EditorHeader({
|
|||
<div className="container-xl header-container">
|
||||
<div className="d-flex w-100">
|
||||
<h1 className="navbar-brand d-none-navbar-horizontal p-0">
|
||||
<Link to={'/'} data-cy="editor-page-logo">
|
||||
<Link data-cy="editor-page-logo" onClick={handleLogoClick}>
|
||||
<AppLogo isLoadingFromHeader={true} />
|
||||
</Link>
|
||||
</h1>
|
||||
|
|
|
|||
|
|
@ -50,10 +50,10 @@ function DataSourcePicker({ dataSources, staticDataSources, darkMode, globalData
|
|||
return (
|
||||
<>
|
||||
<h4 className="w-100 text-center" data-cy={'label-select-datasource'} style={{ fontWeight: 500 }}>
|
||||
Connect to a data source
|
||||
Connect to a Data Source
|
||||
</h4>
|
||||
<p className="mb-3" style={{ textAlign: 'center' }}>
|
||||
Select a data source to start creating a new query. To know more about queries in ToolJet, you can read our
|
||||
Select a Data Source to start creating a new query. To know more about queries in ToolJet, you can read our
|
||||
|
||||
<a target="_blank" href="https://docs.tooljet.com/docs/app-builder/query-panel" rel="noreferrer">
|
||||
documentation
|
||||
|
|
@ -83,7 +83,7 @@ function DataSourcePicker({ dataSources, staticDataSources, darkMode, globalData
|
|||
</div>
|
||||
<div className="d-flex d-flex justify-content-between">
|
||||
<label className="form-label py-1" style={{ width: 'auto' }} data-cy={`label-avilable-ds`}>
|
||||
{`Available data sources ${!isEmpty(allUserDefinedSources) ? '(' + allUserDefinedSources.length + ')' : 0}`}
|
||||
{`Available Data Sources ${!isEmpty(allUserDefinedSources) ? '(' + allUserDefinedSources.length + ')' : 0}`}
|
||||
</label>
|
||||
{admin && (
|
||||
<ButtonSolid
|
||||
|
|
@ -143,7 +143,7 @@ const EmptyDataSourceBanner = () => (
|
|||
<div className="me-2">
|
||||
<Information fill="var(--slate9)" />
|
||||
</div>
|
||||
<div>No global data sources have been added yet.</div>
|
||||
<div>No data sources have been added yet.</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup }) {
|
|||
<div>
|
||||
{index === 0 && (
|
||||
<div className="color-slate9 mb-2 pb-1" style={{ fontWeight: 500, marginTop: '-8px' }}>
|
||||
Global data sources
|
||||
Data Sources
|
||||
</div>
|
||||
)}
|
||||
<DataSourceIcon source={sources?.[0]} height={16} />
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ const EmptyGlobalDataSources = ({ darkMode }) => {
|
|||
<InfoIcon />
|
||||
</div>
|
||||
<div className="info">
|
||||
No global datasources have been added.
|
||||
No data sources have been added.
|
||||
<br />
|
||||
Add a new datasource to connect to your app.
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -253,7 +253,7 @@ export const QueryManagerBody = ({
|
|||
<div
|
||||
className={`d-flex query-manager-border-color hr-text-left py-2 form-label font-weight-500 change-data-source`}
|
||||
>
|
||||
Datasource
|
||||
Data Source
|
||||
</div>
|
||||
<div className="d-flex flex-grow-1">
|
||||
<ChangeDataSource
|
||||
|
|
|
|||
|
|
@ -161,7 +161,12 @@ const ViewerHeader = ({ showHeader, appName, changeDarkMode, darkMode, pages, cu
|
|||
{showHeader && (
|
||||
<>
|
||||
<h1 className="navbar-brand d-none-navbar-horizontal pe-0">
|
||||
<Link to="/" data-cy="viewer-page-logo">
|
||||
<Link
|
||||
data-cy="viewer-page-logo"
|
||||
onClick={() => {
|
||||
window.location.href = '/';
|
||||
}}
|
||||
>
|
||||
<LogoIcon />
|
||||
</Link>
|
||||
</h1>
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@ const EmptyState = ({ canCreateVariable, setIsManageVarDrawerOpen, isLoading })
|
|||
<div className="w-50 mt-2">
|
||||
<h3 data-cy="empty-state-header">No Workspace constants yet</h3>
|
||||
<p className="text-muted mt-2" data-cy="empty-state-text">
|
||||
Use workspace constants seamlessly in both the app builder and global data source connections across
|
||||
ToolJet.
|
||||
Use workspace constants seamlessly in both the app builder and data source connections across ToolJet.
|
||||
</p>
|
||||
{canCreateVariable && (
|
||||
<ButtonSolid
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
|
||||
export const CopyToClipboardComponent = ({ data, callback }) => {
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
|
|
@ -32,7 +31,14 @@ export const CopyToClipboardComponent = ({ data, callback }) => {
|
|||
}}
|
||||
>
|
||||
<span style={{ height: '13px', width: '13px', marginBottom: '2px' }} className="mx-1 copy-to-clipboard">
|
||||
<SolidIcon width="12" height="12" name="copy" />
|
||||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
clip-rule="evenodd"
|
||||
d="M19.3113 4.68871C18.5834 3.9609 17.4034 3.9609 16.6757 4.68871L15.8421 5.5223C15.4237 5.94071 14.7453 5.94071 14.3269 5.5223C13.9084 5.10389 13.9084 4.42549 14.3269 4.00707L15.1605 3.17348C16.7251 1.60884 19.2619 1.60884 20.8266 3.17348C22.3911 4.73811 22.3911 7.2749 20.8266 8.83954L19.9929 9.67313C19.5746 10.0916 18.8961 10.0916 18.4777 9.67313C18.0593 9.25471 18.0593 8.57633 18.4777 8.1579L19.3113 7.32431C20.0391 6.59651 20.0391 5.41651 19.3113 4.68871ZM17.406 6.59394C17.8244 7.01236 17.8244 7.69074 17.406 8.10917L15.1982 10.317C14.7798 10.7354 14.1014 10.7354 13.683 10.317C13.2645 9.89856 13.2645 9.22017 13.683 8.80174L15.8908 6.59394C16.3091 6.17551 16.9876 6.17551 17.406 6.59394ZM12.6651 7.184C13.0835 7.60241 13.0835 8.28081 12.6651 8.69923L11.8315 9.53283C11.1037 10.2606 11.1037 11.4406 11.8315 12.1684C12.5593 12.8962 13.7393 12.8962 14.4671 12.1684L15.3007 11.3348C15.7191 10.9164 16.3974 10.9164 16.8159 11.3348C17.2343 11.7533 17.2343 12.4316 16.8159 12.8501L15.9823 13.6837C14.4177 15.2483 11.8809 15.2483 10.3162 13.6837C8.75161 12.119 8.75161 9.58223 10.3162 8.0176L11.1498 7.184C11.5683 6.76559 12.2467 6.76559 12.6651 7.184ZM17.245 14.9463C14.983 17.2083 11.3156 17.2083 9.05356 14.9463C6.79156 12.6843 6.79156 9.01691 9.05356 6.7549L9.52276 6.28571H4.14286C2.95939 6.28571 2 7.2451 2 8.42857V19.8571C2 21.0406 2.95939 22 4.14286 22H15.5714C16.7549 22 17.7143 21.0406 17.7143 19.8571V14.4771L17.245 14.9463Z"
|
||||
fill={'#C1C8CD'}
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
</CopyToClipboard>
|
||||
</ToolTip>
|
||||
|
|
|
|||
|
|
@ -3,8 +3,6 @@ import _ from 'lodash';
|
|||
import cx from 'classnames';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import CopyToClipboardComponent from '@/_components/CopyToClipboard';
|
||||
import { Popover } from 'react-bootstrap';
|
||||
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
|
||||
import JSONNodeObject from './JSONNodeObject';
|
||||
import JSONNodeArray from './JSONNodeArray';
|
||||
import JSONNodeValue from './JSONNodeValue';
|
||||
|
|
@ -214,35 +212,6 @@ export const JSONNode = ({ data, ...restProps }) => {
|
|||
expandable &&
|
||||
(typeofCurrentNode === 'Object' || typeofCurrentNode === 'Array' || typeofCurrentNode === 'Map');
|
||||
|
||||
function moreActionsPopover(actions) {
|
||||
//Todo: For adding more actions to the menu popover!
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
|
||||
return (
|
||||
<Popover
|
||||
id="popover-basic popover-positioned-right json-tree-popover"
|
||||
style={{ maxWidth: '350px', padding: '0px' }}
|
||||
className={`shadow ${darkMode && 'popover-dark-themed theme-dark'}`}
|
||||
>
|
||||
<div className="list-group">
|
||||
{actions?.map((action, index) => (
|
||||
<span
|
||||
key={index}
|
||||
type="button"
|
||||
className="list-group-item list-group-item-action popover-more-actions"
|
||||
aria-current="true"
|
||||
onClick={() => {
|
||||
action.dispatchAction(data, currentNode);
|
||||
}}
|
||||
>
|
||||
{action.name}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
const renderHiddenOptionsForNode = () => {
|
||||
const moreActions = actionsList.filter((action) => action.for === 'all')[0];
|
||||
|
||||
|
|
@ -268,27 +237,20 @@ export const JSONNode = ({ data, ...restProps }) => {
|
|||
};
|
||||
|
||||
return (
|
||||
<div style={{ fontSize: '9px', marginTop: '0px' }} className="d-flex end-0 position-absolute">
|
||||
<div style={{ fontSize: '9px', marginTop: '0px', right: '10px' }} className="d-flex position-absolute">
|
||||
{enableCopyToClipboard && (
|
||||
<CopyToClipboardComponent data={currentNodePath} path={true} callback={getAbsoluteNodePath} />
|
||||
)}
|
||||
{renderOptions()}
|
||||
|
||||
{moreActions.actions?.length > 0 && (
|
||||
<OverlayTrigger
|
||||
rootClose={true}
|
||||
rootCloseEvent="mousedown"
|
||||
trigger="click"
|
||||
placement={'right'}
|
||||
overlay={moreActionsPopover(moreActions?.actions)}
|
||||
<ToolTip message={'Copy value'}>
|
||||
<span
|
||||
onClick={() => {
|
||||
moreActions['actions'][0].dispatchAction(data, currentNode);
|
||||
}}
|
||||
>
|
||||
<span>
|
||||
<ToolTip message={'More actions'}>
|
||||
<SolidIcon width="13" name="morevertical" />
|
||||
</ToolTip>
|
||||
</span>
|
||||
</OverlayTrigger>
|
||||
)}
|
||||
<SolidIcon width="12" height="12" name="copy" />
|
||||
</span>
|
||||
</ToolTip>
|
||||
{renderOptions()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
2.17.3
|
||||
2.18.0
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
import { WORKSPACE_USER_STATUS } from 'src/helpers/user_lifecycle';
|
||||
import { MigrationProgress } from 'src/helpers/utils.helper';
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
export class organizationUsersRemoveDuplicates1693288308041 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const entityManager = queryRunner.manager;
|
||||
const duplicates = await entityManager.query(
|
||||
'select sub_query.organization_id, sub_query.user_id from (select count(*) as org_user_count, organization_id, user_id from organization_users group by organization_id, user_id) sub_query where org_user_count > 1'
|
||||
);
|
||||
console.log(`found ${duplicates?.length || 0} duplicates in organization_users table`);
|
||||
|
||||
if (duplicates && duplicates.length) {
|
||||
const migrationProgress = new MigrationProgress(
|
||||
'organizationUsersRemoveDuplicates1693288308041',
|
||||
duplicates.length
|
||||
);
|
||||
|
||||
for (const duplicate of duplicates) {
|
||||
let idToKeep;
|
||||
const duplicatesWithStatus = await entityManager.query(
|
||||
'select id, status from organization_users where organization_id=$1 and user_id=$2',
|
||||
[duplicate.organization_id, duplicate.user_id]
|
||||
);
|
||||
|
||||
if (duplicatesWithStatus && duplicatesWithStatus.length > 0) {
|
||||
// Keeping record with status active
|
||||
idToKeep = duplicatesWithStatus.find((e) => e.status === WORKSPACE_USER_STATUS.ACTIVE)?.id;
|
||||
|
||||
if (!idToKeep) {
|
||||
// Keeping random record
|
||||
idToKeep = duplicatesWithStatus[0].id;
|
||||
}
|
||||
}
|
||||
if (idToKeep) {
|
||||
await entityManager.query(
|
||||
'delete from organization_users where id!=$1 and organization_id=$2 and user_id=$3',
|
||||
[idToKeep, duplicate.organization_id, duplicate.user_id]
|
||||
);
|
||||
}
|
||||
migrationProgress.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { DataBaseConstraints } from 'src/helpers/db_constraints.constants';
|
||||
import { MigrationInterface, QueryRunner, TableUnique } from 'typeorm';
|
||||
|
||||
export class addUniqueKeyConstrainForOrganizationUsers1693309526388 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.createUniqueConstraint(
|
||||
'organization_users',
|
||||
new TableUnique({
|
||||
name: DataBaseConstraints.USER_ORGANIZATION_UNIQUE,
|
||||
columnNames: ['user_id', 'organization_id'],
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropUniqueConstraint('organization_users', DataBaseConstraints.USER_ORGANIZATION_UNIQUE);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,13 @@ import {
|
|||
ManyToOne,
|
||||
JoinColumn,
|
||||
BaseEntity,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { Organization } from './organization.entity';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity({ name: 'organization_users' })
|
||||
@Unique(['userId', 'organizationId'])
|
||||
export class OrganizationUser extends BaseEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
|
|
|||
|
|
@ -2,4 +2,5 @@ export enum DataBaseConstraints {
|
|||
FOLDER_NAME_UNIQUE = 'folder_name_organization_id_unique',
|
||||
APP_NAME_UNIQUE = 'app_name_organization_id_unique',
|
||||
WORKSPACE_NAME_UNIQUE = 'name_organizations_unique',
|
||||
USER_ORGANIZATION_UNIQUE = 'user_organization_unique',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -412,6 +412,19 @@ export class AppImportExportService {
|
|||
);
|
||||
appResourceMappings.dataQueryMapping = dataQueryMapping;
|
||||
}
|
||||
|
||||
const newDataQueries = await manager.find(DataQuery, {
|
||||
where: { appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id] },
|
||||
});
|
||||
|
||||
for (const newQuery of newDataQueries) {
|
||||
const newOptions = this.replaceDataQueryOptionsWithNewDataQueryIds(
|
||||
newQuery.options,
|
||||
appResourceMappings.dataQueryMapping
|
||||
);
|
||||
newQuery.options = newOptions;
|
||||
await manager.save(newQuery);
|
||||
}
|
||||
}
|
||||
|
||||
return appResourceMappings;
|
||||
|
|
@ -455,37 +468,27 @@ export class AppImportExportService {
|
|||
externalResourceMappings: { [x: string]: any }
|
||||
) {
|
||||
appResourceMappings = { ...appResourceMappings };
|
||||
const newDataQueries = importingDataQueriesForAppVersion
|
||||
.filter((dq: { dataSourceId: any }) => dq.dataSourceId === importingDataSource.id)
|
||||
.map((importingQuery: { options: any; name: any }) => {
|
||||
const options =
|
||||
importingDataSource.kind === 'tooljetdb'
|
||||
? this.replaceTooljetDbTableIds(importingQuery.options, externalResourceMappings['tooljet_database'])
|
||||
: importingQuery.options;
|
||||
const importingQueriesForSource = importingDataQueriesForAppVersion.filter(
|
||||
(dq: { dataSourceId: any }) => dq.dataSourceId === importingDataSource.id
|
||||
);
|
||||
if (isEmpty(importingDataQueriesForAppVersion)) return appResourceMappings;
|
||||
|
||||
return manager.create(DataQuery, {
|
||||
name: importingQuery.name,
|
||||
options,
|
||||
dataSourceId: dataSourceForAppVersion.id,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
});
|
||||
for (const importingQuery of importingQueriesForSource) {
|
||||
const options =
|
||||
importingDataSource.kind === 'tooljetdb'
|
||||
? this.replaceTooljetDbTableIds(importingQuery.options, externalResourceMappings['tooljet_database'])
|
||||
: importingQuery.options;
|
||||
|
||||
const newQuery = manager.create(DataQuery, {
|
||||
name: importingQuery.name,
|
||||
options,
|
||||
dataSourceId: dataSourceForAppVersion.id,
|
||||
appVersionId: appResourceMappings.appVersionMapping[importingAppVersion.id],
|
||||
});
|
||||
|
||||
await Promise.all(newDataQueries.map((newQuery: any) => manager.save(newQuery)));
|
||||
|
||||
newDataQueries.forEach((newQuery, index) => {
|
||||
const importingDataQuery = importingDataQueriesForAppVersion[index];
|
||||
appResourceMappings.dataQueryMapping[importingDataQuery.id] = newQuery.id;
|
||||
});
|
||||
|
||||
for (const newQuery of newDataQueries) {
|
||||
const newOptions = this.replaceDataQueryOptionsWithNewDataQueryIds(
|
||||
newQuery.options,
|
||||
appResourceMappings.dataQueryMapping
|
||||
);
|
||||
newQuery.options = newOptions;
|
||||
await manager.save(newQuery);
|
||||
appResourceMappings.dataQueryMapping[importingQuery.id] = newQuery.id;
|
||||
}
|
||||
|
||||
return appResourceMappings;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
} from '../../test.helper';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
describe('Form Onboarding', () => {
|
||||
describe.skip('Form Onboarding', () => {
|
||||
let app: INestApplication;
|
||||
let userRepository: Repository<User>;
|
||||
let orgRepository: Repository<Organization>;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import got from 'got';
|
|||
jest.mock('got');
|
||||
const mockedGot = mocked(got);
|
||||
|
||||
describe('Git Onboarding', () => {
|
||||
describe.skip('Git Onboarding', () => {
|
||||
let app: INestApplication;
|
||||
let userRepository: Repository<User>;
|
||||
let orgRepository: Repository<Organization>;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import {
|
|||
import { getManager, Repository } from 'typeorm';
|
||||
import { OAuth2Client } from 'google-auth-library';
|
||||
|
||||
describe('Google SSO Onboarding', () => {
|
||||
describe.skip('Google SSO Onboarding', () => {
|
||||
let app: INestApplication;
|
||||
let userRepository: Repository<User>;
|
||||
let orgRepository: Repository<Organization>;
|
||||
|
|
|
|||
Loading…
Reference in a new issue