Skip to content

Commit

Permalink
fix(ListPlugin): 'selectFromScope' support relative addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
soofstad committed Jan 3, 2024
1 parent e205d7f commit 9845a44
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 44 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"_id": "wash_task",
"_id": "wash_task_templates",
"name": "Wash_the_car",
"type": "./blueprints/Task",
"description": "Car needs a thorough wash inside and outside.",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
{
"_id": "uncontainedTaskList",
"_id": "uncontainedTaskList_templates",
"name": "uncontainedTaskList",
"type": "./blueprints/UncontainedTaskList",
"task_list": [
{
"type": "CORE:Reference",
"address": "/$wash_task",
"address": "/$wash_task_templates",
"referenceType": "link"
}
]
Expand Down
6 changes: 5 additions & 1 deletion packages/dm-core-plugins/src/list/ListPlugin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
TEntityPickerReturn,
TemplateMenu,
TTemplate,
resolveRelativeAddressSimplified,
} from '@development-framework/dm-core'
import { toast } from 'react-toastify'
import {
Expand Down Expand Up @@ -183,7 +184,10 @@ export const ListPlugin = (props: IUIPlugin & { config?: TListConfig }) => {
showModal={showModal}
setShowModal={setShowModal}
typeFilter={type}
scope={config.selectFromScope}
scope={resolveRelativeAddressSimplified(
config.selectFromScope,
idReference
)}
onChange={async (entities: TEntityPickerReturn[]) => {
const newKeys: Record<string, boolean> = {}
for (const { address, entity } of entities) {
Expand Down
17 changes: 9 additions & 8 deletions packages/dm-core/src/components/TreeView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ export type TNodeWrapperProps = {
const TypeIcon = (props: { node: TreeNode; expanded: boolean }) => {
const { node, expanded } = props

if (node.type === 'error')
return (
<FaExclamationTriangle
style={{ color: 'orange' }}
title='failed to load'
/>
)

const showAsReference =
node.parent?.type !== EBlueprint.PACKAGE &&
node?.type !== EBlueprint.PACKAGE &&
Expand Down Expand Up @@ -75,13 +83,6 @@ const TypeIcon = (props: { node: TreeNode; expanded: boolean }) => {
}
case 'dataSource':
return <FaDatabase style={{ color: 'gray' }} title='data source' />
case 'error':
return (
<FaExclamationTriangle
style={{ color: 'orange' }}
title='failed to load'
/>
)
case EBlueprint.BLUEPRINT:
return <FaRegFileAlt style={{ color: '#2966FF' }} title='blueprint' />
case EBlueprint.PACKAGE:
Expand Down Expand Up @@ -224,8 +225,8 @@ export const TreeView = (props: {
includeTypes?: string[] // Types to include in the tree (excludes the 'ignoredTypes' option)
}) => {
const { nodes, onSelect, NodeWrapper, ignoredTypes, includeTypes } = props

if (includeTypes && includeTypes.length) {
includeTypes?.push('error') // Never hide error nodes
return (
<StyledUl>
{nodes
Expand Down
64 changes: 50 additions & 14 deletions packages/dm-core/src/utils/addressUtilities.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { resolveRelativeAddress, splitAddress } from './addressUtilities'
import {
resolveRelativeAddress,
resolveRelativeAddressSimplified,
splitAddress,
} from './addressUtilities'

test('split dmss://test_source/complex/myCarRental.cars[0].engine', async () => {
const { protocol, dataSource, documentPath, attributePath } = splitAddress(
Expand Down Expand Up @@ -58,27 +62,35 @@ test('split test_source/$1', async () => {
})

test('resolve address with ^', () => {
const resolved = resolveRelativeAddress('^.cars[0]', '$1', 'test_source')
expect(resolved).toBe('/test_source/$1.cars[0]')
const resolved = resolveRelativeAddressSimplified(
'^.cars[0]',
'dmss://test_source/$1'
)
expect(resolved).toBe('dmss://test_source/$1.cars[0]')
})

test('resolve address with multiple attributes', () => {
const resolved = resolveRelativeAddress(
const resolved = resolveRelativeAddressSimplified(
'$2.cars[0].engine',
'$1',
'test_source'
'dmss://test_source/$1'
)
expect(resolved).toBe('/test_source/$2.cars[0].engine')
expect(resolved).toBe('dmss://test_source/$2.cars[0].engine')
})

test('resolve address with no attributes', () => {
const resolved = resolveRelativeAddress('$2', '$1', 'test_source')
expect(resolved).toBe('/test_source/$2')
const resolved = resolveRelativeAddressSimplified(
'$2',
'dmss://test_source/$1'
)
expect(resolved).toBe('dmss://test_source/$2')
})

test('resolve address with id', () => {
const resolved = resolveRelativeAddress('$2.cars[0]', '$1', 'test_source')
expect(resolved).toBe('/test_source/$2.cars[0]')
const resolved = resolveRelativeAddressSimplified(
'$2.cars[0]',
'dmss://test_source/$1'
)
expect(resolved).toBe('dmss://test_source/$2.cars[0]')
})

test('resolve address with slash and id', () => {
Expand Down Expand Up @@ -107,10 +119,34 @@ test('resolve address with package path', () => {
})

test('resolve address with data source', () => {
const resolved = resolveRelativeAddress(
const resolved = resolveRelativeAddressSimplified(
'dmss://other_source/$2.cars[0]',
'$1',
'test_source'
'whatever'
)
expect(resolved).toBe('dmss://other_source/$2.cars[0]')
})

test('resolve relative to parent address should raise error', () => {
function badReference() {
resolveRelativeAddressSimplified('~.cars[0]', 'dmss://test_source/$2')
}
expect(badReference).toThrowError(
'Invalid relative reference. Cannot traverse out of uncontained parent'
)
})

test('resolve relative to parent address', () => {
const resolved = resolveRelativeAddressSimplified(
'~.cars[0]',
'dmss://test_source/$2.bravo'
)
expect(resolved).toBe('dmss://test_source/$2.cars[0]')
})

test('resolve relative to parent address again', () => {
const resolved = resolveRelativeAddressSimplified(
'~.~.~.car_s[0]',
'dmss://test_source/$2.t_y[2].char-lie'
)
expect(resolved).toBe('dmss://test_source/$2.car_s[0]')
})
87 changes: 69 additions & 18 deletions packages/dm-core/src/utils/addressUtilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { splitString } from './stringUtilities'
*
* @param address An absolute address. Valid formats include (both DOCUMENT_PATH and ATTRIBUTE_PATH is optional):
* PROTOCOL://DATA_SOURCE/DOCUMENT_PATH.ATTRIBUTE_PATH
* /DATA_SOURCE/DOCUMENT_PATH.ATTRIBUTE_PATH
* DATA_SOURCE/DOCUMENT_PATH.ATTRIBUTE_PATH
*/
export const splitAddress = (address: string) => {
Expand All @@ -24,43 +23,95 @@ export const splitAddress = (address: string) => {

/**
*
* @param address A relative address. Valid formats include (ATTRIBUTE_PATH is optional):
* @param relativeAddress A relative address. Valid formats include (ATTRIBUTE_PATH is optional):
* PROTOCOL://DATA_SOURCE/DOCUMENT_PATH.ATTRIBUTE_PATH
* /DOCUMENT_PATH.ATTRIBUTE_PATH
* DOCUMENT_PATH.ATTRIBUTE_PATH
* ^.ATTRIBUTE_PATH
* @param fallbackDocumentPath
* @param knownPrefix
* @param dataSource
* @returns
*/
export const resolveRelativeAddress = (
address: string,
fallbackDocumentPath: string,
relativeAddress: string,
knownPrefix: string,
dataSource: string,
relative_path?: string[]
) => {
address = address.replace(/^[/. ]+|[/. ]+$/g, '')
if (address.includes('://')) return address
const [documentPath, attributePath] = address.includes('.')
? splitString(address, '.', 1)
: [address, '']
relativeAddress = relativeAddress.replace(/^[/. ]+|[/. ]+$/g, '')
if (relativeAddress.includes('://')) return relativeAddress
const [documentPath, attributePath] = relativeAddress.includes('.')
? splitString(relativeAddress, '.', 1)
: [relativeAddress, '']

if (documentPath === '~') {
if (!relative_path)
throw 'Missing relative path to be able to resolve the address'
let go_up = address.split('~').length - 1
let go_up = relativeAddress.split('~').length - 1
const path = Array.from(relative_path)
while (go_up !== 0) {
path.pop()
go_up -= 1
}
const rest = address.split('~', -1).slice(-1)
const rest = relativeAddress.split('~', -1).slice(-1)
if (path.length > 0)
return `/${dataSource}/${fallbackDocumentPath}.${path.join('.')}${rest}`
else return `/${dataSource}/${fallbackDocumentPath}${rest}`
return `/${dataSource}/${knownPrefix}.${path.join('.')}${rest}`
else return `/${dataSource}/${knownPrefix}${rest}`
}

return `/${dataSource}/${
documentPath === '^' ? fallbackDocumentPath : documentPath
}${attributePath ? '.' + attributePath : ''}`
return `/${dataSource}/${documentPath === '^' ? knownPrefix : documentPath}${
attributePath ? '.' + attributePath : ''
}`
}

/**
*
* Aim's to have a simpler to use interface than resolveRelativeAddress
*
* @param relativeAddress An address, possibly relative .
* Valid formats include (ATTRIBUTE_PATH is optional):
* PROTOCOL://DATA_SOURCE/DOCUMENT_PATH.ATTRIBUTE_PATH
* $2.ATTRIBUTE_PATH
* ~.~.ATTRIBUTE_PATH
* ^.ATTRIBUTE_PATH
* @param location Where the relative address is being encountered. Must be an absolute address.
* @returns absoluteAddress
*/
export const resolveRelativeAddressSimplified = (
relativeAddress: string,
location: string
): string => {
if (relativeAddress.includes('://')) return relativeAddress // It's absolute

const { dataSource, documentPath, attributePath, protocol } =
splitAddress(location)
relativeAddress = relativeAddress.replace(/^[/. ]+|[/. ]+$/g, '')

if (relativeAddress[0] === '~') {
let go_up = relativeAddress.split('~').length - 1

// Split a string like "$23.car[5].b" into ["$23", "car", "[5]", "b"]
const regex = /(\$[\w]+|[a-zA-Z0-9-_]+|\[\d+\])/g
const pathElements = `${documentPath}.${attributePath}`.match(
regex
) as string[]
while (go_up !== 0) {
pathElements.pop()
go_up -= 1
}
const rest = relativeAddress.split('~', -1).slice(-1)
if (!pathElements.length)
throw 'Invalid relative reference. Cannot traverse out of uncontained parent'

return `${protocol}://${dataSource}/${pathElements.join('.')}${rest}`
}
if (relativeAddress[0] === '$') {
return `${protocol}://${dataSource}/${relativeAddress}`
}
if (relativeAddress[0] === '^') {
const attributes = relativeAddress.slice(1)
return `${protocol}://${dataSource}/${documentPath}${attributes}`
}
return `/${documentPath === '^' ? location : documentPath}${
attributePath ? '.' + attributePath : ''
}`
}

0 comments on commit 9845a44

Please sign in to comment.