Added links to EU Supply tenders, as there are to Find a Tender notices

This commit is contained in:
bwilliams committed 2026-10-21 14:14:47 +01:00
1 parent 3a06132071
commit 64886a2f1e
5 files changed
+143 -50

No files matched your search

+55 -8
View File
@@ -13,14 +13,6 @@ export function mergeOpportunities(
return [...byUuid.values()];
}
/** The source the backend gives opportunities fetched from Find a Tender. */
const FIND_A_TENDER_SOURCE = 'FindATender';
/** Whether the opportunity was fetched from Find a Tender, so its releases are notices there. */
export function isFromFindATender(opportunity: Opportunity): boolean {
return opportunity.source === FIND_A_TENDER_SOURCE;
}
/** The opportunity's newest release, or undefined if it has none. */
export function latestRelease(opportunity: Opportunity): OpportunityRelease | undefined {
let latest: OpportunityRelease | undefined;
@@ -36,3 +28,58 @@ export function latestRelease(opportunity: Opportunity): OpportunityRelease | un
export function findATenderNoticeUrl(releaseId: string): string {
return `https://www.find-tender.service.gov.uk/notice/${encodeURIComponent(releaseId)}`;
}
// EU Supply's releases are the portal's tenders, as the backend stores them
// (internal/eusupply's Tender), with the address of the tender's page.
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === 'object' && value !== null;
const isWebAddress = (address: string): boolean => {
try {
const { protocol } = new URL(address);
return protocol === 'https:' || protocol === 'http:';
} catch {
return false;
}
};
/**
* The page of an EU Supply tender: the url in its release's data, as it is,
* or undefined if there's none. Only http and https addresses are used, so
* a malformed or javascript: one is never opened.
*/
export function euSupplyTenderUrl(data: string): string | undefined {
let tender: unknown;
try {
tender = JSON.parse(data);
} catch {
return undefined;
}
if (!isRecord(tender) || typeof tender.url !== 'string') return undefined;
return isWebAddress(tender.url) ? tender.url : undefined;
}
/** A site an opportunity's releases can be opened on. */
export interface NoticeSite {
/** The site's name, for labels: "Open the latest notice on <name>". */
name: string;
/** The page of release on the site, or undefined if it has none. */
urlFor: (release: OpportunityRelease) => string | undefined;
}
// By the source the backend gives the opportunities fetched from each site.
const NOTICE_SITES: Record<string, NoticeSite> = {
FindATender: {
name: 'Find a Tender',
urlFor: (release) => findATenderNoticeUrl(release.release_id),
},
'EU Supply - UK': {
name: 'EU Supply',
urlFor: (release) => euSupplyTenderUrl(release.data),
},
};
/** The site the opportunity's releases can be opened on, or undefined for sources without one. */
export function noticeSite(opportunity: Opportunity): NoticeSite | undefined {
return NOTICE_SITES[opportunity.source];
}