Files
supabase/apps/studio/components/interfaces/ConfigDrift/ConfigurationDriftPage.utils.ts
Ivan Vasilov 8422045b86 chore: Use @supabase/config for the code configuration page (#50398)
How to test:
1. Connect a project to GH repo
2. Deploy the `config.toml` once
3. Change some setting in Auth
4. You should see a change in
`/dashboard/project/_/settings/code-configuration`

<img width="1271" height="1186" alt="Screenshot 2026-09-16 at 16 26 39"
src="https://github.com/user-attachments/assets/dfc135a4-e495-489e-88fd-b760383793b4"
/>

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Configuration drift comparisons now use a consistent project
configuration schema.
- Drift details display complete current-environment and `config.toml`
values, grouped by section.
- Matching and unmanaged settings are organized into dedicated sections.
  - Configuration fields link directly to relevant Studio settings.
  - Added a warning that GitHub deployments overwrite local changes.

- **Bug Fixes**
- Configuration updates now refresh project configuration data
automatically.
  - Improved labels and formatting for boolean and redirect URL values.
- Drift errors identify invalid configuration paths and provide
corrective guidance.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-09-21 16:38:37 +02:00

138 lines
3.8 KiB
TypeScript

import { startCase } from 'lodash'
import {
CONFIG_SECTIONS,
getFieldDefinition,
type ConfigSection,
} from '@/components/interfaces/ConfigDrift/ConfigurationDriftPage.constants'
import {
type GitHubConfigDriftField,
type MatchedConfigField,
type UnmanagedConfigField,
} from '@/components/interfaces/ConfigDrift/github-config-drift'
const CONFIG_SECTION_LABELS: Record<ConfigSection, string> = {
api: 'API',
auth: 'Auth',
db: 'Database',
storage: 'Storage',
compute: 'Compute',
experimental: 'Experimental',
}
interface ConfigurationIssueRowMeta {
settingLabel: string
settingHref: string
valueDiff: ConfigurationDriftValueDiff
}
export type ConfigurationDriftRow = Omit<GitHubConfigDriftField, 'settingHref'> &
ConfigurationIssueRowMeta & { status: 'drifted' }
type ConfigurationDriftValueDiff = {
kind: 'scalar'
dashboardValue: string
configValue: string
}
interface UnmanagedConfigRow {
configPath: string
label: string
value: string
}
export interface ConfigSectionGroup {
section: ConfigSection
sectionLabel: string
rows: UnmanagedConfigRow[]
}
export function createConfigurationDriftRows(
fields: GitHubConfigDriftField[],
projectRef: string
): ConfigurationDriftRow[] {
return fields.map((field) => ({
...field,
status: 'drifted' as const,
settingLabel: getConfigFieldLabel(field.configPath),
settingHref: field.settingHref(projectRef),
valueDiff: createConfigurationDriftValueDiff(field),
}))
}
export function groupUnmanagedConfigFields(
fields: readonly UnmanagedConfigField[]
): ConfigSectionGroup[] {
return groupConfigFieldsBySection(fields, (field) => field.dashboardValue)
}
export function groupMatchedConfigFields(
fields: readonly MatchedConfigField[]
): ConfigSectionGroup[] {
return groupConfigFieldsBySection(fields, (field) => field.value)
}
function groupConfigFieldsBySection<T extends { section: ConfigSection; configPath: string }>(
fields: readonly T[],
getValue: (field: T) => unknown
): ConfigSectionGroup[] {
const bySection = new Map<ConfigSection, UnmanagedConfigRow[]>()
for (const field of fields) {
const rows = bySection.get(field.section) ?? []
rows.push({
configPath: field.configPath,
label: getConfigFieldLabel(field.configPath),
value: formatConfigFieldValue(getValue(field)),
})
bySection.set(field.section, rows)
}
return CONFIG_SECTIONS.filter((section) => bySection.has(section)).map((section) => ({
section,
sectionLabel: CONFIG_SECTION_LABELS[section],
rows: bySection.get(section)!,
}))
}
function formatConfigFieldValue(value: unknown): string {
const normalizedValue = value
if (normalizedValue === undefined || normalizedValue === null || normalizedValue === '') {
return 'Not set'
}
if (Array.isArray(normalizedValue)) {
return normalizedValue.length === 0
? 'Not set'
: normalizedValue.map((item) => formatScalarValue(item)).join('\n')
}
return formatScalarValue(normalizedValue)
}
function createConfigurationDriftValueDiff(
field: GitHubConfigDriftField
): ConfigurationDriftValueDiff {
return {
kind: 'scalar',
dashboardValue: formatConfigFieldValue(field.dashboardValue),
configValue: formatConfigFieldValue(field.githubValue),
}
}
/**
* Prefers the registry's hand-written label, and otherwise title-cases the last path segment —
* never the whole path, which would leave the dots in.
*/
function getConfigFieldLabel(configPath: string): string {
const label = getFieldDefinition(configPath)?.label
return label ?? startCase(configPath.split('.').at(-1) ?? configPath)
}
function formatScalarValue(value: unknown): string {
if (typeof value === 'string') return value || 'Not set'
if (typeof value === 'boolean') return value ? 'true' : 'false'
if (typeof value === 'number') return String(value)
return JSON.stringify(value, null, 2) ?? 'Not set'
}