Skip to content

Commit

Permalink
- ADD: Added support for remote URL tokens for more security.
Browse files Browse the repository at this point in the history
- CHG: Changes to BrAPI interactions.
-
  • Loading branch information
sebastian-raubach committed Sep 3, 2024
1 parent 0fe8f62 commit 29e4328
Show file tree
Hide file tree
Showing 18 changed files with 280 additions and 67 deletions.
53 changes: 48 additions & 5 deletions src/components/BrapiExportSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
</b-col>
</b-row>

<b-button type="submit" variant="primary" :disabled="!canProceed"><IBiCloudUpload /> {{ $t('buttonSendData') }}</b-button>
<b-button class="mb-3" type="submit" variant="primary" :disabled="!canProceed"><IBiCloudUpload /> {{ $t('buttonSendData') }}</b-button>
</div>
</b-form>
</div>
Expand All @@ -59,7 +59,7 @@
import { mapState, mapStores } from 'pinia'
import { coreStore } from '@/store'
import { brapiGetPrograms, brapiGetTrials, brapiGetStudies, brapiGetStudyTypes, brapiPostGermplasmSearch, brapiPostObservationVariables, brapiPostObservationVariableSearch, brapiDefaultCatchHandler, brapiPostObservationUnits, brapiPostObservations } from '@/plugins/brapi'
import { brapiGetPrograms, brapiGetTrials, brapiGetStudies, brapiGetStudyTypes, brapiPostGermplasmSearch, brapiPostObservationVariables, brapiPostObservationVariableSearch, brapiDefaultCatchHandler, brapiPostObservationUnits, brapiPostObservations, brapiGetObservationUnits } from '@/plugins/brapi'
import { getTrialDataCached } from '@/plugins/datastore'
import { updateGermplasmBrapiIds, updateTraitBrapiIds } from '@/plugins/idb'
Expand Down Expand Up @@ -224,7 +224,7 @@ export default {
case 'float':
case 'int':
case 'range':
scale.dataType = 'Numeric'
scale.dataType = 'Numerical'
break
case 'categorical':
scale.dataType = 'Ordinal'
Expand Down Expand Up @@ -266,6 +266,34 @@ export default {
if (trialData) {
emitter.emit('set-loading', true)
// Generate a mapping based on display row and column for lookup
const displayMapping = {}
for (let y = 0; y < this.trial.layout.rows; y++) {
// And each field column
for (let x = 0; x < this.trial.layout.columns; x++) {
// Get the data cell
const cell = trialData[`${y}|${x}`]
if (cell) {
displayMapping[`${cell.displayRow}|${cell.displayColumn}`] = `${y}|${x}`
}
}
}
// First, search for observation units from this study, save the observationUnitDbId for those that have local matches
brapiGetObservationUnits(this.selectedStudy.studyDbId)
.then(ous => {
if (ous && ous.length > 0) {
ous.forEach(ou => {
const match = displayMapping[`${ou.observationUnitPosition.positionCoordinateY}|${ou.observationUnitPosition.positionCoordinateX}`]
if (match) {
trialData[match].observationUnitDbId = ou.observationUnitDbId
}
})
}
})
// For legacy reasons we now need to send the observations as part of the ObservationUnit, then check if what we get back is what we sent.
// If so, then the Germinate BrAPI backend is the old (wrong) version. However, that's all that's required in that case. If not, we need
// to go on and actually send the observations separately using the observationunits we got back.
Expand Down Expand Up @@ -298,9 +326,9 @@ export default {
entryType: cell.categories && cell.categories.includes(CELL_CATEGORY_CONTROL) ? 'CHECK' : 'TEST',
geoCoordinates: geoCoordinates,
positionCoordinateXType: 'GRID_COL',
positionCoordinateX: cell.displayColumn,
positionCoordinateX: `${cell.displayColumn}`,
positionCoordinateYType: 'GRID_ROW',
positionCoordinateY: cell.displayRow,
positionCoordinateY: `${cell.displayRow}`,
observationLevel: (cell.rep !== undefined && cell.rep !== null)
? {
levelName: 'rep',
Expand Down Expand Up @@ -365,6 +393,19 @@ export default {
const data = []
const observationUnitMap = {}
// Get the locally know ids first from the GET observationunits call
for (let y = 0; y < this.trial.layout.rows; y++) {
// And each field column
for (let x = 0; x < this.trial.layout.columns; x++) {
// Get the data cell
const cell = trialData[`${y}|${x}`]
if (cell.observationUnitDbId) {
observationUnitMap[`${cell.displayRow}|${cell.displayColumn}`] = cell.observationUnitDbId
}
}
}
// Then write the remote ones as well
observationUnits.forEach(ou => {
observationUnitMap[`${ou.observationUnitPosition.positionCoordinateY}|${ou.observationUnitPosition.positionCoordinateX}`] = ou.observationUnitDbId
})
Expand Down Expand Up @@ -455,11 +496,13 @@ export default {
matches = t.dataType === 'text'
break
case 'Numeric':
case 'Numerical':
matches = t.dataType === 'float' || t.dataType === 'int' || t.dataType === 'range'
break
case 'Duration':
matches = t.dataType === 'int'
break
case 'Nominal':
case 'Ordinal':
matches = t.dataType === 'categorical'
break
Expand Down
32 changes: 28 additions & 4 deletions src/components/BrapiTrialImportSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,15 @@

<TrialLayoutDimensionComponent class="mt-3" :editLabelsAllowed="false" :editValuesAllowed="false" :layout="layout" @layout-changed="updateLayout" ref="trialDimensionComponent" />

<b-button @click="importData" variant="primary" :disabled="!canProceed"><IBiCloudDownload /> {{ $t('buttonImportData') }}</b-button>
<div v-if="errors && errors.length > 0" class="my-3">
<h3>{{ $t('widgetBrapiTrialImportErrorsTitle') }}</h3>

<b-list-group class="import-errors">
<b-list-group-item variant="danger" v-for="(error, index) in errors" :key="`import-error-${index}`">{{ error }}</b-list-group-item>
</b-list-group>
</div>

<b-button @click="importData" variant="primary" :disabled="!canImport"><IBiCloudDownload /> {{ $t('buttonImportData') }}</b-button>
</template>
</b-form>
</b-container>
Expand Down Expand Up @@ -80,7 +88,8 @@ export default {
columnLabels: [],
columnOrder: DISPLAY_ORDER_LEFT_TO_RIGHT,
rowOrder: DISPLAY_ORDER_TOP_TO_BOTTOM
}
},
errors: []
}
},
watch: {
Expand Down Expand Up @@ -108,6 +117,9 @@ export default {
canProceed: function () {
return this.selectedProgram && this.selectedTrial && this.selectedStudyType && this.selectedStudy
},
canImport: function () {
return this.canProceed && this.errors.length < 1 && this.layoutValid
},
programOptions: function () {
if (this.programs) {
return this.programs.map(t => {
Expand Down Expand Up @@ -162,6 +174,7 @@ export default {
this.layout = JSON.parse(JSON.stringify(layout))
},
importData: function () {
this.errors = []
const plotMapping = {}
this.cells.forEach(c => {
Expand Down Expand Up @@ -206,16 +219,24 @@ export default {
if (ou.observationUnitPosition.positionCoordinateXType === 'GRID_COL') {
columnIndex = +ou.observationUnitPosition.positionCoordinateX
columns.add(columnIndex)
} else {
this.errors.push(this.$t('widgetBrapiTrialImportErrorNoColumnInformation', { germplasm: ou.germplasmName || ou.observationUnitDbId }))
}
if (ou.observationUnitPosition.positionCoordinateYType === 'GRID_ROW') {
rowIndex = +ou.observationUnitPosition.positionCoordinateY
rows.add(rowIndex)
} else {
this.errors.push(this.$t('widgetBrapiTrialImportErrorNoRowInformation', { germplasm: ou.germplasmName || ou.observationUnitDbId }))
}
if (ou.germplasmName) {
germplasmIdentifier = ou.germplasmName
} else {
this.errors.push(this.$t('widgetBrapiTrialImportErrorNoGermplasmName', { germplasm: ou.observationUnitDbId }))
}
if (ou.germplasmDbId) {
brapiId = ou.germplasmDbId
} else {
this.errors.push(this.$t('widgetBrapiTrialImportErrorNoGermplasmDbId', { germplasm: ou.observationUnitDbId }))
}
if (ou.observationUnitPosition.observationLevel) {
if (ou.observationUnitPosition.observationLevel.levelName === 'rep') {
Expand Down Expand Up @@ -342,6 +363,9 @@ export default {
}
</script>

<style>
<style scoped>
.import-errors {
max-height: min(300px, 50vh);
overflow-y: auto;
}
</style>
4 changes: 3 additions & 1 deletion src/components/TraitDefinitionComponent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -372,11 +372,13 @@ export default {
type = 'text'
break
case 'Numeric':
case 'Numerical':
type = 'float'
break
case 'Duration':
type = 'int'
break
case 'Nominal':
case 'Ordinal':
type = 'categorical'
break
Expand All @@ -395,7 +397,7 @@ export default {
restrictions.max = +t.scale.validValues.maximumValue
}
if (t.scale.validValues.categories && t.scale.validValues.categories.length > 0) {
restrictions.categories = t.scale.validValues.categories.map(c => c.value)
restrictions.categories = t.scale.validValues.categories.map(c => c.label)
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/components/TrialCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
</template>
</a>
<TrialInformation :trial="trial" @on-share-clicked="$emit('showShareCodes')" />
<b-button v-if="selectable" :variant="selectedToEdit ? 'info' : 'secondary'" @click="selectedToEdit = !selectedToEdit">
<IBiCheckSquare v-if="selectedToEdit" /><IBiSquare v-else /> {{ selectedToEdit ? $t('buttonDeselect') : $t('buttonSelect') }}
</b-button>
<b-button v-if="trial.remoteUrl" class="button-disabled" variant="secondary" v-b-tooltip.hover="trial.remoteUrl">
<IBiCloudPlusFill /> {{ $t('buttonTrialRemoteUrl') }}
</b-button>
<b-button v-if="selectable" :variant="selectedToEdit ? 'info' : 'secondary'" @click="selectedToEdit = !selectedToEdit">
<IBiCheckSquare v-if="selectedToEdit" /><IBiSquare v-else /> {{ selectedToEdit ? $t('buttonDeselect') : $t('buttonSelect') }}
</b-button>
<b-button @click="$emit('handleTrialExpiration')" v-if="trial.showExpiryWarning === true" variant="danger" v-b-tooltip.hover="$t('tooltipTrialSelectorTrialExpiryWarning', { date: new Date(trial.expiresOn).toLocaleDateString() })">
<IBiCalendarXFill />
</b-button>
Expand Down
11 changes: 6 additions & 5 deletions src/components/modals/BrapiTraitImportModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,13 @@ export default {
case 'Text':
type = this.$t('traitTypeShortText')
break
case 'Numeric':
case 'Numerical':
type = this.$t('traitTypeShortFloat')
break
case 'Duration':
type = this.$t('traitTypeShortInt')
break
case 'Nominal':
case 'Ordinal':
type = this.$t('traitTypeShortCategorical')
break
Expand All @@ -112,18 +113,18 @@ export default {
}
if (type && type.length > 0) {
type = `<span class="badge badge-primary ms-2">${type}</span>`
type = `<span class="badge text-bg-primary ms-2">${type}</span>`
}
if (t.scale.validValues) {
if (t.scale.validValues.minimumValue !== undefined && t.scale.validValues.minimumValue !== null) {
restrictions += `<span class="badge badge-secondary ms-2">&ge;${t.scale.validValues.minimumValue}</span>`
restrictions += `<span class="badge text-bg-secondary ms-2">&ge;${t.scale.validValues.minimumValue}</span>`
}
if (t.scale.validValues.maximumValue !== undefined && t.scale.validValues.maximumValue !== null) {
restrictions += `<span class="badge badge-secondary ms-2">&le;${t.scale.validValues.maximumValue}</span>`
restrictions += `<span class="badge text-bg-secondary ms-2">&le;${t.scale.validValues.maximumValue}</span>`
}
if (t.scale.validValues.categories) {
restrictions += `<span class="badge badge-secondary ms-2">${t.scale.validValues.categories.map(tr => tr.value).join(', ')}</span>`
restrictions += `<span class="badge text-bg-secondary ms-2">${t.scale.validValues.categories.map(tr => tr.label).join(', ')}</span>`
}
}
}
Expand Down
11 changes: 10 additions & 1 deletion src/components/modals/MissingTrialModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,16 @@ export default {
},
methods: {
checkArchiveExists: function () {
checkTrialArchiveExists((this.trial && this.trial.remoteUrl) ? this.trial.remoteUrl : null, this.shareCode)
let remoteConfig = null
if (this.trial && this.trial.remoteUrl) {
remoteConfig = {
url: this.trial.remoteUrl,
token: this.trial.remoteToken || null
}
}
checkTrialArchiveExists(remoteConfig, this.shareCode)
.then(result => {
this.archiveExists = true
this.archiveInformation = result
Expand Down
12 changes: 11 additions & 1 deletion src/components/modals/TrialExpirationModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,17 @@ export default {
},
sendCaptcha: function () {
emitter.emit('show-loading', true)
extendTrialPeriod((this.trial && this.trial.remoteUrl) ? this.trial.remoteUrl : null, this.shareCode, { captcha: this.captcha })
let remoteConfig = null
if (this.trial && this.trial.remoteUrl) {
remoteConfig = {
url: this.trial.remoteUrl,
token: this.trial.remoteToken || null
}
}
extendTrialPeriod(remoteConfig, this.shareCode, { captcha: this.captcha })
.then(() => {
this.hide()
emitter.emit('trials-updated')
Expand Down
12 changes: 10 additions & 2 deletions src/components/modals/TrialShareCodeModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@
<span v-html="$t('formDescriptionTrialShareRemoteUrl')" />
</template>
</b-form-group>

<b-form-group v-if="shareWithRemote" :label="$t('formLabelTrialShareRemoteToken')" label-for="remoteToken">
<b-form-input v-model="remoteToken" id="remoteToken" />
<template #description>
<span v-html="$t('formDescriptionTrialShareRemoteToken')" />
</template>
</b-form-group>
</div>

<b-button @click="getShareCodes" :disabled="isOnline === false || buttonDisabled === true" variant="primary"><IBiQrCodeScan /> {{ $t('buttonGenerateShareCodes') }}</b-button>
Expand Down Expand Up @@ -83,7 +90,8 @@ export default {
TRIAL_STATE_OWNER,
TRIAL_STATE_VIEWER,
shareWithRemote: false,
remoteUrl: null
remoteUrl: null,
remoteToken: null
}
},
computed: {
Expand All @@ -100,7 +108,7 @@ export default {
methods: {
getShareCodes: function () {
emitter.emit('show-loading', true)
shareTrial(this.shareWithRemote ? this.remoteUrl : null, this.trial.localId)
shareTrial({ url: this.shareWithRemote ? this.remoteUrl : null, token: this.remoteToken }, this.trial.localId)
.then(() => emitter.emit('plausible-event', { key: 'trial-shared' }))
.catch(error => {
console.error(error)
Expand Down
24 changes: 22 additions & 2 deletions src/components/modals/TrialSynchronizationModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,17 @@ export default {
askToSynchronize: function () {
if (!this.trial.shareCodes.ownerCode && !this.trial.shareCodes.editorCode) {
emitter.emit('show-loading', true)
getTrialByCode((this.trial && this.trial.remoteUrl) ? this.trial.remoteUrl : null, this.trial.shareCodes.viewerCode)
let remoteConfig = null
if (this.trial && this.trial.remoteUrl) {
remoteConfig = {
url: this.trial.remoteUrl,
token: this.trial.remoteToken || null
}
}
getTrialByCode(remoteConfig, this.trial.shareCodes.viewerCode)
.then(result => {
return deleteTrial(this.trial.localId)
.then(() => {
Expand Down Expand Up @@ -270,7 +280,17 @@ export default {
},
synchronize: function () {
emitter.emit('show-loading', true)
synchronizeTrial((this.trial && this.trial.remoteUrl) ? this.trial.remoteUrl : null, this.trial.shareCodes.ownerCode || this.trial.shareCodes.editorCode, this.transaction)
let remoteConfig = null
if (this.trial && this.trial.remoteUrl) {
remoteConfig = {
url: this.trial.remoteUrl,
token: this.trial.remoteToken || null
}
}
synchronizeTrial(remoteConfig, this.trial.shareCodes.ownerCode || this.trial.shareCodes.editorCode, this.transaction)
.then(result => {
if (this.trial.group && this.trial.group.name) {
result.group = {
Expand Down
Loading

0 comments on commit 29e4328

Please sign in to comment.