Switch console file fields to File download URLs

Replace presigned URL string fields (logoUrl, fileUrl, ndaFileName,
etc.) with nested File references resolved through /api/files/v1/.
Update console Relay queries and e2e coverage accordingly.

Route NDA upload through filemanager.PutFile and return stable IAM
org logo URLs for consistency with the files API.

Signed-off-by: Ludovic Vielle <ludovic@probo.com>
This commit is contained in:
Ludovic Vielle
2026-06-10 14:40:33 +02:00
parent 003fd2baf1
commit 3475dd0560
50 changed files with 545 additions and 386 deletions

View File

@@ -189,8 +189,8 @@ export default function AuditDetailsPage(props: Props) {
<div className="flex items-center gap-3">
<FrameworkLogo
name={auditEntry.framework?.name || ""}
lightLogoURL={auditEntry.framework?.lightLogoURL}
darkLogoURL={auditEntry.framework?.darkLogoURL}
lightLogoURL={auditEntry.framework?.lightLogo?.downloadUrl}
darkLogoURL={auditEntry.framework?.darkLogo?.downloadUrl}
/>
<div className="text-2xl">{auditEntry.framework?.name}</div>
</div>

View File

@@ -26,7 +26,10 @@ const fragment = graphql`
fragment ElectronicSignatureSectionFragment on ElectronicSignature {
status
signedAt
certificateFileUrl
certificate {
downloadUrl
fileName
}
events {
id
eventType
@@ -36,16 +39,6 @@ const fragment = graphql`
}
`;
function getFilenameFromUrl(url: string): string | null {
try {
const disposition = new URL(url).searchParams.get("response-content-disposition");
const match = disposition?.match(/filename="([^"]+)"/);
return match ? decodeURIComponent(match[1]) : null;
} catch {
return null;
}
}
export function ElectronicSignatureSection({
fragmentRef,
}: {
@@ -72,17 +65,17 @@ export function ElectronicSignatureSection({
</span>
</div>
)}
{signature.certificateFileUrl && (
{signature.certificate?.downloadUrl && (
<div className="flex items-center justify-between">
<span className="text-sm text-txt-secondary">{__("Certificate")}</span>
<a
href={signature.certificateFileUrl}
href={signature.certificate.downloadUrl}
target="_blank"
rel="noopener noreferrer"
className="text-sm text-txt-primary hover:underline"
download
>
{getFilenameFromUrl(signature.certificateFileUrl) ?? __("Download")}
{signature.certificate.fileName ?? __("Download")}
</a>
</div>
)}

View File

@@ -42,8 +42,12 @@ export const compliancePageBrandPageQuery = graphql`
... on Organization {
compliancePage: trustCenter @required(action: THROW) {
id
logoFileUrl
darkLogoFileUrl
logo {
downloadUrl
}
darkLogo {
downloadUrl
}
canUpdate: permission(action: "core:trust-center:update")
...CompliancePageFrameworkList_compliancePageFragment
...CompliancePageExternalUrlsSection_trustCenterFragment
@@ -58,8 +62,12 @@ const updateTrustCenterBrandMutation = graphql`
updateTrustCenterBrand(input: $input) {
trustCenter {
id
logoFileUrl
darkLogoFileUrl
logo {
downloadUrl
}
darkLogo {
downloadUrl
}
}
}
}
@@ -77,8 +85,8 @@ export function CompliancePageBrandPage(props: { queryRef: PreloadedQuery<Compli
}
const trustCenterId = organization.compliancePage.id;
const logoFileUrl = organization.compliancePage.logoFileUrl;
const darkLogoFileUrl = organization.compliancePage.darkLogoFileUrl;
const logoDownloadUrl = organization.compliancePage.logo?.downloadUrl;
const darkLogoDownloadUrl = organization.compliancePage.darkLogo?.downloadUrl;
const [logoPreview, setLogoPreview] = useState<string | null>(null);
const [darkLogoPreview, setDarkLogoPreview] = useState<string | null>(null);
@@ -234,8 +242,8 @@ export function CompliancePageBrandPage(props: { queryRef: PreloadedQuery<Compli
});
};
const currentLogoUrl = logoPreview || logoFileUrl;
const currentDarkLogoUrl = darkLogoPreview || darkLogoFileUrl;
const currentLogoUrl = logoPreview || logoDownloadUrl;
const currentDarkLogoUrl = darkLogoPreview || darkLogoDownloadUrl;
return (
<div className="space-y-8">

View File

@@ -35,7 +35,9 @@ const fileFragment = graphql`
id
name
category
fileUrl
file {
downloadUrl
}
trustCenterVisibility
createdAt
canUpdate: permission(action: "core:trust-center-file:update")
@@ -124,7 +126,7 @@ export function CompliancePageFileListItem(props: {
variant="secondary"
icon={IconArrowLink}
onClick={() =>
window.open(file.fileUrl, "_blank", "noopener,noreferrer")}
window.open(file.file?.downloadUrl, "_blank", "noopener,noreferrer")}
title={__("Download")}
/>
{file.canUpdate && (

View File

@@ -45,8 +45,12 @@ const compliancePageFragment = graphql`
framework {
id
name
lightLogoURL
darkLogoURL
lightLogo {
downloadUrl
}
darkLogo {
downloadUrl
}
}
}
}
@@ -198,8 +202,8 @@ function CompliancePageFrameworkListItem(props: {
<div className="flex items-center gap-3">
<FrameworkLogo
className="size-8"
lightLogoURL={framework.lightLogoURL}
darkLogoURL={framework.darkLogoURL}
lightLogoURL={framework.lightLogo?.downloadUrl}
darkLogoURL={framework.darkLogo?.downloadUrl}
name={framework.name}
/>
{framework.name}

View File

@@ -24,8 +24,10 @@ const fragment = graphql`
fragment CompliancePageNDASectionFragment on Organization {
compliancePage: trustCenter {
id
ndaFileName
ndaFileUrl
nda {
fileName
downloadUrl
}
canUploadNDA: permission(action: "core:trust-center:upload-nda")
canDeleteNDA: permission(action: "core:trust-center:delete-nda")
}
@@ -103,7 +105,7 @@ export function CompliancePageNDASection(props: { fragmentRef: CompliancePageNDA
{(isUploadingNDA || isDeletingNDA) && <Spinner />}
</div>
<div className="space-y-2">
{!organization.compliancePage?.ndaFileName
{!organization.compliancePage?.nda?.fileName
&& organization.compliancePage?.canUploadNDA
? (
<p className="text-sm text-txt-tertiary">
@@ -115,14 +117,14 @@ export function CompliancePageNDASection(props: { fragmentRef: CompliancePageNDA
: (
<></>
)}
{organization.compliancePage?.ndaFileName
{organization.compliancePage?.nda?.fileName
? (
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
<p className="text-sm font-medium">
{organization.compliancePage.ndaFileName
{organization.compliancePage.nda?.fileName
|| __("Non-Disclosure Agreement")}
</p>
</div>
@@ -137,9 +139,9 @@ export function CompliancePageNDASection(props: { fragmentRef: CompliancePageNDA
type="button"
variant="secondary"
onClick={() => {
if (organization.compliancePage?.ndaFileUrl) {
if (organization.compliancePage?.nda?.downloadUrl) {
window.open(
organization.compliancePage.ndaFileUrl,
organization.compliancePage.nda.downloadUrl,
"_blank",
"noopener,noreferrer",
);

View File

@@ -24,7 +24,9 @@ import { DeleteTrustCenterReferenceDialog } from "#/components/trustCenter/Delet
const fragment = graphql`
fragment CompliancePageReferenceListItemFragment on TrustCenterReference {
id
logoUrl
logo {
downloadUrl
}
name
description
websiteUrl
@@ -81,7 +83,7 @@ export function CompliancePageReferenceListItem(props: {
>
<Td>
<div className="flex items-center gap-3">
<Avatar src={reference.logoUrl} name={reference.name} size="m" />
<Avatar src={reference.logo?.downloadUrl} name={reference.name} size="m" />
<span className="font-medium">{reference.name}</span>
</div>
</Td>

View File

@@ -53,10 +53,13 @@ const frameworkDetailFragment = graphql`
name
# eslint-disable-next-line relay/unused-fields
description
lightLogo {
downloadUrl
}
# eslint-disable-next-line relay/unused-fields
lightLogoURL
# eslint-disable-next-line relay/unused-fields
darkLogoURL
darkLogo {
downloadUrl
}
canExport: permission(action: "core:franework:export")
canUpdate: permission(action: "core:framework:update")
canDelete: permission(action: "core:framework:delete")
@@ -147,7 +150,11 @@ export default function FrameworkDetailPage(props: Props) {
<PageHeader
title={(
<>
<FrameworkLogo {...framework} />
<FrameworkLogo
name={framework.name}
lightLogoURL={framework.lightLogo?.downloadUrl}
darkLogoURL={framework.darkLogo?.downloadUrl}
/>
{framework.name}
</>
)}

View File

@@ -187,8 +187,12 @@ const frameworkCardFragment = graphql`
id
name
description
lightLogoURL
darkLogoURL
lightLogo {
downloadUrl
}
darkLogo {
downloadUrl
}
canUpdate: permission(action: "core:framework:update")
canDelete: permission(action: "core:framework:delete")
}
@@ -220,8 +224,8 @@ function FrameworkCard(props: FrameworkCardProps) {
<div className="flex justify-between mb-3">
<FrameworkLogo
name={framework.name}
lightLogoURL={framework.lightLogoURL}
darkLogoURL={framework.darkLogoURL}
lightLogoURL={framework.lightLogo?.downloadUrl}
darkLogoURL={framework.darkLogo?.downloadUrl}
/>
{props.hasAnyAction && (
<ActionDropdown className="z-10 relative">

View File

@@ -30,7 +30,9 @@ export type CommonThirdPartyRef
export const commonThirdPartyFragment = graphql`
fragment CommonThirdPartyCombobox_commonThirdParty on CommonThirdParty @inline {
name
logoUrl
logo {
downloadUrl
}
category
websiteUrl
headquarterAddress
@@ -51,7 +53,9 @@ export const commonThirdPartiesQuery = graphql`
commonThirdParties(name: $name) {
id
name
logoUrl
logo {
downloadUrl
}
...CommonThirdPartyCombobox_commonThirdParty
}
}
@@ -107,7 +111,7 @@ export function CommonThirdPartyCombobox({
>
<Avatar
name={thirdParty.name}
src={thirdParty.logoUrl}
src={thirdParty.logo?.downloadUrl}
/>
{thirdParty.name}
</ComboboxItem>

View File

@@ -36,7 +36,9 @@ const updateBusinessAssociateAgreementMutation = graphql`
updateThirdPartyBusinessAssociateAgreement(input: $input) {
thirdPartyBusinessAssociateAgreement {
id
fileUrl
file {
downloadUrl
}
validFrom
validUntil
createdAt

View File

@@ -36,7 +36,9 @@ const updateDataPrivacyAgreementMutation = graphql`
updateThirdPartyDataPrivacyAgreement(input: $input) {
thirdPartyDataPrivacyAgreement {
id
fileUrl
file {
downloadUrl
}
validFrom
validUntil
createdAt

View File

@@ -38,8 +38,10 @@ const uploadBusinessAssociateAgreementMutation = graphql`
uploadThirdPartyBusinessAssociateAgreement(input: $input) {
thirdPartyBusinessAssociateAgreement {
id
fileName
fileUrl
file {
fileName
downloadUrl
}
validFrom
validUntil
createdAt

View File

@@ -38,8 +38,10 @@ const uploadDataPrivacyAgreementMutation = graphql`
uploadThirdPartyDataPrivacyAgreement(input: $input) {
thirdPartyDataPrivacyAgreement {
id
fileName
fileUrl
file {
fileName
downloadUrl
}
validFrom
validUntil
createdAt

View File

@@ -50,8 +50,10 @@ const thirdPartyBusinessAssociateAgreementFragment = graphql`
fragment ThirdPartyOverviewTabBusinessAssociateAgreementFragment on ThirdParty {
businessAssociateAgreement {
id
fileName
fileUrl
file {
fileName
downloadUrl
}
validFrom
validUntil
canUpdate: permission(
@@ -68,8 +70,10 @@ const thirdPartyDataPrivacyAgreementFragment = graphql`
fragment ThirdPartyOverviewTabDataPrivacyAgreementFragment on ThirdParty {
dataPrivacyAgreement {
id
fileName
fileUrl
file {
fileName
downloadUrl
}
validFrom
validUntil
canUpdate: permission(action: "core:thirdParty-data-privacy-agreement:update")
@@ -299,7 +303,7 @@ export default function ThirdPartyOverviewTab() {
</h3>
<p className="text-sm text-txt-secondary mt-1">
{businessAssociateAgreement
? businessAssociateAgreement.fileName
? businessAssociateAgreement.file.fileName
: __("No business associate agreement available")}
</p>
{(businessAssociateAgreement?.validFrom
@@ -322,8 +326,8 @@ export default function ThirdPartyOverviewTab() {
variant="secondary"
onClick={() =>
downloadFile(
businessAssociateAgreement.fileUrl,
businessAssociateAgreement.fileName,
businessAssociateAgreement.file.downloadUrl,
businessAssociateAgreement.file.fileName,
)}
>
{__("Download PDF")}
@@ -343,7 +347,7 @@ export default function ThirdPartyOverviewTab() {
{businessAssociateAgreement.canDelete && (
<DeleteBusinessAssociateAgreementDialog
thirdPartyId={thirdParty.id}
fileName={businessAssociateAgreement.fileName}
fileName={businessAssociateAgreement.file.fileName}
onSuccess={() => window.location.reload()}
>
<Button variant="quaternary" icon={IconTrashCan} />
@@ -373,7 +377,7 @@ export default function ThirdPartyOverviewTab() {
</h3>
<p className="text-sm text-txt-secondary mt-1">
{dataPrivacyAgreement
? dataPrivacyAgreement.fileName
? dataPrivacyAgreement.file.fileName
: __("No data privacy agreement available")}
</p>
{(dataPrivacyAgreement?.validFrom
@@ -396,8 +400,8 @@ export default function ThirdPartyOverviewTab() {
variant="secondary"
onClick={() =>
downloadFile(
dataPrivacyAgreement.fileUrl,
dataPrivacyAgreement.fileName,
dataPrivacyAgreement.file.downloadUrl,
dataPrivacyAgreement.file.fileName,
)}
>
{__("Download PDF")}
@@ -417,7 +421,7 @@ export default function ThirdPartyOverviewTab() {
{dataPrivacyAgreement.canDelete && (
<DeleteDataPrivacyAgreementDialog
thirdPartyId={thirdParty.id}
fileName={dataPrivacyAgreement.fileName}
fileName={dataPrivacyAgreement.file.fileName}
onSuccess={() => window.location.reload()}
>
<Button variant="quaternary" icon={IconTrashCan} />