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 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
144 changes: 103 additions & 41 deletions api/controllers/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const config = require('../config');
const db = require('../models');
const common = require('../common');
const provenance = require('../lib/provenance');
const e = require('express');

function canedit(user, rec) {
if(user) {
Expand All @@ -37,9 +38,9 @@ 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 limit = req.query.limit||100;
var ands = [];
const skip = req.query.skip||0;
const limit = req.query.limit||100;
const ands = [];
if(req.query.find) ands.push(JSON.parse(req.query.find));

common.getprojects(req.user, (err, project_ids)=>{
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,54 +106,115 @@ 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)]};

let datatype_ids = [];

if(req.query.includeIncompatible === 'true') {
// When incompatible flag is true, remove the datatype filter
if(findQuery.$and) {
findQuery.$and = findQuery.$and.filter(query => !query["inputs.datatype"]);
}
datatype_ids = JSON.parse(req.query.find)?.['inputs.datatype']?.['$in'];
}

let 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
apps.forEach(app=>{
let tokens = [
app.name,
app.github_branch,
app.github,
app.desc,
app.desc_override,
app.doi,
...app.tags
];
app.inputs.forEach(input=>{
tokens = [...tokens, ...input.datatype_tags, input.datatype.name, input.datatype.desc];
});
app.outputs.forEach(output=>{
tokens = [...tokens, ...output.datatype_tags, output.datatype.name, output.datatype.desc];
});
tokens = tokens.filter(token=>!!token).map(token=>token.toLowerCase());
console.log("found apps", apps.length, "query", findQuery);

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


if(req.query.q && !req.query.includeIncompatible) {
const queryTokens = req.query.q.toLowerCase().split(" ");
//then construct list of tokens for each app to search by
apps.forEach(app=>{
let tokens = [
app.name,
app.github_branch,
app.github,
app.desc,
app.desc_override,
app.doi,
...app.tags
];
app.inputs.forEach(input=>{
tokens = [...tokens, ...input.datatype_tags, input.datatype.name, input.datatype.desc];
});
app.outputs.forEach(output=>{
tokens = [...tokens, ...output.datatype_tags, output.datatype.name, output.datatype.desc];
});
tokens = tokens.filter(token=>!!token).map(token=>token.toLowerCase());

//let's just store it as part of the app
app._tokens = tokens.join(" ");
});
//let's just store it as part of the app
app._tokens = tokens.join(" ");
});

//then filter apps using _tokens
const filtered = apps.filter(app=>{
//for each query token, make sure all token matches somewhere in _tokens
let match = true;
queryTokens.forEach(token=>{
if(!match) return; //we already know it won't match
if(!app._tokens.includes(token)) match = false;
//then filter apps using _tokens
const filtered = apps.filter(app=>{
//for each query token, make sure all token matches somewhere in _tokens
let match = true;
queryTokens.forEach(token=>{
if(!match) return; //we already know it won't match
if(!app._tokens.includes(token)) match = false;
});
return match;
});
return match;
});

//remove _tokens from the apps to reduce returning weight a bit
filtered.forEach(app=>{ delete app._tokens; });
//remove _tokens from the apps to reduce returning weight a bit
apps = filtered.forEach(app=>{ delete app._tokens; });
}

// if includeIncompatible then include with app.compatible = false / true based on datatype_ids
if(req.query.includeIncompatible === 'true' && !req.query.q) {

const datasetIds = req.query.datasetIds ? JSON.parse(req.query.datasetIds) : [];
const datasets = await db.Datasets.find({$or: [{datatype: {$in: datatype_ids}}, {_id: {$in: datasetIds}}]}).lean();


apps.forEach(app => {
let missingInputIDs = [];
let match = true;

app.inputs.forEach(input => {
if(input.optional) return;

let matching_dataset = datasets.find(dataset => {
if(!input.datatype) return false;
if(dataset.datatype.toString() !== input.datatype._id.toString()) return false;

let match_tag = true;
if(dataset.datatype_tags && input.datatype_tags) {
input.datatype_tags.forEach(tag => {
if(tag[0] === "!" && dataset.datatype_tags.includes(tag.substring(1))) match_tag = false;
if(tag[0] !== "!" && !dataset.datatype_tags.includes(tag)) match_tag = false;
});
}
return match_tag;
});

if(!matching_dataset) {
missingInputIDs.push(input._id);
match = false;
}
});

app.compatible = match;
if(!match) {
app.missingInputIDs = missingInputIDs;
}
});
}

res.json(filtered);
res.json(apps);
});
});

Expand Down
93 changes: 80 additions & 13 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="app_.compatible === false">
<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,20 @@ export default {
if(this.app) this.app_ = this.app;
},

computed: {

cardClasses() {
return {
'clickable': this.clickable && this.isCompatible ,
'incompatible': this.isCompatible,
'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 +174,12 @@ export default {
}
},

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

},
}
</script>
Expand Down Expand Up @@ -256,16 +289,50 @@ line-height: 100%;
.deprecated h4 {
opacity: 0.7;
}

.incompatible-label,
.deprecated-label {
position: absolute;
right: 0;
top: 0;
background-color: #666;
color: white;
padding: 2px 4px;
opacity: 0.9;
text-transform: uppercase;
font-size: 80%;
font-weight: bold;
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;
}

.deprecated-label {
top: 0;
}

.incompatible-label {
top: 0px;
}
</style>

.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>
Loading
Loading