From da698bb095d1d098d3a17be94e38a34527440e6d Mon Sep 17 00:00:00 2001 From: simonredfern Date: Fri, 16 Jan 2026 12:50:14 +0100 Subject: [PATCH 1/4] Json Schema for Message Docs --- components.d.ts | 1 + src/components/HeaderNav.vue | 29 ++++++++++-- src/main.ts | 28 +++++++++++- src/obp/keys.ts | 5 +- src/obp/message-docs.ts | 89 +++++++++++++++++++++++++++++++++--- src/router/index.ts | 12 +++++ 6 files changed, 150 insertions(+), 14 deletions(-) diff --git a/components.d.ts b/components.d.ts index 3b866c3..5ebafb0 100644 --- a/components.d.ts +++ b/components.d.ts @@ -45,6 +45,7 @@ declare module 'vue' { GlossarySearchNav: typeof import('./src/components/GlossarySearchNav.vue')['default'] HeaderNav: typeof import('./src/components/HeaderNav.vue')['default'] Menu: typeof import('./src/components/Menu.vue')['default'] + MessageDocsJsonSchemaSearchNav: typeof import('./src/components/MessageDocsJsonSchemaSearchNav.vue')['default'] MessageDocsSearchNav: typeof import('./src/components/MessageDocsSearchNav.vue')['default'] Preview: typeof import('./src/components/Preview.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] diff --git a/src/components/HeaderNav.vue b/src/components/HeaderNav.vue index 34ac860..90fbb93 100644 --- a/src/components/HeaderNav.vue +++ b/src/components/HeaderNav.vue @@ -36,7 +36,7 @@ import { HEADER_LINKS_HOVER_COLOR as headerLinksHoverColorSetting, HEADER_LINKS_BACKGROUND_COLOR as headerLinksBackgroundColorSetting } from '../obp/style-setting' -import { obpApiActiveVersionsKey, obpGroupedMessageDocsKey, obpMyCollectionsEndpointKey } from '@/obp/keys' +import { obpApiActiveVersionsKey, obpGroupedMessageDocsKey, obpGroupedMessageDocsJsonSchemaKey, obpMyCollectionsEndpointKey } from '@/obp/keys' import SvelteDropdown from './SvelteDropdown.vue' const route = useRoute() @@ -51,6 +51,14 @@ const loginUsername = ref('') const logoffurl = ref('') const obpApiVersions = ref(inject(obpApiActiveVersionsKey) || []) const obpMessageDocs = ref(Object.keys(inject(obpGroupedMessageDocsKey) || {})) +const obpMessageDocsJsonSchema = ref(Object.keys(inject(obpGroupedMessageDocsJsonSchemaKey) || {})) + +// Combine message docs with JSON Schema items (with "J Schema" postfix) +const combinedMessageDocs = computed(() => { + const regularDocs = obpMessageDocs.value || [] + const jsonSchemaDocs = (obpMessageDocsJsonSchema.value || []).map(connector => `${connector} J Schema`) + return [...regularDocs, ...jsonSchemaDocs] +}) // Debug menu items const debugMenuItems = ref(['/debug/providers-status', '/debug/oidc']) @@ -189,8 +197,8 @@ const setActive = (target: HTMLElement | null) => { } } -const handleMore = (command: string) => { - console.log('handleMore called with command:', command) +const handleMore = (command: string, source?: string) => { + console.log('handleMore called with command:', command, 'source:', source) // Ignore divider if (command === '---') { @@ -201,11 +209,22 @@ const handleMore = (command: string) => { if (element !== null) { element.textContent = command; } - if (command === '/message-docs') { + + // Check if command ends with " J Schema" - if so, it's a JSON Schema message doc + if (command.endsWith(' J Schema')) { + const connector = command.replace(' J Schema', '') + console.log('Navigating to message docs JSON schema:', connector) + router.push({ name: 'message-docs-json-schema', params: { id: connector } }) + } else if (command === '/message-docs') { // Navigate to message docs list console.log('Navigating to message docs list') router.push({ name: 'message-docs-list' }) + } else if (command === '/message-docs-json-schema') { + // Navigate to message docs JSON schema list + console.log('Navigating to message docs JSON schema list') + router.push({ name: 'message-docs-json-schema-list' }) } else if (command.includes('_')) { + // Regular message docs (connector names contain underscores) console.log('Navigating to message docs:', command) router.push({ name: 'message-docs', params: { id: command } }) } else if (command.startsWith('/debug/')) { @@ -292,7 +311,7 @@ const getCurrentPath = () => { class="menu-right" id="header-nav-message-docs" label="Message Docs" - :items="obpMessageDocs" + :items="combinedMessageDocs" :hover-color="headerLinksHoverColor" :background-color="headerLinksBackgroundColor" @select="handleMore" diff --git a/src/main.ts b/src/main.ts index e43fdb0..83ef4b5 100644 --- a/src/main.ts +++ b/src/main.ts @@ -38,7 +38,12 @@ import { createI18n } from 'vue-i18n' import { languages, defaultLocale } from './language' import { cache as cacheResourceDocs, cacheDoc as cacheResourceDocsDoc } from './obp/resource-docs' -import { cache as cacheMessageDocs, cacheDoc as cacheMessageDocsDoc } from './obp/message-docs' +import { + cache as cacheMessageDocs, + cacheDoc as cacheMessageDocsDoc, + cacheJsonSchema as cacheMessageDocsJsonSchema, + cacheDocJsonSchema as cacheMessageDocsJsonSchemaDoc +} from './obp/message-docs' import { OBP_API_VERSION, getMyAPICollections, getMyAPICollectionsEndpoint } from './obp' import { getOBPGlossary } from './obp/glossary' @@ -47,16 +52,18 @@ import './assets/main.css' import '@fontsource/roboto/300.css' import '@fontsource/roboto/400.css' import '@fontsource/roboto/700.css' + +import { getCacheStorageInfo } from './obp/common-functions' import { obpApiActiveVersionsKey, obpApiHostKey, obpGlossaryKey, obpGroupedMessageDocsKey, + obpGroupedMessageDocsJsonSchemaKey, obpGroupedResourceDocsKey, obpMyCollectionsEndpointKey, obpResourceDocsKey } from './obp/keys' -import { getCacheStorageInfo } from './obp/common-functions' ;(async () => { const app = createApp(App) const router = await appRouter() @@ -272,6 +279,13 @@ async function setupData(app: App, worker: Worker) { const cacheStorageOfMessageDocs = await caches.open('obp-message-docs-cache') // Please note: The global 'caches' read-only property returns the 'CacheStorage' object associated with the current context. // 'match': Checks if a given Request is a key in any of the Cache objects that the CacheStorage object tracks, and returns a Promise that resolves to that match. const cachedResponseOfMessageDocs = await cacheStorageOfMessageDocs.match('/') + // 'open': Returns a Promise that resolves to the Cache object matching the cacheName(obp-message-docs-json-schema-cache) (a new cache is created if it doesn't already exist.) + const cacheStorageOfMessageDocsJsonSchema = await caches.open( + 'obp-message-docs-json-schema-cache' + ) // Please note: The global 'caches' read-only property returns the 'CacheStorage' object associated with the current context. + // 'match': Checks if a given Request is a key in any of the Cache objects that the CacheStorage object tracks, and returns a Promise that resolves to that match. + const cachedResponseOfMessageDocsJsonSchema = + await cacheStorageOfMessageDocsJsonSchema.match('/') // Listen to Web worker worker.onmessage = async (event) => { @@ -286,6 +300,10 @@ async function setupData(app: App, worker: Worker) { await cacheMessageDocsDoc(cacheStorageOfMessageDocs) console.log('Message Docs cache was updated.') } + if (event.data === 'update-message-docs-json-schema') { + await cacheMessageDocsJsonSchemaDoc(cacheStorageOfMessageDocsJsonSchema) + console.log('Message Docs JSON Schema cache was updated.') + } } const { resourceDocs, groupedDocs } = await cacheResourceDocs( @@ -298,6 +316,11 @@ async function setupData(app: App, worker: Worker) { cachedResponseOfMessageDocs, worker ) + const messageDocsJsonSchema = await cacheMessageDocsJsonSchema( + cacheStorageOfMessageDocsJsonSchema, + cachedResponseOfMessageDocsJsonSchema, + worker + ) // Provide data to a component's descendants // App-level provides are available to all components rendered in the app @@ -306,6 +329,7 @@ async function setupData(app: App, worker: Worker) { app.provide(obpApiActiveVersionsKey, Object.keys(resourceDocs).sort()) app.provide(obpGroupedResourceDocsKey, groupedDocs) app.provide(obpGroupedMessageDocsKey, messageDocs) + app.provide(obpGroupedMessageDocsJsonSchemaKey, messageDocsJsonSchema) app.provide(obpApiHostKey, import.meta.env.VITE_OBP_API_HOST) const glossary = await getOBPGlossary() app.provide(obpGlossaryKey, glossary) diff --git a/src/obp/keys.ts b/src/obp/keys.ts index 8f7a77f..e682a3a 100644 --- a/src/obp/keys.ts +++ b/src/obp/keys.ts @@ -31,6 +31,9 @@ export const obpResourceDocsKey = Symbol('OBP-ResourceDocs') as InjectionKey export const obpGroupedResourceDocsKey = Symbol('OBP-GroupedResourceDocs') as InjectionKey export const obpGroupedMessageDocsKey = Symbol('OBP-GroupedMessageDocs') as InjectionKey // This cause an issue +export const obpGroupedMessageDocsJsonSchemaKey = Symbol( + 'OBP-GroupedMessageDocsJsonSchema' +) as InjectionKey export const obpApiHostKey = Symbol('OBP-API-Host') as InjectionKey export const obpGlossaryKey = Symbol('OBP-Glossary') as InjectionKey -export const obpMyCollectionsEndpointKey = Symbol('OBP-MyCollectionsEndpoint') as InjectionKey \ No newline at end of file +export const obpMyCollectionsEndpointKey = Symbol('OBP-MyCollectionsEndpoint') as InjectionKey diff --git a/src/obp/message-docs.ts b/src/obp/message-docs.ts index b67c6ec..028d49f 100644 --- a/src/obp/message-docs.ts +++ b/src/obp/message-docs.ts @@ -43,7 +43,15 @@ export async function getOBPMessageDocs(item: string): Promise { return await get(`obp/${OBP_API_VERSION}/message-docs/${item}`) } -export function getGroupedMessageDocs(docs: any): Promise { +// Get Message Docs JSON Schema +export async function getOBPMessageDocsJsonSchema(item: string): Promise { + const logMessage = `Loading message docs JSON schema { connector: ${item} }` + console.log(logMessage) + updateLoadingInfoMessage(logMessage) + return await get(`obp/v6.0.0/message-docs/${item}/json-schema`) +} + +export function getGroupedMessageDocs(docs: any): any { return docs.message_docs.reduce((values: any, doc: any) => { const tag = doc.adapter_implementation.group.replace('-', '').trim() ;(values[tag] = values[tag] || []).push(doc) @@ -51,6 +59,39 @@ export function getGroupedMessageDocs(docs: any): Promise { }, {}) } +export function getGroupedMessageDocsJsonSchema(docs: any): any { + if (!docs.definitions || typeof docs.definitions !== 'object') { + return {} + } + + // Convert definitions object to array format and group by InBound/OutBound prefix + const grouped: any = {} + Object.keys(docs.definitions).forEach((methodName: string) => { + const schema = docs.definitions[methodName] + + // Determine category based on method name prefix + let category = 'Uncategorized' + if (methodName.startsWith('InBound')) { + category = 'Inbound Methods' + } else if (methodName.startsWith('OutBound')) { + category = 'Outbound Methods' + } + + if (!grouped[category]) { + grouped[category] = [] + } + + grouped[category].push({ + method_name: methodName, + category: category, + request_schema: schema, + response_schema: schema + }) + }) + + return grouped +} + export async function cacheDoc(cacheStorageOfMessageDocs: any): Promise { const messageDocs = await connectors.reduce(async (agroup: any, connector: any) => { const logMessage = `Caching message docs { connector: ${connector} }` @@ -71,11 +112,30 @@ async function getCacheDoc(cacheStorageOfMessageDocs: any): Promise { return await cacheDoc(cacheStorageOfMessageDocs) } -export async function cache( - cacheStorage: any, - cachedResponse: any, - worker: any -): Promise { +export async function cacheDocJsonSchema(cacheStorageOfMessageDocsJsonSchema: any): Promise { + const messageDocsJsonSchema = await connectors.reduce(async (agroup: any, connector: any) => { + const logMessage = `Caching message docs JSON schema { connector: ${connector} }` + console.log(logMessage) + updateLoadingInfoMessage(logMessage) + const group = await agroup + const docs = await getOBPMessageDocsJsonSchema(connector) + if (!Object.keys(docs).includes('code')) { + group[connector] = getGroupedMessageDocsJsonSchema(docs) + } + return group + }, Promise.resolve({})) + await cacheStorageOfMessageDocsJsonSchema.put( + '/', + new Response(JSON.stringify(messageDocsJsonSchema)) + ) + return messageDocsJsonSchema +} + +async function getCacheDocJsonSchema(cacheStorageOfMessageDocsJsonSchema: any): Promise { + return await cacheDocJsonSchema(cacheStorageOfMessageDocsJsonSchema) +} + +export async function cache(cacheStorage: any, cachedResponse: any, worker: any): Promise { try { worker.postMessage('update-message-docs') return await cachedResponse.json() @@ -87,3 +147,20 @@ export async function cache( return await getCacheDoc(cacheStorage) } } + +export async function cacheJsonSchema( + cacheStorage: any, + cachedResponse: any, + worker: any +): Promise { + try { + worker.postMessage('update-message-docs-json-schema') + return await cachedResponse.json() + } catch (error) { + console.warn('No message docs JSON schema cache or malformed cache.') + console.log('Caching message docs JSON schema...') + const isServerActive = await isServerUp() + if (!isServerActive) throw new Error('API Server is not responding.') + return await getCacheDocJsonSchema(cacheStorage) + } +} diff --git a/src/router/index.ts b/src/router/index.ts index 1e38e80..ab03d44 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -30,6 +30,8 @@ import GlossaryView from '../views/GlossaryView.vue' import HelpView from '../views/HelpView.vue' import MessageDocsView from '../views/MessageDocsView.vue' import MessageDocsListView from '../views/MessageDocsListView.vue' +import MessageDocsJsonSchemaView from '../views/MessageDocsJsonSchemaView.vue' +import MessageDocsJsonSchemaListView from '../views/MessageDocsJsonSchemaListView.vue' import BodyView from '../views/BodyView.vue' import Content from '../components/Content.vue' import Preview from '../components/Preview.vue' @@ -86,6 +88,16 @@ export default async function router(): Promise { name: 'message-docs', component: isServerActive ? MessageDocsView : InternalServerErrorView }, + { + path: '/message-docs-json-schema', + name: 'message-docs-json-schema-list', + component: isServerActive ? MessageDocsJsonSchemaListView : InternalServerErrorView + }, + { + path: '/message-docs-json-schema/:id', + name: 'message-docs-json-schema', + component: isServerActive ? MessageDocsJsonSchemaView : InternalServerErrorView + }, { path: '/resource-docs', redirect: () => { From f7b7dfb59818365a73d676e0b7f91b3284fc780c Mon Sep 17 00:00:00 2001 From: simonredfern Date: Sat, 17 Jan 2026 10:43:10 +0100 Subject: [PATCH 2/4] Message docs json schema page --- components.d.ts | 1 + src/obp/message-docs.ts | 72 +++++++++++++++++++++++++++++++---------- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/components.d.ts b/components.d.ts index 5ebafb0..06fd8d6 100644 --- a/components.d.ts +++ b/components.d.ts @@ -44,6 +44,7 @@ declare module 'vue' { ElTooltip: typeof import('element-plus/es')['ElTooltip'] GlossarySearchNav: typeof import('./src/components/GlossarySearchNav.vue')['default'] HeaderNav: typeof import('./src/components/HeaderNav.vue')['default'] + JsonSchemaViewer: typeof import('./src/components/JsonSchemaViewer.vue')['default'] Menu: typeof import('./src/components/Menu.vue')['default'] MessageDocsJsonSchemaSearchNav: typeof import('./src/components/MessageDocsJsonSchemaSearchNav.vue')['default'] MessageDocsSearchNav: typeof import('./src/components/MessageDocsSearchNav.vue')['default'] diff --git a/src/obp/message-docs.ts b/src/obp/message-docs.ts index 028d49f..3b71cc6 100644 --- a/src/obp/message-docs.ts +++ b/src/obp/message-docs.ts @@ -60,36 +60,74 @@ export function getGroupedMessageDocs(docs: any): any { } export function getGroupedMessageDocsJsonSchema(docs: any): any { - if (!docs.definitions || typeof docs.definitions !== 'object') { - return {} + console.log('getGroupedMessageDocsJsonSchema - Raw docs:', docs) + + // Access messages from the correct path: properties.messages.items + const messages = docs.properties?.messages?.items + const definitions = docs.definitions || {} + + if (!messages || !Array.isArray(messages)) { + console.log('No messages array found, falling back to definitions') + // Fallback to old structure if messages array doesn't exist + if (!definitions || typeof definitions !== 'object') { + console.log('No definitions object found either') + return { grouped: {}, definitions: {} } + } + + // Convert definitions object to array format and group by InBound/OutBound prefix + const grouped: any = {} + Object.keys(definitions).forEach((methodName: string) => { + const schema = definitions[methodName] + + // Determine category based on method name prefix + let category = 'Uncategorized' + if (methodName.startsWith('InBound')) { + category = 'Inbound Methods' + } else if (methodName.startsWith('OutBound')) { + category = 'Outbound Methods' + } + + if (!grouped[category]) { + grouped[category] = [] + } + + grouped[category].push({ + method_name: methodName, + category: category, + outbound_schema: schema, + inbound_schema: schema + }) + }) + + console.log('Grouped definitions result:', grouped) + return { grouped, definitions } } - // Convert definitions object to array format and group by InBound/OutBound prefix + // Group messages by adapter_implementation.group + console.log('Processing messages array') const grouped: any = {} - Object.keys(docs.definitions).forEach((methodName: string) => { - const schema = docs.definitions[methodName] - - // Determine category based on method name prefix - let category = 'Uncategorized' - if (methodName.startsWith('InBound')) { - category = 'Inbound Methods' - } else if (methodName.startsWith('OutBound')) { - category = 'Outbound Methods' - } + messages.forEach((message: any) => { + const category = + message.adapter_implementation?.group?.replace('-', '').trim() || 'Uncategorized' if (!grouped[category]) { grouped[category] = [] } + // Keep original schemas with $refs intact grouped[category].push({ - method_name: methodName, + method_name: message.process, category: category, - request_schema: schema, - response_schema: schema + description: message.description, + outbound_schema: message.outbound_schema, + inbound_schema: message.inbound_schema, + message_format: message.message_format }) }) - return grouped + console.log('Grouped messages result:', grouped) + console.log('Definitions:', definitions) + return { grouped, definitions } } export async function cacheDoc(cacheStorageOfMessageDocs: any): Promise { From 6f9a5d14bde0002c64b0ccf5dce31431a7f98d72 Mon Sep 17 00:00:00 2001 From: simonredfern Date: Sat, 17 Jan 2026 10:44:05 +0100 Subject: [PATCH 3/4] Message Docs json schema --- src/components/JsonSchemaViewer.vue | 328 +++++++++++++ .../MessageDocsJsonSchemaSearchNav.vue | 183 ++++++++ src/views/MessageDocsJsonSchemaListView.vue | 114 +++++ src/views/MessageDocsJsonSchemaView.vue | 439 ++++++++++++++++++ 4 files changed, 1064 insertions(+) create mode 100644 src/components/JsonSchemaViewer.vue create mode 100644 src/components/MessageDocsJsonSchemaSearchNav.vue create mode 100644 src/views/MessageDocsJsonSchemaListView.vue create mode 100644 src/views/MessageDocsJsonSchemaView.vue diff --git a/src/components/JsonSchemaViewer.vue b/src/components/JsonSchemaViewer.vue new file mode 100644 index 0000000..c4a1851 --- /dev/null +++ b/src/components/JsonSchemaViewer.vue @@ -0,0 +1,328 @@ + + + + + + + diff --git a/src/components/MessageDocsJsonSchemaSearchNav.vue b/src/components/MessageDocsJsonSchemaSearchNav.vue new file mode 100644 index 0000000..84ab88d --- /dev/null +++ b/src/components/MessageDocsJsonSchemaSearchNav.vue @@ -0,0 +1,183 @@ + + + + + + + diff --git a/src/views/MessageDocsJsonSchemaListView.vue b/src/views/MessageDocsJsonSchemaListView.vue new file mode 100644 index 0000000..6d4a845 --- /dev/null +++ b/src/views/MessageDocsJsonSchemaListView.vue @@ -0,0 +1,114 @@ + + + + + + + diff --git a/src/views/MessageDocsJsonSchemaView.vue b/src/views/MessageDocsJsonSchemaView.vue new file mode 100644 index 0000000..1246a58 --- /dev/null +++ b/src/views/MessageDocsJsonSchemaView.vue @@ -0,0 +1,439 @@ + + + + + + + From 2b5b824d1fb7ed8bac2f8bedc4eb6d348f9e877f Mon Sep 17 00:00:00 2001 From: simonredfern Date: Fri, 30 Jan 2026 20:22:40 +0100 Subject: [PATCH 4/4] untraced_docs in gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index a145316..beac932 100644 --- a/.gitignore +++ b/.gitignore @@ -62,3 +62,6 @@ test-results/ playwright-report/ playwright-coverage/ shared-constants.js + +# Documentation +untracked_docs/