Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions src/apps/api/serializers/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from api.mixins import DefaultUserCreateMixin
from datasets.models import Data, DataGroup
from competitions.models import CompetitionCreationTaskStatus, CompetitionDump


class DataSerializer(DefaultUserCreateMixin, serializers.ModelSerializer):
Expand Down Expand Up @@ -101,13 +102,32 @@ class Meta:
)

def get_competition(self, obj):

# return competition dict with id and title if available
if obj.competition:
# Submission
return {
"id": obj.competition.id,
"title": obj.competition.title,
}
else:
competition = None
try:
# Check if it is a bundle
competition = CompetitionCreationTaskStatus.objects.get(dataset=obj).resulting_competition
except CompetitionCreationTaskStatus.DoesNotExist:
competition = None
if not competition:
# Check if it is a dump
try:
competition = CompetitionDump.objects.get(dataset=obj).competition
except CompetitionDump.DoesNotExist:
competition = None

if competition:
return {
"id": competition.id,
"title": competition.title
}

return None

def get_owner_display_name(self, instance):
Expand Down
12 changes: 10 additions & 2 deletions src/apps/api/views/datasets.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ def get_queryset(self):
# _type = dataset if called from datasets and programs tab to filter datasets and programs
is_dataset = self.request.query_params.get('_type', '') == 'dataset'

# _type = dataset if called from datasets and programs tab to filter datasets and programs
is_bundle = self.request.query_params.get('_type', '') == 'bundle'

# get queryset
qs = self.queryset

Expand All @@ -50,6 +53,11 @@ def get_queryset(self):
# filter datasets and programs
if is_dataset:
qs = qs.filter(~Q(type=Data.SUBMISSION))
qs = qs.exclude(Q(type=Data.COMPETITION_BUNDLE))

# filter bundles
if is_bundle:
qs = qs.filter(Q(type=Data.COMPETITION_BUNDLE))

# public filter check
if is_public:
Expand All @@ -58,15 +66,15 @@ def get_queryset(self):
qs = qs.filter(Q(created_by=self.request.user))

# if GET is called but provided no filters, fall back to default behaviour
if (not is_submission) and (not is_dataset) and (not is_public):
if (not is_submission) and (not is_dataset) and (not is_bundle) and (not is_public):
qs = self.queryset
qs = qs.filter(Q(is_public=True) | Q(created_by=self.request.user))

else:
qs = self.queryset
qs = qs.filter(Q(is_public=True) | Q(created_by=self.request.user))

qs = qs.exclude(Q(type=Data.COMPETITION_BUNDLE) | Q(name__isnull=True))
qs = qs.exclude(Q(name__isnull=True))

qs = qs.select_related('created_by').order_by('-created_when')

Expand Down
2 changes: 2 additions & 0 deletions src/apps/competitions/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,8 @@ def _get_error_string(error_dict):
# call again, to make sure phases get sent to chahub
competition.save()
logger.info("Competition saved!")
status.dataset.name += f" - {competition.title}"
status.dataset.save()

except CompetitionUnpackingException as e:
# We want to catch well handled exceptions and display them to the user
Expand Down
273 changes: 273 additions & 0 deletions src/static/riot/competitions/bundle_management.tag
Original file line number Diff line number Diff line change
@@ -0,0 +1,273 @@
<bundle-management>
<!-- Search -->
<div class="ui icon input">
<input type="text" placeholder="Search..." ref="search" onkeyup="{ filter.bind(this, undefined) }">
<i class="search icon"></i>
</div>
<button class="ui red right floated labeled icon button {disabled: marked_datasets.length === 0}" onclick="{delete_datasets}">
<i class="icon delete"></i>
Delete Selected
</button>

<!-- Data Table -->
<table id="bundlesTable" class="ui {selectable: datasets.length > 0} celled compact sortable table">
<thead>
<tr>
<th>File Name</th>
<th>Benchmark</th>
<th width="175px">Size</th>
<th width="125px">Uploaded</th>
<th width="50px" class="no-sort">Delete?</th>
<th width="25px" class="no-sort"></th>
</tr>
</thead>

