diff --git a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx index 4fb98099095..8fe6fe053fd 100644 --- a/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx +++ b/apps/studio/components/interfaces/Database/Functions/FunctionsList/FunctionsList.tsx @@ -2,9 +2,10 @@ import * as Tooltip from '@radix-ui/react-tooltip' import type { PostgresFunction } from '@supabase/postgres-meta' import { PermissionAction } from '@supabase/shared-types/out/constants' import { noop, partition } from 'lodash' -import { useState } from 'react' +import { useRouter } from 'next/router' import { Button, IconSearch, Input } from 'ui' +import { useParams } from 'common' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' import Table from 'components/to-be-cleaned/Table' @@ -15,6 +16,7 @@ import { useDatabaseFunctionsQuery } from 'data/database-functions/database-func import { useSchemasQuery } from 'data/database/schemas-query' import { useCheckPermissions } from 'hooks' import { EXCLUDED_SCHEMAS } from 'lib/constants/schemas' +import { useEffect } from 'react' import ProtectedSchemaWarning from '../../ProtectedSchemaWarning' import FunctionList from './FunctionList' @@ -30,8 +32,33 @@ const FunctionsList = ({ deleteFunction = noop, }: FunctionsListProps) => { const { project } = useProjectContext() - const [selectedSchema, setSelectedSchema] = useState('public') - const [filterString, setFilterString] = useState('') + const router = useRouter() + const { schema, search } = useParams() + const selectedSchema = schema ?? 'public' + const filterString = search ?? '' + + const setSelectedSchema = (s: string) => { + const url = new URL(document.URL) + url.searchParams.delete('search') + url.searchParams.set('schema', s) + router.push(url) + } + const setFilterString = (str: string) => { + const url = new URL(document.URL) + if (str === '') { + url.searchParams.delete('search') + } else { + url.searchParams.set('search', str) + } + router.push(url) + } + + // update the url to point to public schema + useEffect(() => { + if (schema !== selectedSchema) { + setSelectedSchema(selectedSchema) + } + }, []) const canCreateFunctions = useCheckPermissions( PermissionAction.TENANT_SQL_ADMIN_WRITE, @@ -45,8 +72,8 @@ const FunctionsList = ({ const [protectedSchemas] = partition(schemas ?? [], (schema) => EXCLUDED_SCHEMAS.includes(schema?.name ?? '') ) - const schema = schemas?.find((schema) => schema.name === selectedSchema) - const isLocked = protectedSchemas.some((s) => s.id === schema?.id) + const foundSchema = schemas?.find((schema) => schema.name === selectedSchema) + const isLocked = protectedSchemas.some((s) => s.id === foundSchema?.id) const { data: functions, diff --git a/apps/studio/components/interfaces/Reports/ReportLints.utils.tsx b/apps/studio/components/interfaces/Reports/ReportLints.utils.tsx index 14e26da19fb..b044d3148a3 100644 --- a/apps/studio/components/interfaces/Reports/ReportLints.utils.tsx +++ b/apps/studio/components/interfaces/Reports/ReportLints.utils.tsx @@ -20,6 +20,18 @@ export function getHumanReadableTitle(title: LINT_TYPES) { return 'Unused Index' case 'multiple_permissive_policies': return 'Multiple Permissive Policies' + case 'function_search_path_mutable': + return 'Function Search Path Mutable' + case 'rls_enabled_no_policy': + return 'RLS Enabled No Policy' + case 'policy_exists_rls_disabled': + return 'Policy Exists RLS Disabled' + case 'rls_disabled_in_public': + return 'RLS Disabled in Public' + case 'security_definer_view': + return 'Security Definer View' + case 'duplicate_index': + return 'Duplicate Index' default: assertUnreachable(title) throw new Error('This case should never be reached') @@ -85,6 +97,18 @@ export const LintCTA = ({ ) + case 'duplicate_index': + return ( + + ) case 'multiple_permissive_policies': return ( ) + case 'function_search_path_mutable': + return ( + + ) + case 'rls_enabled_no_policy': + return ( + + ) + case 'policy_exists_rls_disabled': + return ( + + ) + case 'rls_disabled_in_public': + return ( + + ) + case 'security_definer_view': + // we don't have a good place to send the user to check out a view + return <> + default: assertUnreachable(title) return <> diff --git a/apps/studio/components/interfaces/Reports/ReportLintsTableRow.tsx b/apps/studio/components/interfaces/Reports/ReportLintsTableRow.tsx index 7d31413a126..ff7b1ea23ec 100644 --- a/apps/studio/components/interfaces/Reports/ReportLintsTableRow.tsx +++ b/apps/studio/components/interfaces/Reports/ReportLintsTableRow.tsx @@ -1,19 +1,9 @@ -import { Eye, EyeIcon, EyeOff, HelpCircle, Table2 } from 'lucide-react' -import { useState } from 'react' +import { Eye, HelpCircle, Table2 } from 'lucide-react' import { useParams } from 'common' import Table from 'components/to-be-cleaned/Table' import { LINT_TYPES, Lint } from 'data/lint/lint-query' -import { useLocalStorageQuery } from 'hooks' -import { LOCAL_STORAGE_KEYS } from 'lib/constants' -import { - Badge, - Button, - Modal, - TooltipContent_Shadcn_, - TooltipTrigger_Shadcn_, - Tooltip_Shadcn_, -} from 'ui' +import { Badge, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_, Tooltip_Shadcn_ } from 'ui' import { Markdown } from '../Markdown' import { LintCTA, getHumanReadableTitle } from './ReportLints.utils' @@ -23,33 +13,35 @@ type ReportLintsTableRowProps = { const ReportLintsTableRow = ({ lint }: ReportLintsTableRowProps) => { const { ref } = useParams() - const [selectedLint, setSelectedLint] = useState(null) + // TODO: Comment out functionality for ignoring lints because it relied on local storage. Will revisit later. + // const [selectedLint, setSelectedLint] = useState(null) - const [lintIgnoreList, setLintIgnoreList] = useLocalStorageQuery( - LOCAL_STORAGE_KEYS.PROJECT_LINT_IGNORE_LIST, - [] - ) - const isIgnored = lintIgnoreList.includes(lint.cache_key) + // const [lintIgnoreList, setLintIgnoreList] = useLocalStorageQuery( + // LOCAL_STORAGE_KEYS.PROJECT_LINT_IGNORE_LIST, + // [] + // ) + // const isIgnored = lintIgnoreList.includes(lint.cache_key) // if the lint type can't be handled (there's no CTA text defined), don't render it if (!LINT_TYPES.includes(lint.name)) { + console.log('Unhandled lint type:', lint.name) return null } - const toggleLintIgnore = () => { - let currentIgnoreList = [] - const cacheKey = lint.cache_key + // const toggleLintIgnore = () => { + // let currentIgnoreList = [] + // const cacheKey = lint.cache_key - // Check if the cacheKey exists in the array and ignore or unignore it - const index = lintIgnoreList.indexOf(cacheKey) - if (index !== -1) { - currentIgnoreList = lintIgnoreList.filter((l) => l !== cacheKey) - } else { - currentIgnoreList = lintIgnoreList.concat(cacheKey) - } - setLintIgnoreList(currentIgnoreList) - setSelectedLint(null) - } + // // Check if the cacheKey exists in the array and ignore or unignore it + // const index = lintIgnoreList.indexOf(cacheKey) + // if (index !== -1) { + // currentIgnoreList = lintIgnoreList.filter((l) => l !== cacheKey) + // } else { + // currentIgnoreList = lintIgnoreList.concat(cacheKey) + // } + // setLintIgnoreList(currentIgnoreList) + // setSelectedLint(null) + // } return ( <> @@ -109,7 +101,7 @@ const ReportLintsTableRow = ({ lint }: ReportLintsTableRowProps) => { )} {lint.remediation && (

- You can read more about this lint rule and the best ways to remedy it{' '} + You can read more about this lint rule and ways to remedy it{' '} {

- {

-
+ */} ) } diff --git a/apps/studio/components/ui/FilterPopover.tsx b/apps/studio/components/ui/FilterPopover.tsx index 8ef28355f7b..3bf02b2dc31 100644 --- a/apps/studio/components/ui/FilterPopover.tsx +++ b/apps/studio/components/ui/FilterPopover.tsx @@ -40,9 +40,10 @@ export const FilterPopover = ({ header={
Select {name.toLowerCase()}
} overlay={ <> -
+
7 ? 'h-[205px]' : ''}> { const value = event.target.value diff --git a/apps/studio/data/lint/lint-query.ts b/apps/studio/data/lint/lint-query.ts index fbee6eca9b0..69bb874caa6 100644 --- a/apps/studio/data/lint/lint-query.ts +++ b/apps/studio/data/lint/lint-query.ts @@ -10,10 +10,10 @@ with foreign_keys as ( ct.conname as fkey_name, ct.conkey col_attnums from - pg_constraint ct - join pg_class cl -- fkey owning table + pg_catalog.pg_constraint ct + join pg_catalog.pg_class cl -- fkey owning table on ct.conrelid = cl.oid - left join pg_depend d + left join pg_catalog.pg_depend d on d.objid = cl.oid and d.deptype = 'e' where @@ -29,7 +29,7 @@ index_ as ( indexrelid::regclass as index_, string_to_array(indkey::text, ' ')::smallint[] as col_attnums from - pg_index + pg_catalog.pg_index where indisvalid ) @@ -42,7 +42,6 @@ select 'Table \`%s.%s\` has a foreign key \`%s\` without a covering index. This can lead to suboptimal query performance.', fk.schema_, fk.table_, - fk.table_, fk.fkey_name ) as detail, 'https://supabase.com/docs/guides/database/database-linter?lint=0001_unindexed_foreign_keys' as remediation, @@ -53,7 +52,7 @@ select 'fkey_name', fk.fkey_name, 'fkey_columns', fk.col_attnums ) as metadata, - format('0001_unindexed_foreign_keys_%s_%s_%s', fk.schema_, fk.table_, fk.fkey_name) as cache_key + format('unindexed_foreign_keys_%s_%s_%s', fk.schema_, fk.table_, fk.fkey_name) as cache_key from foreign_keys fk left join index_ idx @@ -61,6 +60,9 @@ from and fk.col_attnums = idx.col_attnums where idx.index_ is null + and fk.schema_::text not in ( + 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + ) order by fk.table_, fk.fkey_name) @@ -84,17 +86,25 @@ select ) as metadata, format('auth_users_exposed_%s_%s', 'public', c.relname) as cache_key from - pg_depend d - join pg_rewrite r + -- Identify the oid for auth.users + pg_catalog.pg_class auth_users_pg_class + join pg_catalog.pg_namespace auth_users_pg_namespace + on auth_users_pg_class.relnamespace = auth_users_pg_namespace.oid + and auth_users_pg_class.relname = 'users' + and auth_users_pg_namespace.nspname = 'auth' + -- Depends on auth.users + join pg_catalog.pg_depend d + on d.refobjid = auth_users_pg_class.oid + join pg_catalog.pg_rewrite r on r.oid = d.objid - join pg_class c + join pg_catalog.pg_class c on c.oid = r.ev_class - join pg_namespace n + join pg_catalog.pg_namespace n on n.oid = c.relnamespace + join pg_catalog.pg_class pg_class_auth_users + on d.refobjid = pg_class_auth_users.oid where - d.refobjid = 'auth.users'::regclass - and d.deptype = 'n' - and c.relkind in ('v', 'm') -- v for view, m for materialized view + d.deptype = 'n' and n.nspname = 'public' and ( pg_catalog.has_table_privilege('anon', c.oid, 'SELECT') @@ -102,6 +112,43 @@ where ) -- Exclude self and c.relname <> '0002_auth_users_exposed' + -- There are 3 insecure configurations + and + ( + -- Materialized views don't support RLS so this is insecure by default + (c.relkind in ('m')) -- m for materialized view + or + -- Standard View, accessible to anon or authenticated that is security_definer + ( + c.relkind = 'v' -- v for view + -- Exclude security invoker views + and not ( + lower(coalesce(c.reloptions::text,'{}'))::text[] + && array[ + 'security_invoker=1', + 'security_invoker=true', + 'security_invoker=yes', + 'security_invoker=on' + ] + ) + ) + or + -- Standard View, security invoker, but no RLS enabled on auth.users + ( + c.relkind in ('v') -- v for view + -- is security invoker + and ( + lower(coalesce(c.reloptions::text,'{}'))::text[] + && array[ + 'security_invoker=1', + 'security_invoker=true', + 'security_invoker=yes', + 'security_invoker=on' + ] + ) + and not pg_class_auth_users.relrowsecurity + ) + ) group by c.relname, c.oid) union all @@ -151,12 +198,12 @@ with policies as ( qual, with_check from - pg_policy pa - join pg_class pc + pg_catalog.pg_policy pa + join pg_catalog.pg_class pc on pa.polrelid = pc.oid - join pg_namespace nsp + join pg_catalog.pg_namespace nsp on pc.relnamespace = nsp.oid - join pg_policies pb + join pg_catalog.pg_policies pb on pc.relname = pb.tablename and nsp.nspname = pb.schemaname and pa.polname = pb.policyname @@ -182,6 +229,9 @@ from policies where is_rls_active + and schema_::text not in ( + 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + ) and ( ( -- Example: auth.uid() @@ -210,7 +260,7 @@ select pgc.relname ) as detail, 'https://supabase.com/docs/guides/database/database-linter?lint=0004_no_primary_key' as remediation, - jsonb_build_object( + jsonb_build_object( 'schema', pgns.nspname, 'name', pgc.relname, 'type', 'table' @@ -221,15 +271,15 @@ select pgc.relname ) as cache_key from - pg_class pgc - join pg_namespace pgns + pg_catalog.pg_class pgc + join pg_catalog.pg_namespace pgns on pgns.oid = pgc.relnamespace - left join pg_index pgi + left join pg_catalog.pg_index pgi on pgi.indrelid = pgc.oid where pgc.relkind = 'r' -- regular tables and pgns.nspname not in ( - 'pg_catalog', 'information_schema', 'auth', 'storage', 'vault', 'pgsodium' + 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' ) group by pgc.oid, @@ -272,7 +322,7 @@ where and not pi.indisunique and not pi.indisprimary and psui.schemaname not in ( - 'pg_catalog', 'information_schema', 'auth', 'storage', 'vault', 'pgsodium' + 'pg_catalog', 'information_schema', 'auth', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' )) union all ( @@ -326,7 +376,7 @@ from where c.relkind = 'r' -- regular tables and n.nspname not in ( - 'pg_catalog', 'information_schema', 'auth', 'storage', 'vault', 'pgsodium' + 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' ) and r.rolname not like 'pg_%' and r.rolname not like 'supabase%admin' @@ -337,16 +387,256 @@ group by r.rolname, act.cmd having - count(1) > 1)`.trim() + count(1) > 1) +union all +( +select + 'policy_exists_rls_disabled' as name, + 'INFO' as level, + 'EXTERNAL' as facing, + 'Detects cases where row level security (RLS) policies have been created, but RLS has not been enabled for the underlying table.' as description, + format( + 'Table \`%s.%s\` has RLS policies but RLS is not enabled on the table. Policies include %s.', + n.nspname, + c.relname, + array_agg(p.polname order by p.polname) + ) as detail, + 'https://supabase.com/docs/guides/database/database-linter?lint=0007_policy_exists_rls_disabled' as remediation, + jsonb_build_object( + 'schema', n.nspname, + 'name', c.relname, + 'type', 'table' + ) as metadata, + format( + 'policy_exists_rls_disabled_%s_%s', + n.nspname, + c.relname + ) as cache_key +from + pg_catalog.pg_policy p + join pg_catalog.pg_class c + on p.polrelid = c.oid + join pg_catalog.pg_namespace n + on c.relnamespace = n.oid +where + c.relkind = 'r' -- regular tables + and n.nspname not in ( + 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + ) + -- RLS is disabled + and not c.relrowsecurity +group by + n.nspname, + c.relname) +union all +( +select + 'rls_enabled_no_policy' as name, + 'INFO' as level, + 'EXTERNAL' as facing, + 'Detects cases where row level security (RLS) has been enabled on a table but no RLS policies have been created.' as description, + format( + 'Table \`%s.%s\` has RLS enabled, but no policies exist', + n.nspname, + c.relname + ) as detail, + 'https://supabase.com/docs/guides/database/database-linter?lint=0008_rls_enabled_no_policy' as remediation, + jsonb_build_object( + 'schema', n.nspname, + 'name', c.relname, + 'type', 'table' + ) as metadata, + format( + 'rls_enabled_no_policy_%s_%s', + n.nspname, + c.relname + ) as cache_key +from + pg_catalog.pg_class c + left join pg_catalog.pg_policy p + on p.polrelid = c.oid + join pg_catalog.pg_namespace n + on c.relnamespace = n.oid +where + c.relkind = 'r' -- regular tables + and n.nspname not in ( + 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + ) + -- RLS is enabled + and c.relrowsecurity + and p.polname is null +group by + n.nspname, + c.relname) +union all +( +select + 'duplicate_index' as name, + 'WARN' as level, + 'EXTERNAL' as facing, + 'Detects cases where two ore more identical indexes exist.' as description, + format( + 'Table \`%s.%s\` has identical indexes %s. Drop all except one of them', + n.nspname, + c.relname, + array_agg(pi.indexname order by pi.indexname) + ) as detail, + 'https://supabase.com/docs/guides/database/database-linter?lint=0009_duplicate_index' as remediation, + jsonb_build_object( + 'schema', n.nspname, + 'name', c.relname, + 'type', case + when c.relkind = 'r' then 'table' + when c.relkind = 'm' then 'materialized view' + else 'ERROR' + end, + 'indexes', array_agg(pi.indexname order by pi.indexname) + ) as metadata, + format( + 'duplicate_index_%s_%s_%s', + n.nspname, + c.relname, + array_agg(pi.indexname order by pi.indexname) + ) as cache_key +from + pg_catalog.pg_indexes pi + join pg_catalog.pg_namespace n + on n.nspname = pi.schemaname + join pg_catalog.pg_class c + on pi.tablename = c.relname + and n.oid = c.relnamespace +where + c.relkind in ('r', 'm') -- tables and materialized views + and n.nspname not in ( + 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + ) +group by + n.nspname, + c.relkind, + c.relname, + replace(pi.indexdef, pi.indexname, '') +having + count(*) > 1) +union all +( +select + 'security_definer_view' as name, + 'WARN' as level, + 'EXTERNAL' as facing, + 'Detects views that are SECURITY DEFINER meaning that they ignore row level security (RLS) policies.' as description, + format( + 'View \`%s.%s\` is SECURITY DEFINER', + n.nspname, + c.relname + ) as detail, + 'https://supabase.com/docs/guides/database/database-linter?lint=0010_security_definer_view' as remediation, + jsonb_build_object( + 'schema', n.nspname, + 'name', c.relname, + 'type', 'view' + ) as metadata, + format( + 'security_definer_view_%s_%s', + n.nspname, + c.relname + ) as cache_key +from + pg_catalog.pg_class c + join pg_catalog.pg_namespace n + on n.oid = c.relnamespace +where + c.relkind = 'v' + and n.nspname = 'public' + and not ( + lower(coalesce(c.reloptions::text,'{}'))::text[] + && array[ + 'security_invoker=1', + 'security_invoker=true', + 'security_invoker=yes', + 'security_invoker=on' + ] + )) +union all +( +select + 'function_search_path_mutable' as name, + 'WARN' as level, + 'EXTERNAL' as facing, + 'Detects functions with a mutable search_path parameter which could fail to execute successfully for some roles.' as description, + format( + 'Function \`%s.%s\` has a role mutable search_path', + n.nspname, + p.proname + ) as detail, + 'https://supabase.com/docs/guides/database/database-linter?lint=0011_function_search_path_mutable' as remediation, + jsonb_build_object( + 'schema', n.nspname, + 'name', p.proname, + 'type', 'function' + ) as metadata, + format( + 'function_search_path_mutable_%s_%s_%s', + n.nspname, + p.proname, + md5(p.prosrc) -- required when function is polymorphic + ) as cache_key +from + pg_catalog.pg_proc p + join pg_catalog.pg_namespace n + on p.pronamespace = n.oid +where + n.nspname not in ( + 'pg_catalog', 'information_schema', 'auth', 'extensions', 'graphql', 'graphql_public', 'net', 'pgsodium', 'storage', 'supabase_functions', 'vault' + ) + -- Search path not set to '' + and not coalesce(p.proconfig, '{}') && array['search_path=""']) +union all +( +select + 'rls_disabled_in_public' as name, + 'ERROR' as level, + 'EXTERNAL' as facing, + 'Detects cases where row level security (RLS) has not been enabled on a table in the \`public\` schema.' as description, + format( + 'Table \`%s.%s\` is public, but RLS has not been enabled.', + n.nspname, + c.relname + ) as detail, + 'https://supabase.com/docs/guides/database/database-linter?lint=0013_rls_disabled_in_public' as remediation, + jsonb_build_object( + 'schema', n.nspname, + 'name', c.relname, + 'type', 'table' + ) as metadata, + format( + 'rls_disabled_in_public_%s_%s', + n.nspname, + c.relname + ) as cache_key +from + pg_catalog.pg_class c + join pg_catalog.pg_namespace n + on c.relnamespace = n.oid +where + c.relkind = 'r' -- regular tables + and n.nspname = 'public' + -- RLS is disabled + and not c.relrowsecurity)`.trim() // Array of all lint rules we handle right now. export const LINT_TYPES = [ 'unindexed_foreign_keys', 'auth_users_exposed', + 'auth_rls_initplan', 'no_primary_key', 'unused_index', 'multiple_permissive_policies', - 'auth_rls_initplan', + 'policy_exists_rls_disabled', + 'rls_enabled_no_policy', + 'duplicate_index', + 'security_definer_view', + 'function_search_path_mutable', + 'rls_disabled_in_public', ] as const export type LINT_TYPES = (typeof LINT_TYPES)[number] diff --git a/apps/studio/pages/project/[ref]/database/linter.tsx b/apps/studio/pages/project/[ref]/database/linter.tsx index 31f48daa024..039790ad105 100644 --- a/apps/studio/pages/project/[ref]/database/linter.tsx +++ b/apps/studio/pages/project/[ref]/database/linter.tsx @@ -1,25 +1,18 @@ -import { partition, sortBy } from 'lodash' +import { sortBy } from 'lodash' import { Check, ExternalLink, Loader } from 'lucide-react' import { useMemo, useState } from 'react' -import { AccordionTrigger } from '@ui/components/shadcn/ui/accordion' +import { getHumanReadableTitle } from 'components/interfaces/Reports/ReportLints.utils' import ReportLintsTableRow from 'components/interfaces/Reports/ReportLintsTableRow' import { DatabaseLayout } from 'components/layouts' import { ScaffoldContainer, ScaffoldSection } from 'components/layouts/Scaffold' import Table from 'components/to-be-cleaned/Table' -import { FormHeader } from 'components/ui/Forms' -import { useProjectLintsQuery } from 'data/lint/lint-query' -import { useLocalStorageQuery, useSelectedProject } from 'hooks' -import { LOCAL_STORAGE_KEYS } from 'lib/constants' -import type { NextPageWithLayout } from 'types' -import { - AccordionContent_Shadcn_, - AccordionItem_Shadcn_, - Accordion_Shadcn_, - Button, - LoadingLine, -} from 'ui' import { FilterPopover } from 'components/ui/FilterPopover' +import { FormHeader } from 'components/ui/Forms' +import { LINT_TYPES, useProjectLintsQuery } from 'data/lint/lint-query' +import { useSelectedProject } from 'hooks' +import type { NextPageWithLayout } from 'types' +import { Button, LoadingLine } from 'ui' const ProjectLints: NextPageWithLayout = () => { const project = useSelectedProject() @@ -27,10 +20,10 @@ const ProjectLints: NextPageWithLayout = () => { levels: [] as string[], types: [] as string[], }) - const [lintIgnoreList] = useLocalStorageQuery( - LOCAL_STORAGE_KEYS.PROJECT_LINT_IGNORE_LIST, - [] - ) + // const [lintIgnoreList] = useLocalStorageQuery( + // LOCAL_STORAGE_KEYS.PROJECT_LINT_IGNORE_LIST, + // [] + // ) const { data, isLoading, isRefetching, refetch } = useProjectLintsQuery({ projectRef: project?.ref, @@ -45,9 +38,10 @@ const ProjectLints: NextPageWithLayout = () => { return 3 }) - const [ignoredLints, activeLints] = partition(lints, (lint) => - lintIgnoreList.includes(lint.cache_key) - ) + const activeLints = lints + // const [ignoredLints, activeLints] = partition(lints, (lint) => + // lintIgnoreList.includes(lint.cache_key) + // ) const filteredLints = useMemo(() => { return activeLints .filter((x) => (filters.levels.length > 0 ? filters.levels.includes(x.level) : x)) @@ -57,6 +51,16 @@ const ProjectLints: NextPageWithLayout = () => { const warnLintsCount = activeLints.filter((x) => x.level === 'WARN').length const errorLintsCount = activeLints.filter((x) => x.level === 'ERROR').length + const filterOptions = useMemo(() => { + // only show filters for lint types which are present in the results and not ignored + return LINT_TYPES.filter((type) => activeLints.some((lint) => lint.name === type)).map( + (type) => ({ + name: getHumanReadableTitle(type), + value: type, + }) + ) + }, [activeLints]) + return ( @@ -86,14 +90,7 @@ const ProjectLints: NextPageWithLayout = () => { /> { />
- {ignoredLints.length > 0 && ( + {/* {ignoredLints.length > 0 && (
@@ -219,7 +216,7 @@ const ProjectLints: NextPageWithLayout = () => {
- )} + )} */} )