diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx index 9963867ad69..80eef0fa180 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewContext.tsx @@ -22,6 +22,7 @@ export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren<{} const [flags, setFlags] = useState({ [LOCAL_STORAGE_KEYS.UI_PREVIEW_NAVIGATION_LAYOUT]: false, [LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL]: false, + [LOCAL_STORAGE_KEYS.UI_PREVIEW_RLS_AI_ASSISTANT]: false, }) useEffect(() => { @@ -31,6 +32,8 @@ export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren<{} localStorage.getItem(LOCAL_STORAGE_KEYS.UI_PREVIEW_NAVIGATION_LAYOUT) === 'true', [LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL]: localStorage.getItem(LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL) === 'true', + [LOCAL_STORAGE_KEYS.UI_PREVIEW_RLS_AI_ASSISTANT]: + localStorage.getItem(LOCAL_STORAGE_KEYS.UI_PREVIEW_RLS_AI_ASSISTANT) === 'true', }) } }, []) @@ -59,3 +62,8 @@ export const useIsAPIDocsSidePanelEnabled = () => { const { flags } = useFeaturePreviewContext() return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL] } + +export const useIsRLSAIAssistantEnabled = () => { + const { flags } = useFeaturePreviewContext() + return flags[LOCAL_STORAGE_KEYS.UI_PREVIEW_RLS_AI_ASSISTANT] +} diff --git a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx index ab87afcb1c5..7e661102e68 100644 --- a/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx +++ b/apps/studio/components/interfaces/App/FeaturePreview/FeaturePreviewModal.tsx @@ -10,24 +10,32 @@ import Telemetry from 'lib/telemetry' import { useAppStateSnapshot } from 'state/app-state' import APISidePanelPreview from './APISidePanelPreview' import { useFeaturePreviewContext } from './FeaturePreviewContext' - -// [Ivan] We should probably move this to a separate file, together with LOCAL_STORAGE_KEYS. We should make adding new feature previews as simple as possible. - -const FEATURE_PREVIEWS: { key: string; name: string; content: any; discussionsUrl?: string }[] = [ - // { - // key: LOCAL_STORAGE_KEYS.UI_PREVIEW_NAVIGATION_LAYOUT, - // name: 'Global navigation update', - // content: null, - // }, - { - key: LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL, - name: 'Project API documentation', - content: , - discussionsUrl: 'https://github.com/orgs/supabase/discussions/18038', - }, -] +import RLSAIAssistantPreview from './RLSAIAssistantPreview' +import { useFlag } from 'hooks' const FeaturePreviewModal = () => { + const isAiAssistantEnabled = useFlag('policyEditorWithAi') + + // [Ivan] We should probably move this to a separate file, together with LOCAL_STORAGE_KEYS. We should make adding new feature previews as simple as possible. + const FEATURE_PREVIEWS: { key: string; name: string; content: any; discussionsUrl?: string }[] = [ + { + key: LOCAL_STORAGE_KEYS.UI_PREVIEW_API_SIDE_PANEL, + name: 'Project API documentation', + content: , + discussionsUrl: 'https://github.com/orgs/supabase/discussions/18038', + }, + ...(isAiAssistantEnabled + ? [ + { + key: LOCAL_STORAGE_KEYS.UI_PREVIEW_RLS_AI_ASSISTANT, + name: 'AI Assistant for RLS policies', + content: , + discussionsUrl: '/', // Need to update + }, + ] + : []), + ] + const router = useRouter() const snap = useAppStateSnapshot() const telemetryProps = useTelemetryProps() diff --git a/apps/studio/components/interfaces/App/FeaturePreview/RLSAIAssistantPreview.tsx b/apps/studio/components/interfaces/App/FeaturePreview/RLSAIAssistantPreview.tsx new file mode 100644 index 00000000000..f885aac7a5c --- /dev/null +++ b/apps/studio/components/interfaces/App/FeaturePreview/RLSAIAssistantPreview.tsx @@ -0,0 +1,50 @@ +import { useParams } from 'common' +import { Markdown } from 'components/interfaces/Markdown' +import { BASE_PATH } from 'lib/constants' +import Image from 'next/image' + +const RLSAIAssistantPreview = () => { + const { ref } = useParams() + + return ( +
+
+ + +
+ api-docs-side-panel-preview +
+

