Replace third-party owners with administrators
Some checks failed
github / Analyze (go) (push) Has been cancelled
github / Analyze (actions) (push) Has been cancelled
github / Analyze (javascript-typescript) (push) Has been cancelled
make / build-apps (push) Has been cancelled
make / probod binary (darwin/amd64) (push) Has been cancelled
make / probod binary (freebsd/amd64) (push) Has been cancelled
make / probod binary (linux/amd64) (push) Has been cancelled
make / probod binary (openbsd/amd64) (push) Has been cancelled
make / probod binary (windows/amd64) (push) Has been cancelled
make / probod binary (darwin/arm64) (push) Has been cancelled
make / probod binary (freebsd/arm64) (push) Has been cancelled
make / probod binary (linux/arm64) (push) Has been cancelled
make / probod binary (openbsd/arm64) (push) Has been cancelled
make / probo-agent (darwin/amd64) (push) Has been cancelled
make / probo-agent (freebsd/amd64) (push) Has been cancelled
make / probo-agent (linux/amd64) (push) Has been cancelled
make / probo-agent (windows/amd64) (push) Has been cancelled
make / probo-agent (darwin/arm64) (push) Has been cancelled
make / probo-agent (freebsd/arm64) (push) Has been cancelled
make / probo-agent (linux/arm64) (push) Has been cancelled
make / probo-agent (windows/arm64) (push) Has been cancelled
make / docker (amd64) (push) Has been cancelled
make / docker (arm64) (push) Has been cancelled
make / snapshot-scan (push) Has been cancelled
make / build-probod (push) Has been cancelled
make / build-probo-agent (push) Has been cancelled
make / lint-go (push) Has been cancelled
make / lint-js (push) Has been cancelled
make / lint-swift (push) Has been cancelled
make / lint-shell (push) Has been cancelled
make / test (push) Has been cancelled
make / test-e2e (push) Has been cancelled
trufflehog / scan (push) Has been cancelled

Migrate business and security owners into a shared administrators list across GraphQL, MCP, CLI, n8n, and the console.

Signed-off-by: Sacha Al Himdani <sacha@probo.com>
This commit is contained in:
Sacha Al Himdani
2026-07-27 12:24:43 +02:00
parent 084726dbad
commit 6a4f124adb
31 changed files with 808 additions and 601 deletions

View File

