diff --git a/package.json b/package.json index bec56613..d8a3c69c 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ }, "dependencies": { "@codegouvfr/react-dsfr": "^1.0.0", - "@codegouvfr/sill": "^1.20.4", + "@codegouvfr/sill": "^1.20.5", "@emotion/react": "^11.11.0", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.0.4", diff --git a/src/core/adapter/sillApi.ts b/src/core/adapter/sillApi.ts index 5255094b..95a5f3bf 100644 --- a/src/core/adapter/sillApi.ts +++ b/src/core/adapter/sillApi.ts @@ -40,6 +40,10 @@ export function createSillApi(params: { }; const sillApi: SillApi = { + "getExternalSoftwareDataOrigin": memoize( + () => trpcClient.getExternalSoftwareDataOrigin.query(), + { "promise": true } + ), "getRedirectUrl": params => trpcClient.getRedirectUrl.query(params), "getApiVersion": memoize(() => trpcClient.getApiVersion.query(), { "promise": true diff --git a/src/core/adapter/sillApiMock.ts b/src/core/adapter/sillApiMock.ts index 37331c05..11c27c6f 100644 --- a/src/core/adapter/sillApiMock.ts +++ b/src/core/adapter/sillApiMock.ts @@ -10,6 +10,7 @@ import { assert } from "tsafe/assert"; import type { ApiTypes } from "@codegouvfr/sill"; export const sillApi: SillApi = { + "getExternalSoftwareDataOrigin": memoize(async () => "wikidata", { "promise": true }), "getRedirectUrl": async () => undefined, "getApiVersion": memoize(async () => "0.0.0", { "promise": true }), "getOidcParams": memoize( diff --git a/src/core/bootstrap.ts b/src/core/bootstrap.ts index bdeee388..e5d3ad3a 100644 --- a/src/core/bootstrap.ts +++ b/src/core/bootstrap.ts @@ -146,6 +146,7 @@ export async function bootstrapCore( await Promise.all([ dispatch(usecases.sillApiVersion.protectedThunks.initialize()), + dispatch(usecases.externalDataOrigin.protectedThunks.initialize()), dispatch(usecases.softwareCatalog.protectedThunks.initialize()), dispatch(usecases.generalStats.protectedThunks.initialize()), dispatch(usecases.redirect.protectedThunks.initialize()) diff --git a/src/core/ports/SillApi.ts b/src/core/ports/SillApi.ts index bb171315..7b53c511 100644 --- a/src/core/ports/SillApi.ts +++ b/src/core/ports/SillApi.ts @@ -2,6 +2,12 @@ import { assert, type Equals } from "tsafe/assert"; import type { TrpcRouterInput, TrpcRouterOutput } from "@codegouvfr/sill"; export type SillApi = { + getExternalSoftwareDataOrigin: { + (params: TrpcRouterInput["getExternalSoftwareDataOrigin"]): Promise< + TrpcRouterOutput["getExternalSoftwareDataOrigin"] + >; + clear: () => void; + }; getRedirectUrl: ( params: TrpcRouterInput["getRedirectUrl"] ) => Promise; diff --git a/src/core/usecases/externalDataOrigin/index.ts b/src/core/usecases/externalDataOrigin/index.ts new file mode 100644 index 00000000..9d9048fe --- /dev/null +++ b/src/core/usecases/externalDataOrigin/index.ts @@ -0,0 +1,2 @@ +export * from "./state"; +export * from "./thunks"; diff --git a/src/core/usecases/externalDataOrigin/state.ts b/src/core/usecases/externalDataOrigin/state.ts new file mode 100644 index 00000000..2caebacf --- /dev/null +++ b/src/core/usecases/externalDataOrigin/state.ts @@ -0,0 +1,3 @@ +export const name = "externalDataOrigin"; + +export const reducer = null; diff --git a/src/core/usecases/externalDataOrigin/thunks.ts b/src/core/usecases/externalDataOrigin/thunks.ts new file mode 100644 index 00000000..87f4933f --- /dev/null +++ b/src/core/usecases/externalDataOrigin/thunks.ts @@ -0,0 +1,33 @@ +import { ExternalDataOrigin } from "@codegouvfr/sill"; +import type { Thunks } from "core/bootstrap"; +import { createUsecaseContextApi } from "redux-clean-architecture"; + +const { getContext, setContext } = createUsecaseContextApi<{ + externalDataOrigin: ExternalDataOrigin; +}>(); + +export const thunks = { + "getExternalDataOrigin": + () => + (...args): ExternalDataOrigin => { + const [, , rootContext] = args; + + const { externalDataOrigin } = getContext(rootContext); + + return externalDataOrigin; + } +} satisfies Thunks; + +export const protectedThunks = { + "initialize": + () => + async (...args) => { + const [, , rootContext] = args; + + const { sillApi } = rootContext; + + setContext(rootContext, { + "externalDataOrigin": await sillApi.getExternalSoftwareDataOrigin() + }); + } +} satisfies Thunks; diff --git a/src/core/usecases/index.ts b/src/core/usecases/index.ts index a337520f..3fc391a9 100644 --- a/src/core/usecases/index.ts +++ b/src/core/usecases/index.ts @@ -13,6 +13,7 @@ import * as readme from "./readme"; import * as redirect from "./redirect"; import * as declarationRemoval from "./declarationRemoval"; import * as userProfile from "./userProfile"; +import * as externalDataOrigin from "./externalDataOrigin"; export const usecases = { softwareCatalog, @@ -22,6 +23,7 @@ export const usecases = { instanceForm, userAccountManagement, sillApiVersion, + externalDataOrigin, softwareUserAndReferent, generalStats, userAuthentication, diff --git a/src/ui/i18n/i18n.tsx b/src/ui/i18n/i18n.tsx index 6bbe568c..6b0ebbda 100644 --- a/src/ui/i18n/i18n.tsx +++ b/src/ui/i18n/i18n.tsx @@ -1,11 +1,12 @@ import { createI18nApi, declareComponentKeys } from "i18nifty"; -import { languages, type Language } from "@codegouvfr/sill"; +import { languages, type Language, type ExternalDataOrigin } from "@codegouvfr/sill"; import { assert } from "tsafe/assert"; import type { Equals } from "tsafe"; import { statefulObservableToStatefulEvt } from "powerhooks/tools/StatefulObservable/statefulObservableToStatefulEvt"; import { z } from "zod"; import { createUnionSchema } from "ui/tools/zod/createUnionSchema"; import { DeclarationType } from "../shared/DeclarationRemovalModal"; +import { ReactNode } from "react"; export { declareComponentKeys }; export { languages }; @@ -340,36 +341,10 @@ const { "Operating system on which the software can be installed" }, "SoftwareFormStep2": { - "wikidata id": "Wikidata item", - "wikidata id hint": ({ - wikidataUrl, - exampleSoftwareName, - wikidataPageExampleUrl, - softwareSillUrl - }) => ( - <> - Fill up a name or directly the id (of the form Qxxxxx - ) to associate the software with an existing entry{" "} - - Wikidata - - . -
- Most general information, such as the logo or the URL of the code - repository, is extracted from Wikidata. If the software you want - to add does not have a Wikidata entry yet, you can create one . - Find here an{" "} - - example of a Wikidata entry - -   for the software  - - {exampleSoftwareName} - {" "} - - ), - "wikidata id information": - "This information will automatically populate other fields", + "external id": externalId("en"), + "external id hint": externalIdHint("en"), + // "wikidata id information": + // "This information will automatically populate other fields", "comptoir du libre id": "Comptoir du Libre identifier (optional)", "comptoir du libre id hint": "If you the software is listed in the Comptoir du Libre you can add its identifier here. The identifier is either a numeric id or the url of the product page.", @@ -1034,37 +1009,8 @@ const { "Système d'exploitation sur lequel le logiciel peut être installé" }, "SoftwareFormStep2": { - "wikidata id": "Fiche Wikidata", - "wikidata id hint": ({ - wikidataUrl, - wikidataPageExampleUrl, - exampleSoftwareName, - softwareSillUrl - }) => ( - <> - Renseignez le nom du logiciel ou directement l'identifiant (de la - forme QXXXXX) pour associer le logiciel à une fiche - existante{" "} - - Wikidata - - . -
- La plupart des informations générales, telles que le logo ou l'URL - du dépôt de code, sont extraites de Wikidata. Si le logiciel que - vous souhaitez ajouter ne possède pas encore de fiche sur - Wikidata, vous pouvez en créer une. Vous trouverez ici un{" "} - - exemple de fiche Wikidata - {" "} - pour le logiciel  - - {exampleSoftwareName} - .{" "} - - ), - "wikidata id information": - "Cette information remplira automatiquement d'autres champs", + "external id": externalId("fr"), + "external id hint": externalIdHint("fr"), "comptoir du libre id": "Identifiant Comptoir du Libre (Optionnel)", "comptoir du libre id hint": "Si le logiciel est présent sur le comptoir du libre vous pouvez renseigner son identifiant ou l'URL de sa fiche", @@ -1515,3 +1461,175 @@ const declarationTypeToEnglish: Record = { user: "user", referent: "referent" }; + +type I18nTextByExternalSourceByLanguage = Record< + Language, + Record +>; + +const linksByExternalDataSource: Record< + ExternalDataOrigin, + { + externalSourceUrl: string; + externalSourcePageExampleUrl: string; + softwareSillUrl: string; + exampleSoftwareName: string; + } +> = { + wikidata: { + "externalSourceUrl": "https://www.wikidata.org/wiki", + "externalSourcePageExampleUrl": "https://www.wikidata.org/wiki/Q107693197", + "softwareSillUrl": "https://code.gouv.fr/sill/detail?name=Keycloakify", + "exampleSoftwareName": "Keycloakify" + }, + HAL: { + "externalSourceUrl": "https://hal.science", + "externalSourcePageExampleUrl": "https://hal.science/hal-02818886v1", + "softwareSillUrl": "", + "exampleSoftwareName": "" + } +}; + +function externalId(language: Language) { + return (externalDataOrigin: ExternalDataOrigin) => { + const externalIdBySource: I18nTextByExternalSourceByLanguage = { + fr: { + wikidata: "Fiche Wikidata", + HAL: "Fiche HAL" + }, + en: { + wikidata: "Wikidata item", + HAL: "HAL item" + } + }; + + return externalIdBySource[language][externalDataOrigin]; + }; +} + +function externalIdHint(language: Language) { + return (externalDataOrigin: ExternalDataOrigin) => { + const { + exampleSoftwareName, + externalSourcePageExampleUrl, + externalSourceUrl, + softwareSillUrl + } = linksByExternalDataSource[externalDataOrigin]; + + const externalIdHintByExternalSourceByLanguage: I18nTextByExternalSourceByLanguage = + { + fr: { + wikidata: ( + <> + Renseignez le nom du logiciel ou directement l'identifiant (de + la forme QXXXXX) pour associer le logiciel à une + fiche existante{" "} + + Wikidata + + . +
+ La plupart des informations générales, telles que le logo ou + l'URL du dépôt de code, sont extraites de Wikidata. Si le + logiciel que vous souhaitez ajouter ne possède pas encore de + fiche sur Wikidata, vous pouvez en créer une. Vous trouverez + ici un{" "} + + exemple de fiche Wikidata + {" "} + pour le logiciel  + + {exampleSoftwareName} + + .{" "} + + ), + HAL: ( + <> + Renseignez le nom du logiciel ou directement l'identifiant + (attention, les identifiants HAL sont de la forme + hal-123123v1, il faut fournir uniquement le numéro (sans + 'hal-' et sans version), dans ce cas '123123') pour associer + le logiciel à une fiche existante{" "} + + HAL + + . +
+ La plupart des informations générales, telles que l'URL du + dépôt de code, sont extraites de HAL. Si le logiciel que vous + souhaitez ajouter ne possède pas encore de fiche sur HAL, vous + pouvez en créer une. Vous trouverez ici un{" "} + + exemple de fiche HAL + + . + + ) + }, + en: { + wikidata: ( + <> + Fill up a name or directly the id (of the form{" "} + Qxxxxx) to associate the software with an + existing entry{" "} + + Wikidata + + . +
+ Most general information, such as the logo or the URL of the + code repository, is extracted from Wikidata. If the software + you want to add does not have a Wikidata entry yet, you can + create one . Find here an{" "} + + example of a Wikidata entry + +   for the software  + + {exampleSoftwareName} + {" "} + + ), + HAL: ( + <> + Fill up a name or directly the id (careful, HAL ids look like + 'hal-123123v1', but only the number should be provided + (without 'hal-' or the version), in this case it should be + '123123') to associate the software with an existing entry{" "} + + Wikidata + + . +
+ Most general information, such as the URL of the code + repository, is extracted from HAL. If the software you want to + add does not have a Hal entry yet, you can create one. Find + here an{" "} + + example of an HAL entry + + + ) + } + }; + + return externalIdHintByExternalSourceByLanguage[language][externalDataOrigin]; + }; +} diff --git a/src/ui/pages/softwareForm/Step2.tsx b/src/ui/pages/softwareForm/Step2.tsx index 3a10b443..850d9b3e 100644 --- a/src/ui/pages/softwareForm/Step2.tsx +++ b/src/ui/pages/softwareForm/Step2.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState, useId } from "react"; +import React, { useEffect, useState, useId } from "react"; import { SearchInput } from "ui/shared/SearchInput"; import { fr } from "@codegouvfr/react-dsfr"; import { useForm, Controller } from "react-hook-form"; @@ -7,12 +7,13 @@ import { CircularProgressWrapper } from "ui/shared/CircularProgressWrapper"; import { assert } from "tsafe/assert"; import type { NonPostableEvt } from "evt"; import { useEvt } from "evt/hooks"; -import type { useCore } from "core"; +import { useCore } from "core"; import type { FormData } from "core/usecases/softwareForm"; import type { ReturnType } from "tsafe"; import { declareComponentKeys } from "i18nifty"; import { useTranslation, useResolveLocalizedString } from "ui/i18n"; import { useStyles } from "tss-react/dsfr"; +import { ExternalDataOrigin } from "@codegouvfr/sill"; export type Step2Props = { className?: string; @@ -35,6 +36,8 @@ export type Step2Props = { }; export function SoftwareFormStep2(props: Step2Props) { + const { externalDataOrigin: externalDataOriginCore } = useCore().functions; + const externalDataOrigin = externalDataOriginCore.getExternalDataOrigin(); const { className, isUpdateForm, @@ -250,15 +253,8 @@ export function SoftwareFormStep2(props: Step2Props) { noOptionText={tCommon("no result")} loadingText={tCommon("loading")} dsfrInputProps={{ - "label": t("wikidata id"), - "hintText": t("wikidata id hint", { - "wikidataUrl": "https://www.wikidata.org/wiki", - "wikidataPageExampleUrl": - "https://www.wikidata.org/wiki/Q107693197", - "softwareSillUrl": - "https://code.gouv.fr/sill/detail?name=Keycloakify", - "exampleSoftwareName": "Keycloakify" - }), + "label": t("external id")(externalDataOrigin), + "hintText": t("external id hint")(externalDataOrigin), "nativeInputProps": { "ref": field.ref, "onBlur": field.onBlur, @@ -491,18 +487,14 @@ function comptoirDuLibreInputValueToComptoirDuLibreId(comptoirDuLibreInputValue: } export const { i18n } = declareComponentKeys< - | "wikidata id" | { - K: "wikidata id hint"; - P: { - wikidataUrl: string; - wikidataPageExampleUrl: string; - exampleSoftwareName: string; - softwareSillUrl: string; - }; - R: JSX.Element; + K: "external id"; + R: (origin: ExternalDataOrigin) => React.ReactNode; + } + | { + K: "external id hint"; + R: (origin: ExternalDataOrigin) => React.ReactNode; } - | "wikidata id information" | "comptoir du libre id" | "comptoir du libre id hint" | "software name" diff --git a/yarn.lock b/yarn.lock index 8be8c096..26d4e8ad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1240,10 +1240,10 @@ dependencies: tsafe "^1.6.3" -"@codegouvfr/sill@^1.20.4": - version "1.20.4" - resolved "https://registry.yarnpkg.com/@codegouvfr/sill/-/sill-1.20.4.tgz#67950d1e177622954d56c16eb1b197aec414bcd0" - integrity sha512-D6FjTIl4RXwm96hqCVRwBIVtTvzENY/UPpsVoNeSuH3f0xCjpamOyEhgObfpWmeB5sfQraY0dJxEZxwst5AtCg== +"@codegouvfr/sill@^1.20.5": + version "1.20.5" + resolved "https://registry.yarnpkg.com/@codegouvfr/sill/-/sill-1.20.5.tgz#4440ed99eac54e3667cc69b91b048f7e1f9a361e" + integrity sha512-eld//w/kiN26+KstA9xMHYiFsrnMgrBBrzGW3mbhimzrzbh1E4T8LN6FYTIs1trS7iX5m0+JM7lURUTzpWUV/Q== dependencies: "@octokit/graphql" "^7.0.2" "@retorquere/bibtex-parser" "^7.0.11"