Skip to content

Commit 23dfdbb

Browse files
authored
Add self-evaluation form for OS2 products
1 parent 8d6344d commit 23dfdbb

1 file changed

Lines changed: 311 additions & 0 deletions

File tree

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
---
2+
title: Selvevaluering (formular)
3+
layout: default
4+
parent: Evalueringer
5+
nav_order: 1
6+
has_children: false
7+
has_toc: false
8+
---
9+
10+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
11+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
12+
13+
<div id="app" class="container-xl my-4 px-3 px-md-4"></div>
14+
15+
<script>
16+
const kravData = {
17+
relevans: [
18+
["R1","Løsningen skaber lokal værdi","sandkasse","Beskriv den konkrete værdi løsningen skaber i organisationen. F.eks. økonomisk, organisatorisk eller brugerrelateret."],
19+
["R2","Løsningen er accepteret af lokal linjeledelse","2","Beskriv eller henvis til en formel accept fra ledelse hos initiativtagerne til løsningen."],
20+
["R3","Løsningen har fælles offentligt potentiale","2","Redegør for hvordan løsningen kan bruges på tværs af kommuner og/eller offentlige myndigheder."],
21+
["R4","Ophæng til nationale strategier er til stede","3","Henvis til relevante strategier og forklar hvordan løsningen understøtter disse."]
22+
],
23+
formkrav: [
24+
["F1","Alt kildekode til projektet udvikles synligt og aktivt i et repositorie og versionskontrolsystem, anvist af OS2","sandkasse","Upload al kildekode i et offentligt OS2 repository med aktiv versionshistorik."],
25+
["F2","Open Source licenskriterier overholdes","sandkasse","Angiv hvilken OSI-godkendt licens projektet bruger. OS2 standard er MPL 2.0"],
26+
["F3","Udbudsregler og alm. lovformlighed er overholdt","sandkasse","Bekræft at udbudspligt er overholdt eller redegør for undtagelse. Vedlæg evt. beslutningsnotat."],
27+
["F4","Der er tænkt på sikkerheden i løsningen","sandkasse","Beskriv hvordan sikkerhed er indtænkt i design, kode og drift – f.eks. kryptering, adgangsstyring."],
28+
["F5","Løsningens formål og værdi er beskrevet","sandkasse","Henvis til dokumentation (f.eks. README) hvor formål og målgruppe fremgår."],
29+
["F6","Kildekoden er overdraget og er placeret under OS2's kontrol","1","Bekræft og link til det officielle repository i OS2s versionskontrol."],
30+
["F7","Alt dokumentation til projektet udarbejdes med og overholder OS2s standardskabelon for dokumentation.","1","Brug OS2’s standard template til dokumentation. http://github.com/OS2offdig/os2-docs-template"],
31+
["F10","OS2's kommunikationskanaler anvendes (OS2.eu)","1","Bekræft og link til omtale på f.eks. os2.eu, nyhedsbrev eller andet."],
32+
["F11","Der anvendes offentlig issue-tracking anvist af OS2, hvor der tydeligt henvises til specifikke kodeændringer","1","Henvis til f.eks. Issues, hvor opgaver er koblet til pull-requests/commits."],
33+
["F12","Der er kun en version af core koden","2","Bekræft at der kun findes én ‘main’ version og at den er aktivt vedligeholdt."],
34+
["F13","Der er udarbejdet præsentationsmateriale af løsningen","2","Link til f.eks. slides, brochurer eller andet introduktionsmateriale."],
35+
["F14","Der er udarbejdet kommunikationsmateriale til strategisk niveau","2","F.eks. businesscase, one-pager til direktionsniveau og præsentation til udvalg."],
36+
["F15","Best practice for implementering i organisationen dokumenteres","2","Angiv implementeringsvejledning, erfaringsopsamling eller cases."],
37+
["F16","Teknisk dokumentation indeholder best practice for kodestandarder i forhold til de anvendte teknologier","2","Beskriv hvilke kodestandarder projektet følger. Evt. med links til eksterne guides og supplerende retningslinjer."],
38+
["F17","Drifts og vedligeholdelses setup er beskrevet","2","Redegør for driftspartner(e), ansvar og finansiering. Hvem drifter, hvem vedligeholder og hvem koordinerer."],
39+
["F18","Rammearkitekturen og standarder er fulgt og afvigelser er forklaret","2","Beskriv om/hvordan løsningen følger fællesoffentlig rammearkitektur – eller forklar hvorfor ikke."],
40+
["F19","Løsningen er leveret i et containerformat f.eks. docker (anbefaling)","2","Angiv om løsningen tilbydes i en containeriseret version som definerer hvordan applikationen bygges og køres."],
41+
["F20","Uddannelsesmateriale er udarbejdet (anbefaling)","2","Henvis til manual, brugervejledning eller andet brugerrelateret materiale."],
42+
["F21","Politisk kommunikation er udarbejdet (Lokal + Omverden)","3","Angiv indhold der kan bruges i politiske fora – f.eks. beslutningsoplæg eller pressemeddelelse."],
43+
["F22","Procesplan + procesansvar for driftsimplementering er udarbejdet","3","Tilføj en implementeringsplan med ‘hvem gør hvad hvornår’."],
44+
],
45+
strategisk: [
46+
["S1","Produktet har en kobling til OS2's strategi","1","Beskriv hvordan produktet understøtter tværoffentlige behov, deling og fællesskab."],
47+
["S2","Løsningen understøtter innovation og open source","1","Angiv hvordan open source-værdier og nyskabelse er tænkt ind."],
48+
["S3","Produktets (forventlige) kobling til OS2's mission, vision og strategi er beskrevet","2","Angiv hvor produktet matcher med OS2's formål og indsatser."],
49+
["S4","Der er udarbejdet en vision og strategi for produktet","2","Beskriv produktvision og strategiske mål."],
50+
["S5","Produktets kobling til og overensstemmelse med OS2's vision og strategi er tilstede og beskrevet","3","Forklar hvordan løsningen passer ind i OS2’s overordnede værdisæt og visioner."],
51+
],
52+
governance: [
53+
["G1","Produktet er oprettet i OS2's porteføljestyring","1","Produktet er oprettet på OS2s hjemmeside og indgår i årshjul."],
54+
["G2","Der koordineres løbende med OS2-sekretariatet","1","Bekræft, evt. med årshjul/datoer/mails for koordinering."],
55+
["G3","Der er udpeget en projektleder/tovholder","1","Navngiv og beskriv rolle og opgaver."],
56+
["G4","Bestyrelsen er orienteret","1","Vedlæg dokumentation for orientering."],
57+
["G5","Bestyrelsen har godkendt produktet","2","Vedlæg dokumentation for godkendelse."],
58+
["G6","Der er nedsat en styregruppe","2","Beskrivelse af styregruppen og roller/ansvar/opgaver."],
59+
["G7","Der er nedsat en koordinationsgruppe (anbefaling)","2","Beskrivelse af koordinationsgruppen og roller/ansvar/opgaver."],
60+
["G8","En projektmodel anvendes og er dokumenteret (anbefaling)","2","Beskiv den anvendte projektmodel eller metode."],
61+
["G9","Review af kode foretages af tredjepart (anbefaling)","2","Angiv hvilken ekstern part som udfører eller har udført review."],
62+
["G10","Der er udarbejdet en tilslutningserklæring til sikring af økonomi (anbefaling)","2","Vedlæg eller henvis til dokument for tilslutning og økonomi."],
63+
["G11","Bestyrelsen har godkendt styregruppen","3","Vedlæg dokumentation for beslutning."],
64+
["G12","Bestyrelsen er repræsenteret i styregruppen","3","Angiv hvilket medlem som deltager på vegne af bestyrelsen."],
65+
["G13","Der foreligger en aftale der sikrer økonomi til koordinering og videreudvikling","3","Vedlæg eller beskriv finansieringsaftalen."],
66+
["G14","Der er etableret et fagligt fællesskab bag løsningen hvor erfaringer kan udveksles","3","Henvis til brugerforum og/eller årshjul for aktiviteter."],
67+
]
68+
};
69+
70+
const statusChoices = ["Ja", "Nej", "Ikke relevant", "Ved ikke", "Planlagt"];
71+
72+
function sectionTitle(key) {
73+
return ({ relevans: "Relevans", formkrav: "Formkrav", strategisk: "Strategisk sammenhæng", governance: "Governance" })[key] || key;
74+
}
75+
76+
function render() {
77+
const app = document.getElementById("app");
78+
app.innerHTML = `
79+
<style>
80+
.checklist-table{table-layout:fixed}
81+
.checklist-table th,.checklist-table td{vertical-align:top}
82+
.checklist-table th:nth-child(1), .checklist-table td:nth-child(1){width:6%}
83+
.checklist-table th:nth-child(2), .checklist-table td:nth-child(2){width:19%}
84+
.checklist-table th:nth-child(3), .checklist-table td:nth-child(3){width:10%}
85+
.checklist-table th:nth-child(4), .checklist-table td:nth-child(4){width:27%}
86+
.checklist-table th:nth-child(5), .checklist-table td:nth-child(5){width:12%}
87+
.checklist-table th:nth-child(6), .checklist-table td:nth-child(6){width:26%}
88+
.doc-textarea{min-height:110px}
89+
</style>
90+
<div class="row mb-3">
91+
<div class="col-12">
92+
<h1>OS2 selvevaluering</h1>
93+
<p>Udfyld status for hvert krav og tilføj dokumentation. Når du er færdig, kan du eksportere svarene til JSON eller gemme direkte i et GitHub repo. Send JSON filen eller link til GitHub repo til sekretariatet på os2@os2.eu.</p>
94+
<blockquote class="note-title">
95+
<p>
96+
Du udfylder checklisten ved at vælge en markering for hvert krav:
97+
</p>
98+
99+
<ul>
100+
<li>(JA) ✅ Kriteriet er opfyldt</li>
101+
<li>(NEJ) ❌ Kriteriet er IKKE opfyldt</li>
102+
<li>(Ikke relevant) ➖ Kriteriet er ikke relevant for dette produkt</li>
103+
<li>(Ved ikke) ❌ Der er tvivl om, hvordan dette kriterie evalueres</li>
104+
<li>(Planlagt) 🟡 Kriteriet er ikke opfyldt endnu, men der er planlagt en indsats</li>
105+
</ul>
106+
107+
<p>
108+
For hvert krav skal du uddybe din vurdering i fritekstfeltet. Her beskriver du, hvordan kravet er opfyldt, ikke opfyldt eller hvorfor det eventuelt ikke er relevant. Henvis gerne til konkret dokumentation, fx links til repository, dokumenter eller beslutninger.
109+
</p>
110+
111+
<p>
112+
Hvis et felt efterlades tomt, betragtes selvevalueringen som ikke færdiggjort.
113+
</p>
114+
115+
<p>
116+
Formålet med selvevalueringen er ikke at opfylde alle krav, men at give et retvisende billede af produktets nuværende status. En ærlig og præcis vurdering er afgørende for, at evalueringen kan bruges til dialog, prioritering og videre udvikling.
117+
</p>
118+
119+
<p>
120+
Har du spørgsmål? <a href="https://os2.eu/kontakt">Kontakt OS2's sekretariat</a>, vi er her for at hjælpe.
121+
</p>
122+
<p>
123+
Information om OS2's produktniveauer og baggrunden herfor kan der <a href="https://governance.os2.eu">læses mere om her</a>.
124+
</p>
125+
</blockquote>
126+
</div>
127+
</div>
128+
<div class="row g-3 mb-3">
129+
<div class="col-12 col-md-4"><label class="form-label">Produktnavn</label><input class="form-control" id="productName" placeholder="Fx OS2 Produkt X"></div>
130+
<div class="col-12 col-md-4"><label class="form-label">Udfyldt af</label><input class="form-control" id="filledBy" placeholder="Navn/organisation"></div>
131+
<div class="col-12 col-md-4"><label class="form-label">Dato</label><input class="form-control" id="filledDate" type="date"></div>
132+
</div>
133+
<div class="mb-3">
134+
<div class="mb-2">
135+
<button class="btn btn-link p-0 text-decoration-none" type="button" data-bs-toggle="collapse" data-bs-target="#collapseGithub" aria-expanded="false" aria-controls="collapseGithub">
136+
GitHub (for eksperten)
137+
</button>
138+
</div>
139+
<div id="collapseGithub" class="collapse">
140+
<div class="row g-3">
141+
<div class="col-12">Valgfrit: Udfyld GitHub-oplysninger for at gemme JSON direkte i repo under <code>docs/self-assessment/</code>.</div>
142+
<div class="col-12 col-md-4"><label class="form-label">GitHub owner</label><input class="form-control" id="ghOwner" placeholder="fx OS2offdig"></div>
143+
<div class="col-12 col-md-4"><label class="form-label">GitHub repo</label><input class="form-control" id="ghRepo" placeholder="fx os2-product-audits"></div>
144+
<div class="col-12 col-md-4"><label class="form-label">Branch</label><input class="form-control" id="ghRef" value="main"></div>
145+
<div class="col-12"><label class="form-label">GitHub token (fine-grained PAT med Actions:write + Contents:write)</label><input class="form-control" id="ghToken" type="password" placeholder="ghp_... (gemmes ikke automatisk)"></div>
146+
</div>
147+
</div>
148+
</div>
149+
${Object.entries(kravData).map(([section, rows]) => `
150+
<h2>${sectionTitle(section)}</h2>
151+
<div class="table-responsive"><table class="table table-striped table-bordered align-top checklist-table">
152+
<thead><tr><th>#</th><th>Krav</th><th>Produktniveau</th><th>Retningslinjer</th><th>Efterlevet?</th><th>Dokumentation</th></tr></thead>
153+
<tbody>
154+
${rows.map(([id, krav, niveau, guide]) => `
155+
<tr>
156+
<td>${id}</td>
157+
<td>${krav}</td>
158+
<td>${niveau}</td>
159+
<td>${guide}</td>
160+
<td>
161+
<select class="form-select" data-id="${id}" data-field="status">
162+
<option value="">Vælg</option>
163+
${statusChoices.map(s => `<option value="${s}">${s}</option>`).join("")}
164+
</select>
165+
</td>
166+
<td><textarea class="form-control doc-textarea" data-id="${id}" data-field="dokumentation" placeholder="Tekst, links eller henvisninger"></textarea></td>
167+
</tr>
168+
`).join("")}
169+
</tbody>
170+
</table></div></div></div></div>
171+
`).join("")}
172+
</div>
173+
<div class="d-flex gap-2 flex-wrap mt-3">
174+
<button class="btn btn-outline-secondary" id="importJson" type="button">Importér JSON data</button>
175+
<input id="importJsonFile" type="file" accept=".json,application/json" class="d-none">
176+
<button class="btn btn-primary" id="exportJson" type="button">Eksportér data som JSON</button>
177+
<button class="btn btn-success d-none" id="saveGithub" type="button">Gem JSON data i GitHub</button>
178+
</div>
179+
`;
180+
181+
document.getElementById("saveGithub").onclick = async () => {
182+
const owner = document.getElementById("ghOwner").value.trim();
183+
const repo = document.getElementById("ghRepo").value.trim();
184+
const ref = document.getElementById("ghRef").value.trim() || "main";
185+
const token = document.getElementById("ghToken").value.trim();
186+
if (!owner || !repo || !token) return alert("Udfyld GitHub owner, repo og token først.");
187+
188+
const data = collectData();
189+
const payload = {
190+
ref,
191+
inputs: {
192+
product_name: data.productName || "os2-produkt",
193+
checklist_json: JSON.stringify(data)
194+
}
195+
};
196+
197+
const url = `https://api.github.com/repos/${owner}/${repo}/actions/workflows/save-self-assessment.yml/dispatches`;
198+
const res = await fetch(url, {
199+
method: "POST",
200+
headers: {
201+
"Accept": "application/vnd.github+json",
202+
"Authorization": `Bearer ${token}`,
203+
"Content-Type": "application/json"
204+
},
205+
body: JSON.stringify(payload)
206+
});
207+
208+
if (!res.ok) {
209+
const errTxt = await res.text();
210+
console.error(errTxt);
211+
return alert(`Kunne ikke gemme i GitHub (HTTP ${res.status}).`);
212+
}
213+
alert("Workflow startet. JSON gemmes i docs/self-assessment/ i repoet.");
214+
};
215+
216+
document.getElementById("exportJson").onclick = () => {
217+
const data = collectData();
218+
const blob = new Blob([JSON.stringify(data, null, 2)], {type: "application/json"});
219+
const url = URL.createObjectURL(blob);
220+
const a = document.createElement("a");
221+
const product = (data.productName || "os2-produkt").toLowerCase().replace(/\s+/g, "-");
222+
a.href = url;
223+
a.download = `${product}-checkliste.json`;
224+
a.click();
225+
URL.revokeObjectURL(url);
226+
};
227+
228+
document.getElementById("importJson").onclick = () => {
229+
document.getElementById("importJsonFile").click();
230+
};
231+
232+
document.getElementById("importJsonFile").onchange = async (event) => {
233+
const file = event.target.files && event.target.files[0];
234+
if (!file) return;
235+
try {
236+
const txt = await file.text();
237+
const data = JSON.parse(txt);
238+
if (!data || typeof data !== "object" || !Array.isArray(data.krav)) {
239+
throw new Error("Ugyldigt format");
240+
}
241+
populate(data);
242+
alert("JSON importeret. Formularen er udfyldt med tidligere data.");
243+
} catch (err) {
244+
console.error(err);
245+
alert("Kunne ikke importere JSON-filen. Kontroller at filen er en gyldig eksport.");
246+
} finally {
247+
event.target.value = "";
248+
}
249+
};
250+
251+
const ghFields = ["ghOwner", "ghRepo", "ghToken"];
252+
const toggleSaveGithub = () => {
253+
const show = ghFields.every(id => document.getElementById(id).value.trim());
254+
document.getElementById("saveGithub").classList.toggle("d-none", !show);
255+
};
256+
ghFields.forEach(id => {
257+
document.getElementById(id).addEventListener("input", toggleSaveGithub);
258+
});
259+
toggleSaveGithub();
260+
}
261+
262+
function collectData() {
263+
const result = {
264+
productName: document.getElementById("productName").value,
265+
filledBy: document.getElementById("filledBy").value,
266+
filledDate: document.getElementById("filledDate").value,
267+
generatedAt: new Date().toISOString(),
268+
krav: []
269+
};
270+
271+
document.querySelectorAll("select[data-id]").forEach(sel => {
272+
const id = sel.dataset.id;
273+
const doc = document.querySelector(`textarea[data-id='${id}']`);
274+
const item = findRequirement(id);
275+
result.krav.push({
276+
id,
277+
kategori: item.section,
278+
krav: item.krav,
279+
produktniveau: item.niveau,
280+
retningslinjer: item.guide,
281+
efterlevet: sel.value || null,
282+
dokumentation: doc.value || ""
283+
});
284+
});
285+
return result;
286+
}
287+
288+
function populate(data) {
289+
if (!data) return;
290+
document.getElementById("productName").value = data.productName || "";
291+
document.getElementById("filledBy").value = data.filledBy || "";
292+
document.getElementById("filledDate").value = data.filledDate || "";
293+
(data.krav || []).forEach(k => {
294+
const sel = document.querySelector(`select[data-id='${k.id}']`);
295+
const doc = document.querySelector(`textarea[data-id='${k.id}']`);
296+
if (sel) sel.value = k.efterlevet || "";
297+
if (doc) doc.value = k.dokumentation || "";
298+
});
299+
}
300+
301+
function findRequirement(id) {
302+
for (const [section, rows] of Object.entries(kravData)) {
303+
for (const [rid, krav, niveau, guide] of rows) {
304+
if (rid === id) return { section, krav, niveau, guide };
305+
}
306+
}
307+
return { section: "ukendt", krav: "", niveau: "", guide: "" };
308+
}
309+
310+
render();
311+
</script>

0 commit comments

Comments
 (0)