Show CREATION targets of articles.

This commit is contained in:
Emmanuel 2022-08-29 09:37:07 +02:00
parent 5b1c33b27e
commit d331d0ccf8
9 changed files with 345 additions and 4 deletions

View file

@ -1,23 +1,36 @@
import type {
Article,
DossierLegislatif,
Idcc,
Jo,
SectionTa,
Textekali,
Textelr,
TexteVersion,
Versions,
} from "$lib/legal"
export interface Aggregate {
article?: { [id: string]: Article }
dossier_legislatif?: { [id: string]: DossierLegislatif }
id?: string
idcc?: { [id: string]: Idcc }
ids?: string[]
jo?: { [id: string]: Jo }
section_ta?: { [id: string]: SectionTa }
texte_version?: { [id: string]: TexteVersion }
textekali?: { [id: string]: Textekali }
textelr?: { [id: string]: Textelr }
versions?: { [eli: string]: Versions }
}
export type Follow = typeof allFollows[number]
export interface GetArticleResult extends Aggregate {
follow: Follow[]
id: string
}
export interface GetRechercheResult extends Aggregate {
follow: Follow[]
id?: string

View file

@ -1,12 +1,20 @@
<script lang="ts">
import { iterArrayOrSingleton } from "@tricoteuses/explorer-tools"
import { SummaryView } from "augmented-data-viewer"
import type { Aggregate } from "$lib/aggregates"
import LienView from "$lib/components/LienView.svelte"
import type { Article } from "$lib/legal"
import { summarizeLegalObject } from "$lib/summaries"
export let article: Article
export let data: Aggregate
export let level = 1
$: liens = [...iterArrayOrSingleton(article.LIENS?.LIEN)]
$: ciblesCreation = liens.filter(
(lien) => lien["@sens"] === "cible" && lien["@typelien"] === "CREATION",
)
$: summary = summarizeLegalObject({ key: "article" }, "article", article)
</script>
@ -17,3 +25,13 @@
{/if}
{@html article.BLOC_TEXTUEL.CONTENU}
{#if ciblesCreation.length > 0}
<ul>
{#each liens as lien}
<li>
<LienView {data} level={level + 1} {lien} />
</li>
{/each}
</ul>
{/if}

View file

@ -0,0 +1,92 @@
<script lang="ts">
import Icon from "@iconify/svelte"
import dash from "@iconify-icons/codicon/dash"
import triangleDown from "@iconify-icons/codicon/triangle-down"
import triangleRight from "@iconify-icons/codicon/triangle-right"
import type { Aggregate } from "$lib/aggregates"
import ArticleView from "$lib/components/ArticleView.svelte"
import SectionTaView from "$lib/components/SectionTaView.svelte"
import TexteVersionView from "$lib/components/TexteVersionView.svelte"
import {
type LegalObject,
type LegalObjectType,
type Lien,
pathnameFromLegalId,
rootTypeFromLegalId,
} from "$lib/legal"
export let data: Aggregate
export let level = 1
export let lien: Lien
let open = false
$: id = lien["@id"]
$: rootType = rootTypeFromLegalId(id)
$: componentAndProperties = componentAndPropertiesFromTypeAndTarget(rootType)
function toggle() {
open = !open
}
function componentAndPropertiesFromTypeAndTarget(
type: LegalObjectType | undefined,
) {
if (type === undefined) {
return undefined
}
const target =
rootType === undefined
? undefined
: (data[rootType] as { [id: string]: LegalObject })?.[id]
switch (type) {
case "article":
return {
component: ArticleView,
properties: { article: target, data, level },
}
case "section_ta":
return {
component: SectionTaView,
properties: { data, level, sectionTa: target },
}
case "texte_version":
return {
component: TexteVersionView,
properties: { data, level, texteVersion: target },
}
default:
return undefined
}
}
</script>
{#if componentAndProperties === undefined}
<div class="inline-flex align-top" on:click|stopPropagation={toggle}>
<Icon class="mt-1 inline-block shrink-0" icon={dash} inline />
<a class="link link-hover link-primary" href={pathnameFromLegalId(id)}>
{lien["#text"]}
</a>
</div>
{:else}
<div
class="inline-flex cursor-pointer align-top"
on:click|stopPropagation={toggle}
>
<Icon
class="mt-1 inline-block shrink-0"
icon={open ? triangleDown : triangleRight}
inline
/>
{lien["#text"]}
</div>
{#if open}
<div class="ml-2 border-l-4 pl-2">
<svelte:component
this={componentAndProperties.component}
{...componentAndProperties.properties}
/>
</div>
{/if}
{/if}

View file

@ -28,7 +28,7 @@
{#each [...iterArrayOrSingleton(sectionTa.STRUCTURE_TA.LIEN_ART)] as lienArt}
{@const article = data.article?.[lienArt["@id"]]}
{#if article !== undefined}
<ArticleView {article} level={level + 1} />
<ArticleView {article} {data} level={level + 1} />
{/if}
{/each}
{:else if sectionTa.STRUCTURE_TA.LIEN_ART !== undefined}

View file

@ -16,7 +16,7 @@
{#each [...iterArrayOrSingleton(textelr.STRUCT.LIEN_ART)] as lienArt}
{@const article = data.article?.[lienArt["@id"]]}
{#if article !== undefined}
<ArticleView {article} level={level + 1} />
<ArticleView {article} {data} level={level + 1} />
{/if}
{/each}
{:else if textelr.STRUCT.LIEN_ART !== undefined}

View file

@ -395,6 +395,9 @@ export function pathnameFromLegalId(id: string): string | undefined {
if (rootType === undefined) {
return undefined
}
if (rootType === "article") {
return `/articles/${id}`
}
if (["texte_version", "textekali", "textelr"].includes(rootType)) {
return `/textes/${id}`
}
@ -407,7 +410,8 @@ export function pathnameFromLegalObject(
): string {
switch (type) {
case "article":
return `/article/${(object as Article).META.META_COMMUN.ID}`
// return `/article/${(object as Article).META.META_COMMUN.ID}`
return `/articles/${(object as Article).META.META_COMMUN.ID}`
case "dossier_legislatif":
return `/dossier_legislatif/${
(object as DossierLegislatif).META.META_COMMUN.ID
@ -442,7 +446,8 @@ export function pathnameFromLegalObjectId(
): string {
switch (type) {
case "article":
return `/article/${id}`
// return `/article/${id}`
return `/articles/${id}`
case "dossier_legislatif":
return `/dossier_legislatif/${id}`
case "id":

View file

@ -0,0 +1,91 @@
import { type Audit, auditSetNullish, cleanAudit } from "@auditors/core"
import { error } from "@sveltejs/kit"
import { auditFollowSearchParams } from "$lib/auditors/search_params"
import type { Follow } from "$lib/aggregates"
import type { Article } from "$lib/legal"
import { Aggregator } from "$lib/server/aggregates"
import { db } from "$lib/server/database"
import type { RequestHandler } from "./$types"
export function auditSearchParams(
audit: Audit,
query: URLSearchParams,
): [unknown, unknown] {
if (query == null) {
return [query, null]
}
if (!(query instanceof URLSearchParams)) {
return audit.unexpectedType(query, "URLSearchParams")
}
const data: { [key: string]: unknown } = {}
for (const [key, value] of query.entries()) {
let values = data[key] as string[] | undefined
if (values === undefined) {
values = data[key] = []
}
values.push(value)
}
const errors: { [key: string]: unknown } = {}
const remainingKeys = new Set(Object.keys(data))
auditFollowSearchParams(audit, data, errors, remainingKeys)
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
}
export const GET: RequestHandler = async ({ params, url }) => {
const [query, queryError] = auditSearchParams(
cleanAudit,
url.searchParams,
) as [
{
follow: Set<Follow>
},
unknown,
]
if (queryError !== null) {
console.error(
`Error in ${url.pathname} query:\n${JSON.stringify(
query,
null,
2,
)}\n\nError:\n${JSON.stringify(queryError, null, 2)}`,
)
throw error(400, JSON.stringify(queryError, null, 2))
}
const { follow } = query
const article = (
await db<{ data: Article }[]>`
SELECT data FROM article
WHERE ID = ${params.id}
`
).map(({ data }) => data)[0]
if (article === undefined) {
throw error(404, `ARTICLE ${params.id} non trouvé`)
}
const aggregator = new Aggregator(follow)
aggregator.addArticle(article)
await aggregator.getAll()
return new Response(
JSON.stringify(
{
...aggregator.toJson(),
follow: [...follow],
id: params.id,
},
null,
2,
),
{
headers: {
"Content-Type": "application/json; charset=utf-8",
},
},
)
}

View file

@ -0,0 +1,44 @@
<script lang="ts">
import { TreeView, SummaryView } from "augmented-data-viewer"
import ArticleView from "$lib/components/ArticleView.svelte"
import IdPagesSwitcher from "$lib/components/IdPagesSwitcher.svelte"
import { summarizeLegalObject } from "$lib/summaries"
import type { PageData } from "./$types"
export let data: PageData
let showRawData = false
$: id = data.id!
$: article = data.article![id]
$: summary = summarizeLegalObject({ key: "article" }, "article", article)
</script>
<IdPagesSwitcher {id} />
<header class="prose my-6 max-w-full">
<h2>Articles</h2>
{#if summary !== undefined}
<h1>
<SummaryView {summary} />
</h1>
{/if}
</header>
<form class="max-w-xs" on:submit|preventDefault>
<div class="form-control">
<label class="label cursor-pointer">
<span class="label-text">Données brutes</span>
<input class="toggle" type="checkbox" bind:checked={showRawData} />
</label>
</div>
</form>
{#if showRawData}
<TreeView frame={false} open value={data} />
{:else}
<ArticleView {article} {data} />
{/if}

View file

@ -0,0 +1,78 @@
import { error } from "@sveltejs/kit"
import { type Audit, auditSetNullish, cleanAudit } from "@auditors/core"
import type { Follow, GetArticleResult } from "$lib/aggregates"
import { auditFollowWithFalseSearchParams } from "$lib/auditors/search_params"
import { urlFromUrlAndQuery } from "$lib/urls"
import type { PageLoad } from "./$types"
export function auditSearchParams(
audit: Audit,
query: URLSearchParams,
): [unknown, unknown] {
if (query == null) {
return [query, null]
}
if (!(query instanceof URLSearchParams)) {
return audit.unexpectedType(query, "URLSearchParams")
}
const data: { [key: string]: unknown } = {}
for (const [key, value] of query.entries()) {
let values = data[key] as string[] | undefined
if (values === undefined) {
values = data[key] = []
}
values.push(value)
}
const errors: { [key: string]: unknown } = {}
const remainingKeys = new Set(Object.keys(data))
auditFollowWithFalseSearchParams(audit, data, errors, remainingKeys)
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
}
export const load: PageLoad = async ({ fetch, url }) => {
const [query, queryError] = auditSearchParams(
cleanAudit,
url.searchParams,
) as [
{
follow: Set<Follow | "false">
},
unknown,
]
if (queryError !== null) {
console.error(
`Error in ${url.pathname} query:\n${JSON.stringify(
query,
null,
2,
)}\n\nError:\n${JSON.stringify(queryError, null, 2)}`,
)
throw error(400, JSON.stringify(queryError, null, 2))
}
const { follow: requestedFollow } = query
let follow = new Set(requestedFollow)
const followFalse = follow.delete("false")
if (follow.size === 0 && !followFalse) {
// Set default follow
follow = new Set(["LIENS.LIEN[@sens=cible,@typelien=CREATION].@id"])
}
const apiUrl = urlFromUrlAndQuery(`/api${url.pathname}`, { follow })
const response = await fetch(apiUrl, {
headers: { Accept: "application/json" },
})
if (!response.ok) {
const text = await response.text()
console.error(
`Error in ${url.pathname} while calling ${apiUrl}:\n${response.status} ${response.statusText}\n\n${text}`,
)
throw error(response.status, text)
}
return (await response.json()) as GetArticleResult
}