Skip to content

Commit c376f42

Browse files
- ADD: Added trait import via BrAPI.
- ADD: Added initial version of trial creation via BrAPI. -
1 parent 01aaecc commit c376f42

26 files changed

+1625
-170
lines changed

src/App.vue

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@
162162
import { gridScoreVersion } from '@/plugins/constants'
163163
import { UAParser } from 'ua-parser-js'
164164
import { getId } from '@/plugins/id'
165-
import { mdiBarcodeScan, mdiChartGantt, mdiChartWaterfall, mdiCheck, mdiCog, mdiDesktopTowerMonitor, mdiDirectionsFork, mdiGradientHorizontal, mdiGrid, mdiHome, mdiMap, mdiNotebook, mdiNotebookPlus, mdiPencilRuler, mdiThemeLightDark, mdiTranslate, mdiWeatherNight, mdiWhiteBalanceSunny } from '@mdi/js'
165+
import { mdiBarcodeScan, mdiChartGantt, mdiChartWaterfall, mdiCheck, mdiCog, mdiDesktopTowerMonitor, mdiDirectionsFork, mdiGradientHorizontal, mdiGrid, mdiHome, mdiMap, mdiNotebook, mdiNotebookPlus, mdiPencilRuler, mdiThemeLightDark, mdiTranslate, mdiWeatherNight, mdiWhiteBalanceSunny } from '@mdi/js'
166166
167167
const { smAndUp, mdAndUp, smAndDown } = useDisplay()
168168
const theme = useTheme()
@@ -455,4 +455,12 @@ ol:not([class]) li:not([class])
455455
.v-navigation-drawer--rail:not(.v-navigation-drawer--is-hovering) .v-list-group__items * {
456456
display: none;
457457
}
458+
459+
code {
460+
color: #e83e8c;
461+
}
462+
463+
.mdi-flip-v {
464+
transform: scaleY(-1);
465+
}
458466
</style>