<tbody>
<tr each="{ dataset, index in datasets }"
class="dataset-row"
onclick="{show_info_modal.bind(this, dataset)}">
<td>{ dataset.name }</td>
<td>
<div if="{dataset.competition}" class="ui fitted">
<a id="competitionLink" href="{URLS.COMPETITION_DETAIL(dataset.competition.id)}" target="_blank">{dataset.competition.title}</a>
</div>
</td>
<td>{ format_file_size(dataset.file_size) }</td>
<td>{ timeSince(Date.parse(dataset.created_when)) } ago</td>
<td class="center aligned">
<button show="{dataset.created_by === CODALAB.state.user.username}" class="ui mini button red icon" onclick="{ delete_dataset.bind(this, dataset) }">
<i class="icon delete"></i>
</button>
</td>
<td class="center aligned">
<div show="{dataset.created_by === CODALAB.state.user.username}" class="ui fitted checkbox">
<input type="checkbox" name="delete_checkbox" onclick="{ mark_dataset_for_deletion.bind(this, dataset) }">
<label></label>
</div>
</td>
</tr>
<tr if="{datasets.length === 0}">
<td class="center aligned" colspan="6">
<em>No Datasets Yet!</em>
</td>
</tr>
</tbody>

<tfoot>
<!-- Pagination -->
<tr>
<th colspan="8" if="{datasets.length > 0}">
<div class="ui right floated pagination menu" if="{datasets.length > 0}">
<a show="{!!_.get(pagination, 'previous')}" class="icon item" onclick="{previous_page}">
<i class="left chevron icon"></i>
</a>
<div class="item">
<label>{page}</label>
</div>
<a show="{!!_.get(pagination, 'next')}" class="icon item" onclick="{next_page}">
<i class="right chevron icon"></i>
</a>
</div>
</th>
</tr>
</tfoot>
</table>

<!-- Dataset Detail Model -->
<div ref="info_modal" class="ui modal">
<div class="header">
{selected_row.name}
</div>
<div class="content">
<h3>Details</h3>
<table class="ui basic table">
<thead>
<tr>
<th>Key</th>
<th>Created By</th>
<th>Created</th>
</tr>
</thead>
<tbody>
<tr>
<td>{selected_row.key}</td>
<td><a href="/profiles/user/{selected_row.created_by}/" target=_blank>{selected_row.owner_display_name}</a></td>
<td>{pretty_date(selected_row.created_when)}</td>
</tr>
</tbody>
</table>
<virtual if="{!!selected_row.description}">
<div>Description:</div>
<div class="ui segment">
{selected_row.description}
</div>
</virtual>
<div show="{!!_.get(selected_row.in_use, 'length')}"><strong>Used by:</strong>
<div class="ui bulleted list">
<div class="item" each="{comp in selected_row.in_use}">
<a href="{URLS.COMPETITION_DETAIL(comp.pk)}" target="_blank">{comp.title}</a>
</div>
</div>
</div>
</div>
<div class="actions">
<a href="{URLS.DATASET_DOWNLOAD(selected_row.key)}" class="ui green icon button">
<i class="download icon"></i>Download File
</a>
<button class="ui cancel button">Close</button>
</div>
</div>

<script>
var self = this

/*---------------------------------------------------------------------
Init
---------------------------------------------------------------------*/

self.datasets = []
self.marked_datasets = []
self.selected_row = {}
self.page = 1

self.one("mount", function () {
$(".ui.dropdown", self.root).dropdown()
$(".ui.checkbox", self.root).checkbox()
$('#bundlesTable').tablesort()
self.update_datasets()
})

/*---------------------------------------------------------------------
Methods
---------------------------------------------------------------------*/

self.pretty_date = date => luxon.DateTime.fromISO(date).toLocaleString(luxon.DateTime.DATE_FULL)

