-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Kadanza DAM media connector v0.0.1 #81
Open
ivaylo-kostov-kad
wants to merge
4
commits into
chili-publish:main
Choose a base branch
from
ivaylo-kostov-kad:feature/kadanza-dam-connector
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+514
−0
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,358 @@ | ||
import { Connector, Media } from '@chili-publish/studio-connectors'; | ||
|
||
interface DamMedia { | ||
id: number; | ||
name: string; | ||
thumbnail: string; | ||
format: string; | ||
width: number; | ||
height: number; | ||
size: number; | ||
assetHash: string; | ||
tenantHash: string; | ||
fileName: string; | ||
title: string; | ||
createdBy: { | ||
id: number; | ||
userName: string; | ||
firstName: string; | ||
lastName: string; | ||
} | ||
} | ||
|
||
interface DamMediaPage { | ||
'hydra:totalItems': number; | ||
'hydra:itemsPerPage': number; | ||
'hydra:currentPage': number; | ||
'hydra:totalPages': number; | ||
'hydra:member': Array<DamMedia>; | ||
} | ||
|
||
interface AssetId { | ||
id: string; | ||
name: string; | ||
assetHash: string; | ||
tenantHash: string; | ||
thumbnail: string; | ||
} | ||
|
||
interface DAMCustomMetadata { | ||
id: number; | ||
name: string; | ||
label: string; | ||
type: string; | ||
defaultValue: string; | ||
required: boolean; | ||
default: boolean; | ||
readOnly: boolean; | ||
visible: boolean; | ||
sorting: boolean; | ||
sort: number; | ||
dropdownOptions: object; | ||
title: string; | ||
text: string; | ||
deletedAt: string; | ||
filterable: boolean; | ||
sortable: boolean; | ||
attributeName: string; | ||
} | ||
|
||
interface DAMCustomMetadataPage { | ||
'hydra:totalItems': number; | ||
'hydra:itemsPerPage': number; | ||
'hydra:currentPage': number; | ||
'hydra:totalPages': number; | ||
'hydra:member': Array<DAMCustomMetadata>; | ||
} | ||
|
||
export default class DamConnector implements Media.MediaConnector { | ||
runtime: Connector.ConnectorRuntimeContext; | ||
|
||
constructor(runtime: Connector.ConnectorRuntimeContext) { | ||
this.runtime = runtime; | ||
} | ||
|
||
async detail( | ||
id: string, | ||
context: Connector.Dictionary | ||
): Promise<Media.MediaDetail> { | ||
const assetId: AssetId = JSON.parse(id); | ||
const damMedia = await this._getDamMediaById(assetId.id); | ||
const metadata = await this._getCustomMetadata(); | ||
|
||
return this._getMediaDetailFromDamMedia(damMedia, metadata); | ||
} | ||
|
||
async query( | ||
options: Connector.QueryOptions, | ||
context: Connector.Dictionary | ||
): Promise<Media.MediaPage> { | ||
this._logError( | ||
`query options: sortOrder ${options?.sortOrder} sortBy ${options?.sortBy} collection ${options?.collection} filter ${options?.filter} pageToken ${options?.pageToken} pageSize ${options?.pageSize}` | ||
); | ||
|
||
this._logError( | ||
`context: categoryGroup ${context?.categoryGroup} category ${context?.category} searchQuery ${context?.searchQuery}` | ||
); | ||
|
||
const currentPage = Number(options.pageToken) || 1; | ||
const pageSize = Number(options.pageSize) || 15; | ||
this._logError( | ||
`currentPage: ${currentPage} pageSize: ${pageSize}` | ||
); | ||
let queryEndpoint = `${this._getBaseMediaUrl()}/api/assets?page=${currentPage}&pageSize=${pageSize}`; | ||
|
||
// Check if a specific categoryGroup is provided in the settings (context) | ||
if (context?.categoryGroup) { | ||
queryEndpoint += `&categoryGroup=${context.categoryGroup}`; | ||
} | ||
|
||
// Check if a specific category is provided in the settings (context) | ||
if (context?.category) { | ||
queryEndpoint += `&category=${context.category}`; | ||
} | ||
|
||
const filter = options?.filter | ||
? options?.filter | ||
: undefined; | ||
|
||
if (filter && filter.length > 0) { | ||
const stringifiedFilter = filter.toString().trim(); | ||
|
||
let id; | ||
try { | ||
id = JSON.parse(stringifiedFilter).id; | ||
this._logError( | ||
`ID ${id}` | ||
); | ||
} catch (e) { | ||
// filter is not JSON | ||
} | ||
|
||
if (id) { | ||
this._logError( | ||
`Filtering query by _id: ${id}` | ||
); | ||
|
||
queryEndpoint += `&search=_id: ${id}`; | ||
} else if (stringifiedFilter && context?.searchQuery) { | ||
this._logError( | ||
`Filtering query by ${stringifiedFilter} in ${context.searchQuery}` | ||
); | ||
|
||
const search = context.searchQuery.toString().replace('<search_input>', stringifiedFilter); | ||
queryEndpoint += `&search=${search}`; | ||
} | ||
} | ||
|
||
this._logError(`Query: endpoint ${encodeURI(queryEndpoint)}`); | ||
|
||
const result = await this.runtime.fetch(encodeURI(queryEndpoint), { | ||
method: 'GET', | ||
headers: this._getHeaders(), | ||
}); | ||
|
||
if (result.status / 200 != 1) { | ||
this._logError(`Query fetch failed.`); | ||
throw new Error(`Query failed ${result.status} ${result.statusText}`); | ||
} | ||
|
||
const assetsPage: DamMediaPage = JSON.parse(result.text); | ||
const page = assetsPage['hydra:currentPage']; | ||
|
||
const metadata = await this._getCustomMetadata(); | ||
|
||
const nextPage = Number(assetsPage['hydra:currentPage']) < Number(assetsPage['hydra:totalPages']) ? Number(assetsPage['hydra:currentPage']) + 1 : ''; | ||
this._logError(`nextPage: ${nextPage}`); | ||
|
||
return { | ||
pageSize: pageSize, | ||
data: assetsPage['hydra:member'].map((a: DamMedia) => | ||
this._getMediaDetailFromDamMedia(a, metadata) | ||
), | ||
links: { | ||
nextPage: nextPage.toString(), | ||
}, | ||
}; | ||
} | ||
|
||
async download( | ||
id: string, | ||
previewType: Media.DownloadType, | ||
intent: Media.DownloadIntent, | ||
context: Connector.Dictionary | ||
): Promise<Connector.ArrayBufferPointer> { | ||
this._logError(`Download: id ${id}, previewType ${previewType}`); | ||
|
||
// Temporary commented until issue with >= 1 await statements is resolved | ||
// const detail = await this._getDamMediaById(id); | ||
|
||
// Extract all details from stringified id | ||
const detail: AssetId = JSON.parse(id); | ||
|
||
const baseUrl = this._getBaseMediaUrl(); | ||
let downloadEndpoint = `${baseUrl}`; | ||
|
||
switch (previewType) { | ||
// TODO -> handle medium/highres with conversion profiles? | ||
|
||
case 'fullres': | ||
case 'highres': | ||
case 'original': | ||
downloadEndpoint += `/${detail.tenantHash}/${detail.assetHash}/${encodeURIComponent( | ||
detail.name | ||
)}/original`; | ||
break; | ||
|
||
case 'thumbnail': | ||
case 'mediumres': | ||
downloadEndpoint += detail.thumbnail ? detail.thumbnail : `/${detail.tenantHash}/${detail.assetHash}/${encodeURIComponent( | ||
detail.name | ||
)}/original`; | ||
break; | ||
|
||
default: | ||
downloadEndpoint += `/${detail.tenantHash}/${detail.assetHash}/${encodeURIComponent( | ||
detail.name | ||
)}/original`; | ||
break; | ||
} | ||
|
||
this._logError(`Download: endpoint ${downloadEndpoint}`); | ||
|
||
const result = await this.runtime.fetch(downloadEndpoint, { | ||
method: 'GET', | ||
headers: this._getHeaders(), | ||
}); | ||
|
||
this._logError( | ||
`Download: result status ${result.status} ${result.statusText}.` | ||
); | ||
|
||
if (result.status / 200 != 1) { | ||
this._logError( | ||
`Download: fetch failed for media with id ${id} and previewType ${previewType}` | ||
); | ||
throw new Error(`Download failed ${result.status} ${result.statusText}`); | ||
} | ||
|
||
this._logError( | ||
`Download: result array buffer id, bytes: ${ | ||
(result.arrayBuffer.id, result.arrayBuffer.bytes) | ||
}` | ||
); | ||
|
||
return result.arrayBuffer; | ||
} | ||
|
||
getConfigurationOptions(): Connector.ConnectorConfigValue[] | null { | ||
return [ | ||
{ | ||
name: 'categoryGroup', | ||
displayName: 'Category group (entrypoint) ID', | ||
type: 'text', | ||
}, | ||
{ | ||
name: 'category', | ||
displayName: 'Category ID', | ||
type: 'text', | ||
}, | ||
{ | ||
name: 'searchQuery', | ||
displayName: 'Search query', | ||
type: 'text', | ||
}, | ||
]; | ||
} | ||
|
||
getCapabilities(): Media.MediaConnectorCapabilities { | ||
return { | ||
query: true, | ||
detail: true, | ||
filtering: true, | ||
metadata: true, | ||
}; | ||
} | ||
|
||
_getBaseMediaUrl() { | ||
return this.runtime.options['BASE_URL']; | ||
} | ||
|
||
_getDebug() { | ||
return this.runtime.options['DEBUG']; | ||
} | ||
|
||
_getHeaders() { | ||
return { | ||
'Accept': 'application/json', | ||
}; | ||
} | ||
|
||
_getMediaDetailFromDamMedia(damMedia: DamMedia, customMetadata: DAMCustomMetadataPage): Media.MediaDetail { | ||
const assetId: AssetId = { | ||
id: damMedia.id.toString(), | ||
name: damMedia.name, | ||
assetHash: damMedia.assetHash, | ||
tenantHash: damMedia.tenantHash, | ||
thumbnail: damMedia.thumbnail, | ||
}; | ||
|
||
return { | ||
// We save all information required for 'download` under id to avoid details call | ||
id: JSON.stringify(assetId), | ||
name: damMedia.name, | ||
relativePath: 'Media', | ||
type: 0, | ||
metaData: this._getMetadataFromDamMedia(damMedia, customMetadata), | ||
extension: damMedia.format, | ||
width: damMedia.width, | ||
height: damMedia.height, | ||
}; | ||
} | ||
|
||
_getMetadataFromDamMedia(damMedia: DamMedia, customMetadata: DAMCustomMetadataPage): Connector.Dictionary { | ||
const attributeNames: Array<string> = customMetadata['hydra:member'].map((m) => m.attributeName); | ||
|
||
return Object.fromEntries(attributeNames.filter((a) => typeof damMedia[a] === 'string').map((a) => [a, damMedia[a]] )); | ||
} | ||
|
||
_logError(err: string) { | ||
if (this._getDebug()) { | ||
this.runtime.logError(err); | ||
} | ||
} | ||
|
||
private async _getDamMediaById(id: string) { | ||
const detailEndpoint = `${this._getBaseMediaUrl()}/api/assets/${id}`; | ||
|
||
const result = await this.runtime.fetch(detailEndpoint, { | ||
method: 'GET', | ||
headers: this._getHeaders(), | ||
}); | ||
|
||
if (result.status / 200 != 1) { | ||
this._logError(`Detail fetch failed for media with id ${id}`); | ||
throw new Error(`Detail failed ${result.status} ${result.statusText}`); | ||
} | ||
|
||
const damMedia: DamMedia = JSON.parse(result.text); | ||
return damMedia; | ||
} | ||
|
||
private async _getCustomMetadata() { | ||
const customMetadataEndpoint = `${this._getBaseMediaUrl()}/api/custom-metadata`; | ||
|
||
const result = await this.runtime.fetch(customMetadataEndpoint, { | ||
method: 'GET', | ||
headers: this._getHeaders(), | ||
}); | ||
|
||
if (result.status / 200 != 1) { | ||
this._logError(`Custom metadata fetch failed.`); | ||
throw new Error(`Custom metadata fetch failed: ${result.status} ${result.statusText}`); | ||
} | ||
|
||
const customMetadata: DAMCustomMetadataPage = JSON.parse(result.text); | ||
return customMetadata; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
{ | ||
"name": "kadanza", | ||
"description": "Kadanza DAM", | ||
"version": "0.0.1", | ||
"author": { | ||
psamusev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"name": "Kadanza", | ||
"email": "[email protected]", | ||
"url": "https://www.kadanza.com/" | ||
}, | ||
"config": { | ||
"connectorName": "Kadanza DAM", | ||
psamusev marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"iconUrl": "https://s3.eu-central-1.amazonaws.com/static.kadanza.io/logo/Logo_Kadanza_Icon.svg", | ||
"type": "media", | ||
"options": {"BASE_URL": null, "DEBUG": false}, | ||
"mappings": {}, | ||
"supportedAuth": ["oAuth2ResourceOwnerPassword", "oAuth2AuthorizationCode"] | ||
}, | ||
"private": true, | ||
"license": "MIT", | ||
"main": "out/connector.js", | ||
"dependencies": { | ||
"typescript": "^5.2.2", | ||
"@chili-publish/studio-connectors": "^1.15" | ||
}, | ||
"scripts": { | ||
"build": "yarn connector-cli build", | ||
"test": "yarn connector-cli test -t tests.json && yarn connector-cli stress" | ||
}, | ||
"devDependencies": { | ||
"@chili-publish/connector-cli": "^1.6" | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these types seems like didn't return any image or in a wrong format. At least for 'highres' it should image type as for thumbnail and mediums (although can be with higher resolution)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@psamusev could you please add more details and specifics on the assets you tested with ? Additionally, what's an image in a wrong format?
Generally speaking, the current implementation returns the original asset and that does not guarantee that it will always be an image (this is expected as the DAM supports non-image file types as well).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivaylo-kostov-kad you can find supported formats under "Supported Formats" section https://docs.chiligrafx.com/GraFx-Developers/connectors/media-connector/media-connector-fundamentals/#download-method. In addition this PR can be used as a reference chili-publish/studio-sdk#371
In short, we're working only with JPEG, PNG or PDF (for 'fullres' and 'original') only
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@psamusev OK, thanks - that's clear. We don't expect GraFx to support other formats. Our expectation is that if an asset download is requested and the original is a non-supported format, loading (or rendering) the asset in the document will fail - that's fine for us.
That's why you also see a TODO on line 197 - we will eventually decide whether we need to add custom logic for other formats when we actually start using the connector in real-life use-cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ivaylo-kostov-kad this is exact behaviour of the Canvas - you won't see the asset if it's not recognised by the Engine that leads us to partially working connector behaviour. You would need to fix it as part of the PR to let us proceed with further testing and merge