Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Enable salesforce plugin's account queries from app builder #9583

Merged
merged 33 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
caa9263
Dynamic form configuration API logic pending
ShazanRizvi Mar 8, 2024
acd06ed
Merge branch 'develop' into 9011-create-a-new-datasource-plugin-for-s…
ShazanRizvi Mar 8, 2024
436f01e
Merge branch 'develop' into 9011-create-a-new-datasource-plugin-for-s…
ShazanRizvi Mar 11, 2024
07ef68c
OAuth 2.0
ShazanRizvi Mar 12, 2024
5345a24
Merge branch 'develop' into 9011-create-a-new-datasource-plugin-for-s…
ShazanRizvi Mar 12, 2024
394a28f
Testing redirectURI
ShazanRizvi Mar 13, 2024
805a2d7
Merge branch 'develop' into 9011-create-a-new-datasource-plugin-for-s…
ShazanRizvi Mar 13, 2024
92900ed
salesforce-react-component
ShazanRizvi Mar 22, 2024
e0afea4
Merge branch 'develop' into 9011-create-a-new-datasource-plugin-for-s…
ShazanRizvi Mar 22, 2024
4b68fee
Merge branch 'develop' into 9011-create-a-new-datasource-plugin-for-s…
ShazanRizvi Mar 31, 2024
0485a57
Oauth flow
ShazanRizvi Apr 7, 2024
d74062b
Merge branch 'develop' into 9011-create-a-new-datasource-plugin-for-s…
ShazanRizvi Apr 7, 2024
ad414fb
OauthFlowSetup
ShazanRizvi Apr 17, 2024
4506bd1
Merge branch 'develop' into 9011-create-a-new-datasource-plugin-for-s…
ShazanRizvi Apr 17, 2024
cdce62e
Merge develop
ShazanRizvi Apr 17, 2024
4abb91e
Oauth flow changes
ShazanRizvi Apr 22, 2024
8ebb92c
Merge branch 'develop' into 9011-create-a-new-datasource-plugin-for-s…
ShazanRizvi Apr 22, 2024
6d5485c
Implement run function
parthy007 Apr 24, 2024
bda5d51
Fetch query details correctly
parthy007 Apr 25, 2024
3848448
Added instance URL to source options for run function
parthy007 Apr 30, 2024
94cea41
Remove BulkLoad & ApexRESTQuery operation
parthy007 May 1, 2024
3aadbf5
merge new changes
parthy007 May 1, 2024
6d02e54
Increase resource-body height
parthy007 May 1, 2024
c19f4c7
Render plugin icon in marketplace
parthy007 May 7, 2024
62fa0fa
Make the URL dynamic in salesforce form
parthy007 May 8, 2024
340068c
Revert "Make the URL dynamic in salesforce form"
parthy007 May 9, 2024
1a8bc3b
Make the redirectUri dynamic
parthy007 May 9, 2024
e960154
Remove console.log
parthy007 May 10, 2024
1746b9b
Make use of classnames package for conditional styles
parthy007 May 10, 2024
ebaba56
Add back remove elements
parthy007 May 10, 2024
e71738e
Make instanceUrl snakecase
parthy007 May 10, 2024
a0bbc21
Correct description for salesforce
parthy007 May 10, 2024
770bbda
Revert changes to plugin icon in marketplace
parthy007 May 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion frontend/src/Editor/DataSourceManager/DataSourceManager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ class DataSourceManagerComponent extends React.Component {
createDataSource = () => {
const { appId, options, selectedDataSource, selectedDataSourcePluginId, dataSourceMeta, dataSourceSchema } =
this.state;
const OAuthDs = ['slack', 'zendesk', 'googlesheets'];
console.log('selectedDataSource from DatasourceManager', selectedDataSource);
akshaysasidrn marked this conversation as resolved.
Show resolved Hide resolved
const OAuthDs = ['slack', 'zendesk', 'googlesheets', 'salesforce'];
const name = selectedDataSource.name;
const kind = selectedDataSource.kind;
const pluginId = selectedDataSourcePluginId;
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/MarketplacePage/MarketplaceCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ export const MarketplaceCard = ({ id, name, repo, description, version, isInstal
};

let iconSrc;

if (repo) {
if (process.env.NODE_ENV === 'development') {
akshaysasidrn marked this conversation as resolved.
Show resolved Hide resolved
iconSrc = `https://tooljet-plugins-stage.s3.us-east-2.amazonaws.com/marketplace-assets/${id}/lib/icon.svg`;
} else if (repo) {
iconSrc = `https://raw.githubusercontent.com/${repo}/main/lib/icon.svg`;
} else {
iconSrc = `${config.TOOLJET_MARKETPLACE_URL}/marketplace-assets/${id}/lib/icon.svg`;
Expand Down
10 changes: 4 additions & 6 deletions frontend/src/_components/DynamicForm.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import GoogleSheets from '@/_components/Googlesheets';
import Slack from '@/_components/Slack';
import Zendesk from '@/_components/Zendesk';
import { ConditionFilter, CondtionSort, MultiColumn } from '@/_components/MultiConditions';
import Salesforce from '@/_components/Salesforce';
import ToolJetDbOperations from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/ToolJetDbOperations';
import { orgEnvironmentVariableService, orgEnvironmentConstantService } from '../_services';

Expand Down Expand Up @@ -154,12 +155,8 @@ const DynamicForm = ({
return OpenApi;
case 'react-component-zendesk':
return Zendesk;
case 'columns':
return MultiColumn;
case 'filters':
return ConditionFilter;
case 'sorts':
return CondtionSort;
akshaysasidrn marked this conversation as resolved.
Show resolved Hide resolved
case 'react-component-salesforce':
return Salesforce;
default:
return <div>Type is invalid</div>;
}
Expand Down Expand Up @@ -297,6 +294,7 @@ const DynamicForm = ({
case 'react-component-google-sheets':
case 'react-component-slack':
case 'react-component-zendesk':
case 'react-component-salesforce':
return {
optionchanged,
createDataSource,
Expand Down
125 changes: 125 additions & 0 deletions frontend/src/_components/Salesforce.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React, { useState } from 'react';
import { datasourceService } from '@/_services';
import { useTranslation } from 'react-i18next';
import { toast } from 'react-hot-toast';
import Button from '@/_ui/Button';
import Select from '@/_ui/Select';
import Input from '@/_ui/Input';

const Salesforce = ({ optionchanged, createDataSource, options, isSaving, selectedDataSource, workspaceConstants }) => {
const [authStatus, setAuthStatus] = useState(null);
const { t } = useTranslation();
const hostUrl = window.public_config?.TOOLJET_HOST;
const subPathUrl = window.public_config?.SUB_PATH;
const fullUrl = `${hostUrl}${subPathUrl ? subPathUrl : '/'}oauth2/authorize`;
const redirectUri = fullUrl;
const selectOptions = [
{ value: 'v1', label: 'v1' },
{ value: 'v2', label: 'v2' },
];
function authSalesforce() {
const provider = 'salesforce';
const plugin_id = selectedDataSource?.plugin?.id;
const source_options = options;
setAuthStatus('waiting_for_url');

datasourceService
.fetchOauth2BaseUrl(provider, plugin_id, source_options)
.then((data) => {
console.log('options', source_options);
console.log('data from Oauth', data.url);
console.log('selectedDataSource.kind', selectedDataSource.kind);
akshaysasidrn marked this conversation as resolved.
Show resolved Hide resolved
localStorage.setItem('sourceWaitingForOAuth', 'newSource');
optionchanged('provider', provider).then(() => {
optionchanged('oauth2', true);
optionchanged('plugin_id', plugin_id);
});
setAuthStatus('waiting_for_token');
window.open(data.url);
})
.catch(({ error }) => {
toast.error(error);
setAuthStatus(null);
});
}
function saveDataSource() {
console.log('selectedDS', selectedDataSource);
optionchanged('code', localStorage.getItem('OAuthCode')).then(() => {
createDataSource();
});
}

return (
<div>
<div>
<label className="form-label text-muted mt-3">API version</label>
<Select
options={selectOptions}
value={options?.api_version?.value}
onChange={(value) => optionchanged('api_version', value)}
width={'100%'}
useMenuPortal={false}
/>
</div>
<div>
<label className="form-label mt-3">Client ID</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('client_id', e.target.value)}
value={options?.client_id?.value ?? ''}
placeholder="Client ID"
workspaceConstants={workspaceConstants}
/>
</div>
<div>
<label className="form-label mt-3">Client Secret</label>
<Input
type="text"
className="form-control"
onChange={(e) => optionchanged('client_secret', e.target.value)}
value={options?.client_secret?.value ?? ''}
placeholder="Client Secret"
workspaceConstants={workspaceConstants}
encrypted={true}
/>
</div>
<div>
<label className="form-label mt-3">Redirect URI</label>
<Input
value={redirectUri}
helpText="In Salesforce, use the URL above when prompted to enter an OAuth callback or redirect URL"
type="copyToClipboard"
disabled="true"
className="form-control"
/>
</div>
<div className="row mt-3">
<center>
{authStatus === 'waiting_for_token' && (
<div>
<Button
className={`m2 ${isSaving ? ' loading' : ''}`}
akshaysasidrn marked this conversation as resolved.
Show resolved Hide resolved
disabled={isSaving}
onClick={() => saveDataSource()}
>
{isSaving ? t('globals.saving', 'Saving...') : t('globals.saveDatasource', 'Save data source')}
</Button>
</div>
)}

{(!authStatus || authStatus === 'waiting_for_url') && (
<Button
className={`m2 ${authStatus === 'waiting_for_url' ? ' btn-loading' : ''}`}
disabled={isSaving}
onClick={() => authSalesforce()}
>
{t('slack.connectSalesforce', 'Connect to Salesforce')}
</Button>
)}
</center>
</div>
</div>
);
};
export default Salesforce;
5 changes: 3 additions & 2 deletions frontend/src/_services/datasource.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,13 @@ function setOauth2Token(dataSourceId, body, current_organization_id) {
return fetch(`${config.apiUrl}/data_sources/${dataSourceId}/authorize_oauth2`, requestOptions).then(handleResponse);
}

function fetchOauth2BaseUrl(provider) {
function fetchOauth2BaseUrl(provider, plugin_id = null, source_options = {}) {
const payload = { provider, ...(plugin_id && { plugin_id }), ...(source_options && { source_options }) };
const requestOptions = {
method: 'POST',
headers: authHeader(),
credentials: 'include',
body: JSON.stringify({ provider }),
body: JSON.stringify(payload),
};
return fetch(`${config.apiUrl}/data_sources/fetch_oauth2_base_url`, requestOptions).then(handleResponse);
}
6 changes: 6 additions & 0 deletions frontend/src/_styles/theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -10782,6 +10782,12 @@ tbody {
top: 5px;
cursor: pointer;
}
.copy-icon {
position: absolute;
right: 8px;
top: 5px;
cursor: pointer;
}

.form-control {
padding-right: 2.2rem;
Expand Down
36 changes: 35 additions & 1 deletion frontend/src/_ui/Input/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import React, { useEffect, useState } from 'react';
import cx from 'classnames';
import OrgConstantVariablesPreviewBox from '@/_components/OrgConstantsVariablesResolver';
import SolidIcon from '../Icon/SolidIcons';
import Button from '@/_ui/Button';
import { toast } from 'react-hot-toast';

const Input = ({ helpText, ...props }) => {
const { workspaceVariables, workspaceConstants, value, type, disabled, encrypted } = props;
const [isFocused, setIsFocused] = useState(false);
const [isCopied, setIsCopied] = useState(false);
const [showPasswordProps, setShowPasswordProps] = useState({
inputType: type,
iconType: 'eyedisable',
Expand All @@ -19,6 +22,21 @@ const Input = ({ helpText, ...props }) => {
}
};

const handleCopyToClipboard = async () => {
if (type === 'copyToClipboard') {
try {
await navigator.clipboard.writeText(value);
toast.success('Copied to clipboard');
setIsCopied(true);
setTimeout(() => {
setIsCopied(false);
}, 4000);
} catch (err) {
console.error('Failed to copy text: ', err);
}
}
};

useEffect(() => {
if (disabled && encrypted) setShowPasswordProps({ inputType: 'password', iconType: 'eyedisable' });
}, [disabled]);
Expand All @@ -27,15 +45,31 @@ const Input = ({ helpText, ...props }) => {

return (
<div className="tj-app-input">
<div className={cx('', { 'tj-app-input-wrapper': type === 'password' || encrypted })}>
<div
className={cx('', { 'tj-app-input-wrapper': type === 'password' || type === 'copyToClipboard' || encrypted })}
>
<input {...props} type={inputType} onFocus={() => setIsFocused(true)} onBlur={() => setIsFocused(false)} />
{(type === 'password' || encrypted) && (
<div onClick={!disabled && toggleShowPassword}>
{' '}
<SolidIcon className="eye-icon" name={iconType} />
</div>
)}
{type === 'copyToClipboard' &&
value &&
(!isCopied ? (
<div style={{ cursor: 'pointer' }} onClick={handleCopyToClipboard}>
{' '}
<SolidIcon className="copy-icon" name="copy" />
</div>
) : (
<div style={{ color: 'green' }}>
{' '}
<span>Copied!</span>
</div>
))}
</div>

<OrgConstantVariablesPreviewBox
workspaceVariables={workspaceVariables}
workspaceConstants={workspaceConstants}
Expand Down
2 changes: 1 addition & 1 deletion marketplace/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions marketplace/plugins/salesforce/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
lib/*.d.*
lib/*.js
lib/*.js.map
dist/*
4 changes: 4 additions & 0 deletions marketplace/plugins/salesforce/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

# Salesforce

Documentation on: https://docs.tooljet.com/docs/data-sources/salesforce
7 changes: 7 additions & 0 deletions marketplace/plugins/salesforce/__tests__/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict';

const salesforce = require('../lib');

describe('salesforce', () => {
it.todo('needs tests');
});
1 change: 1 addition & 0 deletions marketplace/plugins/salesforce/lib/icon.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.