From cea948710e08cf5a6bcce423d23ef5b3339a8f9f Mon Sep 17 00:00:00 2001 From: arpitnath Date: Sun, 14 May 2023 01:40:48 +0530 Subject: [PATCH 1/3] changing scope of API key from user to workspace --- frontend/src/CopilotSettings/CopilotSetting.jsx | 6 ++++-- frontend/src/Editor/QueryManager/Transformation.jsx | 5 +++-- server/src/controllers/copilot.controller.ts | 2 +- server/src/services/copilot.service.ts | 6 +++--- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frontend/src/CopilotSettings/CopilotSetting.jsx b/frontend/src/CopilotSettings/CopilotSetting.jsx index f4940befa3..9e12e8d351 100644 --- a/frontend/src/CopilotSettings/CopilotSetting.jsx +++ b/frontend/src/CopilotSettings/CopilotSetting.jsx @@ -8,10 +8,12 @@ import { Button } from '@/_ui/LeftSidebar'; import { useLocalStorageState } from '@/_hooks/use-local-storage'; export const CopilotSetting = () => { - const { current_organization_id } = authenticationService.currentSessionValue; + const { current_organization_id, current_organization_name } = authenticationService.currentSessionValue; + const currentOrgName = current_organization_name.replace(/\s/g, '').toLowerCase(); + const [copilotApiKey, setCopilotApiKey] = useState(''); const [isLoading, setIsLoading] = useState(false); - const [state, setState] = useLocalStorageState(`copilotEnabled-${current_organization_id}`, false); + const [state, setState] = useLocalStorageState(`copilotEnabled-${currentOrgName}`, false); const [copilotWorkspaceVarId, set] = useState(null); const saveCopilotApiKey = async (apikey) => { diff --git a/frontend/src/Editor/QueryManager/Transformation.jsx b/frontend/src/Editor/QueryManager/Transformation.jsx index e2f321ab4f..c33b48856a 100644 --- a/frontend/src/Editor/QueryManager/Transformation.jsx +++ b/frontend/src/Editor/QueryManager/Transformation.jsx @@ -19,7 +19,8 @@ import { authenticationService } from '@/_services'; export const Transformation = ({ changeOption, currentState, options, darkMode, queryId }) => { const { t } = useTranslation(); - const { current_organization_id } = authenticationService.currentSessionValue; + const { current_organization_name } = authenticationService.currentSessionValue; + const currentOrgName = current_organization_name.replace(/\s/g, '').toLowerCase(); const [lang, setLang] = React.useState(options?.transformationLanguage ?? 'javascript'); @@ -39,7 +40,7 @@ return [row for row in data if row['amount'] > 1000] const [state, setState] = useLocalStorageState('transformation', defaultValue); const [fetchingRecommendation, setFetchingRecommendation] = useState(false); - const isCopilotEnabled = localStorage.getItem(`copilotEnabled-${current_organization_id}`) === 'true'; + const isCopilotEnabled = localStorage.getItem(`copilotEnabled-${currentOrgName}`) === 'true'; const handleCallToGPT = async () => { setFetchingRecommendation(true); diff --git a/server/src/controllers/copilot.controller.ts b/server/src/controllers/copilot.controller.ts index deda4084f2..204d0f3697 100644 --- a/server/src/controllers/copilot.controller.ts +++ b/server/src/controllers/copilot.controller.ts @@ -31,6 +31,6 @@ export class CopilotController { @UseGuards(JwtAuthGuard) @Post('api-key') async validateCopilotAPIKey(@User() user, @Body() body: { secretKey: string }) { - return await this.copilotService.validateCopilotAPIKey(user.id, body.secretKey); + return await this.copilotService.validateCopilotAPIKey(user.organizationId, body.secretKey); } } diff --git a/server/src/services/copilot.service.ts b/server/src/services/copilot.service.ts index bce7357fde..a23b4e8ea3 100644 --- a/server/src/services/copilot.service.ts +++ b/server/src/services/copilot.service.ts @@ -32,7 +32,7 @@ export class CopilotService { query: query, context: context, language: language, - userId: userId, + workspaceId: orgnaizationId, }), }); @@ -42,13 +42,13 @@ export class CopilotService { }; } - async validateCopilotAPIKey(userId: string, secretKey: string) { + async validateCopilotAPIKey(workspaceId: string, secretKey: string) { const options = { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ userId: userId, action: 'get', apiKey: secretKey }), + body: JSON.stringify({ workspaceId: workspaceId, action: 'get', apiKey: secretKey }), }; const response = await fetch(`${process.env.COPILOT_API_ENDPOINT}/api-key`, options); From db412fcb1e47f829c5ab41db4f4e67b60c370c25 Mon Sep 17 00:00:00 2001 From: arpitnath Date: Mon, 15 May 2023 18:45:09 +0530 Subject: [PATCH 2/3] ui changes to handle admin settings and user settings --- .../src/CopilotSettings/ApiKeyContainer.jsx | 110 +++++++++++------- .../src/CopilotSettings/CopilotSetting.jsx | 76 ++++++++---- .../src/Editor/CodeBuilder/CodeHinter.jsx | 2 + .../QueryManager/CustomToggleSwitch.jsx | 16 ++- .../Editor/QueryManager/Transformation.jsx | 3 +- frontend/src/ManageOrgVars/ManageOrgVars.jsx | 4 +- .../src/OrganizationSettingsPage/index.jsx | 2 +- frontend/src/_components/Portal/Portal.jsx | 19 ++- frontend/src/_hooks/use-portal.jsx | 2 + 9 files changed, 158 insertions(+), 76 deletions(-) diff --git a/frontend/src/CopilotSettings/ApiKeyContainer.jsx b/frontend/src/CopilotSettings/ApiKeyContainer.jsx index 6e8032e4d6..f1a8c66c04 100644 --- a/frontend/src/CopilotSettings/ApiKeyContainer.jsx +++ b/frontend/src/CopilotSettings/ApiKeyContainer.jsx @@ -8,6 +8,7 @@ export const ApiKeyContainer = ({ isLoading = false, darkMode, isCopilotEnabled, + isAdmin = false, }) => { const [inputValue, setInputValue] = useState(copilotApiKey); @@ -19,57 +20,82 @@ export const ApiKeyContainer = ({ setInputValue(copilotApiKey); }, [copilotApiKey]); - return ( -
-
- - - API KEY - -
- + const AdminInfoComponent = () => { + return ( + <> +

Don't have an API key?

+
+ ToolJet Copilot + is currently in beta and provided on request. + Join our waitlist to be notified when API keys become available, or sign up for beta access to get started + today.
-
+
-
+ + ); + }; + + return ( +
+ {isAdmin && ( +
+ + + API KEY + +
+ +
+
+ +
+
+ )}
-

Don't have an API key?

-
- ToolJet Copilot - is currently in beta and provided on request. - Join our waitlist to be notified when API keys become available, or sign up for beta access to get started - today. -
-
- -
+ {isAdmin ? ( + + ) : ( + <> +
+ ToolJet Copilot + is currently in beta and provided on + request. Join our waitlist to be notified when API keys become available, or sign up for beta access to + get started today. +
+
+ Please note : + Copilot functionality is dependent on your workspace admin completing the setup process. +
+ + )}
diff --git a/frontend/src/CopilotSettings/CopilotSetting.jsx b/frontend/src/CopilotSettings/CopilotSetting.jsx index 9e12e8d351..dc6c50496c 100644 --- a/frontend/src/CopilotSettings/CopilotSetting.jsx +++ b/frontend/src/CopilotSettings/CopilotSetting.jsx @@ -8,7 +8,7 @@ import { Button } from '@/_ui/LeftSidebar'; import { useLocalStorageState } from '@/_hooks/use-local-storage'; export const CopilotSetting = () => { - const { current_organization_id, current_organization_name } = authenticationService.currentSessionValue; + const { current_organization_id, current_organization_name, admin } = authenticationService.currentSessionValue; const currentOrgName = current_organization_name.replace(/\s/g, '').toLowerCase(); const [copilotApiKey, setCopilotApiKey] = useState(''); @@ -32,7 +32,10 @@ export const CopilotSetting = () => { console.log(err); return toast.error('Something went wrong'); }) - .finally(() => setIsLoading(false)); + .finally(() => { + setIsLoading(false); + orgEnvironmentVariableService.create(`copilot_enabled-${current_organization_id}`, 'true', 'client', false); + }); } if (isCopilotApiKeyPresent === true && copilotWorkspaceVarId) { @@ -74,33 +77,56 @@ export const CopilotSetting = () => { }); }; - useEffect(() => { - if (!state) { - return; - } - - orgEnvironmentVariableService.getVariables().then((data) => { - const isCopilotApiKeyPresent = data.variables.some( - (variable) => variable.variable_name === `copilot_api_key-${current_organization_id}` - ); - - const shouldUpdate = isCopilotApiKeyPresent; - if (shouldUpdate) { - const copilotVariableId = data.variables.find( - (variable) => variable.variable_name === `copilot_api_key-${current_organization_id}` - )?.id; - const key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; - set(copilotVariableId); - setCopilotApiKey(key); - } + const updateCopilotEnabled = (id, value, variableName) => { + return orgEnvironmentVariableService.update(id, variableName, `${value}`).catch((err) => { + console.log(err); }); + }; + + useEffect(() => { + if (!admin) { + orgEnvironmentVariableService.getVariables().then((data) => { + const { value } = data.variables.find( + (variable) => variable.variable_name === `copilot_enabled-${current_organization_id}` + ); + + setState(value === 'true'); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (admin) { + orgEnvironmentVariableService.getVariables().then((data) => { + const isCopilotApiKeyPresent = data.variables.some( + (variable) => variable.variable_name === `copilot_api_key-${current_organization_id}` + ); + + const { id, variable_name, value } = data.variables.find( + (variable) => variable.variable_name === `copilot_enabled-${current_organization_id}` + ); + + if (value !== `${state}`) updateCopilotEnabled(id, state, variable_name); + + const shouldUpdate = state && isCopilotApiKeyPresent; + if (shouldUpdate) { + const copilotVariableId = data.variables.find( + (variable) => variable.variable_name === `copilot_api_key-${current_organization_id}` + )?.id; + const key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; + set(copilotVariableId); + setCopilotApiKey(key); + } + }); + } return () => { setCopilotApiKey(''); set(null); }; // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [state]); const darkMode = localStorage.getItem('darkMode') === 'true'; @@ -113,7 +139,7 @@ export const CopilotSetting = () => { padding: '4rem', }} > - +
{ isLoading={isLoading} darkMode={darkMode} isCopilotEnabled={state} + isAdmin={admin} />
@@ -132,7 +159,7 @@ export const CopilotSetting = () => { ); }; -const Container = ({ children, isCopilotEnabled, handleCopilotToggle, darkMode }) => { +const Container = ({ children, isCopilotEnabled, handleCopilotToggle, darkMode, isAdmin }) => { return (
@@ -153,6 +180,7 @@ const Container = ({ children, isCopilotEnabled, handleCopilotToggle, darkMode } toggleSwitchFunction={handleCopilotToggle} action="enableTransformation" dataCy={'copilot'} + disabled={!isAdmin} /> diff --git a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx index 2729c35c48..55d10832ef 100644 --- a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx +++ b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx @@ -69,6 +69,7 @@ export function CodeHinter({ popOverCallback, cyLabel = '', callgpt = () => null, + isCopilotEnabled = false, }) { const darkMode = localStorage.getItem('darkMode') === 'true'; const options = { @@ -323,6 +324,7 @@ export function CodeHinter({ /> )} { return ( -
+
@@ -31,6 +38,13 @@ export const CustomToggleSwitch = ({ {label} )} + {disabled && dataCy === 'copilot' && ( + + )}
); }; diff --git a/frontend/src/Editor/QueryManager/Transformation.jsx b/frontend/src/Editor/QueryManager/Transformation.jsx index c33b48856a..881f67e131 100644 --- a/frontend/src/Editor/QueryManager/Transformation.jsx +++ b/frontend/src/Editor/QueryManager/Transformation.jsx @@ -292,7 +292,7 @@ return [row for row in data if row['amount'] > 1000] styles={{ width: '100%', fontSize: '12px', fontWeight: 500, borderColor: darkMode && 'transparent' }} disabled={!isCopilotEnabled} > - +
@@ -318,6 +318,7 @@ return [row for row in data if row['amount'] > 1000] componentName={`transformation`} cyLabel={'transformation-input'} callgpt={handleCallToGPT} + isCopilotEnabled={isCopilotEnabled} />
)} diff --git a/frontend/src/ManageOrgVars/ManageOrgVars.jsx b/frontend/src/ManageOrgVars/ManageOrgVars.jsx index 40d09de3b7..ba435bea8b 100644 --- a/frontend/src/ManageOrgVars/ManageOrgVars.jsx +++ b/frontend/src/ManageOrgVars/ManageOrgVars.jsx @@ -74,9 +74,7 @@ class ManageOrgVarsComponent extends React.Component { }); orgEnvironmentVariableService.getVariables().then((data) => { - const variables = _.cloneDeep(data.variables)?.filter( - ({ variable_name }) => !/copilot_api_key/.test(variable_name) - ); + const variables = _.cloneDeep(data.variables)?.filter(({ variable_name }) => !/copilot_/.test(variable_name)); this.setState({ variables: variables, isLoading: false, diff --git a/frontend/src/OrganizationSettingsPage/index.jsx b/frontend/src/OrganizationSettingsPage/index.jsx index 23a8b7b206..519f646f82 100644 --- a/frontend/src/OrganizationSettingsPage/index.jsx +++ b/frontend/src/OrganizationSettingsPage/index.jsx @@ -53,7 +53,7 @@ export function OrganizationSettings(props) { {sideBarNavs.map((item, index) => { return ( <> - {(admin || item == 'Workspace variables') && ( + {(admin || item == 'Workspace variables' || item == 'Copilot') && ( { - const { isOpen, trigger, styles, className, componentName, dragResizePortal, callgpt } = restProps; + const { isOpen, trigger, styles, className, componentName, dragResizePortal, callgpt, isCopilotEnabled } = restProps; + const [name, setName] = React.useState(componentName); const handleClose = (e) => { e.stopPropagation(); @@ -45,6 +46,7 @@ const Portal = ({ children, ...restProps }) => { componentName={name} dragResizePortal={dragResizePortal} callgpt={callgpt} + isCopilotEnabled={isCopilotEnabled} > {children} @@ -57,7 +59,17 @@ const Container = ({ children, ...restProps }) => { return {children}; }; -const Modal = ({ children, handleClose, portalStyles, styles, componentName, darkMode, dragResizePortal, callgpt }) => { +const Modal = ({ + children, + handleClose, + portalStyles, + styles, + componentName, + darkMode, + dragResizePortal, + callgpt, + isCopilotEnabled, +}) => { const [loading, setLoading] = React.useState(false); const handleCallGpt = () => { @@ -66,7 +78,6 @@ const Modal = ({ children, handleClose, portalStyles, styles, componentName, dar callgpt().then(() => setLoading(false)); }; - const isCopilotEnabled = localStorage.getItem('copilotEnabled') === 'true'; const includeGPT = ['Runjs', 'Runpy', 'transformation'].includes(componentName) && isCopilotEnabled; const renderModalContent = () => ( @@ -95,7 +106,7 @@ const Modal = ({ children, handleClose, portalStyles, styles, componentName, dar classNames={`${loading ? (darkMode ? 'btn-loading' : 'button-loading') : ''}`} styles={{ width: '100%', fontSize: '12px', fontWeight: 500, borderColor: darkMode && 'transparent' }} > - +
)} diff --git a/frontend/src/_hooks/use-portal.jsx b/frontend/src/_hooks/use-portal.jsx index acfaa79fef..35d83174ed 100644 --- a/frontend/src/_hooks/use-portal.jsx +++ b/frontend/src/_hooks/use-portal.jsx @@ -13,6 +13,7 @@ const usePortal = ({ children, ...restProps }) => { selectors = {}, dragResizePortal = false, callgpt, + isCopilotEnabled = false, } = restProps; const renderCustomComponent = ({ component, ...restProps }) => { @@ -38,6 +39,7 @@ const usePortal = ({ children, ...restProps }) => { componentName={componentName} dragResizePortal={dragResizePortal} callgpt={callgpt} + isCopilotEnabled={isCopilotEnabled} >
{React.cloneElement(children, { ...styleProps })} From 91c971832f9c7e5967b967cd95bbcff737c428c2 Mon Sep 17 00:00:00 2001 From: arpitnath Date: Tue, 16 May 2023 12:29:34 +0530 Subject: [PATCH 3/3] current organization Id to be passed from frontend --- frontend/src/CopilotSettings/CopilotSetting.jsx | 2 +- frontend/src/_services/copilot.service.js | 3 ++- server/src/controllers/copilot.controller.ts | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frontend/src/CopilotSettings/CopilotSetting.jsx b/frontend/src/CopilotSettings/CopilotSetting.jsx index dc6c50496c..b49ae1194d 100644 --- a/frontend/src/CopilotSettings/CopilotSetting.jsx +++ b/frontend/src/CopilotSettings/CopilotSetting.jsx @@ -63,7 +63,7 @@ export const CopilotSetting = () => { const validateApiKey = (apiKey) => { return new Promise((resolve, reject) => { copilotService - .validateCopilotAPIKey(apiKey) + .validateCopilotAPIKey(apiKey, current_organization_id) .then(({ status }) => { if (status === 'ok') { return resolve(true); diff --git a/frontend/src/_services/copilot.service.js b/frontend/src/_services/copilot.service.js index b1cb01e4f1..776edb7078 100644 --- a/frontend/src/_services/copilot.service.js +++ b/frontend/src/_services/copilot.service.js @@ -20,9 +20,10 @@ async function getCopilotRecommendations(options) { return data || {}; } -function validateCopilotAPIKey(key) { +function validateCopilotAPIKey(key, organizationId) { const body = { secretKey: key, + organizationId, }; const requestOptions = { method: 'POST', headers: authHeader(), credentials: 'include', body: JSON.stringify(body) }; diff --git a/server/src/controllers/copilot.controller.ts b/server/src/controllers/copilot.controller.ts index 204d0f3697..e95f7320a5 100644 --- a/server/src/controllers/copilot.controller.ts +++ b/server/src/controllers/copilot.controller.ts @@ -30,7 +30,7 @@ export class CopilotController { @UseGuards(JwtAuthGuard) @Post('api-key') - async validateCopilotAPIKey(@User() user, @Body() body: { secretKey: string }) { - return await this.copilotService.validateCopilotAPIKey(user.organizationId, body.secretKey); + async validateCopilotAPIKey(@User() user, @Body() body: { secretKey: string; organizationId: string }) { + return await this.copilotService.validateCopilotAPIKey(body.organizationId, body.secretKey); } }