self.filter = function (filters) {
filters = filters || {}
_.defaults(filters, {
search: $(self.refs.search).val(),
page: 1,
})
self.page = filters.page
self.update_datasets(filters)
}

self.next_page = function () {
if (!!self.pagination.next) {
self.page += 1
self.filter({page: self.page})
} else {
alert("No valid page to go to!")
}
}

self.previous_page = function () {
if (!!self.pagination.previous) {
self.page -= 1
self.filter({page: self.page})
} else {
alert("No valid page to go to!")
}
}

self.update_datasets = function (filters) {
filters = filters || {}
filters._type = "bundle"
CODALAB.api.get_datasets(filters)
.done(function (data) {
self.datasets = data.results
self.pagination = {
"count": data.count,
"next": data.next,
"previous": data.previous
}
self.update()
})
.fail(function (response) {
toastr.error("Could not load datasets...")
})
}

self.delete_dataset = function (dataset, e) {
if (confirm(`Are you sure you want to delete '${dataset.name}'?`)) {
CODALAB.api.delete_dataset(dataset.id)
.done(function () {
self.update_datasets()
toastr.success("Dataset deleted successfully!")
CODALAB.events.trigger('reload_quota_cleanup')
})
.fail(function (response) {
toastr.error(response.responseJSON['error'])
})
}
event.stopPropagation()
}

self.delete_datasets = function () {
if (confirm(`Are you sure you want to delete multiple datasets?`)) {
CODALAB.api.delete_datasets(self.marked_datasets)
.done(function () {
self.update_datasets()
toastr.success("Dataset deleted successfully!")
self.marked_datasets = []
CODALAB.events.trigger('reload_quota_cleanup')
})
.fail(function (response) {
for (e in response.responseJSON) {
toastr.error(`${e}: '${response.responseJSON[e]}'`)
}
})
}
event.stopPropagation()
}

self.mark_dataset_for_deletion = function(dataset, e) {
if (e.target.checked) {
self.marked_datasets.push(dataset.id)
}
else {
self.marked_datasets.splice(self.marked_datasets.indexOf(dataset.id), 1)
}
}

self.show_info_modal = function (row, e) {
// Return here so the info modal doesn't pop up when a checkbox is clicked
if (e.target.type === 'checkbox' || e.target.id === 'competitionLink') {
return
}
self.selected_row = row
self.update()
$(self.refs.info_modal).modal('show')
}

// Function to format file size
self.format_file_size = function(file_size) {
// parse file size from string to float
try {
n = parseFloat(file_size)
}
catch(err) {
// return empty string if parsing fails
return ""
}
// a file_size of -1 indicated an error
if(n < 0) {
return ""
}
// constant units to show with files size
// file size is in KB, converting it to MB and GB
const units = ['KB', 'MB', 'GB']
// loop incrementer for selecting desired unit
let i = 0
// loop over n until it is greater than 1000
while(n >= 1000 && ++i){
n = n/1000;
}
// restrict file size to 1 decimal number concatinated with unit
return(n.toFixed(1) + ' ' + units[i]);
}

</script>

</bundle-management>
4 changes: 4 additions & 0 deletions src/static/riot/management.tag
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<div class="ui top attached tabular menu">
<div class="active item" data-tab="submissions">Submissions</div>
<div class="item" data-tab="datasets">Datasets and programs</div>
<div class="item" data-tab="bundles">Bundles</div>
<div class="item" data-tab="tasks">Tasks</div>
<div class="right menu">
<div class="item">
Expand All @@ -19,6 +20,9 @@
<div class="ui bottom attached tab segment" data-tab="datasets">
<data-management></data-management>
</div>
<div class="ui bottom attached tab segment" data-tab="bundles">
<bundle-management></bundle-management>
</div>
<div class="ui bottom attached tab segment" data-tab="tasks">
<task-management></task-management>
</div>
Expand Down