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

View File

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

View File

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

View File

@ -99,7 +99,7 @@
<Icon
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"
@click.prevent.stop
/>

View File

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

View File

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

View File

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

View File

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