From 0d832508c9aceff341f4ac988ae2f35b8c4ab6fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 21 Jul 2026 19:06:55 +0200 Subject: [PATCH] Replace portal title with entity name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Store a short entity name instead of the full home heading so orgs can brand portals for sub-entities. Restore hero i18n composition and keep the English document title composed from the entity name. Signed-off-by: Émile Ré --- .../compliance-portal/src/_locales/de-DE.json | 1 + .../compliance-portal/src/_locales/en-US.json | 3 +- .../compliance-portal/src/_locales/es-ES.json | 1 + .../compliance-portal/src/_locales/fr-FR.json | 1 + .../compliance-portal/src/_locales/id-ID.json | 1 + .../compliance-portal/src/_locales/it-IT.json | 1 + .../compliance-portal/src/_locales/ja-JP.json | 1 + .../compliance-portal/src/_locales/ko-KR.json | 1 + .../compliance-portal/src/_locales/pl-PL.json | 1 + .../compliance-portal/src/_locales/pt-PT.json | 1 + .../compliance-portal/src/_locales/tr-TR.json | 1 + .../compliance-portal/src/_locales/uk-UA.json | 1 + .../compliance-portal/src/_locales/zh-CN.json | 1 + .../src/components/TopBar/TopBar.tsx | 8 ++--- .../mailingList/SubscribeDialogProvider.tsx | 6 ++-- apps/compliance-portal/src/pages/HomePage.tsx | 6 ++-- .../src/pages/nda/NDAPage.tsx | 4 +-- .../src/hooks/graph/CompliancePageGraph.ts | 2 +- .../CompliancePageProfileSection.tsx | 16 ++++----- .../src/components/OrganizationSidebar.tsx | 2 +- apps/trust/src/hooks/useCompliancePortal.ts | 2 +- apps/trust/src/pages/NDAPage.tsx | 4 +-- apps/trust/src/pages/OverviewPage.tsx | 2 +- apps/trust/src/pages/SubprocessorsPage.tsx | 2 +- apps/trust/src/pages/auth/ConnectPage.tsx | 8 ++--- .../src/queries/CompliancePortalGraph.ts | 3 +- e2e/console/compliance_portal_profile_test.go | 8 ++--- e2e/mcp/compliance_portal_test.go | 14 ++++---- .../compliance_portal_oauth_connect_test.go | 6 ++-- .../compliancePortal/update.operation.ts | 12 +++---- pkg/cmd/compliance-portal/update/update.go | 12 +++---- .../management/portal_service.go | 10 +++--- pkg/complianceportal/visitor/cimd.go | 2 +- pkg/complianceportal/visitor/cimd_test.go | 6 ++-- pkg/coredata/compliance_portal.go | 22 ++++++------ pkg/coredata/migrations/20260721T165706Z.sql | 36 +++++++++++++++++++ pkg/iam/organization_service.go | 2 +- .../v1/graphql/compliance_portal.graphql | 2 +- pkg/server/api/complianceportal/v1/mux.go | 5 +-- .../v1/types/compliance_portal.go | 2 +- .../console/v1/compliance_portal_resolvers.go | 2 +- .../v1/graphql/compliance_portal.graphql | 4 +-- .../api/console/v1/types/compliance_portal.go | 4 +-- pkg/server/api/mcp/v1/schema.resolvers.go | 4 +-- pkg/server/api/mcp/v1/specification.yaml | 10 +++--- .../api/mcp/v1/types/compliance_portal.go | 2 +- 46 files changed, 148 insertions(+), 97 deletions(-) create mode 100644 pkg/coredata/migrations/20260721T165706Z.sql diff --git a/apps/compliance-portal/src/_locales/de-DE.json b/apps/compliance-portal/src/_locales/de-DE.json index a065897d1..31c435f81 100644 --- a/apps/compliance-portal/src/_locales/de-DE.json +++ b/apps/compliance-portal/src/_locales/de-DE.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "Vertrauen bei {{name}}.", "heroDescription": "Willkommen auf unserem Compliance-Portal. Hier finden Sie unsere Sicherheitsdokumentation und Zertifizierungen.", "sections": { "compliance": "Compliance", diff --git a/apps/compliance-portal/src/_locales/en-US.json b/apps/compliance-portal/src/_locales/en-US.json index 49e1fdfc7..453cfa103 100644 --- a/apps/compliance-portal/src/_locales/en-US.json +++ b/apps/compliance-portal/src/_locales/en-US.json @@ -55,6 +55,7 @@ } }, "home": { + "heroTitle": "Compliance at {{name}}.", "heroDescription": "Welcome to our Compliance Portal. Find our security documentation and certifications here.", "sections": { "compliance": "Compliance", @@ -104,4 +105,4 @@ "previous": "Previous page", "next": "Next page" } -} \ No newline at end of file +} diff --git a/apps/compliance-portal/src/_locales/es-ES.json b/apps/compliance-portal/src/_locales/es-ES.json index a0b588aa4..b87ad7598 100644 --- a/apps/compliance-portal/src/_locales/es-ES.json +++ b/apps/compliance-portal/src/_locales/es-ES.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "Confianza en {{name}}.", "heroDescription": "Bienvenido a nuestro Portal de cumplimiento. Aquí encontrará nuestra documentación de seguridad y nuestras certificaciones.", "sections": { "compliance": "Cumplimiento", diff --git a/apps/compliance-portal/src/_locales/fr-FR.json b/apps/compliance-portal/src/_locales/fr-FR.json index 7b21579ab..95144e73c 100644 --- a/apps/compliance-portal/src/_locales/fr-FR.json +++ b/apps/compliance-portal/src/_locales/fr-FR.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "La confiance chez {{name}}.", "heroDescription": "Bienvenue dans notre Portail de conformité. Retrouvez ici notre documentation et nos certifications de sécurité.", "sections": { "compliance": "Conformité", diff --git a/apps/compliance-portal/src/_locales/id-ID.json b/apps/compliance-portal/src/_locales/id-ID.json index 4fe1e4ecd..91031ae8a 100644 --- a/apps/compliance-portal/src/_locales/id-ID.json +++ b/apps/compliance-portal/src/_locales/id-ID.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "Kepercayaan di {{name}}.", "heroDescription": "Selamat datang di Portal Kepatuhan kami. Temukan dokumentasi keamanan dan sertifikasi kami di sini.", "sections": { "compliance": "Kepatuhan", diff --git a/apps/compliance-portal/src/_locales/it-IT.json b/apps/compliance-portal/src/_locales/it-IT.json index 1e008da70..a8779c118 100644 --- a/apps/compliance-portal/src/_locales/it-IT.json +++ b/apps/compliance-portal/src/_locales/it-IT.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "La fiducia in {{name}}.", "heroDescription": "Benvenuti nel nostro Portale di conformità. Qui potete trovare la nostra documentazione di sicurezza e le nostre certificazioni.", "sections": { "compliance": "Conformità", diff --git a/apps/compliance-portal/src/_locales/ja-JP.json b/apps/compliance-portal/src/_locales/ja-JP.json index 67b0582ed..43a03872f 100644 --- a/apps/compliance-portal/src/_locales/ja-JP.json +++ b/apps/compliance-portal/src/_locales/ja-JP.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "{{name}}における信頼。", "heroDescription": "コンプライアンスポータルへようこそ。セキュリティに関する文書や認証はこちらからご確認いただけます。", "sections": { "compliance": "コンプライアンス", diff --git a/apps/compliance-portal/src/_locales/ko-KR.json b/apps/compliance-portal/src/_locales/ko-KR.json index 7052a35f6..5112cdbd6 100644 --- a/apps/compliance-portal/src/_locales/ko-KR.json +++ b/apps/compliance-portal/src/_locales/ko-KR.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "{{name}}의 신뢰.", "heroDescription": "컴플라이언스 포털에 오신 것을 환영합니다. 이곳에서 보안 문서와 인증 정보를 확인하실 수 있습니다.", "sections": { "compliance": "컴플라이언스", diff --git a/apps/compliance-portal/src/_locales/pl-PL.json b/apps/compliance-portal/src/_locales/pl-PL.json index a55e89769..55f9e5c1c 100644 --- a/apps/compliance-portal/src/_locales/pl-PL.json +++ b/apps/compliance-portal/src/_locales/pl-PL.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "Zaufanie w {{name}}.", "heroDescription": "Witamy w naszym Portalu Zgodności. Znajdziesz tu naszą dokumentację bezpieczeństwa i certyfikaty.", "sections": { "compliance": "Zgodność", diff --git a/apps/compliance-portal/src/_locales/pt-PT.json b/apps/compliance-portal/src/_locales/pt-PT.json index 267c3c7c6..7d80600e9 100644 --- a/apps/compliance-portal/src/_locales/pt-PT.json +++ b/apps/compliance-portal/src/_locales/pt-PT.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "A confiança na {{name}}.", "heroDescription": "Bem-vindo ao nosso Portal de Conformidade. Encontre aqui a nossa documentação de segurança e certificações.", "sections": { "compliance": "Conformidade", diff --git a/apps/compliance-portal/src/_locales/tr-TR.json b/apps/compliance-portal/src/_locales/tr-TR.json index 9f6d93e5c..19601973f 100644 --- a/apps/compliance-portal/src/_locales/tr-TR.json +++ b/apps/compliance-portal/src/_locales/tr-TR.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "{{name}}'de Güven.", "heroDescription": "Uyumluluk Portalımıza hoş geldiniz. Güvenlik belgelerimizi ve sertifikalarımızı burada bulabilirsiniz.", "sections": { "compliance": "Uyumluluk", diff --git a/apps/compliance-portal/src/_locales/uk-UA.json b/apps/compliance-portal/src/_locales/uk-UA.json index 506d044d4..18ed87f8d 100644 --- a/apps/compliance-portal/src/_locales/uk-UA.json +++ b/apps/compliance-portal/src/_locales/uk-UA.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "Довіра в {{name}}.", "heroDescription": "Вітаємо у нашому Порталі відповідності. Тут ви знайдете нашу документацію з безпеки та сертифікати.", "sections": { "compliance": "Відповідність", diff --git a/apps/compliance-portal/src/_locales/zh-CN.json b/apps/compliance-portal/src/_locales/zh-CN.json index bdfe251ee..d61400869 100644 --- a/apps/compliance-portal/src/_locales/zh-CN.json +++ b/apps/compliance-portal/src/_locales/zh-CN.json @@ -45,6 +45,7 @@ } }, "home": { + "heroTitle": "信赖 {{name}}。", "heroDescription": "欢迎访问我们的合规门户。您可以在此查看我们的安全文档和认证。", "sections": { "compliance": "合规", diff --git a/apps/compliance-portal/src/components/TopBar/TopBar.tsx b/apps/compliance-portal/src/components/TopBar/TopBar.tsx index 085bb6e27..4a4aea961 100644 --- a/apps/compliance-portal/src/components/TopBar/TopBar.tsx +++ b/apps/compliance-portal/src/components/TopBar/TopBar.tsx @@ -45,7 +45,7 @@ const topBarFragment = graphql` } currentCompliancePortal @required(action: THROW) { themedLogoUrl - title + entityName } } `; @@ -66,7 +66,7 @@ export function TopBar({ queryKey }: TopBarProps) { const localizedPath = useLocalizedPath(); const { currentCompliancePortal } = data; - const title = currentCompliancePortal.title; + const entityName = currentCompliancePortal.entityName; const logoUrl = currentCompliancePortal.themedLogoUrl ?? undefined; const slots = topBar(); @@ -82,11 +82,11 @@ export function TopBar({ queryKey }: TopBarProps) { color="neutral" radius="small" src={logoUrl} - fallback={title.charAt(0) || "?"} + fallback={entityName.charAt(0) || "?"} className={slots.logo()} /> - {title} + {entityName} {t("topBar.tagline")} diff --git a/apps/compliance-portal/src/lib/mailingList/SubscribeDialogProvider.tsx b/apps/compliance-portal/src/lib/mailingList/SubscribeDialogProvider.tsx index b90214f88..5651b52f0 100644 --- a/apps/compliance-portal/src/lib/mailingList/SubscribeDialogProvider.tsx +++ b/apps/compliance-portal/src/lib/mailingList/SubscribeDialogProvider.tsx @@ -45,7 +45,7 @@ export const subscribeDialogProviderFragment = graphql` } currentCompliancePortal @required(action: THROW) { id - title + entityName viewerSubscription { id } @@ -71,7 +71,7 @@ export function SubscribeDialogProvider({ const [unsubscribeFromMailingList, isUnsubscribing] = useUnsubscribeFromMailingList(); const viewer = data.viewer; - const { id: trustCenterId, title, viewerSubscription } = data.currentCompliancePortal; + const { id: trustCenterId, entityName, viewerSubscription } = data.currentCompliancePortal; const isSubscribed = viewerSubscription != null; const openSubscribe = useCallback(() => { @@ -127,7 +127,7 @@ export function SubscribeDialogProvider({ onOpenChange={setDialogOpen} trustCenterId={trustCenterId} viewerEmail={viewer.email} - organizationName={title} + organizationName={entityName} /> )} diff --git a/apps/compliance-portal/src/pages/HomePage.tsx b/apps/compliance-portal/src/pages/HomePage.tsx index 940fac1d2..f234c0051 100644 --- a/apps/compliance-portal/src/pages/HomePage.tsx +++ b/apps/compliance-portal/src/pages/HomePage.tsx @@ -34,7 +34,7 @@ import type { HomePageQuery } from "./__generated__/HomePageQuery.graphql"; export const homePageQuery = graphql` query HomePageQuery @throwOnFieldError { currentCompliancePortal @required(action: THROW) { - title + entityName ...CompliancePortalContactInfo_compliancePortal ...ComplianceFrameworksSection_compliancePortal ...SecurityCommitmentsSection_compliancePortal @@ -52,12 +52,12 @@ export function HomePage({ queryRef }: HomePageProps) { const { t } = useTranslation(); const data = usePreloadedQuery(homePageQuery, queryRef); const { currentCompliancePortal } = data; - const { title } = currentCompliancePortal; + const { entityName } = currentCompliancePortal; return ( <> diff --git a/apps/compliance-portal/src/pages/nda/NDAPage.tsx b/apps/compliance-portal/src/pages/nda/NDAPage.tsx index 90320abaf..db9dc4138 100644 --- a/apps/compliance-portal/src/pages/nda/NDAPage.tsx +++ b/apps/compliance-portal/src/pages/nda/NDAPage.tsx @@ -63,7 +63,7 @@ export const ndaPageQuery = graphql` id } currentCompliancePortal @required(action: THROW) { - title + entityName nonDisclosureAgreement { fileUrl } @@ -238,7 +238,7 @@ export function NDAPage({ queryRef }: NDAPageProps) { {t("title")} - {t("subtitle", { name: compliancePortal.title })} + {t("subtitle", { name: compliancePortal.entityName })} {signature.consentText != null && ( diff --git a/apps/console/src/hooks/graph/CompliancePageGraph.ts b/apps/console/src/hooks/graph/CompliancePageGraph.ts index 3eaefeb01..32fccf192 100644 --- a/apps/console/src/hooks/graph/CompliancePageGraph.ts +++ b/apps/console/src/hooks/graph/CompliancePageGraph.ts @@ -32,7 +32,7 @@ const updateCompliancePageMutation = graphql` id active searchEngineIndexing - title + entityName description websiteUrl email diff --git a/apps/console/src/pages/organizations/compliance-page/brand/_components/CompliancePageProfileSection.tsx b/apps/console/src/pages/organizations/compliance-page/brand/_components/CompliancePageProfileSection.tsx index 6df95f83b..4b2cc1ce0 100644 --- a/apps/console/src/pages/organizations/compliance-page/brand/_components/CompliancePageProfileSection.tsx +++ b/apps/console/src/pages/organizations/compliance-page/brand/_components/CompliancePageProfileSection.tsx @@ -25,7 +25,7 @@ import { useFormWithSchema } from "#/hooks/useFormWithSchema"; const compliancePageFragment = graphql` fragment CompliancePageProfileSection_compliancePageFragment on CompliancePortal { id - title + entityName description websiteUrl email @@ -35,7 +35,7 @@ const compliancePageFragment = graphql` `; const profileSchema = z.object({ - title: z.string().min(1), + entityName: z.string().min(1), description: z.string().optional(), websiteUrl: z.string().optional(), email: z.string().optional(), @@ -58,7 +58,7 @@ export function CompliancePageProfileSection(props: { const { formState, handleSubmit, register } = useFormWithSchema(profileSchema, { defaultValues: { - title: compliancePage.title, + entityName: compliancePage.entityName, description: compliancePage.description || "", websiteUrl: compliancePage.websiteUrl || "", email: compliancePage.email || "", @@ -73,7 +73,7 @@ export function CompliancePageProfileSection(props: { variables: { input: { compliancePortalId: compliancePage.id, - title: data.title, + entityName: data.entityName, description: data.description || null, websiteUrl: data.websiteUrl || null, email: data.email || null, @@ -96,11 +96,11 @@ export function CompliancePageProfileSection(props: {
diff --git a/apps/trust/src/components/OrganizationSidebar.tsx b/apps/trust/src/components/OrganizationSidebar.tsx index 242928f34..240e1c830 100644 --- a/apps/trust/src/components/OrganizationSidebar.tsx +++ b/apps/trust/src/components/OrganizationSidebar.tsx @@ -233,7 +233,7 @@ export function OrganizationSidebar({ : (
)} -

{compliancePortal.title}

+

{compliancePortal.entityName}

{compliancePortal.description}

diff --git a/apps/trust/src/hooks/useCompliancePortal.ts b/apps/trust/src/hooks/useCompliancePortal.ts index 27fe2077e..8dcd3385e 100644 --- a/apps/trust/src/hooks/useCompliancePortal.ts +++ b/apps/trust/src/hooks/useCompliancePortal.ts @@ -24,7 +24,7 @@ import { CompliancePortalContext } from "#/providers/CompliancePortalProvider"; export function useCompliancePortal(): { id: string; - title: string; + entityName: string; } { const context = useContext(CompliancePortalContext); if (!context) { diff --git a/apps/trust/src/pages/NDAPage.tsx b/apps/trust/src/pages/NDAPage.tsx index ecda9e996..05f8aef7b 100644 --- a/apps/trust/src/pages/NDAPage.tsx +++ b/apps/trust/src/pages/NDAPage.tsx @@ -47,7 +47,7 @@ export const ndaPageQuery = graphql` id } currentCompliancePortal @required(action: THROW) { - title + entityName nonDisclosureAgreement { fileName fileUrl @@ -244,7 +244,7 @@ export function NDAPage(props: { __( "%s requires you to sign an NDA before accessing compliance documents.", ), - compliancePortal.title, + compliancePortal.entityName, )}

{isMobile && nda?.fileUrl && ( diff --git a/apps/trust/src/pages/OverviewPage.tsx b/apps/trust/src/pages/OverviewPage.tsx index a7c2eafaf..ca2eb1f3e 100644 --- a/apps/trust/src/pages/OverviewPage.tsx +++ b/apps/trust/src/pages/OverviewPage.tsx @@ -107,7 +107,7 @@ export function OverviewPage() { url={getCompliancePortalUrl("documents")} /> diff --git a/apps/trust/src/pages/SubprocessorsPage.tsx b/apps/trust/src/pages/SubprocessorsPage.tsx index 426f35415..338ced46b 100644 --- a/apps/trust/src/pages/SubprocessorsPage.tsx +++ b/apps/trust/src/pages/SubprocessorsPage.tsx @@ -45,7 +45,7 @@ export function SubprocessorsPage({ queryRef }: Props) {

{sprintf( __("Third-party subprocessors %s work with:"), - data.currentCompliancePortal?.title ?? "", + data.currentCompliancePortal?.entityName ?? "", )}

diff --git a/apps/trust/src/pages/auth/ConnectPage.tsx b/apps/trust/src/pages/auth/ConnectPage.tsx index 719ddf031..9ca7486fd 100644 --- a/apps/trust/src/pages/auth/ConnectPage.tsx +++ b/apps/trust/src/pages/auth/ConnectPage.tsx @@ -31,7 +31,7 @@ import type { ConnectPageQuery } from "./__generated__/ConnectPageQuery.graphql" export const connectPageQuery = graphql` query ConnectPageQuery { currentCompliancePortal @required(action: THROW) { - title + entityName } } `; @@ -45,10 +45,10 @@ export function ConnectPage(props: { const safeContinueUrl = useSafeContinueUrl(); const { - currentCompliancePortal: { title }, + currentCompliancePortal: { entityName }, } = usePreloadedQuery(connectPageQuery, queryRef); - usePageTitle(__(`Connect to ${title}'s Compliance Page`)); + usePageTitle(__(`Connect to ${entityName}'s Compliance Page`)); const initiateURL = new URL("/initiate", window.location.origin); initiateURL.searchParams.set("continue", safeContinueUrl.toString()); @@ -57,7 +57,7 @@ export function ConnectPage(props: {

- {__(`Connect to ${title}'s Compliance Page`)} + {__(`Connect to ${entityName}'s Compliance Page`)}

{__( diff --git a/apps/trust/src/queries/CompliancePortalGraph.ts b/apps/trust/src/queries/CompliancePortalGraph.ts index bfd6a9b98..a49a92725 100644 --- a/apps/trust/src/queries/CompliancePortalGraph.ts +++ b/apps/trust/src/queries/CompliancePortalGraph.ts @@ -31,7 +31,7 @@ export const currentCompliancePortalGraphQuery = graphql` currentCompliancePortal @required(action: THROW) { id slug - title + entityName description websiteUrl email @@ -120,6 +120,7 @@ export const currentTrustSubprocessorsQuery = graphql` query CompliancePortalGraphCurrentSubprocessorsQuery { currentCompliancePortal { id + entityName subprocessors(first: 50) { edges { node { diff --git a/e2e/console/compliance_portal_profile_test.go b/e2e/console/compliance_portal_profile_test.go index c978a57a6..c2b1e95ee 100644 --- a/e2e/console/compliance_portal_profile_test.go +++ b/e2e/console/compliance_portal_profile_test.go @@ -61,7 +61,7 @@ func TestCompliancePortal_UpdateProfile(t *testing.T) { updateCompliancePortal(input: $input) { compliancePortal { id - title + entityName description websiteUrl email @@ -75,7 +75,7 @@ func TestCompliancePortal_UpdateProfile(t *testing.T) { UpdateCompliancePortal struct { CompliancePortal struct { ID string `json:"id"` - Title string `json:"title"` + EntityName string `json:"entityName"` Description *string `json:"description"` WebsiteURL *string `json:"websiteUrl"` Email *string `json:"email"` @@ -87,7 +87,7 @@ func TestCompliancePortal_UpdateProfile(t *testing.T) { err = owner.Execute(updateMutation, map[string]any{ "input": map[string]any{ "compliancePortalId": compliancePortalID, - "title": "Acme Security", + "entityName": "Acme Security", "description": "We keep your data safe.", "websiteUrl": "https://example.com", "email": "security@example.com", @@ -98,7 +98,7 @@ func TestCompliancePortal_UpdateProfile(t *testing.T) { tc := result.UpdateCompliancePortal.CompliancePortal assert.Equal(t, compliancePortalID, tc.ID) - assert.Equal(t, "Acme Security", tc.Title) + assert.Equal(t, "Acme Security", tc.EntityName) require.NotNil(t, tc.Description) assert.Equal(t, "We keep your data safe.", *tc.Description) require.NotNil(t, tc.WebsiteURL) diff --git a/e2e/mcp/compliance_portal_test.go b/e2e/mcp/compliance_portal_test.go index c790d4fc3..615747312 100644 --- a/e2e/mcp/compliance_portal_test.go +++ b/e2e/mcp/compliance_portal_test.go @@ -35,10 +35,10 @@ type mcpFile struct { } type compliancePortal struct { - ID string `json:"id"` - Title string `json:"title"` - Active bool `json:"active"` - Logo *mcpFile `json:"logo,omitempty"` + ID string `json:"id"` + EntityName string `json:"entity_name"` + Active bool `json:"active"` + Logo *mcpFile `json:"logo,omitempty"` } type compliancePortalReference struct { @@ -175,7 +175,7 @@ func TestMCP_UpdateCompliancePortal(t *testing.T) { var updateResult struct { CompliancePortal struct { ID string `json:"id"` - Title string `json:"title"` + EntityName string `json:"entity_name"` Description *string `json:"description"` WebsiteURL *string `json:"website_url"` Email *string `json:"email"` @@ -184,7 +184,7 @@ func TestMCP_UpdateCompliancePortal(t *testing.T) { } mc.CallToolInto("updateCompliancePortal", map[string]any{ "compliance_portal_id": getResult.CompliancePortal.ID, - "title": "Acme Security", + "entity_name": "Acme Security", "description": "We keep your data safe.", "website_url": "https://example.com", "email": "security@example.com", @@ -192,7 +192,7 @@ func TestMCP_UpdateCompliancePortal(t *testing.T) { }, &updateResult) assert.Equal(t, getResult.CompliancePortal.ID, updateResult.CompliancePortal.ID) - assert.Equal(t, "Acme Security", updateResult.CompliancePortal.Title) + assert.Equal(t, "Acme Security", updateResult.CompliancePortal.EntityName) require.NotNil(t, updateResult.CompliancePortal.Description) assert.Equal(t, "We keep your data safe.", *updateResult.CompliancePortal.Description) require.NotNil(t, updateResult.CompliancePortal.WebsiteURL) diff --git a/e2e/trust/compliance_portal_oauth_connect_test.go b/e2e/trust/compliance_portal_oauth_connect_test.go index 23e157e59..f27ec47b3 100644 --- a/e2e/trust/compliance_portal_oauth_connect_test.go +++ b/e2e/trust/compliance_portal_oauth_connect_test.go @@ -83,20 +83,20 @@ func TestCompliancePortal_VisitorConnectViaCIMD(t *testing.T) { const query = ` query { currentCompliancePortal { - title + entityName } } ` var result struct { CurrentCompliancePortal struct { - Title string `json:"title"` + EntityName string `json:"entityName"` } `json:"currentCompliancePortal"` } err := visitor.ExecuteTrust(trustHost, query, nil, &result) require.NoError(t, err, "visitor session must authenticate trust GraphQL after CIMD connect") - assert.NotEmpty(t, result.CurrentCompliancePortal.Title) + assert.NotEmpty(t, result.CurrentCompliancePortal.EntityName) } func TestCompliancePortal_UnknownCIMDClientRejected(t *testing.T) { diff --git a/packages/n8n-node/nodes/Probo/actions/compliancePortal/update.operation.ts b/packages/n8n-node/nodes/Probo/actions/compliancePortal/update.operation.ts index d1ccf64cb..ca761e57b 100644 --- a/packages/n8n-node/nodes/Probo/actions/compliancePortal/update.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/compliancePortal/update.operation.ts @@ -77,8 +77,8 @@ export const description: INodeProperties[] = [ description: 'Whether search engines should index the compliance portal', }, { - displayName: 'Title', - name: 'title', + displayName: 'Entity Name', + name: 'entityName', type: 'string', displayOptions: { show: { @@ -87,7 +87,7 @@ export const description: INodeProperties[] = [ }, }, default: '', - description: 'The title shown on the public compliance page', + description: 'The entity name shown on the public compliance page', }, { displayName: 'Description', @@ -155,7 +155,7 @@ export async function execute( const websiteUrl = this.getNodeParameter('websiteUrl', itemIndex, '') as string; const email = this.getNodeParameter('email', itemIndex, '') as string; const headquarterAddress = this.getNodeParameter('headquarterAddress', itemIndex, '') as string; - const title = this.getNodeParameter('title', itemIndex, '') as string; + const entityName = this.getNodeParameter('entityName', itemIndex, '') as string; const query = ` mutation UpdateCompliancePortal($input: UpdateCompliancePortalInput!) { @@ -164,7 +164,7 @@ export async function execute( id active searchEngineIndexing - title + entityName description websiteUrl email @@ -178,7 +178,7 @@ export async function execute( const input: Record = { compliancePortalId, - title, + entityName, description, websiteUrl, email, diff --git a/pkg/cmd/compliance-portal/update/update.go b/pkg/cmd/compliance-portal/update/update.go index df3be56d4..346e5a0c0 100644 --- a/pkg/cmd/compliance-portal/update/update.go +++ b/pkg/cmd/compliance-portal/update/update.go @@ -49,7 +49,7 @@ mutation($input: UpdateCompliancePortalInput!) { id active searchEngineIndexing - title + entityName description websiteUrl email @@ -74,7 +74,7 @@ type updateResponse struct { ID string `json:"id"` Active bool `json:"active"` SearchEngineIndexing string `json:"searchEngineIndexing"` - Title string `json:"title"` + EntityName string `json:"entityName"` Description *string `json:"description"` WebsiteURL *string `json:"websiteUrl"` Email *string `json:"email"` @@ -92,7 +92,7 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command { flagWebsiteURL string flagEmail string flagHeadquarterAddress string - flagTitle string + flagEntityName string ) cmd := &cobra.Command{ @@ -193,8 +193,8 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command { input["headquarterAddress"] = flagHeadquarterAddress } - if cmd.Flags().Changed("title") { - input["title"] = flagTitle + if cmd.Flags().Changed("entity-name") { + input["entityName"] = flagEntityName } if len(input) == 1 { @@ -232,7 +232,7 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command { cmd.Flags().StringVar(&flagWebsiteURL, "website-url", "", "Compliance page website URL") cmd.Flags().StringVar(&flagEmail, "email", "", "Compliance page contact email") cmd.Flags().StringVar(&flagHeadquarterAddress, "headquarter-address", "", "Compliance page headquarter address") - cmd.Flags().StringVar(&flagTitle, "title", "", "Public compliance page title") + cmd.Flags().StringVar(&flagEntityName, "entity-name", "", "Entity name shown on the public compliance page") return cmd } diff --git a/pkg/complianceportal/management/portal_service.go b/pkg/complianceportal/management/portal_service.go index 73133bb01..064c3a542 100644 --- a/pkg/complianceportal/management/portal_service.go +++ b/pkg/complianceportal/management/portal_service.go @@ -46,7 +46,7 @@ type ( Slug *string SearchEngineIndexing *coredata.SearchEngineIndexing NonDisclosureAgreementFileID *gid.GID - Title *string + EntityName *string Description **string WebsiteURL **string Email **string @@ -75,8 +75,8 @@ func (utcr *UpdateRequest) Validate() error { v.Check(utcr.Slug, "slug", validator.SafeText(NameMaxLength)) v.Check(utcr.NonDisclosureAgreementFileID, "non_disclosure_agreement_file_id", validator.GID(coredata.FileEntityType)) - if utcr.Title != nil { - v.Check(*utcr.Title, "title", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength)) + if utcr.EntityName != nil { + v.Check(*utcr.EntityName, "entity_name", validator.Required(), validator.SafeTextNoNewLine(TitleMaxLength)) } if utcr.Description != nil { @@ -214,8 +214,8 @@ func (s *Service) Update( portal.SearchEngineIndexing = *req.SearchEngineIndexing } - if req.Title != nil { - portal.Title = *req.Title + if req.EntityName != nil { + portal.EntityName = *req.EntityName } if req.Description != nil { diff --git a/pkg/complianceportal/visitor/cimd.go b/pkg/complianceportal/visitor/cimd.go index c49555ae4..9b4c9b49b 100644 --- a/pkg/complianceportal/visitor/cimd.go +++ b/pkg/complianceportal/visitor/cimd.go @@ -85,7 +85,7 @@ func BuildClientMetadataDocument( doc := oauth2.ClientMetadataDocument{ ClientID: clientID, - ClientName: portal.Title, + ClientName: "Compliance at " + portal.EntityName + ".", ClientURI: portalRootURL, RedirectURIs: []string{redirectURI}, TokenEndpointAuthMethod: "none", diff --git a/pkg/complianceportal/visitor/cimd_test.go b/pkg/complianceportal/visitor/cimd_test.go index 27c6f60ab..021970468 100644 --- a/pkg/complianceportal/visitor/cimd_test.go +++ b/pkg/complianceportal/visitor/cimd_test.go @@ -66,7 +66,7 @@ func TestBuildClientMetadataDocument(t *testing.T) { websiteURL := "https://www.acme.com" portal := &coredata.CompliancePortal{ - Title: "Acme Compliance Page", + EntityName: "Acme", WebsiteURL: &websiteURL, } @@ -76,7 +76,7 @@ func TestBuildClientMetadataDocument(t *testing.T) { ) require.NoError(t, err) assert.Equal(t, "https://acme.example.com/.well-known/oauth-client-metadata", doc.ClientID) - assert.Equal(t, "Acme Compliance Page", doc.ClientName) + assert.Equal(t, "Compliance at Acme.", doc.ClientName) assert.Equal(t, []string{"https://acme.example.com/callback"}, doc.RedirectURIs) assert.Equal(t, "https://acme.example.com", doc.ClientURI) assert.Equal(t, VisitorOAuthScope, doc.Scope) @@ -88,7 +88,7 @@ func TestBuildClientMetadataDocument_LogoURIUsesBrandLogoEndpoint(t *testing.T) logoFileID := gid.MustParseGID("WR-qMrB5AAEAGQAAAZ9mIO8B8vDFQ-i3") portal := &coredata.CompliancePortal{ - Title: "Acme Compliance Page", + EntityName: "Acme", LogoFileID: &logoFileID, } diff --git a/pkg/coredata/compliance_portal.go b/pkg/coredata/compliance_portal.go index 1a39ba441..cb93bd116 100644 --- a/pkg/coredata/compliance_portal.go +++ b/pkg/coredata/compliance_portal.go @@ -49,7 +49,7 @@ type ( NonDisclosureAgreementFileID *gid.GID `db:"non_disclosure_agreement_file_id"` DefaultDomainID *gid.GID `db:"default_domain_id"` CustomDomainID *gid.GID `db:"custom_domain_id"` - Title string `db:"title"` + EntityName string `db:"entity_name"` Description *string `db:"description"` WebsiteURL *string `db:"website_url"` Email *string `db:"email"` @@ -129,7 +129,7 @@ SELECT non_disclosure_agreement_file_id, default_domain_id, custom_domain_id, - title, + entity_name, description, website_url, email, @@ -188,7 +188,7 @@ SELECT non_disclosure_agreement_file_id, default_domain_id, custom_domain_id, - title, + entity_name, description, website_url, email, @@ -247,7 +247,7 @@ SELECT non_disclosure_agreement_file_id, default_domain_id, custom_domain_id, - title, + entity_name, description, website_url, email, @@ -306,7 +306,7 @@ SELECT non_disclosure_agreement_file_id, default_domain_id, custom_domain_id, - title, + entity_name, description, website_url, email, @@ -365,7 +365,7 @@ SELECT non_disclosure_agreement_file_id, default_domain_id, custom_domain_id, - title, + entity_name, description, website_url, email, @@ -420,7 +420,7 @@ INSERT INTO trust_centers ( non_disclosure_agreement_file_id, default_domain_id, custom_domain_id, - title, + entity_name, description, website_url, email, @@ -440,7 +440,7 @@ INSERT INTO trust_centers ( @non_disclosure_agreement_file_id, @default_domain_id, @custom_domain_id, - @title, + @entity_name, @description, @website_url, @email, @@ -463,7 +463,7 @@ INSERT INTO trust_centers ( "non_disclosure_agreement_file_id": tc.NonDisclosureAgreementFileID, "default_domain_id": tc.DefaultDomainID, "custom_domain_id": tc.CustomDomainID, - "title": tc.Title, + "entity_name": tc.EntityName, "description": tc.Description, "website_url": tc.WebsiteURL, "email": tc.Email, @@ -502,7 +502,7 @@ SET non_disclosure_agreement_file_id = @non_disclosure_agreement_file_id, default_domain_id = @default_domain_id, custom_domain_id = @custom_domain_id, - title = @title, + entity_name = @entity_name, description = @description, website_url = @website_url, email = @email, @@ -525,7 +525,7 @@ WHERE "non_disclosure_agreement_file_id": tc.NonDisclosureAgreementFileID, "default_domain_id": tc.DefaultDomainID, "custom_domain_id": tc.CustomDomainID, - "title": tc.Title, + "entity_name": tc.EntityName, "description": tc.Description, "website_url": tc.WebsiteURL, "email": tc.Email, diff --git a/pkg/coredata/migrations/20260721T165706Z.sql b/pkg/coredata/migrations/20260721T165706Z.sql new file mode 100644 index 000000000..656ce0aae --- /dev/null +++ b/pkg/coredata/migrations/20260721T165706Z.sql @@ -0,0 +1,36 @@ +-- Copyright (c) 2026 Probo Inc . +-- +-- Permission is hereby granted, free of charge, to any person obtaining a copy +-- of this software and associated documentation files (the "Software"), to deal +-- in the Software without restriction, including without limitation the rights +-- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +-- copies of the Software, and to permit persons to whom the Software is +-- furnished to do so, subject to the following conditions: +-- +-- The above copyright notice and this permission notice shall be included in +-- all copies or substantial portions of the Software. +-- +-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +-- SOFTWARE. + +-- entity_name is the short name shown in the hero ("Compliance at {{name}}.") +-- and TopBar brand. Prefill from the organization so existing portals keep a +-- sensible default; customized full titles in the old title column are dropped. +ALTER TABLE trust_centers + ADD COLUMN entity_name TEXT NOT NULL DEFAULT ''; + +UPDATE trust_centers tc +SET entity_name = o.name +FROM organizations o +WHERE tc.organization_id = o.id; + +ALTER TABLE trust_centers + ALTER COLUMN entity_name DROP DEFAULT; + +ALTER TABLE trust_centers + DROP COLUMN title; diff --git a/pkg/iam/organization_service.go b/pkg/iam/organization_service.go index ee18d7262..2950f1fb4 100644 --- a/pkg/iam/organization_service.go +++ b/pkg/iam/organization_service.go @@ -614,7 +614,7 @@ func (s *OrganizationService) CreateOrganization( TenantID: organization.TenantID, Active: false, Slug: slug.MakeWithEntropy(organization.Name), - Title: "Compliance at " + organization.Name + ".", + EntityName: organization.Name, SearchEngineIndexing: coredata.SearchEngineIndexingNotIndexable, MailingListID: &mailingList.ID, CreatedAt: now, diff --git a/pkg/server/api/complianceportal/v1/graphql/compliance_portal.graphql b/pkg/server/api/complianceportal/v1/graphql/compliance_portal.graphql index 11cfd83d5..d4f1b51bf 100644 --- a/pkg/server/api/complianceportal/v1/graphql/compliance_portal.graphql +++ b/pkg/server/api/complianceportal/v1/graphql/compliance_portal.graphql @@ -9,7 +9,7 @@ type CompliancePortal implements Node { websiteUrl: String email: String headquarterAddress: String - title: String! + entityName: String! nonDisclosureAgreement: NonDisclosureAgreement @goField(forceResolver: true) diff --git a/pkg/server/api/complianceportal/v1/mux.go b/pkg/server/api/complianceportal/v1/mux.go index fd0e92873..4a791adb1 100644 --- a/pkg/server/api/complianceportal/v1/mux.go +++ b/pkg/server/api/complianceportal/v1/mux.go @@ -134,7 +134,8 @@ func compliancePageHeadData() HeadDataFunc { compliancePageBaseURL := complianceportal.CompliancePortalBaseURLFromContext(r.Context()) - description := tc.Title + pageTitle := "Compliance at " + tc.EntityName + "." + description := pageTitle if tc.Description != nil && *tc.Description != "" { description = *tc.Description } @@ -142,7 +143,7 @@ func compliancePageHeadData() HeadDataFunc { htmlLang, canonical, hreflang := SEOFromRequest(r) headData := HeadData{ - Title: tc.Title, + Title: pageTitle, Description: description, HTMLLang: htmlLang, OGURL: canonical, diff --git a/pkg/server/api/complianceportal/v1/types/compliance_portal.go b/pkg/server/api/complianceportal/v1/types/compliance_portal.go index 0c840daf5..0500a6b73 100644 --- a/pkg/server/api/complianceportal/v1/types/compliance_portal.go +++ b/pkg/server/api/complianceportal/v1/types/compliance_portal.go @@ -29,7 +29,7 @@ func NewCompliancePortal(cp *coredata.CompliancePortal) *CompliancePortal { ID: cp.ID, Active: cp.Active, Slug: cp.Slug, - Title: cp.Title, + EntityName: cp.EntityName, Description: cp.Description, WebsiteURL: cp.WebsiteURL, Email: cp.Email, diff --git a/pkg/server/api/console/v1/compliance_portal_resolvers.go b/pkg/server/api/console/v1/compliance_portal_resolvers.go index 2c51c9748..e0af837f6 100644 --- a/pkg/server/api/console/v1/compliance_portal_resolvers.go +++ b/pkg/server/api/console/v1/compliance_portal_resolvers.go @@ -804,7 +804,7 @@ func (r *mutationResolver) UpdateCompliancePortal(ctx context.Context, input typ WebsiteURL: gqlutils.UnwrapOmittable(input.WebsiteURL), Email: gqlutils.UnwrapOmittable(input.Email), HeadquarterAddress: gqlutils.UnwrapOmittable(input.HeadquarterAddress), - Title: input.Title, + EntityName: input.EntityName, }, ) if err != nil { diff --git a/pkg/server/api/console/v1/graphql/compliance_portal.graphql b/pkg/server/api/console/v1/graphql/compliance_portal.graphql index b5eac36f9..d1b9cf3cb 100644 --- a/pkg/server/api/console/v1/graphql/compliance_portal.graphql +++ b/pkg/server/api/console/v1/graphql/compliance_portal.graphql @@ -344,7 +344,7 @@ type CompliancePortal implements Node websiteUrl: String email: String headquarterAddress: String - title: String! + entityName: String! createdAt: Datetime! updatedAt: Datetime! organization: Organization! @goField(forceResolver: true) @@ -747,7 +747,7 @@ input UpdateCompliancePortalInput { compliancePortalId: ID! active: Boolean searchEngineIndexing: SearchEngineIndexing - title: String + entityName: String description: String @goField(omittable: true) websiteUrl: String @goField(omittable: true) email: String @goField(omittable: true) diff --git a/pkg/server/api/console/v1/types/compliance_portal.go b/pkg/server/api/console/v1/types/compliance_portal.go index bfc384901..0e384165e 100644 --- a/pkg/server/api/console/v1/types/compliance_portal.go +++ b/pkg/server/api/console/v1/types/compliance_portal.go @@ -38,7 +38,7 @@ type CompliancePortal struct { WebsiteURL *string `json:"websiteUrl,omitempty"` Email *string `json:"email,omitempty"` HeadquarterAddress *string `json:"headquarterAddress,omitempty"` - Title string `json:"title"` + EntityName string `json:"entityName"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` Organization *Organization `json:"organization"` @@ -67,7 +67,7 @@ func NewCompliancePortal(tc *coredata.CompliancePortal) *CompliancePortal { WebsiteURL: tc.WebsiteURL, Email: tc.Email, HeadquarterAddress: tc.HeadquarterAddress, - Title: tc.Title, + EntityName: tc.EntityName, CreatedAt: tc.CreatedAt, UpdatedAt: tc.UpdatedAt, } diff --git a/pkg/server/api/mcp/v1/schema.resolvers.go b/pkg/server/api/mcp/v1/schema.resolvers.go index d2acb5b5b..16aadf7a3 100644 --- a/pkg/server/api/mcp/v1/schema.resolvers.go +++ b/pkg/server/api/mcp/v1/schema.resolvers.go @@ -4954,8 +4954,8 @@ func (r *Resolver) UpdateCompliancePortalTool(ctx context.Context, req *mcp.Call updateReq.Email = UnwrapOmittable(input.Email) updateReq.HeadquarterAddress = UnwrapOmittable(input.HeadquarterAddress) - if title := UnwrapOmittable(input.Title); title != nil { - updateReq.Title = *title + if entityName := UnwrapOmittable(input.EntityName); entityName != nil { + updateReq.EntityName = *entityName } compliancePortal, _, err := prb.Update(ctx, scope, updateReq) diff --git a/pkg/server/api/mcp/v1/specification.yaml b/pkg/server/api/mcp/v1/specification.yaml index 15ca56ff6..9bf5b3884 100644 --- a/pkg/server/api/mcp/v1/specification.yaml +++ b/pkg/server/api/mcp/v1/specification.yaml @@ -8832,7 +8832,7 @@ components: - organization_id - active - search_engine_indexing - - title + - entity_name - created_at - updated_at properties: @@ -8870,9 +8870,9 @@ components: - string - "null" description: Compliance page headquarter address - title: + entity_name: type: string - description: Public compliance page title + description: Entity name shown on the public compliance page created_at: type: string format: date-time @@ -9079,11 +9079,11 @@ components: - "null" description: Compliance page headquarter address go.probo.inc/mcpgen/omittable: true - title: + entity_name: type: - string - "null" - description: Public compliance page title + description: Entity name shown on the public compliance page go.probo.inc/mcpgen/omittable: true UpdateCompliancePortalOutput: diff --git a/pkg/server/api/mcp/v1/types/compliance_portal.go b/pkg/server/api/mcp/v1/types/compliance_portal.go index 310d8efd9..e53d99957 100644 --- a/pkg/server/api/mcp/v1/types/compliance_portal.go +++ b/pkg/server/api/mcp/v1/types/compliance_portal.go @@ -31,7 +31,7 @@ func NewCompliancePortal(tc *coredata.CompliancePortal) *CompliancePortal { OrganizationID: tc.OrganizationID, Active: tc.Active, SearchEngineIndexing: tc.SearchEngineIndexing, - Title: tc.Title, + EntityName: tc.EntityName, Description: tc.Description, WebsiteURL: tc.WebsiteURL, Email: tc.Email,