Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show apps not supported in search #349

Open
wants to merge 17 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 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
20 changes: 16 additions & 4 deletions api/controllers/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ function canedit(user, rec) {
* @apiSuccess {Object} List of apps (maybe limited / skipped) and total count
*/
router.get('/', common.jwt({credentialsRequired: false}), (req, res, next)=>{
var skip = req.query.skip||0;
let skip = req.query.skip||0;
let limit = req.query.limit||100;
var ands = [];
let ands = [];
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved
if(req.query.find) ands.push(JSON.parse(req.query.find));
const listIncompatible = req.query.listIncompatible || false;
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved

common.getprojects(req.user, (err, project_ids)=>{
if(err) return next(err);
Expand Down Expand Up @@ -93,7 +94,7 @@ router.get('/query', common.jwt({credentialsRequired: false}), (req, res, next)=
//and get *all* apps (minus some heavy/unnecessary stuff)
common.getprojects(req.user, async (err, project_ids)=>{
if(err) return next(err);
const apps = await db.Apps.find({
let findQuery = {
removed: false,
$or: [
//if projects is set, user need to have access to it
Expand All @@ -105,14 +106,25 @@ router.get('/query', common.jwt({credentialsRequired: false}), (req, res, next)=
{projects: null}, //if projects is set to null, it's available to everyoone
{projects: {$exists: false}}, //if projects not set, it's availableo to everyone
]
})
};
if(req.query.find) findQuery = {$and: [findQuery, JSON.parse(req.query.find)]};
if(req.query.incompatible === 'true') {
// When incompatible flag is true, remove the datatype filter
if(findQuery.$and) {
findQuery.$and = findQuery.$and.filter(query => !query["inputs.datatype"]);
}
}
anibalsolon marked this conversation as resolved.
Show resolved Hide resolved


const apps = await db.Apps.find(findQuery)
.select('-config -stats.gitinfo -contributors') //cut things we don't need
//we want to search into datatype name/desc (desc might be too much?)
.populate('inputs.datatype', 'name desc')
.populate('outputs.datatype', 'name desc')
.lean();

if(!req.query.q) return res.json(apps); //if not query is set, return everything

const queryTokens = req.query.q.toLowerCase().split(" ");

//then construct list of tokens for each app to search by
Expand Down
90 changes: 86 additions & 4 deletions ui/src/components/app.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
<template>
<div v-if="app_" class="appcard" :class="{'compact': compact, 'clickable': clickable, 'deprecated': app_.deprecated_by}" @click="click">
<div v-if="app_" class="appcard" :class="cardClasses" @click="handleClick">
<div v-if="isIncompatible">
<div class="incompatible-label">Incompatible</div>
</div>
<div v-if="app_.deprecated_by" class="deprecated-label">Deprecated</div>

<div v-if="compact">
<appavatar :app="app_" style="position: absolute; right: 0;" :width="80" :height="80"/>
<span v-if="app_.deprecated_by" class="deprecated-label" style="top: inherit; bottom: 0;">Deprecated</span>
Expand All @@ -26,9 +31,14 @@
</h4>
<h5 class="github">{{app_.github}} <b-badge>{{branch||app_.github_branch}}</b-badge></h5>
<div class="datatypes">

<b-badge v-if="app_.missingInputIDs && app_.missingInputIDs.length > 0" pill variant="danger">Missing inputs <br/></b-badge> <br/>

In
<div class="datatype" v-for="input in app_.inputs" :key="'input.'+input.id" :class="[input.optional?'input-optional':'']">
<datatypetag :datatype="input.datatype" :tags="input.datatype_tags" :clickable="false"/>

<datatypetag :datatype="input.datatype" :tags="input.datatype_tags" :clickable="false" :missing="app_.missingInputIDs && app_.missingInputIDs.includes(input._id)"/>

<b v-if="input.multi">multi</b>
<b v-if="input.optional">opt</b>
</div>
Expand Down Expand Up @@ -83,7 +93,10 @@
</span>
<span v-if="showDoi && app_.doi">{{app_.doi}}</span>
</div>


</div>

</div>
</template>

Expand Down Expand Up @@ -133,6 +146,23 @@ export default {
if(this.app) this.app_ = this.app;
},

computed: {
isIncompatible() {
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved
return this.app_.compatible === false;
},

cardClasses() {
return {
'clickable': this.clickable && !this.isIncompatible ,
'incompatible': this.isIncompatible,
'deprecated': this.app_.deprecated_by,
'compact': this.compact,
}
// :class="{'compact': compact, 'clickable': clickable, 'deprecated': app_.deprecated_by}"
}

},

methods: {
load_app() {
this.appcache(this.appid, (err, app)=>{
Expand All @@ -147,6 +177,12 @@ export default {
}
},

handleClick() {
if(!this.isIncompatible && this.clickable) {
this.click();
}
}

},
}
</script>
Expand Down Expand Up @@ -256,7 +292,7 @@ line-height: 100%;
.deprecated h4 {
opacity: 0.7;
}
.deprecated-label {
/* .deprecated-label {
position: absolute;
right: 0;
top: 0;
Expand All @@ -267,5 +303,51 @@ opacity: 0.9;
text-transform: uppercase;
font-size: 80%;
font-weight: bold;
} */
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved

.incompatible-label,
.deprecated-label {
position: absolute;
right: 0;
background-color: #666;
color: white;
padding: 2px 4px;
opacity: 0.9;
text-transform: uppercase;
font-size: 80%;
font-weight: bold;
z-index: 1;
}

.incompatible-error {
margin-left: 10px;
color: #d9534f;
}
</style>

.deprecated-label {
top: 0;
}

.incompatible-label {
top: 0px;
}

.appcard.incompatible,
.appcard.deprecated {
pointer-events: none;
opacity: 0.5;
}

.appcard.incompatible.name,
.appcard.deprecated.name,
.appcard.incompatible.github,
.appcard.deprecated.github {
color: #838383;
}

.incompatible-label {
font-size: 70%;
/* //smaller to fit with icon of app */
}

</style>
6 changes: 5 additions & 1 deletion ui/src/components/datatypetag.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<div v-if="ready" class="dt" :class="{'dt-clickable': clickable}" @click="click">
<div v-if="ready" class="dt" :class="{'dt-clickable': clickable, 'dt-missing': missing}" @click="click">
<icon v-if="_datatype && _datatype.groupAnalysis" name="dot-circle" :style="{color}" scale="1" class="dot"/>
<icon v-else name="circle" :style="{color}" scale="1" class="dot"/>
{{name}}
Expand All @@ -22,6 +22,7 @@ export default {
tags: { type: Array, },
trimname: { type: Boolean, default: true, },
clickable: { type: Boolean, default: true, },
missing: { type: Boolean, default: false },
},

data() {
Expand Down Expand Up @@ -122,4 +123,7 @@ export default {
.tags:not(:last-child) {
border-right: 1px solid #0001;
}
.dt-missing {
color: red;
}
</style>
77 changes: 61 additions & 16 deletions ui/src/modals/newtask.vue
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ export default {
this.loading = true;

//create list of all datatypes that user has staged / generated
var datatype_ids = [];
let datatype_ids = [];
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved
this.datasets.forEach(dataset=>{
if(!~datatype_ids.indexOf(dataset.datatype)) datatype_ids.push(dataset.datatype);
});
Expand All @@ -264,35 +264,54 @@ export default {
});

//now find apps that user can submit
this.$http.get('app', {params: {
find: JSON.stringify({
"inputs.datatype": {$in: datatype_ids},
removed: false,
}),
this.$http.get('/app/query', {params: {
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved
sort: 'name',
populate: 'inputs.datatype outputs.datatype',
limit: 500, //TODO - this is not sustailable
incompatible: true
}})
.then(res=>{
//now, pick apps that we have *all* input datasets that matches the input datatype/tags
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved
res.data.apps.forEach(app=>{
var match = true;
res.data.forEach(app=>{
let match = true;
let missingInputIDs = []; // Store missing inputs here
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved

app.inputs.forEach(input=>{
//In this context, return is used to skip the current iteration and move on to the next one,
//not to return a value from a function.
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved
if(input.optional) return; //optional
var matching_dataset = this.datasets.find(dataset=>{
let matching_dataset = this.datasets.find(dataset=>{
if(!input.datatype) return false; //only happens on dev?
if(dataset.datatype != input.datatype._id) return false;
var match_tag = true;

let datatype_id = input.datatype._id;
// Now check if the current input datatype is in the provided datatype_ids
if(datatype_ids.indexOf(datatype_id) === -1) {
// If not, the app is incompatible
match = false;
}

let match_tag = true;
if(dataset.datatype_tags) input.datatype_tags.forEach(tag=>{
//make sure tag matches
if(tag[0] == "!" && ~dataset.datatype_tags.indexOf(tag.substring(1))) match_tag = false;
if(tag[0] != "!" && !~dataset.datatype_tags.indexOf(tag)) match_tag = false;
});
return match_tag;
});
if(!matching_dataset) match = false;
if(!matching_dataset){
missingInputIDs.push(input._id); // Add the missing input to the list
match = false;
}
});
if(match) this.apps.all.push(app);
app.compatible = match;

//should I just push the array of missing inputs?
if (!match) {
app.missingInputIDs = missingInputIDs;
}

this.apps.all.push(app);
});
this.update_lists();
this.loading = false;
Expand Down Expand Up @@ -331,6 +350,7 @@ export default {
//apply filter
if(!this.filter) this.apps.filtered = this.apps.all.sort((a,b)=>a.name - b.name);
let l_filter = this.filter.toLowerCase();

this.apps.filtered = this.apps.all.filter(app=>{
let match = false;
if(app.name && app.name.toLowerCase().includes(l_filter)) match = true;
Expand Down Expand Up @@ -358,10 +378,17 @@ export default {
return match;
});

let popular_ordered = this.apps.filtered.map(a=>{
if(!a.stats) a.stats = {users: 0};
//should i put a check for if filtered contains apps that are incompatible?
bhatiadheeraj marked this conversation as resolved.
Show resolved Hide resolved
this.apps.filtered = this.sortByMissingDatatypes(this.apps.filtered);


let popular_ordered = this.apps.filtered
.filter(a => a.compatible) // Only include compatible apps
.map(a => {
if (!a.stats) a.stats = { users: 0 };
return a;
}).sort((a,b)=>b.stats.users-a.stats.users)
})
.sort((a, b) => b.stats.users - a.stats.users);

this.apps.popular = popular_ordered.slice(0, 9);
this.apps.not_popular = popular_ordered.slice(9);
Expand Down Expand Up @@ -631,7 +658,25 @@ export default {
let inputs = this.wrap_with_label(this.filter_datasets(input));
input.selected.push(...inputs);
this.validate();
}
},
sortByMissingDatatypes(apps) {
// For each app, calculate the number of missing datatypes
apps.forEach(app => {
let missingDatatypesCount = 0;
app.inputs.forEach(input => {
if (!input.optional) {
const isDatatypePresent = this.datasets.some(dataset => dataset.datatype === input.datatype._id);
if (!isDatatypePresent) {
missingDatatypesCount++;
}
}
});
app.missingDatatypesCount = missingDatatypesCount;
});

// Sort apps by the number of missing datatypes in ascending order
return apps.sort((a, b) => a.missingDatatypesCount - b.missingDatatypesCount);
},
},
}
</script>
Expand Down