|
| 1 | +/* |
| 2 | + * Copyright (c) 2025 The Ontario Institute for Cancer Research. All rights reserved |
| 3 | + * |
| 4 | + * This program and the accompanying materials are made available under the terms of |
| 5 | + * the GNU Affero General Public License v3.0. You should have received a copy of the |
| 6 | + * GNU Affero General Public License along with this program. |
| 7 | + * If not, see <http://www.gnu.org/licenses/>. |
| 8 | + * |
| 9 | + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY |
| 10 | + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| 11 | + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT |
| 12 | + * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, |
| 13 | + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED |
| 14 | + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; |
| 15 | + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER |
| 16 | + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN |
| 17 | + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 18 | + */ |
| 19 | +import { |
| 20 | + RestrictionCondition, |
| 21 | + RestrictionRange, |
| 22 | + SchemaField, |
| 23 | + SchemaRestrictions, |
| 24 | +} from '@overture-stack/lectern-dictionary'; |
| 25 | +import { CellContext } from '@tanstack/react-table'; |
| 26 | + |
| 27 | +export type RestrictionItem = { |
| 28 | + prefix: string[]; |
| 29 | + content: string[]; |
| 30 | +}; |
| 31 | + |
| 32 | +export type RestrictionField = RestrictionItem | undefined; |
| 33 | + |
| 34 | +export type AllowedValuesBaseDisplayItem = { |
| 35 | + dependsOn?: RestrictionItem; |
| 36 | + regularExpression?: RestrictionItem; |
| 37 | + codeList?: RestrictionItem; |
| 38 | + range?: RestrictionItem; |
| 39 | + codeListWithCountRestrictions?: RestrictionItem; |
| 40 | + unique?: RestrictionItem; |
| 41 | +}; |
| 42 | + |
| 43 | +export type AllowedValuesColumnProps = { |
| 44 | + restrictions: CellContext<SchemaField, SchemaRestrictions>; |
| 45 | +}; |
| 46 | + |
| 47 | +//Helper functions |
| 48 | +const handleRange = (range: RestrictionRange): RestrictionItem | undefined => { |
| 49 | + const computeRestrictions = [ |
| 50 | + { |
| 51 | + condition: range.min !== undefined && range.max !== undefined, |
| 52 | + prefix: ['Min:', 'Max:'], |
| 53 | + content: [`${range.min}`, `${range.max}`], |
| 54 | + }, |
| 55 | + { |
| 56 | + condition: range.min !== undefined, |
| 57 | + prefix: 'Min:', |
| 58 | + content: `${range.min}`, |
| 59 | + }, |
| 60 | + { |
| 61 | + condition: range.max !== undefined, |
| 62 | + prefix: 'Max:', |
| 63 | + content: `${range.max}`, |
| 64 | + }, |
| 65 | + { |
| 66 | + condition: range.exclusiveMin !== undefined, |
| 67 | + prefix: 'Greater than:', |
| 68 | + content: `${range.exclusiveMin}`, |
| 69 | + }, |
| 70 | + { |
| 71 | + condition: range.exclusiveMax !== undefined, |
| 72 | + prefix: 'Less than:', |
| 73 | + content: `${range.exclusiveMax}`, |
| 74 | + }, |
| 75 | + ]; |
| 76 | + |
| 77 | + const computedRestrictionItem = computeRestrictions.find((item) => item.condition); |
| 78 | + |
| 79 | + return computedRestrictionItem ? |
| 80 | + { |
| 81 | + prefix: |
| 82 | + Array.isArray(computedRestrictionItem.prefix) ? |
| 83 | + [...computedRestrictionItem.prefix] |
| 84 | + : [computedRestrictionItem.prefix], |
| 85 | + content: |
| 86 | + Array.isArray(computedRestrictionItem.content) ? |
| 87 | + [...computedRestrictionItem.content] |
| 88 | + : [computedRestrictionItem.content], |
| 89 | + } |
| 90 | + : { |
| 91 | + prefix: [], |
| 92 | + content: [], |
| 93 | + }; |
| 94 | +}; |
| 95 | + |
| 96 | +const handleCodeListsWithCountRestrictions = ( |
| 97 | + codeList: string | string[] | number[], |
| 98 | + count: RestrictionRange, |
| 99 | + schemaField: SchemaField, |
| 100 | +): RestrictionItem | undefined => { |
| 101 | + const { isArray, delimiter } = schemaField; |
| 102 | + const delimiterText = isArray ? `, delimited by "${delimiter}"` : ''; |
| 103 | + |
| 104 | + const computeRestrictions = [ |
| 105 | + { |
| 106 | + condition: typeof count == 'number', |
| 107 | + prefix: `Exactly ${count}${delimiterText} from:`, |
| 108 | + }, |
| 109 | + { |
| 110 | + condition: count.min !== undefined && count.max !== undefined, |
| 111 | + prefix: `Select ${count.min} to ${count.max}${delimiterText} from:`, |
| 112 | + }, |
| 113 | + { |
| 114 | + condition: count.min !== undefined, |
| 115 | + prefix: `At least ${count.min}${delimiterText} from:`, |
| 116 | + }, |
| 117 | + { |
| 118 | + condition: count.max !== undefined, |
| 119 | + prefix: `Up to ${count.max}${delimiterText} from:`, |
| 120 | + }, |
| 121 | + { |
| 122 | + condition: count.exclusiveMin !== undefined, |
| 123 | + prefix: `More than ${count.exclusiveMin}${delimiterText} from:`, |
| 124 | + }, |
| 125 | + { |
| 126 | + condition: count.exclusiveMax !== undefined, |
| 127 | + prefix: `Fewer than ${count.exclusiveMax}${delimiterText} from:`, |
| 128 | + }, |
| 129 | + ]; |
| 130 | + |
| 131 | + const computedRestrictionItem = computeRestrictions.find((item) => item.condition); |
| 132 | + return computedRestrictionItem ? |
| 133 | + { |
| 134 | + prefix: [computedRestrictionItem.prefix], |
| 135 | + content: Array.isArray(codeList) ? codeList.map((item: string | number) => `${item}`) : [codeList], |
| 136 | + } |
| 137 | + : { |
| 138 | + prefix: [], |
| 139 | + content: [], |
| 140 | + }; |
| 141 | +}; |
| 142 | + |
| 143 | +const handleDependsOn = (conditions: RestrictionCondition[]): RestrictionItem | undefined => { |
| 144 | + const allFields: string[] = conditions.flatMap((condition: RestrictionCondition) => { |
| 145 | + return condition.fields; |
| 146 | + }); |
| 147 | + |
| 148 | + return allFields.length > 0 ? |
| 149 | + { |
| 150 | + prefix: ['Depends on:'], |
| 151 | + content: [...new Set(allFields)], |
| 152 | + } |
| 153 | + : { |
| 154 | + prefix: [], |
| 155 | + content: [], |
| 156 | + }; |
| 157 | +}; |
| 158 | + |
| 159 | +const handleRegularExpression = (regularExpression: string[] | string) => { |
| 160 | + const patterns = Array.isArray(regularExpression) ? regularExpression : [regularExpression]; |
| 161 | + |
| 162 | + return { |
| 163 | + prefix: Array.isArray(regularExpression) ? ['Must match patterns:'] : ['Must match pattern:'], |
| 164 | + content: patterns, |
| 165 | + }; |
| 166 | +}; |
| 167 | + |
| 168 | +const handleCodeList = (codeList: string | string[] | number[]): RestrictionItem => { |
| 169 | + return { |
| 170 | + prefix: ['One of:'], |
| 171 | + content: Array.isArray(codeList) ? codeList.map((item: string | number) => `${item}`) : [codeList], |
| 172 | + }; |
| 173 | +}; |
| 174 | + |
| 175 | +export const computeAllowedValuesColumn = ( |
| 176 | + restrictions: SchemaRestrictions, |
| 177 | + schemaField: SchemaField, |
| 178 | +): AllowedValuesBaseDisplayItem => { |
| 179 | + const allowedValuesBaseDisplayItem: AllowedValuesBaseDisplayItem = {}; |
| 180 | + |
| 181 | + if (!restrictions || Object.keys(restrictions).length === 0) { |
| 182 | + return {}; |
| 183 | + } |
| 184 | + |
| 185 | + if ('if' in restrictions && restrictions.if !== undefined && restrictions.if.conditions !== undefined) { |
| 186 | + allowedValuesBaseDisplayItem.dependsOn = handleDependsOn(restrictions.if.conditions); |
| 187 | + } |
| 188 | + |
| 189 | + if ('regex' in restrictions && restrictions.regex !== undefined) { |
| 190 | + allowedValuesBaseDisplayItem.regularExpression = handleRegularExpression(restrictions.regex); |
| 191 | + } |
| 192 | + |
| 193 | + if ('codeList' in restrictions && restrictions.codeList !== undefined && !('count' in restrictions)) { |
| 194 | + allowedValuesBaseDisplayItem.codeList = handleCodeList(restrictions.codeList); |
| 195 | + } |
| 196 | + |
| 197 | + if ('range' in restrictions && restrictions.range !== undefined) { |
| 198 | + allowedValuesBaseDisplayItem.range = handleRange(restrictions.range); |
| 199 | + } |
| 200 | + |
| 201 | + if ( |
| 202 | + 'codeList' in restrictions && |
| 203 | + restrictions.codeList !== undefined && |
| 204 | + 'count' in restrictions && |
| 205 | + restrictions.count != undefined && |
| 206 | + schemaField.isArray !== undefined |
| 207 | + ) { |
| 208 | + allowedValuesBaseDisplayItem.codeListWithCountRestrictions = handleCodeListsWithCountRestrictions( |
| 209 | + restrictions.codeList, |
| 210 | + restrictions.count, |
| 211 | + schemaField, |
| 212 | + ); |
| 213 | + } |
| 214 | + |
| 215 | + if (schemaField.unique) { |
| 216 | + allowedValuesBaseDisplayItem.unique = { prefix: [], content: ['Must be unique'] }; |
| 217 | + } |
| 218 | + |
| 219 | + return allowedValuesBaseDisplayItem; |
| 220 | +}; |
0 commit comments