Skip to content

Commit

Permalink
add contact field list support (#1789, #2033)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikkonie committed Oct 31, 2024
1 parent 0b51435 commit d03e6d5
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 17 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Added
- Study plugin override via ISA-Tab comments (#1885)
- Token auth support in study plugin IGV XML serving views (#1999, #2021)
- Support for newlines in altamISA error messages (#2033)
- Support for performer and contact field values as list (#1789, #2033)
- **Taskflowbackend**
- ``TaskflowAPI.raise_submit_api_exception()`` helper (#1847)

Expand Down Expand Up @@ -65,7 +66,7 @@ Changed
- Refactor ``SampleSheetAssayPluginPoint.get_assay_path()`` (#2016)
- Return ``503`` in ``IrodsCollsCreateAPIView`` if project is locked (#1847)
- Return ``503`` in ``IrodsDataRequestAcceptAPIView`` if project is locked (#1847)
- Remove length limitation from ``Process.performer`` (#1789, #1942, #2033)
- Remove length limit from ``Process.performer`` (#1789, #1942, #2033)
- **Taskflowbackend**
- Refactor task tests (#2002)
- Unify user name parameter naming in flows (#1653)
Expand Down
4 changes: 3 additions & 1 deletion docs_manual/source/app_samplesheets_edit.rst
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ Node Names
orphaned files.
Contacts
Contact cells act as string cells with the following expected syntax:
``Contact Name <[email protected]>``. The email can be omitted.
``Contact Name <[email protected]>``. The email can be omitted. Multiple
contacts can be provided using the semicolon character as a delimter. For
example: ``Contact1 <c1.example.com>;Contact1 <c2.example.com>``.
Dates
Date cells also provide standard string editing but enforce the ISO 8601
``YYYY-MM-DD`` syntax.
Expand Down
1 change: 1 addition & 0 deletions docs_manual/source/sodar_release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Release for SODAR Core v1.0 upgrade, iRODS v4.3 upgrade and feature updates.
- Add study plugin override via ISA-Tab comments
- Add session control in Django settings and environment variables
- Add token-based iRODS/IGV basic auth support for OIDC users
- Add support for performer and contact field values as list
- Update minimum supported iRODS version to v4.3.3
- Update REST API versioning
- Update REST API views for OpenAPI support
Expand Down
18 changes: 12 additions & 6 deletions samplesheets/rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -527,16 +527,22 @@ def _is_num(value):

col_type = self._field_header[i]['col_type']
if col_type == 'CONTACT':
contact_vals = []
for x in self._table_data:
if not x[i].get('value'):
contact_vals.append('')
elif isinstance(x[i]['value'], list):
contact_vals.append('; '.join(x[i]['value']))
else:
contact_vals.append(x[i]['value'])
max_cell_len = max(
[
(
_get_length(
re.findall(link_re, x[i]['value'])[0][0]
)
if re.findall(link_re, x[i].get('value'))
else len(x[i].get('value') or '')
_get_length(re.findall(link_re, x)[0][0])
if re.findall(link_re, x)
else len(x or '')
)
for x in self._table_data
for x in contact_vals
]
)
elif col_type == 'EXTERNAL_LINKS': # Special case, count elements
Expand Down
29 changes: 29 additions & 0 deletions samplesheets/tests/test_views_ajax.py
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,35 @@ def test_post_performer(self):
obj.refresh_from_db()
self.assertEqual(obj.performer, value)

def test_post_performer_list(self):
"""Test POST with process performer and list value"""
obj = Process.objects.filter(study=self.study, assay=None).first()
value = [
'Alice Example <[email protected]>',
'Bob Example <[email protected]>',
]
self.values['updated_cells'].append(
{
'uuid': str(obj.sodar_uuid),
'header_name': 'Performer',
'header_type': 'performer',
'obj_cls': 'Process',
'value': value,
}
)
with self.login(self.user):
response = self.client.post(
reverse(
'samplesheets:ajax_edit_cell',
kwargs={'project': self.project.sodar_uuid},
),
json.dumps(self.values),
content_type='application/json',
)
self.assertEqual(response.status_code, 200)
obj.refresh_from_db()
self.assertEqual(obj.performer, ';'.join(value))

def test_post_perform_date(self):
"""Test POST with process perform date"""
obj = Process.objects.filter(study=self.study, assay=None).first()
Expand Down
5 changes: 4 additions & 1 deletion samplesheets/views_ajax.py
Original file line number Diff line number Diff line change
Expand Up @@ -913,7 +913,10 @@ def _update_cell(self, node_obj, cell, save=False):

# Performer (special case)
elif header_type == 'performer':
node_obj.performer = cell['value']
if isinstance(cell['value'], list):
node_obj.performer = ';'.join(cell['value'])
else:
node_obj.performer = cell['value']

# Perform date (special case)
elif header_type == 'perform_date':
Expand Down
37 changes: 29 additions & 8 deletions samplesheets/vueapp/src/components/renderers/DataCellRenderer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,23 @@
<span v-if="!params.app.editMode">
<a :href="term.accession"
:title="term.ontology_name"
target="_blank">{{ term.name }}</a><span v-if="termIndex + 1 < value.value.length">; </span>
target="_blank">{{ term.name }}</a><span v-if="termIndex < value.value.length - 1">; </span>
</span>
<span v-else>
{{ term.name }}<span v-if="termIndex + 1 < value.value.length">; </span>
{{ term.name }}<span v-if="termIndex < value.value.length - 1">; </span>
</span>
</span>
</span>
<!-- Contacts with email -->
<span v-else-if="colType === 'CONTACT' && renderData">
<a :href="'mailto:' + renderData.email">{{ renderData.name }}</a>
<span v-for="(contact, index) in renderData" :key="index">
<span v-if="contact.email !== null">
<a :href="'mailto:' + contact.email">{{ contact.name }}</a><span v-if="index < renderData.length - 1">; </span>
</span>
<span v-else>
{{ contact.name }}<span v-if="index < renderData.length - 1">; </span>
</span>
</span>
</span>
<!-- External links -->
<span v-else-if="colType === 'EXTERNAL_LINKS' && renderData">
Expand Down Expand Up @@ -121,11 +128,25 @@ export default Vue.extend({
return this.params.colDef.headerName.toLowerCase()
},
getContact () {
// Return contact name and email
if (contactRegex.test(this.value.value) === true) {
const contactGroup = contactRegex.exec(this.value.value)
return { name: contactGroup[1], email: contactGroup[2] }
} else this.colType = null // Fall back to standard field
// Return contact name(s) and email(s)
const ret = []
if (this.value.value) {
// console.debug('value type = ' + typeof this.value.value)
let splitVal
if (typeof this.value.value === 'string') {
splitVal = this.value.value.split(';')
} else {
splitVal = this.value.value
}
for (let i = 0; i < splitVal.length; i++) {
if (contactRegex.test(splitVal[i]) === true) {
const contactGroup = contactRegex.exec(splitVal[i])
ret.push({ name: contactGroup[1].trim(), email: contactGroup[2] })
} else ret.push({ name: splitVal[i].trim(), email: null })
}
}
if (ret.length === 0) this.colType = null // Fall back to standard field
return ret
},
getExternalLinks () {
// Return external links
Expand Down
19 changes: 19 additions & 0 deletions samplesheets/vueapp/tests/unit/DataCellRenderer.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,25 @@ describe('DataCellRenderer.vue', () => {
expect(cell.classes()).not.toContain('text-muted')
})

it('renders contact cell with list value', async () => {
const table = copy(studyTablesOneCol).tables.study
table.field_header[0] = studyTables.tables.study.field_header[5]
table.table_data[0][0] = studyTables.tables.study.table_data[0][5]
table.table_data[0][0].value = 'John Doe <[email protected]>;Jane Doe'
const wrapper = mountSheetTable({ table: table })
await waitAG(wrapper)
await waitRAF()

const cell = wrapper.find('.sodar-ss-data-cell')
const cellData = cell.find('.sodar-ss-data')
expect(cellData.text()).toContain('John Doe;')
expect(cellData.text()).toContain('Jane Doe')
expect(cellData.find('a').exists()).toBe(true)
expect(cellData.find('a').attributes().href).toBe('mailto:[email protected]')
expect(cell.classes()).not.toContain('text-right')
expect(cell.classes()).not.toContain('text-muted')
})

it('renders contact cell with bracket syntax', async () => {
const table = copy(studyTablesOneCol).tables.study
table.field_header[0] = studyTables.tables.study.field_header[5]
Expand Down

0 comments on commit d03e6d5

Please sign in to comment.