Added AND/OR toggle to alerts.

This commit is contained in:
DebaucheryLibrarian 2023-11-24 02:10:03 +01:00
parent 238dce78b5
commit 0369446681
8 changed files with 97 additions and 40 deletions

View File

@ -1,6 +1,7 @@
<template> <template>
<router-link <RouterLink
:to="`/actor/${actor.id}/${actor.slug}`" :to="`/actor/${actor.id}/${actor.slug}`"
:target="target"
class="actor nolink" class="actor nolink"
> >
<div class="avatar"> <div class="avatar">
@ -18,7 +19,7 @@
</div> </div>
<span class="name">{{ actor.name }}</span> <span class="name">{{ actor.name }}</span>
</router-link> </RouterLink>
</template> </template>
<script> <script>
@ -36,6 +37,10 @@ export default {
type: Object, type: Object,
default: null, default: null,
}, },
target: {
type: String,
default: null,
},
}, },
methods: { methods: {
unstashActor, unstashActor,

View File

@ -14,7 +14,17 @@
> >
<div class="dialog-section"> <div class="dialog-section">
<h3 class="dialog-heading"> <h3 class="dialog-heading">
When<span class="dialog-description">All to appear in the same scene</span> When
<label class="dialog-description noselect">
<template v-if="all">Scene must match&nbsp;<strong>all</strong>&nbsp;fields</template>
<template v-else>Scene must match&nbsp;<strong>any</strong>&nbsp;field</template>
<Toggle
:checked="all"
@change="(checked) => all = checked"
/>
</label>
</h3> </h3>
<div class="alert-section"> <div class="alert-section">
@ -34,7 +44,10 @@
:key="`actor-${actor.id}`" :key="`actor-${actor.id}`"
class="actor" class="actor"
> >
<ActorPreview :actor="actor" /> <ActorPreview
:actor="actor"
target="_blank"
/>
<Icon <Icon
icon="cross3" icon="cross3"
@ -109,10 +122,15 @@
<div class="entities"> <div class="entities">
<div <div
v-if="entity" v-for="(entity, index) in entities"
:key="`entity-${entity.id}`"
:class="{ invalid: all && index > 0 }"
class="entity" class="entity"
> >
<Entity :entity="entity" /> <Entity
:entity="entity"
target="_blank"
/>
<Icon <Icon
icon="cross3" icon="cross3"
@ -121,7 +139,7 @@
/> />
</div> </div>
<Tooltip v-if="!entity"> <Tooltip v-if="entities.length < 1 || !all">
<div class="entity placeholder"> <div class="entity placeholder">
Any channel Any channel
@ -278,6 +296,7 @@
import ActorPreview from '../actors/preview.vue'; import ActorPreview from '../actors/preview.vue';
import Entity from '../entities/tile.vue'; import Entity from '../entities/tile.vue';
import Checkbox from '../form/checkbox.vue'; import Checkbox from '../form/checkbox.vue';
import Toggle from '../form/toggle.vue';
import Search from './search.vue'; import Search from './search.vue';
async function addAlert() { async function addAlert() {
@ -285,10 +304,11 @@ async function addAlert() {
try { try {
await this.$store.dispatch('addAlert', { await this.$store.dispatch('addAlert', {
all: this.all,
actors: this.actors.map((actor) => actor.id), actors: this.actors.map((actor) => actor.id),
tags: this.tags.map((tag) => tag.id), tags: this.tags.map((tag) => tag.id),
matches: this.matches, matches: this.matches,
entity: this.entity?.id, entities: this.entities.map((entity) => entity.id),
notify: this.notify, notify: this.notify,
email: this.email, email: this.email,
stashes: this.stashes.map((stash) => stash.id), stashes: this.stashes.map((stash) => stash.id),
@ -309,7 +329,7 @@ function addActor(actor) {
} }
function addEntity(entity) { function addEntity(entity) {
this.entity = entity; this.entities = this.entities.concat(entity);
this.events.emit('blur'); this.events.emit('blur');
} }
@ -325,8 +345,8 @@ function removeActor(actor) {
this.actors = this.actors.filter((listedActor) => listedActor.id !== actor.id); this.actors = this.actors.filter((listedActor) => listedActor.id !== actor.id);
} }
function removeEntity() { function removeEntity(entity) {
this.entity = null; this.entities = this.entities.filter((alertEntity) => alertEntity.id !== entity.id);
} }
function removeTag(tag) { function removeTag(tag) {
@ -371,6 +391,7 @@ export default {
Checkbox, Checkbox,
Entity, Entity,
Search, Search,
Toggle,
}, },
emits: ['close'], emits: ['close'],
data() { data() {
@ -378,7 +399,8 @@ export default {
error: null, error: null,
actors: [], actors: [],
tags: [], tags: [],
entity: null, all: true,
entities: [],
matches: [], matches: [],
matchProperty: 'title', matchProperty: 'title',
matchExpression: null, matchExpression: null,
@ -424,9 +446,15 @@ export default {
} }
.dialog-description { .dialog-description {
display: flex;
align-items: center;
color: var(--shadow); color: var(--shadow);
font-size: .9rem; font-size: .9rem;
font-weight: normal; font-weight: normal;
.toggle-container {
margin-left: .5rem;
}
} }
.dialog-error { .dialog-error {
@ -488,6 +516,11 @@ export default {
margin: 0 .5rem .5rem 0; margin: 0 .5rem .5rem 0;
} }
.entity.invalid {
opacity: .5;
pointer-events: none;
}
.entity .tile { .entity .tile {
width: 10rem; width: 10rem;
height: 2.5rem; height: 2.5rem;

View File

@ -1,7 +1,8 @@
<template> <template>
<router-link <RouterLink
:to="`/${entity.type}/${entity.slug}`" :to="`/${entity.type}/${entity.slug}`"
:title="entity.name" :title="entity.name"
:target="target"
class="tile" class="tile"
> >
<div class="tile-logo"> <div class="tile-logo">
@ -47,7 +48,7 @@
<span v-if="typeof entity.sceneTotal !== 'undefined'">{{ entity.sceneTotal }} scenes</span> <span v-if="typeof entity.sceneTotal !== 'undefined'">{{ entity.sceneTotal }} scenes</span>
<span v-if="entity.type === 'network'">{{ entity.childrenTotal }} channels</span> <span v-if="entity.type === 'network'">{{ entity.childrenTotal }} channels</span>
</span> </span>
</router-link> </RouterLink>
</template> </template>
<script> <script>
@ -57,6 +58,10 @@ export default {
type: Object, type: Object,
default: null, default: null,
}, },
target: {
type: String,
default: null,
},
}, },
emits: ['load'], emits: ['load'],
}; };

View File

@ -99,7 +99,7 @@
<Icon <Icon
v-if="notification.alert" v-if="notification.alert"
v-tooltip="`You set an alert for <strong>${notification.alert.tags.map(tag => tag.name).join(', ') || 'all'}</strong> scenes with <strong>${notification.alert.actors.map(actor => actor.name).join(', ') || 'any actor'}</strong> from <strong>${notification.alert.entities.map((entity) => entity.name).join(', ') || 'any channel'}</strong> matching <strong>${notification.alert.matches.map((match) => `${match.property}: ${match.expression}`).join(', ') || 'anything'}</strong>`" v-tooltip="`You set an alert for scenes with <strong>${notification.alert.all ? 'all of' : 'any of'}</strong> <strong>${notification.alert.actors.map(actor => actor.name).join(', ') || 'any actor'}</strong> containing <strong>${notification.alert.tags.map(tag => tag.name).join(', ') || 'any tags'}</strong> from <strong>${notification.alert.entities.map((entity) => entity.name).join(', ') || 'any channel'}</strong> matching <strong>${notification.alert.matches.map((match) => `${match.property}: ${match.expression}`).join(', ') || 'any text'}</strong>`"
icon="question5" icon="question5"
@click.prevent.stop @click.prevent.stop
/> />

View File

@ -68,6 +68,7 @@ function initUiActions(store, _router) {
${releaseFields} ${releaseFields}
} }
alert { alert {
all
tags: alertsTags { tags: alertsTags {
tag { tag {
id id

View File

@ -71,6 +71,7 @@ function initUsersActions(store, _router) {
id id
notify notify
email email
all
stashes: alertsStashes { stashes: alertsStashes {
stash { stash {
id id

View File

@ -34,5 +34,9 @@ exports.down = async (knex) => {
table.dropColumn('all'); table.dropColumn('all');
}); });
await knex.schema.alterTable('alerts_entities', (table) => {
table.unique('alert_id');
});
await knex.schema.dropTable('alerts_matches'); await knex.schema.dropTable('alerts_matches');
}; };

View File

@ -24,6 +24,7 @@ async function addAlert(alert, sessionUser) {
user_id: sessionUser.id, user_id: sessionUser.id,
notify: alert.notify, notify: alert.notify,
email: alert.email, email: alert.email,
all: alert.all,
}) })
.returning('id'); .returning('id');
@ -45,10 +46,10 @@ async function addAlert(alert, sessionUser) {
alert_id: alertId, alert_id: alertId,
stash_id: stashId, stash_id: stashId,
})), false), })), false),
alert.entity && bulkInsert('alerts_entities', [{ alert.entities && bulkInsert('alerts_entities', alert.entities.map((entityId) => ({
alert_id: alertId, alert_id: alertId,
entity_id: alert.entity, entity_id: entityId,
}], false), })).slice(0, alert.all ? 1 : Infinity), false), // one scene can never match multiple entities in AND mode
]); ]);
return alertId; return alertId;
@ -121,8 +122,6 @@ async function notify(scenes) {
return acc; return acc;
}, {}); }, {});
console.log(alertsStashesByAlertId);
const alerts = rawAlerts.map((alert) => ({ const alerts = rawAlerts.map((alert) => ({
id: alert.id, id: alert.id,
userId: alert.user_id, userId: alert.user_id,
@ -148,32 +147,45 @@ async function notify(scenes) {
const triggers = alerts.flatMap((alert) => { const triggers = alerts.flatMap((alert) => {
const alertScenes = curatedScenes.filter((scene) => { const alertScenes = curatedScenes.filter((scene) => {
console.log(scene.title, alert.tags, scene.tagIds); if (alert.all) {
if (alert.actors.length > 0 && !alert.actors.every((actorId) => scene.actorIds.includes(actorId))) { if (alert.actors.length > 0 && !alert.actors.every((actorId) => scene.actorIds.includes(actorId))) {
console.log('THROW ACTORS');
return false; return false;
} }
if (alert.tags.length > 0 && !alert.tags.every((tagId) => scene.tagIds.includes(tagId))) { if (alert.tags.length > 0 && !alert.tags.every((tagId) => scene.tagIds.includes(tagId))) {
console.log('THROW TAGS');
return false; return false;
} }
// multiple entities can only be matched in OR mode // multiple entities can only be matched in OR mode
if (alert.entities.length > 0 && !alert.entities.some((alertEntityId) => alertEntityId === scene.entityId || alertEntityId === scene.parentEntityId)) { if (alert.entities.length > 0 && !alert.entities.some((alertEntityId) => alertEntityId === scene.entityId || alertEntityId === scene.parentEntityId)) {
console.log('THROW ENTITIES');
return false; return false;
} }
if (alert.matches.length > 0 && !alert.matches.every((match) => match.expression.test(scene[match.property]))) { if (alert.matches.length > 0 && !alert.matches.every((match) => match.expression.test(scene[match.property]))) {
console.log('THROW MATCHES');
return false; return false;
} }
console.log('OK');
return true; return true;
}
if (alert.actors.some((actorId) => scene.actorIds.includes(actorId))) {
return true;
}
if (alert.tags.some((tagId) => scene.tagIds.includes(tagId))) {
return true;
}
// multiple entities can only be matched in OR mode
if (alert.entities.some((alertEntityId) => alertEntityId === scene.entityId || alertEntityId === scene.parentEntityId)) {
return true;
}
if (alert.matches.some((match) => match.expression.test(scene[match.property]))) {
return true;
}
return false;
}); });
return alertScenes.map((scene) => ({ return alertScenes.map((scene) => ({
@ -191,15 +203,11 @@ async function notify(scenes) {
scene_id: trigger.sceneId, scene_id: trigger.sceneId,
})); }));
console.log('triggers', triggers);
const stashes = Object.values(Object.fromEntries(triggers.flatMap((trigger) => trigger.alert.stashes.map((stashId) => ({ const stashes = Object.values(Object.fromEntries(triggers.flatMap((trigger) => trigger.alert.stashes.map((stashId) => ({
scene_id: trigger.sceneId, scene_id: trigger.sceneId,
stash_id: stashId, stash_id: stashId,
}))).map((stash) => [`${stash.stash_id}:${stash.scene_id}`, stash]))); }))).map((stash) => [`${stash.stash_id}:${stash.scene_id}`, stash])));
console.log('stashes', stashes);
await Promise.all([ await Promise.all([
bulkInsert('notifications', notifications, false), bulkInsert('notifications', notifications, false),
bulkInsert('stashes_scenes', stashes, false), bulkInsert('stashes_scenes', stashes, false),