src/components.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ declare module 'vue' {
1313
AppFooter: typeof import('./components/AppFooter.vue')['default']
1414
ArrowDirectionGrid: typeof import('./components/util/ArrowDirectionGrid.vue')['default']
1515
BaseChart: typeof import('./components/chart/BaseChart.vue')['default']
16+
BrapiConfig: typeof import('./components/util/BrapiConfig.vue')['default']
17+
BrapiStudySelect: typeof import('./components/util/BrapiStudySelect.vue')['default']
1618
ChangelogModal: typeof import('./components/modals/ChangelogModal.vue')['default']
1719
ColumnHeader: typeof import('./components/data/ColumnHeader.vue')['default']
1820
CommentModal: typeof import('./components/modals/CommentModal.vue')['default']
1921
ConfirmModal: typeof import('./components/modals/ConfirmModal.vue')['default']
22+
copy: typeof import('./components/modals/TraitImportFromBrapiModal copy.vue')['default']
2023
CornerPointsMap: typeof import('./components/setup/CornerPointsMap.vue')['default']
2124
DataCanvas: typeof import('./components/data/DataCanvas.vue')['default']
2225
DataEntryActions: typeof import('./components/modals/DataEntryActions.vue')['default']
@@ -33,6 +36,7 @@ declare module 'vue' {
3336
GuideOrderSelector: typeof import('./components/trial/GuideOrderSelector.vue')['default']
3437
HScroll: typeof import('./components/data/HScroll.vue')['default']
3538
JumpToDropdown: typeof import('./components/util/JumpToDropdown.vue')['default']
39+
LayoutDimensions: typeof import('./components/setup/LayoutDimensions.vue')['default']
3640
LayoutMarkers: typeof import('./components/setup/LayoutMarkers.vue')['default']
3741
MapComponent: typeof import('./components/util/MapComponent.vue')['default']
3842
MediaModal: typeof import('./components/modals/MediaModal.vue')['default']
@@ -54,13 +58,16 @@ declare module 'vue' {
5458
TabbedInputModal: typeof import('./components/modals/TabbedInputModal.vue')['default']
5559
TraitDataHistoryModal: typeof import('./components/modals/TraitDataHistoryModal.vue')['default']
5660
TraitDropdown: typeof import('./components/trial/TraitDropdown.vue')['default']
61+
TraitImportFromBrapiModal: typeof import('./components/modals/TraitImportFromBrapiModal.vue')['default']
5762
TraitImportFromTrialModal: typeof import('./components/modals/TraitImportFromTrialModal.vue')['default']
5863
TraitInput: typeof import('./components/inputs/TraitInput.vue')['default']
5964
TraitInputSection: typeof import('./components/trait/TraitInputSection.vue')['default']
6065
TraitSection: typeof import('./components/trait/TraitSection.vue')['default']
6166
TraitSelect: typeof import('./components/trait/TraitSelect.vue')['default']
67+
TraitTreeSelect: typeof import('./components/trait/TraitTreeSelect.vue')['default']
6268
TrialCard: typeof import('./components/trial/TrialCard.vue')['default']
6369
TrialCreation: typeof import('./components/trial/TrialCreation.vue')['default']
70+
TrialCreationFromBrapiModal: typeof import('./components/modals/TrialCreationFromBrapiModal.vue')['default']
6471
TrialDetails: typeof import('./components/setup/TrialDetails.vue')['default']
6572
TrialImport: typeof import('./components/trial/TrialImport.vue')['default']
6673
TrialLayout: typeof import('./components/setup/TrialLayout.vue')['default']

src/components/modals/GenericAddEditFormModal.vue

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -104,14 +104,10 @@
104104
<p v-if="error" class="mt-5 text-error">{{ error }}</p>
105105
</template>
106106

107-
<v-divider />
108-
109-
<v-card-actions class="bg-surface-light">
110-
<v-btn :text="$t('buttonCancel')" variant="plain" @click="dialog = false" />
111-
107+
<v-card-actions>
112108
<v-spacer />
113-
114-
<v-btn :text="$t(compProps.okTitle)" :disabled="!valid" @click="save" />
109+
<v-btn :text="$t('buttonCancel')" variant="plain" @click="dialog = false" />
110+
<v-btn :text="$t(compProps.okTitle)" color="primary" variant="tonal" :disabled="!valid" @click="save" />
115111
</v-card-actions>
116112
</v-card>
117113
</v-dialog>
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
<template>
2+
<v-dialog v-model="dialog" scrollable max-width="min(90vw, 1024px)">
3+
<v-card :title="$t('modalTitleBrapiTraitImport')">
4+
<template #text>
5+
<v-form @submit.prevent>
6+
<BrapiConfig @brapi-config-updated="getTraits" />
7+
</v-form>
8+
9+
<TraitTreeSelect v-model="selectedTraits" :traits="traits" />
10+
</template>
11+
12+
<v-card-actions>
13+
<v-spacer />
14+
<v-btn :text="$t('buttonCancel')" @click="hide" />
15+
<v-btn :text="$t('buttonImport')" @click="save" :disabled="!canContinue" color="primary" variant="tonal" />
16+
</v-card-actions>
17+
</v-card>
18+
</v-dialog>
19+
</template>
20+
21+
<script setup lang="ts">
22+
import { TraitDataType, type Restrictions, type Trait } from '@/plugins/types/gridscore'
23+
import BrapiConfig from '@/components/util/BrapiConfig.vue'
24+
import { brapiGetVariables } from '@/plugins/brapi'
25+
import { isNumber } from '@/plugins/util'
26+
import { getId } from '@/plugins/id'
27+
import TraitTreeSelect from '@/components/trait/TraitTreeSelect.vue'
28+
29+
const dialog = ref(false)
30+
const traits = ref<Trait[]>([])
31+
const selectedTraits = ref<Trait[]>([])
32+
const canContinue = computed(() => (selectedTraits.value || []).length > 0)
33+
34+
const emit = defineEmits(['traits-selected'])
35+
36+
function getTraits () {
37+
brapiGetVariables()
38+
.then(variables => {
39+
traits.value = variables.map(v => {
40+
let type = TraitDataType.text
41+
42+
if (v.scale && v.scale.dataType) {
43+
switch (v.scale.dataType) {
44+
case 'Date':
45+
type = TraitDataType.date
46+
break
47+
case 'Text':
48+
type = TraitDataType.text
49+
break
50+
case 'Numeric':
51+
case 'Numerical':
52+
type = TraitDataType.float
53+
break
54+
case 'Duration':
55+
type = TraitDataType.int
56+
break
57+
case 'Nominal':
58+
case 'Ordinal':
59+
type = TraitDataType.categorical
60+
break
61+
default:
62+
type = TraitDataType.text
63+
break
64+
}
65+
}
66+
67+
const restrictions: Restrictions = {}
68+
// Check if there are any value restrictions on the trait
69+
if (v.scale && v.scale.validValues) {
70+
if (v.scale.validValues.minimumValue !== undefined && v.scale.validValues.minimumValue !== null) {
71+
restrictions.min = +v.scale.validValues.minimumValue
72+
}
73+
if (v.scale.validValues.maximumValue !== undefined && v.scale.validValues.maximumValue !== null) {
74+
restrictions.max = +v.scale.validValues.maximumValue
75+
}
76+
if (v.scale.validValues.categories && v.scale.validValues.categories.length > 0) {
77+
restrictions.categories = v.scale.validValues.categories.map(c => c.label)
78+
}
79+
}
80+
81+
let setSize = 1
82+
if (v.additionalInfo && v.additionalInfo.setSize && isNumber(v.additionalInfo.setSize, true)) {
83+
setSize = +v.additionalInfo.setSize
84+
}
85+
86+
let allowRepeats = false
87+
if (v.additionalInfo && v.additionalInfo.isTimeseries === 'true') {
88+
allowRepeats = true
89+
}
90+
91+
let description = undefined
92+
if (v.trait && v.trait.traitDescription) {
93+
description = v.trait.traitDescription
94+
}
95+
96+
let imageUrl = undefined
97+
98+
if (v.trait && v.trait.externalReferences) {
99+
const possible = v.trait.externalReferences.filter(e => {
100+
if (e.referenceId) {
101+
try {
102+
new URL(e.referenceId)
103+
return true
104+
} catch {
105+
return false
106+
}
107+
} else {
108+
return false
109+
}
110+
}).map(e => e.referenceId)
111+
112+
if (possible.length > 0) {
113+
imageUrl = possible[0]
114+
}
115+
}
116+
117+
return {
118+
id: getId(),
119+
brapiId: v.observationVariableDbId,
120+
name: v.observationVariableName,
121+
dataType: type,
122+
restrictions: Object.keys(restrictions).length === 0 ? undefined : restrictions,
123+
setSize,
124+
allowRepeats,
125+
description,
126+
group: (v.trait && v.trait.traitClass) ? { name: v.trait.traitClass } : undefined,
127+
timeframe: undefined,
128+
imageUrl,
129+
hasImage: imageUrl !== undefined,
130+
}
131+
})
132+
})
133+
}
134+
function show () {
135+
dialog.value = true
136+
}
137+
function hide () {
138+
dialog.value = false
139+
traits.value = []
140+
selectedTraits.value = []
141+
}
142+
function save () {
143+
emit('traits-selected', selectedTraits.value)
144+
145+
hide()
146+
}
147+
148+
defineExpose({
149+
show,
150+
hide,
151+
})
152+
</script>

src/components/modals/TraitImportFromTrialModal.vue

Lines changed: 3 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -15,45 +15,7 @@
1515
v-model="selectedTrial"
1616
/>
1717

18-
<v-treeview
19-
v-model:selected="selectedTraits"
20-
:items="groupedTraits"
21-
:key="`trait-selection-${selectedTrial.localId}`"
22-
item-value="id"
23-
item-title="name"
24-
:open-on-click="false"
25-
select-strategy="classic"
26-
return-object
27-
open-all
28-
selectable
29-
v-if="selectedTrial"
30-
>
31-
<template #toggle="{ props: toggleProps, isOpen, isSelected, isIndeterminate }">
32-
<v-badge
33-
:color="isSelected ? 'success' : 'warning'"
34-
:model-value="isSelected || isIndeterminate"
35-
>
36-
<template #badge>
37-
<v-icon
38-
v-if="isSelected"
39-
icon="$complete"
40-
/>
41-
</template>
42-
<v-btn
43-
v-bind="toggleProps"
44-
:color="isIndeterminate ? 'warning' : isSelected ? 'success' : 'medium-emphasis'"
45-
:variant="isOpen ? 'outlined' : 'tonal'"
46-
/>
47-
</v-badge>
48-
</template>
49-
50-
<template #prepend="{ item }">
51-
<v-icon :icon="mdiCircle" :color="item.color" v-if="!item.children" />
52-
</template>
53-
<template #append="{ item }">
54-
<v-chip label v-if="!item.children" :text="$t(dataTypes.find(dt => dt.value === item.dataType)?.shortTitle || '')" :prepend-icon="dataTypes.find(dt => dt.value === item.dataType)?.icon" />
55-
</template>
56-
</v-treeview>
18+
<TraitTreeSelect v-model="selectedTraits" :traits="selectedTrial.traits" v-if="selectedTrial" />
5719
</v-form>
5820
</template>
5921

@@ -67,19 +29,9 @@
6729
</template>
6830

6931
<script setup lang="ts">
70-
import { dataTypes } from '@/plugins/constants'
71-
import { getId } from '@/plugins/id'
7232
import { getTrials } from '@/plugins/idb'
73-
import type { TraitPlus, TrialPlus } from '@/plugins/types/client'
74-
import { TraitDataType, type Trait } from '@/plugins/types/gridscore'
75-
import { mdiCircle } from '@mdi/js'
76-
import { useI18n } from 'vue-i18n'
77-
78-
interface TraitGroup extends TraitPlus {
79-
children: Trait[]
80-
}
81-
82-
const { t } = useI18n()
33+
import type { TrialPlus } from '@/plugins/types/client'
34+
import type { Trait } from '@/plugins/types/gridscore'
8335
8436
const dialog = ref(false)
8537
const trials = ref<TrialPlus[]>([])
@@ -93,37 +45,6 @@
9345
selectedTraits.value = []
9446
})
9547
96-
const groupedTraits = computed(() => {
97-
const result: { [key: string]: TraitPlus[] } = {}
98-
99-
if (selectedTrial.value) {
100-
selectedTrial.value.traits.forEach(trait => {
101-
const groupName = trait.group?.name || t('dropdownOptionTraitNoGroup')
102-
103-
if (!result[groupName]) {
104-
result[groupName] = []
105-
}
106-
107-
result[groupName].push(trait)
108-
})
109-
}
110-
111-
const groupedTraits: TraitGroup[] = []
112-
113-
Object.keys(result).forEach(k => {
114-
groupedTraits.push({
115-
id: getId(),
116-
name: k,
117-
dataType: TraitDataType.boolean,
118-
setSize: 1,
119-
allowRepeats: true,
120-
children: result[k] || [],
121-
})
122-
})
123-
124-
return groupedTraits
125-
})
126-
12748
function show () {
12849
dialog.value = true
12950
selectedTrial.value = undefined
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<template>
2+
<v-dialog v-model="dialog" scrollable max-width="min(90vw, 1024px)">
3+
<v-card :title="$t('modalTitleBrapiTrialImport')">
4+
<template #text>
5+
<p>{{ $t('modalTextBrapiTrialImport') }}</p>
6+
<v-form @submit.prevent>
7+
<BrapiConfig @brapi-config-updated="getPrograms" />
8+
9+
<BrapiStudySelect
10+
class="mt-3"
11+
ref="brapiStudySelect"
12+
/>
13+
</v-form>
14+
</template>
15+
16+
<v-card-actions>
17+
<v-spacer />
18+
<v-btn :text="$t('buttonCancel')" @click="hide" />
19+
<v-btn :text="$t('buttonImport')" @click="save" :disabled="!canContinue" color="primary" variant="tonal" />
20+
</v-card-actions>
21+
</v-card>
22+
</v-dialog>
23+
</template>
24+
25+
<script setup lang="ts">
26+
import BrapiConfig from '@/components/util/BrapiConfig.vue'
27+
import BrapiStudySelect from '@/components/util/BrapiStudySelect.vue'
28+
29+
const dialog = ref(false)
30+
const canContinue = computed(() => false)
31+
32+
const brapiStudySelect = useTemplateRef('brapiStudySelect')
33+
34+
function getPrograms () {
35+
brapiStudySelect.value?.updatePrograms()
36+
}
37+
38+
function show () {
39+
dialog.value = true
40+
}
41+
function hide () {
42+
dialog.value = false
43+
}
44+
function save () {
45+
hide()
46+
}
47+
48+
defineExpose({
49+
show,
50+
hide,
51+
})
52+
</script>

0 commit comments

Comments
 (0)