Enabling this preview will:

+
    +
  • + +
  • +
  • + +
  • +
+
+
+ ) +} + +export default RLSAIAssistantPreview diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/AIPolicyChat.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/AIPolicyChat.tsx index 1cb65058957..46a917878ca 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/AIPolicyChat.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/AIPolicyChat.tsx @@ -6,16 +6,27 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { useForm } from 'react-hook-form' import { AiIcon, + Button, FormControl_Shadcn_, FormField_Shadcn_, FormItem_Shadcn_, Form_Shadcn_, + IconSettings, Input_Shadcn_, } from 'ui' import * as z from 'zod' import { useProfile } from 'lib/profile' import Message from './Message' +import { useAppStateSnapshot } from 'state/app-state' + +interface AIPolicyChatProps { + messages: OpenAI.Beta.Threads.Messages.ThreadMessage[] + loading: boolean + onSubmit: (s: string) => void + onDiff: (s: string) => void + onChange: (value: boolean) => void +} export const AIPolicyChat = ({ messages, @@ -23,15 +34,11 @@ export const AIPolicyChat = ({ onSubmit, onDiff, onChange, -}: { - messages: OpenAI.Beta.Threads.Messages.ThreadMessage[] - loading: boolean - onSubmit: (s: string) => void - onDiff: (s: string) => void - onChange: (value: boolean) => void -}) => { +}: AIPolicyChatProps) => { const { profile } = useProfile() + const snap = useAppStateSnapshot() const bottomRef = useRef(null) + const name = compact([profile?.first_name, profile?.last_name]).join(' ') const sorted = useMemo(() => { return sortBy(messages, (m) => m.created_at).filter((m) => { @@ -82,7 +89,17 @@ export const AIPolicyChat = ({ }, how can I help you? I'm powered by AI, so surprises and mistakes are possible. Make sure to verify any generated code or suggestions, and share feedback so that we can learn and improve.`} - /> + > +
+ +
+ {sorted.map((m, idx) => ( { + if (policy === undefined) { + return ` +CREATE POLICY *name* ON *table_name*\n +AS PERMISSIVE -- PERMISSIVE | RESTRICTIVE\n +FOR ALL -- ALL | SELECT | INSERT | UPDATE | DELETE\n +TO *role_name* -- Default: public\n +USING ( *using_expression* )\n +WITH CHECK ( *check_expression* ); +`.trim() + } else { + let expression = '' + if (policy.definition !== null && policy.definition !== undefined) { + expression += `USING ( *${policy.definition}* )${ + policy.check === null || policy.check === undefined ? ';' : '' + }\n` + } + if (policy.check !== null && policy.check !== undefined) { + expression += `WITH CHECK ( *${policy.check}* );\n` + } + + return ` +BEGIN;\n + \n +-- To update your policy definition\n +ALTER POLICY "${policy.name}"\n +ON "${policy.schema}"."${policy.table}"\n +TO *${policy.roles.join(', ')}*\n +${expression} + \n +-- To rename your policy\n +ALTER POLICY "${policy.name}"\n +ON "${policy.schema}"."${policy.table}"\n +RENAME TO "*New Policy Name*";\n + \n +COMMIT; +`.trim() + } +} diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/AIPolicyHeader.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/AIPolicyHeader.tsx index e3d0d866e12..72ed2bfef49 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/AIPolicyHeader.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/AIPolicyHeader.tsx @@ -1,24 +1,37 @@ +import { PostgresPolicy } from '@supabase/postgres-meta' +import styles from '@ui/layout/ai-icon-animation/ai-icon-animation-style.module.css' +import clsx from 'clsx' +import { X } from 'lucide-react' import { AiIcon, Button, SheetClose_Shadcn_, SheetHeader_Shadcn_, SheetTitle_Shadcn_, cn } from 'ui' -import styles from '@ui/layout/ai-icon-animation/ai-icon-animation-style.module.css' -import { X } from 'lucide-react' - export const AIPolicyHeader = ({ + selectedPolicy, assistantVisible, setAssistantVisible, }: { + selectedPolicy?: PostgresPolicy assistantVisible: boolean setAssistantVisible: (v: boolean) => void }) => { return ( -
- +
+ Close -
- Create a new row level security policy +
+ + {selectedPolicy !== undefined + ? `Update policy: ${selectedPolicy.name}` + : 'Create a new row level security policy'} +
) }) diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/RLSCodeEditor.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/RLSCodeEditor.tsx index dcaff4c2b9d..ff22b95e239 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/RLSCodeEditor.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/RLSCodeEditor.tsx @@ -16,35 +16,17 @@ interface RLSCodeEditorProps { wrapperClassName?: string className?: string value?: string + placeholder?: string editorRef: MutableRefObject } -// const placeholderText = ` -// CREATE POLICY *name* ON *table_name*\n -// [ AS { PERMISSIVE | RESTRICTIVE } ]\n -// [ FOR { ALL | SELECT | INSERT | UPDATE | DELETE } ]\n -// [ TO *role_name* ]\n -// [ USING ( *using_expression* ) ]\n -// [ WITH CHECK ( *check_expression* ) ]; -// `.trim() - -const placeholderText = ` -CREATE POLICY *name* ON *table_name*\n -AS PERMISSIVE -- PERMISSIVE | RESTRICTIVE\n -FOR ALL -- ALL | SELECT | INSERT | UPDATE | DELETE\n -TO *role_name* -- Default: public\n -USING ( *using_expression* )\n -WITH CHECK ( *check_expression* );\n - \n --- Docs: https://www.postgresql.org/docs/current/sql-createpolicy.html -`.trim() - const RLSCodeEditor = ({ id, defaultValue, wrapperClassName, className, value, + placeholder, editorRef, }: RLSCodeEditorProps) => { const hasValue = useRef() @@ -55,8 +37,8 @@ const RLSCodeEditor = ({ hasValue.current = editor.createContextKey('hasValue', false) - const placeholder = document.querySelector('.monaco-placeholder') as HTMLElement | null - if (placeholder) placeholder.style.display = 'block' + const placeholderEl = document.querySelector('.monaco-placeholder') as HTMLElement | null + if (placeholderEl) placeholderEl.style.display = 'block' editor.addCommand( monaco.KeyCode.Tab, @@ -66,7 +48,7 @@ const RLSCodeEditor = ({ // @ts-ignore identifier: 'add-placeholder', range: new monaco.Range(1, 1, 1, 1), - text: placeholderText + text: (placeholder ?? '') .split('\n\n') .join('\n') .replaceAll('*', '') @@ -83,12 +65,12 @@ const RLSCodeEditor = ({ const onChange: OnChange = (value) => { hasValue.current.set((value ?? '').length > 0) - const placeholder = document.querySelector('.monaco-placeholder') as HTMLElement | null - if (placeholder) { + const placeholderEl = document.querySelector('.monaco-placeholder') as HTMLElement | null + if (placeholderEl) { if (!value) { - placeholder.style.display = 'block' + placeholderEl.style.display = 'block' } else { - placeholder.style.display = 'none' + placeholderEl.style.display = 'none' } } } @@ -122,12 +104,14 @@ const RLSCodeEditor = ({ onMount={onMount} onChange={onChange} /> -
- -
+ {placeholder !== undefined && ( +
+ +
+ )} ) } diff --git a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx index 3b2f25e5234..42cf0e7e381 100644 --- a/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/AIPolicyEditorPanel/index.tsx @@ -1,3 +1,4 @@ +import { PostgresPolicy } from '@supabase/postgres-meta' import { FileDiff } from 'lucide-react' import dynamic from 'next/dynamic' import { ThreadMessage } from 'openai/resources/beta/threads/messages/messages' @@ -18,7 +19,7 @@ import { QueryResponseError, useExecuteSqlMutation } from 'data/sql/execute-sql- import { useSelectedProject, useStore } from 'hooks' import { uuidv4 } from 'lib/helpers' import { AIPolicyChat } from './AIPolicyChat' -import { generateThreadMessage } from './AIPolicyEditorPanel.utils' +import { generatePlaceholder, generateThreadMessage } from './AIPolicyEditorPanel.utils' import { AIPolicyHeader } from './AIPolicyHeader' import QueryError from './QueryError' import RLSCodeEditor from './RLSCodeEditor' @@ -30,6 +31,7 @@ const DiffEditor = dynamic( interface AIPolicyEditorPanelProps { visible: boolean + selectedPolicy?: PostgresPolicy onSelectCancel: () => void } @@ -38,6 +40,7 @@ interface AIPolicyEditorPanelProps { */ export const AIPolicyEditorPanel = memo(function ({ visible, + selectedPolicy, onSelectCancel, }: AIPolicyEditorPanelProps) { const { meta } = useStore() @@ -45,6 +48,7 @@ export const AIPolicyEditorPanel = memo(function ({ const editorRef = useRef(null) const diffEditorRef = useRef(null) + const placeholder = generatePlaceholder(selectedPolicy) const [error, setError] = useState() // [Joshen] Separate state here as there's a delay between submitting and the API updating the loading status @@ -142,9 +146,9 @@ export const AIPolicyEditorPanel = memo(function ({ const errorLines = error?.formattedError.split('\n').filter((x: string) => x.length > 0).length ?? 0 - const createNewPolicy = useCallback(() => { + const onExecuteSQL = useCallback(() => { // clean up the sql before sending - const policy = editorRef.current?.getValue().replaceAll('\n', ' ').replaceAll(' ', ' ') + const policy = editorRef.current?.getValue().replaceAll(' ', ' ') if (policy) { setError(undefined) @@ -255,10 +259,14 @@ export const AIPolicyEditorPanel = memo(function ({ onClosingPanel()}>
@@ -311,6 +319,7 @@ export const AIPolicyEditorPanel = memo(function ({ wrapperClassName={incomingChange ? '!hidden' : ''} defaultValue={''} editorRef={editorRef} + placeholder={placeholder} />
@@ -325,7 +334,7 @@ export const AIPolicyEditorPanel = memo(function ({ loading={isExecuting} htmlType="submit" disabled={isExecuting || incomingChange !== undefined} - onClick={() => createNewPolicy()} + onClick={() => onExecuteSQL()} > Save policy diff --git a/apps/studio/components/interfaces/Auth/Policies/Policies.tsx b/apps/studio/components/interfaces/Auth/Policies/Policies.tsx index c0e2b921b45..3e27d58fe0a 100644 --- a/apps/studio/components/interfaces/Auth/Policies/Policies.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/Policies.tsx @@ -14,19 +14,27 @@ import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' import ConfirmModal from 'components/ui/Dialogs/ConfirmDialog' import InformationBox from 'components/ui/InformationBox' import { tableKeys } from 'data/tables/keys' +import { useIsRLSAIAssistantEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' interface PoliciesProps { tables: PostgresTable[] hasTables: boolean isLocked: boolean + onSelectEditPolicy: (policy: PostgresPolicy) => void } -const Policies = ({ tables, hasTables, isLocked }: PoliciesProps) => { +const Policies = ({ + tables, + hasTables, + isLocked, + onSelectEditPolicy: onSelectEditPolicyAI, +}: PoliciesProps) => { const router = useRouter() const { ref } = useParams() const { ui, meta } = useStore() const queryClient = useQueryClient() + const isAiAssistantEnabled = useIsRLSAIAssistantEnabled() const roles = meta.roles.list((role: PostgresRole) => !meta.roles.systemRoles.includes(role.name)) const [selectedSchemaAndTable, setSelectedSchemaAndTable] = useState({}) @@ -57,8 +65,12 @@ const Policies = ({ tables, hasTables, isLocked }: PoliciesProps) => { } const onSelectEditPolicy = (policy: any) => { - setSelectedPolicyToEdit(policy) - setSelectedSchemaAndTable({ schema: policy.schema, table: policy.table }) + if (isAiAssistantEnabled) { + onSelectEditPolicyAI(policy) + } else { + setSelectedPolicyToEdit(policy) + setSelectedSchemaAndTable({ schema: policy.schema, table: policy.table }) + } } const onSelectDeletePolicy = (policy: any) => { diff --git a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx index 5ddb0f1c916..c72ee734ecc 100644 --- a/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx +++ b/apps/studio/components/interfaces/Auth/Policies/PolicyTableRow/PolicyTableRowHeader.tsx @@ -7,6 +7,7 @@ import { useRouter } from 'next/router' import { Badge, Button, IconLock } from 'ui' import { useCheckPermissions } from 'hooks' +import { useIsRLSAIAssistantEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' interface PolicyTableRowHeaderProps { table: PostgresTable @@ -23,6 +24,7 @@ const PolicyTableRowHeader = ({ }: PolicyTableRowHeaderProps) => { const router = useRouter() const { ref } = router.query + const isAiAssistantEnabled = useIsRLSAIAssistantEnabled() const canToggleRLS = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'tables') const canCreatePolicies = useCheckPermissions(PermissionAction.TENANT_SQL_ADMIN_WRITE, 'policies') @@ -46,36 +48,7 @@ const PolicyTableRowHeader = ({
{!isLocked && (
-
- - - - - {!canCreatePolicies && ( - - - -
- - You need additional permissions to create RLS policies - -
-
-
- )} -
+
+ + {!canCreatePolicies && ( + + + +
+ + You need additional permissions to create RLS policies + +
+
+
+ )} +
+ )}
)} diff --git a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx index 3660bfb6af3..115521bf062 100644 --- a/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx +++ b/apps/studio/components/interfaces/SQLEditor/SQLEditor.tsx @@ -31,6 +31,7 @@ import { SqlSnippet } from 'data/content/sql-snippets-query' import { useEntityDefinitionsQuery } from 'data/database/entity-definitions-query' import { useExecuteSqlMutation } from 'data/sql/execute-sql-mutation' import { useFormatQueryMutation } from 'data/sql/format-sql-query' +import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { isError } from 'data/utils/error-check' import { useFlag, @@ -44,10 +45,10 @@ import { IS_PLATFORM, OPT_IN_TAGS } from 'lib/constants' import { uuidv4 } from 'lib/helpers' import { useProfile } from 'lib/profile' import Telemetry from 'lib/telemetry' +import { useAppStateSnapshot } from 'state/app-state' import { getSqlEditorStateSnapshot, useSqlEditorStateSnapshot } from 'state/sql-editor' import { subscriptionHasHipaaAddon } from '../Billing/Subscription/Subscription.utils' import AISchemaSuggestionPopover from './AISchemaSuggestionPopover' -import AISettingsModal from './AISettingsModal' import { sqlAiDisclaimerComment, untitledSnippetTitle } from './SQLEditor.constants' import { ContentDiff, @@ -63,7 +64,6 @@ import { getDiffTypeDropdownLabel, } from './SQLEditor.utils' import UtilityPanel from './UtilityPanel/UtilityPanel' -import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' // Load the monaco editor client-side only (does not behave well server-side) const MonacoEditor = dynamic(() => import('./MonacoEditor'), { ssr: false }) @@ -99,6 +99,7 @@ const SQLEditor = () => { const { profile } = useProfile() const project = useSelectedProject() const organization = useSelectedOrganization() + const appSnap = useAppStateSnapshot() const snap = useSqlEditorStateSnapshot() const { mutate: formatQuery } = useFormatQueryMutation() @@ -121,8 +122,6 @@ const SQLEditor = () => { const hasHipaaAddon = subscriptionHasHipaaAddon(subscription) const [isAiOpen, setIsAiOpen] = useLocalStorageQuery('supabase_sql-editor-ai-open', true) - - const [isAISettingsOpen, setIsAISettingsOpen] = useState(false) const [isConfirmModalOpen, setIsConfirmModalOpen] = useState(false) const selectedOrganization = useSelectedOrganization() @@ -514,7 +513,6 @@ const SQLEditor = () => { setDebugSolution, }} > - setIsAISettingsOpen(false)} /> { {isAiOpen && supabaseAIEnabled && !hasHipaaAddon && ( { - setIsAISettingsOpen(true) + appSnap.setShowAiSettingsModal(true) }} > {
+ @@ -269,6 +271,7 @@ export const ProjectLayoutNonBlocking = ({ + diff --git a/apps/studio/components/interfaces/SQLEditor/AISettingsModal.tsx b/apps/studio/components/ui/AISettingsModal.tsx similarity index 88% rename from apps/studio/components/interfaces/SQLEditor/AISettingsModal.tsx rename to apps/studio/components/ui/AISettingsModal.tsx index 8ab1da2e8b8..a511b7df804 100644 --- a/apps/studio/components/interfaces/SQLEditor/AISettingsModal.tsx +++ b/apps/studio/components/ui/AISettingsModal.tsx @@ -1,13 +1,13 @@ -import { ModalProps } from '@ui/components/Modal/Modal' -import { useLocalStorageQuery, useSelectedOrganization, useStore } from 'hooks' -import { IS_PLATFORM, OPT_IN_TAGS } from 'lib/constants' import Link from 'next/link' import { Alert, IconExternalLink, Modal, Toggle } from 'ui' -export interface AISettingsModalProps { - visible: boolean -} -const AISettingsModal = (props: ModalProps) => { +import { useLocalStorageQuery, useSelectedOrganization, useStore } from 'hooks' +import { IS_PLATFORM, OPT_IN_TAGS } from 'lib/constants' +import { useAppStateSnapshot } from 'state/app-state' + +const AISettingsModal = () => { + const snap = useAppStateSnapshot() + const selectedOrganization = useSelectedOrganization() const isOptedInToAI = selectedOrganization?.opt_in_tags?.includes(OPT_IN_TAGS.AI_SQL) ?? false const [hasEnabledAISchema, setHasEnabledAISchema] = useLocalStorageQuery( @@ -24,7 +24,12 @@ const AISettingsModal = (props: ModalProps) => { } return ( - + snap.setShowAiSettingsModal(false)} + >
{IS_PLATFORM && !isOptedInToAI && selectedOrganization && ( `dashboard-history-${ref}`, } diff --git a/apps/studio/pages/project/[ref]/auth/policies.tsx b/apps/studio/pages/project/[ref]/auth/policies.tsx index 17a299fd8fa..1a522572943 100644 --- a/apps/studio/pages/project/[ref]/auth/policies.tsx +++ b/apps/studio/pages/project/[ref]/auth/policies.tsx @@ -7,6 +7,7 @@ import { observer } from 'mobx-react-lite' import { useEffect, useState } from 'react' import { Button, IconExternalLink, IconSearch, Input } from 'ui' +import { useIsRLSAIAssistantEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { Policies } from 'components/interfaces/Auth/Policies' import { AIPolicyEditorPanel } from 'components/interfaces/Auth/Policies/AIPolicyEditorPanel' import { AuthLayout } from 'components/layouts' @@ -17,7 +18,7 @@ import SchemaSelector from 'components/ui/SchemaSelector' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useSchemasQuery } from 'data/database/schemas-query' import { useTablesQuery } from 'data/tables/tables-query' -import { useCheckPermissions, useFlag, useStore } from 'hooks' +import { useCheckPermissions, useStore } from 'hooks' import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas' import { useTableEditorStateSnapshot } from 'state/table-editor' import { NextPageWithLayout } from 'types' @@ -63,9 +64,10 @@ const AuthPoliciesPage: NextPageWithLayout = () => { const { meta } = useStore() const { search } = useParams() const [searchString, setSearchString] = useState('') - const canCreatePolicyWithAi = useFlag('policyEditorWithAi') const [showPolicyAiEditor, setShowPolicyAiEditor] = useState(false) + const [selectedPolicyToEdit, setSelectedPolicyToEdit] = useState() + const isAiAssistantEnabled = useIsRLSAIAssistantEnabled() useEffect(() => { if (search) setSearchString(search) @@ -137,7 +139,7 @@ const AuthPoliciesPage: NextPageWithLayout = () => { Documentation - {canCreatePolicyWithAi && ( + {isAiAssistantEnabled && (
) diff --git a/apps/studio/public/img/previews/rls-ai-assistant-preview.png b/apps/studio/public/img/previews/rls-ai-assistant-preview.png new file mode 100644 index 00000000000..de1875f1fa7 Binary files /dev/null and b/apps/studio/public/img/previews/rls-ai-assistant-preview.png differ diff --git a/apps/studio/state/app-state.ts b/apps/studio/state/app-state.ts index b04623aab6c..87cab4d00fa 100644 --- a/apps/studio/state/app-state.ts +++ b/apps/studio/state/app-state.ts @@ -50,6 +50,10 @@ export const appState = proxy({ setShowFeaturePreviewModal: (value: boolean) => { appState.showFeaturePreviewModal = value }, + showAiSettingsModal: false, + setShowAiSettingsModal: (value: boolean) => { + appState.showAiSettingsModal = value + }, }) export const getAppStateSnapshot = () => snapshot(appState)