mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 08:49:13 +00:00
feat(filters): show enabled and disabled actions in list view (#1304)
* feat(filters): reflect enabled actions * dont store release unless enabled action found * store the release after the delay * add new parameter to FindByFilterID method
This commit is contained in:
parent
95cd053db5
commit
6e12654f6a
11 changed files with 59 additions and 45 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -38,6 +38,7 @@ bin/
|
||||||
dist/
|
dist/
|
||||||
.run/
|
.run/
|
||||||
tmp/
|
tmp/
|
||||||
|
.golangci.yml
|
||||||
|
|
||||||
# Preserve files
|
# Preserve files
|
||||||
!.gitkeep
|
!.gitkeep
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Service interface {
|
||||||
Store(ctx context.Context, action domain.Action) (*domain.Action, error)
|
Store(ctx context.Context, action domain.Action) (*domain.Action, error)
|
||||||
List(ctx context.Context) ([]domain.Action, error)
|
List(ctx context.Context) ([]domain.Action, error)
|
||||||
Get(ctx context.Context, req *domain.GetActionRequest) (*domain.Action, error)
|
Get(ctx context.Context, req *domain.GetActionRequest) (*domain.Action, error)
|
||||||
FindByFilterID(ctx context.Context, filterID int) ([]*domain.Action, error)
|
FindByFilterID(ctx context.Context, filterID int, active *bool) ([]*domain.Action, error)
|
||||||
Delete(ctx context.Context, req *domain.DeleteActionRequest) error
|
Delete(ctx context.Context, req *domain.DeleteActionRequest) error
|
||||||
DeleteByFilterID(ctx context.Context, filterID int) error
|
DeleteByFilterID(ctx context.Context, filterID int) error
|
||||||
ToggleEnabled(actionID int) error
|
ToggleEnabled(actionID int) error
|
||||||
|
@ -76,8 +76,8 @@ func (s *service) Get(ctx context.Context, req *domain.GetActionRequest) (*domai
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) FindByFilterID(ctx context.Context, filterID int) ([]*domain.Action, error) {
|
func (s *service) FindByFilterID(ctx context.Context, filterID int, active *bool) ([]*domain.Action, error) {
|
||||||
return s.repo.FindByFilterID(ctx, filterID)
|
return s.repo.FindByFilterID(ctx, filterID, active)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *service) Delete(ctx context.Context, req *domain.DeleteActionRequest) error {
|
func (s *service) Delete(ctx context.Context, req *domain.DeleteActionRequest) error {
|
||||||
|
|
|
@ -30,7 +30,7 @@ func NewActionRepo(log logger.Logger, db *DB, clientRepo domain.DownloadClientRe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ActionRepo) FindByFilterID(ctx context.Context, filterID int) ([]*domain.Action, error) {
|
func (r *ActionRepo) FindByFilterID(ctx context.Context, filterID int, active *bool) ([]*domain.Action, error) {
|
||||||
tx, err := r.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
|
tx, err := r.db.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelReadCommitted})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -38,7 +38,7 @@ func (r *ActionRepo) FindByFilterID(ctx context.Context, filterID int) ([]*domai
|
||||||
|
|
||||||
defer tx.Rollback()
|
defer tx.Rollback()
|
||||||
|
|
||||||
actions, err := r.findByFilterID(ctx, tx, filterID)
|
actions, err := r.findByFilterID(ctx, tx, filterID, active)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ func (r *ActionRepo) FindByFilterID(ctx context.Context, filterID int) ([]*domai
|
||||||
return actions, nil
|
return actions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) ([]*domain.Action, error) {
|
func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int, active *bool) ([]*domain.Action, error) {
|
||||||
queryBuilder := r.db.squirrel.
|
queryBuilder := r.db.squirrel.
|
||||||
Select(
|
Select(
|
||||||
"id",
|
"id",
|
||||||
|
@ -95,6 +95,10 @@ func (r *ActionRepo) findByFilterID(ctx context.Context, tx *Tx, filterID int) (
|
||||||
From("action").
|
From("action").
|
||||||
Where(sq.Eq{"filter_id": filterID})
|
Where(sq.Eq{"filter_id": filterID})
|
||||||
|
|
||||||
|
if active != nil {
|
||||||
|
queryBuilder = queryBuilder.Where(sq.Eq{"enabled": *active})
|
||||||
|
}
|
||||||
|
|
||||||
query, args, err := queryBuilder.ToSql()
|
query, args, err := queryBuilder.ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error building query")
|
return nil, errors.Wrap(err, "error building query")
|
||||||
|
|
|
@ -214,7 +214,7 @@ func TestActionRepo_FindByFilterID(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
// Actual test for FindByFilterID
|
// Actual test for FindByFilterID
|
||||||
actions, err := repo.FindByFilterID(context.Background(), createdFilters[0].ID)
|
actions, err := repo.FindByFilterID(context.Background(), createdFilters[0].ID, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, actions)
|
assert.NotNil(t, actions)
|
||||||
assert.Equal(t, 1, len(actions))
|
assert.Equal(t, 1, len(actions))
|
||||||
|
@ -235,7 +235,7 @@ func TestActionRepo_FindByFilterID(t *testing.T) {
|
||||||
assert.NotNil(t, createdFilters)
|
assert.NotNil(t, createdFilters)
|
||||||
|
|
||||||
// Actual test for FindByFilterID
|
// Actual test for FindByFilterID
|
||||||
actions, err := repo.FindByFilterID(context.Background(), createdFilters[0].ID)
|
actions, err := repo.FindByFilterID(context.Background(), createdFilters[0].ID, nil)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, 0, len(actions))
|
assert.Equal(t, 0, len(actions))
|
||||||
|
|
||||||
|
@ -244,7 +244,7 @@ func TestActionRepo_FindByFilterID(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run(fmt.Sprintf("FindByFilterID_Succeeds_With_Invalid_FilterID [%s]", dbType), func(t *testing.T) {
|
t.Run(fmt.Sprintf("FindByFilterID_Succeeds_With_Invalid_FilterID [%s]", dbType), func(t *testing.T) {
|
||||||
actions, err := repo.FindByFilterID(context.Background(), 9999) // 9999 is an invalid filter ID
|
actions, err := repo.FindByFilterID(context.Background(), 9999, nil) // 9999 is an invalid filter ID
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, actions)
|
assert.NotNil(t, actions)
|
||||||
assert.Equal(t, 0, len(actions))
|
assert.Equal(t, 0, len(actions))
|
||||||
|
@ -254,7 +254,7 @@ func TestActionRepo_FindByFilterID(t *testing.T) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
actions, err := repo.FindByFilterID(ctx, 1)
|
actions, err := repo.FindByFilterID(ctx, 1, nil)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Nil(t, actions)
|
assert.Nil(t, actions)
|
||||||
})
|
})
|
||||||
|
|
|
@ -57,6 +57,12 @@ func (r *FilterRepo) find(ctx context.Context, tx *Tx, params domain.FilterQuery
|
||||||
From("action a").
|
From("action a").
|
||||||
Where("a.filter_id = f.id")
|
Where("a.filter_id = f.id")
|
||||||
|
|
||||||
|
actionEnabledCountQuery := r.db.squirrel.
|
||||||
|
Select("COUNT(*)").
|
||||||
|
From("action a").
|
||||||
|
Where("a.filter_id = f.id").
|
||||||
|
Where("a.enabled = '1'")
|
||||||
|
|
||||||
queryBuilder := r.db.squirrel.
|
queryBuilder := r.db.squirrel.
|
||||||
Select(
|
Select(
|
||||||
"f.id",
|
"f.id",
|
||||||
|
@ -68,6 +74,7 @@ func (r *FilterRepo) find(ctx context.Context, tx *Tx, params domain.FilterQuery
|
||||||
).
|
).
|
||||||
Distinct().
|
Distinct().
|
||||||
Column(sq.Alias(actionCountQuery, "action_count")).
|
Column(sq.Alias(actionCountQuery, "action_count")).
|
||||||
|
Column(sq.Alias(actionEnabledCountQuery, "actions_enabled_count")).
|
||||||
LeftJoin("filter_indexer fi ON f.id = fi.filter_id").
|
LeftJoin("filter_indexer fi ON f.id = fi.filter_id").
|
||||||
LeftJoin("indexer i ON i.id = fi.indexer_id").
|
LeftJoin("indexer i ON i.id = fi.indexer_id").
|
||||||
From("filter f")
|
From("filter f")
|
||||||
|
@ -89,7 +96,6 @@ func (r *FilterRepo) find(ctx context.Context, tx *Tx, params domain.FilterQuery
|
||||||
for _, v := range params.Filters.Indexers {
|
for _, v := range params.Filters.Indexers {
|
||||||
filter = append(filter, sq.Eq{"i.identifier": v})
|
filter = append(filter, sq.Eq{"i.identifier": v})
|
||||||
}
|
}
|
||||||
|
|
||||||
queryBuilder = queryBuilder.Where(filter)
|
queryBuilder = queryBuilder.Where(filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,14 +108,13 @@ func (r *FilterRepo) find(ctx context.Context, tx *Tx, params domain.FilterQuery
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "error executing query")
|
return nil, errors.Wrap(err, "error executing query")
|
||||||
}
|
}
|
||||||
|
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
|
||||||
var filters []domain.Filter
|
var filters []domain.Filter
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var f domain.Filter
|
var f domain.Filter
|
||||||
|
|
||||||
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Priority, &f.CreatedAt, &f.UpdatedAt, &f.ActionsCount); err != nil {
|
if err := rows.Scan(&f.ID, &f.Enabled, &f.Name, &f.Priority, &f.CreatedAt, &f.UpdatedAt, &f.ActionsCount, &f.ActionsEnabledCount); err != nil {
|
||||||
return nil, errors.Wrap(err, "error scanning row")
|
return nil, errors.Wrap(err, "error scanning row")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import (
|
||||||
type ActionRepo interface {
|
type ActionRepo interface {
|
||||||
Store(ctx context.Context, action Action) (*Action, error)
|
Store(ctx context.Context, action Action) (*Action, error)
|
||||||
StoreFilterActions(ctx context.Context, filterID int64, actions []*Action) ([]*Action, error)
|
StoreFilterActions(ctx context.Context, filterID int64, actions []*Action) ([]*Action, error)
|
||||||
FindByFilterID(ctx context.Context, filterID int) ([]*Action, error)
|
FindByFilterID(ctx context.Context, filterID int, active *bool) ([]*Action, error)
|
||||||
List(ctx context.Context) ([]Action, error)
|
List(ctx context.Context) ([]Action, error)
|
||||||
Get(ctx context.Context, req *GetActionRequest) (*Action, error)
|
Get(ctx context.Context, req *GetActionRequest) (*Action, error)
|
||||||
Delete(ctx context.Context, req *DeleteActionRequest) error
|
Delete(ctx context.Context, req *DeleteActionRequest) error
|
||||||
|
|
|
@ -133,6 +133,7 @@ type Filter struct {
|
||||||
ExceptDescription string `json:"except_description,omitempty"`
|
ExceptDescription string `json:"except_description,omitempty"`
|
||||||
UseRegexDescription bool `json:"use_regex_description,omitempty"`
|
UseRegexDescription bool `json:"use_regex_description,omitempty"`
|
||||||
ActionsCount int `json:"actions_count"`
|
ActionsCount int `json:"actions_count"`
|
||||||
|
ActionsEnabledCount int `json:"actions_enabled_count"`
|
||||||
Actions []*Action `json:"actions,omitempty"`
|
Actions []*Action `json:"actions,omitempty"`
|
||||||
External []FilterExternal `json:"external,omitempty"`
|
External []FilterExternal `json:"external,omitempty"`
|
||||||
Indexers []Indexer `json:"indexers"`
|
Indexers []Indexer `json:"indexers"`
|
||||||
|
|
|
@ -118,7 +118,7 @@ func (s *service) FindByID(ctx context.Context, filterID int) (*domain.Filter, e
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
actions, err := s.actionRepo.FindByFilterID(ctx, filter.ID)
|
actions, err := s.actionRepo.FindByFilterID(ctx, filter.ID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.log.Error().Err(err).Msgf("could not find filter actions for filter id: %v", filter.ID)
|
s.log.Error().Err(err).Msgf("could not find filter actions for filter id: %v", filter.ID)
|
||||||
}
|
}
|
||||||
|
@ -691,7 +691,7 @@ func (s *service) webhook(ctx context.Context, external domain.FilterExternal, r
|
||||||
}
|
}
|
||||||
|
|
||||||
// add header to req
|
// add header to req
|
||||||
req.Header.Add(http.CanonicalHeaderKey(h[0]), h[1])
|
req.Header.Add(h[0], h[1]) // go already canonicalizes the provided header key.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -162,6 +162,27 @@ func (s *service) processFilters(ctx context.Context, filters []*domain.Filter,
|
||||||
|
|
||||||
l.Info().Msgf("Matched '%s' (%s) for %s", release.TorrentName, release.FilterName, release.Indexer)
|
l.Info().Msgf("Matched '%s' (%s) for %s", release.TorrentName, release.FilterName, release.Indexer)
|
||||||
|
|
||||||
|
// found matching filter, lets find the filter actions and attach
|
||||||
|
active := true
|
||||||
|
actions, err := s.actionSvc.FindByFilterID(ctx, f.ID, &active)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error().Err(err).Msgf("release.Process: error finding actions for filter: %s", f.Name)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no actions, continue to next filter
|
||||||
|
if len(actions) == 0 {
|
||||||
|
s.log.Warn().Msgf("release.Process: no active actions found for filter '%s', trying next one..", f.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// sleep for the delay period specified in the filter before running actions
|
||||||
|
delay := release.Filter.Delay
|
||||||
|
if delay > 0 {
|
||||||
|
l.Debug().Msgf("release.Process: delaying processing of '%s' (%s) for %s by %d seconds as specified in the filter", release.TorrentName, release.FilterName, release.Indexer, delay)
|
||||||
|
time.Sleep(time.Duration(delay) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
// save release here to only save those with rejections from actions instead of all releases
|
// save release here to only save those with rejections from actions instead of all releases
|
||||||
if release.ID == 0 {
|
if release.ID == 0 {
|
||||||
release.FilterStatus = domain.ReleaseStatusFilterApproved
|
release.FilterStatus = domain.ReleaseStatusFilterApproved
|
||||||
|
@ -172,26 +193,6 @@ func (s *service) processFilters(ctx context.Context, filters []*domain.Filter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// found matching filter, lets find the filter actions and attach
|
|
||||||
actions, err := s.actionSvc.FindByFilterID(ctx, f.ID)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error().Err(err).Msgf("release.Process: error finding actions for filter: %s", f.Name)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no actions, continue to next filter
|
|
||||||
if len(actions) == 0 {
|
|
||||||
s.log.Warn().Msgf("release.Process: no actions found for filter '%s', trying next one..", f.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sleep for the delay period specified in the filter before running actions
|
|
||||||
delay := release.Filter.Delay
|
|
||||||
if delay > 0 {
|
|
||||||
l.Debug().Msgf("release.Process: delaying processing of '%s' (%s) for %s by %d seconds as specified in the filter", release.TorrentName, release.FilterName, release.Indexer, delay)
|
|
||||||
time.Sleep(time.Duration(delay) * time.Second)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rejections []string
|
var rejections []string
|
||||||
|
|
||||||
// run actions (watchFolder, test, exec, qBittorrent, Deluge, arr etc.)
|
// run actions (watchFolder, test, exec, qBittorrent, Deluge, arr etc.)
|
||||||
|
|
|
@ -645,19 +645,20 @@ function FilterListItem({ filter, values, idx }: FilterListItemProps) {
|
||||||
to={`${filter.id.toString()}/actions`}
|
to={`${filter.id.toString()}/actions`}
|
||||||
className="flex items-center cursor-pointer hover:text-black dark:hover:text-gray-300"
|
className="flex items-center cursor-pointer hover:text-black dark:hover:text-gray-300"
|
||||||
>
|
>
|
||||||
<span className={classNames(!filter.actions_count ? "text-red-500 hover:text-red-400 dark:hover:text-red-400" : "")}>
|
<span className={filter.actions_count === 0 || filter.actions_enabled_count === 0 ? "text-red-500 hover:text-red-400 dark:hover:text-red-400" : ""}>
|
||||||
Actions: {filter.actions_count}
|
Actions: {filter.actions_enabled_count}/{filter.actions_count}
|
||||||
</span>
|
</span>
|
||||||
{!filter.actions_count && (
|
|
||||||
<span className="mr-2 ml-2 flex h-3 w-3 relative">
|
|
||||||
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</Link>
|
</Link>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{!filter.actions_count ? (
|
{filter.actions_count === 0 ? (
|
||||||
<>{"You need to setup an action in the filter otherwise you will not get any snatches."}</>
|
<>
|
||||||
|
{"No actions defined. Set up actions to enable snatching."}
|
||||||
|
</>
|
||||||
|
) : filter.actions_enabled_count === 0 ? (
|
||||||
|
<>
|
||||||
|
{"You need to enable at least one action in the filter otherwise you will not get any snatches."}
|
||||||
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</span>
|
</span>
|
||||||
|
|
1
web/src/types/Filter.d.ts
vendored
1
web/src/types/Filter.d.ts
vendored
|
@ -68,6 +68,7 @@ interface Filter {
|
||||||
tags_match_logic: string;
|
tags_match_logic: string;
|
||||||
except_tags_match_logic: string;
|
except_tags_match_logic: string;
|
||||||
actions_count: number;
|
actions_count: number;
|
||||||
|
actions_enabled_count: number;
|
||||||
actions: Action[];
|
actions: Action[];
|
||||||
indexers: Indexer[];
|
indexers: Indexer[];
|
||||||
external: ExternalFilter[];
|
external: ExternalFilter[];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue