kyd-shared-badge 0.2.32 → 0.2.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -479,6 +479,7 @@ const SharedBadgeDisplay = ({ badgeData }: { badgeData: PublicBadgeData }) => {
|
|
|
479
479
|
sources={graphInsights.business_rules_all as any}
|
|
480
480
|
searchedAt={updatedAt}
|
|
481
481
|
developerName={developerName || 'this developer'}
|
|
482
|
+
genreMapping={genreMapping as Record<string, string[]>}
|
|
482
483
|
/>
|
|
483
484
|
</div>
|
|
484
485
|
</Reveal>
|
|
@@ -36,6 +36,8 @@ interface AppendixTableProps {
|
|
|
36
36
|
sources: (string | DomainCSVRow | SanctionSource | BusinessRuleRow)[];
|
|
37
37
|
searchedAt: string;
|
|
38
38
|
developerName: string;
|
|
39
|
+
// Used for business_rules: map genres (e.g., Technical/Risk) to category arrays
|
|
40
|
+
genreMapping?: Record<string, string[]>;
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
const SanctionsRow = ({
|
|
@@ -125,7 +127,7 @@ const DomainRow = ({ source, searchedAt, developerName }: { source: DomainSource
|
|
|
125
127
|
);
|
|
126
128
|
|
|
127
129
|
|
|
128
|
-
const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName }) => {
|
|
130
|
+
const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedAt, developerName, genreMapping }) => {
|
|
129
131
|
const [visibleCount, setVisibleCount] = useState(PAGE_SIZE);
|
|
130
132
|
const [expanded, setExpanded] = useState<{ [k: number]: boolean }>({});
|
|
131
133
|
|
|
@@ -160,7 +162,22 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
160
162
|
? ["Issuing Entity", "List Name", "List Searched On", "Value", "Findings"]
|
|
161
163
|
: type === 'domains'
|
|
162
164
|
? ["Country", "Entity Type", "Entity/Domain", "Searched On", "Value", "Findings"]
|
|
163
|
-
: ["Platform", "Category", "Observation", "
|
|
165
|
+
: ["Platform", "KYD Pillar", "Category", "Observation", "Weight Impact"];
|
|
166
|
+
|
|
167
|
+
// Build quick lookup: category -> KYD Pillar (e.g., Technical or Risk)
|
|
168
|
+
const categoryToPillar: Record<string, string> = (() => {
|
|
169
|
+
const map: Record<string, string> = {};
|
|
170
|
+
try {
|
|
171
|
+
const gm = genreMapping || {};
|
|
172
|
+
Object.keys(gm || {}).forEach((genre) => {
|
|
173
|
+
const cats = (gm as Record<string, string[]>)[genre] || [];
|
|
174
|
+
cats.forEach((c) => {
|
|
175
|
+
if (typeof c === 'string') map[c] = genre;
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
} catch {}
|
|
179
|
+
return map;
|
|
180
|
+
})();
|
|
164
181
|
|
|
165
182
|
const parsedSources = type === 'sanctions'
|
|
166
183
|
? (sources).map((src) => {
|
|
@@ -179,18 +196,32 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
179
196
|
}))
|
|
180
197
|
: (sources as BusinessRuleRow[]).map(s => ({
|
|
181
198
|
provider: s.provider,
|
|
199
|
+
// Keep original category label; pillar derived from genreMapping
|
|
182
200
|
category: s.category || '',
|
|
183
201
|
label: s.label || '',
|
|
184
202
|
likert_label: s.likert_label || '',
|
|
185
203
|
likert_value: s.likert_value || 0,
|
|
186
204
|
weight: s.weight || 0,
|
|
187
205
|
uid: s.uid || '',
|
|
206
|
+
pillar: categoryToPillar[(s.category || '')] || '',
|
|
188
207
|
}));
|
|
189
208
|
|
|
190
209
|
const sortedParsedSources = (type === 'sanctions')
|
|
191
210
|
? (parsedSources as SanctionSource[]).slice().sort((a, b) => (b.matched === true ? 1 : 0) - (a.matched === true ? 1 : 0))
|
|
192
211
|
: type === 'business_rules'
|
|
193
|
-
? (
|
|
212
|
+
? (() => {
|
|
213
|
+
const orderForPillar = (p: string) => (p === 'Technical' ? 0 : p === 'Risk' ? 1 : 2);
|
|
214
|
+
return (parsedSources as (BusinessRuleRow & { pillar?: string })[]).slice().sort((a, b) => {
|
|
215
|
+
const ap = (a as any).pillar || '';
|
|
216
|
+
const bp = (b as any).pillar || '';
|
|
217
|
+
const cmpPillar = orderForPillar(ap) - orderForPillar(bp);
|
|
218
|
+
if (cmpPillar !== 0) return cmpPillar;
|
|
219
|
+
const ac = (a.category || '').localeCompare(b.category || '');
|
|
220
|
+
if (ac !== 0) return ac;
|
|
221
|
+
// Within same category, sort by descending weight for usefulness
|
|
222
|
+
return Number(b.weight || 0) - Number(a.weight || 0);
|
|
223
|
+
});
|
|
224
|
+
})()
|
|
194
225
|
: parsedSources;
|
|
195
226
|
|
|
196
227
|
const visibleParsedSources = (type === 'business_rules')
|
|
@@ -242,7 +273,7 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
242
273
|
if (type === 'domains') {
|
|
243
274
|
return <DomainRow key={index} source={source as DomainSource} searchedAt={formattedDate} developerName={developerName} />
|
|
244
275
|
}
|
|
245
|
-
const br = source as BusinessRuleRow;
|
|
276
|
+
const br = source as BusinessRuleRow & { pillar?: string };
|
|
246
277
|
const anchorId = br.uid ? `rule-${br.uid}` : undefined;
|
|
247
278
|
const ProviderIcon = ({ name }: { name?: string }) => {
|
|
248
279
|
const n = (name || '').toLowerCase();
|
|
@@ -258,18 +289,18 @@ const AppendixTables: React.FC<AppendixTableProps> = ({ type, sources, searchedA
|
|
|
258
289
|
};
|
|
259
290
|
return (
|
|
260
291
|
<tr id={anchorId} key={index} className={'transition-colors hover:bg-black/5'}>
|
|
261
|
-
<td className={'px-4 py-4 whitespace-normal text-sm
|
|
292
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>
|
|
262
293
|
<span title={br.provider || ''} className={'inline-flex items-center text-4xl'} style={{ color: 'var(--text-main)' }}>
|
|
263
294
|
<ProviderIcon name={br.provider} />
|
|
264
295
|
</span>
|
|
265
296
|
</td>
|
|
266
|
-
<td className={'px-4 py-4 whitespace-normal text-sm
|
|
267
|
-
<td className={'px-4 py-4 whitespace-normal text-sm
|
|
268
|
-
<td className={'px-4 py-4 whitespace-normal text-sm
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (weight
|
|
272
|
-
return '
|
|
297
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{br.pillar || '—'}</td>
|
|
298
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{br.category || '—'}</td>
|
|
299
|
+
<td className={'px-4 py-4 whitespace-normal text-sm font-medium'} style={{ color: 'var(--text-main)' }}>{br.label || '—'}</td>
|
|
300
|
+
<td className={'px-4 py-4 whitespace-normal text-sm'} style={{ color: 'var(--text-secondary)' }}>{(() => {
|
|
301
|
+
const weight = Number(br.weight || 0);
|
|
302
|
+
if (!Number.isFinite(weight)) return '—';
|
|
303
|
+
return weight.toFixed(3).replace(/\.000$/, '.0');
|
|
273
304
|
})()}</td>
|
|
274
305
|
</tr>
|
|
275
306
|
);
|
|
@@ -67,7 +67,7 @@ const CategoryBars: React.FC<CategoryBarsProps> = ({
|
|
|
67
67
|
}}
|
|
68
68
|
>
|
|
69
69
|
{/* signed fill originating from center */}
|
|
70
|
-
<div className="absolute top-0 h-full" style={{ left, width: `${fillWidth}%`, backgroundColor: 'var(--
|
|
70
|
+
<div className="absolute top-0 h-full" style={{ left, width: `${fillWidth}%`, backgroundColor: isNegative ? 'var(--status-negative, #EC6662)' : 'var(--status-positive, #02a389)' }} />
|
|
71
71
|
</div>
|
|
72
72
|
<div className="hidden group-hover:block absolute z-30 left-1/2 -translate-x-1/2 top-full mt-2 w-80">
|
|
73
73
|
<div
|