@@ -2462,9 +2462,9 @@
},
"thirdPartyOverviewPage": {
"pageTitle": "{{name}} - Overview",
"sections": { "details": "Third party details", "countries": "Countries", "ownership": "Ownership details", "links": "Links", "dataAgreements": "Data agreements" },
"fields": { "name": "Name", "description": "Description", "category": "Category", "legalName": "Legal name", "headquarterAddress": "Headquarter address", "websiteUrl": "Website URL", "businessOwner": "Business owner", "securityOwner": "Security owner" },
"placeholders": { "category": "Select a category" },
"sections": { "details": "Third party details", "countries": "Countries", "administrators": "Administrators", "links": "Links", "dataAgreements": "Data agreements" },
"fields": { "name": "Name", "description": "Description", "category": "Category", "legalName": "Legal name", "headquarterAddress": "Headquarter address", "websiteUrl": "Website URL", "administrators": "Administrators" },
"placeholders": { "category": "Select a category", "administrators": "Add administrators..." },
"categories": { "analytics": "Analytics", "cloudMonitoring": "Cloud Monitoring", "cloudProvider": "Cloud Provider", "collaboration": "Collaboration", "customerSupport": "Customer Support", "dataStorageAndProcessing": "Data Storage and Processing", "documentManagement": "Document Management", "employeeManagement": "Employee Management", "engineering": "Engineering", "finance": "Finance", "identityProvider": "Identity Provider", "it": "IT", "marketing": "Marketing", "officeOperations": "Office Operations", "other": "Other", "passwordManagement": "Password Management", "productAndDesign": "Product and Design", "professionalServices": "Professional Services", "recruiting": "Recruiting", "sales": "Sales", "security": "Security", "versionControl": "Version Control" },
"urlLabels": { "statusPage": "Status page URL", "termsOfService": "Terms of service URL", "privacyPolicy": "Privacy document URL", "serviceLevelAgreement": "Service level agreement URL", "dataProcessingAgreement": "Data processing agreement URL", "securityPage": "Security page URL", "trustPage": "Trust page URL" },
"agreements": { "businessAssociate": "Business Associate Agreement", "dataPrivacy": "Data Privacy Agreement", "noBusinessAssociate": "No business associate agreement available", "noDataPrivacy": "No data privacy agreement available", "validity": { "range": "Valid from {{from}} until {{until}}", "from": "Valid from {{date}}", "until": "Valid until {{date}}" } },

View File

@@ -5743,7 +5743,7 @@
"sections": {
"details": "Détails du tiers",
"countries": "Pays",
"ownership": "Détails de la propriété",
"administrators": "Administrateurs",
"links": "Liens",
"dataAgreements": "Accords de données"
},
@@ -5754,11 +5754,11 @@
"legalName": "Nom légal",
"headquarterAddress": "Adresse du siège",
"websiteUrl": "URL du site web",
"businessOwner": "Propriétaire métier",
"securityOwner": "Propriétaire sécurité"
"administrators": "Administrateurs"
},
"placeholders": {
"category": "Sélectionner une catégorie"
"category": "Sélectionner une catégorie",
"administrators": "Ajouter des administrateurs..."
},
"categories": {
"analytics": "Analytique",

View File

@@ -45,8 +45,7 @@ const schema = z.object({
countries: z.array(z.string()),
securityPageUrl: z.string().optional().nullable(),
trustPageUrl: z.string().optional().nullable(),
businessOwnerId: z.string().nullish(),
securityOwnerId: z.string().nullish(),
administratorIds: z.array(z.string()),
});
const thirdPartyFormFragment = graphql`
@@ -67,11 +66,10 @@ const thirdPartyFormFragment = graphql`
countries
securityPageUrl
trustPageUrl
businessOwner {
id
}
securityOwner {
administrators {
id
fullName
emailAddress
}
}
`;
@@ -112,8 +110,7 @@ export function useThirdPartyForm(thirdPartyKey: useThirdPartyFormFragment$key)
countries: [...(thirdParty.countries ?? [])],
securityPageUrl: thirdParty.securityPageUrl || null,
trustPageUrl: thirdParty.trustPageUrl || null,
businessOwnerId: thirdParty.businessOwner?.id,
securityOwnerId: thirdParty.securityOwner?.id,
administratorIds: thirdParty.administrators.map(a => a.id),
}),
[thirdParty],
);
@@ -151,5 +148,6 @@ export function useThirdPartyForm(thirdPartyKey: useThirdPartyFormFragment$key)
return {
...form,
handleSubmit,
administrators: thirdParty.administrators,
};
}

View File

@@ -41,7 +41,7 @@ import type { ThirdPartyOverviewPageQuery } from "#/__generated__/core/ThirdPart
import type { ThirdPartyCategory } from "#/__generated__/core/useThirdPartyFormFragment.graphql";
import { ControlledField } from "#/components/form/ControlledField";
import { CountriesField } from "#/components/form/CountriesField";
import { PeopleSelectField } from "#/components/form/PeopleSelectField";
import { PeopleMultiSelectField } from "#/components/form/PeopleMultiSelectField";
import { useThirdPartyForm } from "#/hooks/forms/useThirdPartyForm";
import { useOrganizationId } from "#/hooks/useOrganizationId";
@@ -156,6 +156,7 @@ export default function ThirdPartyOverviewPage(props: ThirdPartyOverviewPageProp
register,
handleSubmit,
formState: { errors, isSubmitting },
administrators,
} = useThirdPartyForm(thirdParty);
const thirdPartyWithBAA
@@ -271,25 +272,21 @@ export default function ThirdPartyOverviewPage(props: ThirdPartyOverviewPageProp
</div>
<div className="space-y-4">
<h2 className="text-base font-medium">{t("thirdPartyOverviewPage.sections.ownership")}</h2>
<h2 className="text-base font-medium">{t("thirdPartyOverviewPage.sections.administrators")}</h2>
<Card className="space-y-4" padded>
<PeopleSelectField
<PeopleMultiSelectField
organizationId={organizationId}
control={control}
name="businessOwnerId"
label={t("thirdPartyOverviewPage.fields.businessOwner")}
error={errors.businessOwnerId?.message}
name="administratorIds"
label={t("thirdPartyOverviewPage.fields.administrators")}
error={errors.administratorIds?.message}
disabled={isFormDisabled}
optional={true}
/>
<PeopleSelectField
organizationId={organizationId}
control={control}
name="securityOwnerId"
label={t("thirdPartyOverviewPage.fields.securityOwner")}
error={errors.securityOwnerId?.message}
disabled={isFormDisabled}
optional={true}
selectedPeople={administrators.map(a => ({
id: a.id,
fullName: a.fullName,
emailAddress: a.emailAddress,
}))}
placeholder={t("thirdPartyOverviewPage.placeholders.administrators")}
/>
</Card>
</div>