phplib/REST/Searches.php

Summary

Maintainability
C
1 day
Test Coverage
<?php

namespace FOO;

/**
 * Class Searches_REST
 * REST endpoint for manipulating Searches.
 * @package FOO
 */
class Searches_REST extends Models_REST {
    const SLOG_TYPE = SLog::T_SEARCH;
    const RANGE = 10;

    protected static $MODEL = 'Search';
    protected static $CREATABLE = [
        'name', 'source', 'type', 'query_data', 'description', 'category', 'tags',
        'priority', 'schedule_type', 'frequency', 'cron_expression', 'range',
        'notif_type', 'notif_format', 'notif_data', 'autoclose_threshold',
        'enabled', 'owner', 'assignee_type', 'assignee'
    ];
    protected static $QUERYABLE = [
        'type', 'source', 'tags', 'category', 'enabled', 'owner', 'assignee_type',
        'assignee'
    ];
    protected static $READABLE = null;
    protected static $UPDATEABLE = [
        'name', 'source', 'query_data', 'description', 'category', 'tags', 'priority',
        'schedule_type', 'frequency', 'cron_expression', 'notif_type',
        'notif_format', 'notif_data', 'autoclose_threshold', 'range',
        'enabled', 'owner', 'assignee_type', 'assignee', 'renderer_data'
    ];

    /** @var Search Cached Search object. */
    protected $old_model = null;

    protected function construct($data=null) {
        $type = Util::get($data, 'type', '');

        return Search::newSearch($type);
    }

    public function GET(array $get) {
        $action = Util::get($get, 'action');

        switch($action) {
            case 'stats';
                return self::format($this->stats($get));
            default:
                return self::format($this->read($get));
        }
    }

    public function POST(array $get, array $data) {
        $action = Util::get($get, 'action');

        switch($action) {
            case 'preview':
                return self::format($this->preview($get, $data));
            case 'test':
                return self::format($this->test($get, $data));
            case 'execute':
                return self::format($this->execute($get, $data));
            default:
                return self::format($this->create($get, $data));
        }
    }

    public function allowRead() {
        return Auth::isAuthenticated();
    }
    public function allowCreate() {
        return Auth::isAuthenticated();
    }
    public function allowUpdate() {
        return Auth::isAuthenticated();
    }
    public function allowDelete() {
        return Auth::isAuthenticated();
    }

    public function beforeStore($model, $data, $new, $delete) {
        if($delete) {
            return;
        }

        $description = Util::get($data, 'change_description', '');
        if(!$new && empty(trim($description))) {
            throw new ValidationException('No change description provided');
        }

        // Cache the old model.
        if(!$new) {
            $this->old_model = SearchFinder::getById($model['search_id']);
        }
    }

    public function afterStore($model, $data, $new, $delete) {
        $fields = ['tags', 'priority', 'category', 'owner'];

        // Trigger a job to delete all Alerts if the Search was deleted.
        if($delete) {
            $deljob = new Delete_Job();
            $deljob['target_id'] = $model[Search::$PKEY];
            $deljob['target_date'] = $_SERVER['REQUEST_TIME'];
            $deljob->store();
        // Trigger a job to update any Alerts if we've changed any denormalized fields.
        } elseif($this->old_model) {
            foreach($fields as $field) {
                if($model[$field] == $this->old_model[$field]) {
                    continue;
                }

                $syncjob = new Sync_Job();
                $syncjob['target_id'] = $model[Search::$PKEY];
                $syncjob['target_date'] = $_SERVER['REQUEST_TIME'];
                $syncjob->store();
                // We don't want to schedule a Sync for every field that's changed.
                break;
            }
        }

        $this->old_model = null;

        // Log change.
        $description = Util::get($data, 'change_description', '');
        if($new) {
            $description = 'Initial entry';
        }

        $log = new SearchLog();
        $log['user_id'] = Auth::getUserId();
        $log['search_id'] = $model[Search::$PKEY];
        $log['data'] = $model->toArray();
        $log['description'] = $description;

        $log->store();
    }

    public function preview($get, $data) {
        if(!$this->allowCreate()) {
            throw new ForbiddenException;
        }

        $id = Util::get($data, 'id');
        $type = Util::get($data, 'type', '');

        $schema = Search::getSchema();

        $search = SearchFinder::getById($id);
        if(!$search) {
            $search = Search::newSearch($type);
        }
        foreach($data as $k=>$v) {
            if(!Util::exists($schema, $k)) {
                continue;
            }
            $search[$k] = $v;
        }
        $search->validate();

        $alerts = [];
        $alertkeys = array_keys($data['renderer_data']);
        $vertical = false;
        for($i = 0; $i < 3; ++$i) {
            $alert = new Alert;
            $alert['alert_date'] = $_SERVER['REQUEST_TIME'];
            $alert['content'] = [];
            foreach($search['renderer_data'] as $k=>$v) {
                $alert['content'][$k] = 'test';
            }
            $alerts[] = $alert;
        }

        return self::format(Notification::render('alerts', [
            'search' => $search,
            'alerts' => Notification::renderAlerts($search, $alerts),
            'alertkeys' => $alertkeys,
            'content_only' => false,
            'vertical' => $vertical,
        ]));
    }

    public function test($get, $data) {
        if(!$this->allowCreate()) {
            throw new ForbiddenException;
        }

        $id = Util::get($data, 'id');
        $type = Util::get($data, 'type', '');

        $schema = Search::getSchema();

        $search = SearchFinder::getById($id);
        if(!$search) {
            $search = Search::newSearch($type);
        }
        foreach($data as $k=>$v) {
            if(!Util::exists($schema, $k)) {
                continue;
            }
            $search[$k] = $v;
        }
        $search->validate();

        $execution_time = (int) Util::get($data, 'execution_time', 0);
        if($execution_time == 0) {
            $execution_time = $_SERVER['REQUEST_TIME'];
        }

        $searchjob = new Search_Job();
        $searchjob['target_date'] = $execution_time;
        list($alerts, $errors, $ignorable) = $searchjob->_run(false, $search, true);
        $this->slog(SLog::AS_TEST, $search['id']);
        return self::format($alerts, is_null($alerts), $errors);
    }

    public function execute($get, $data) {
        if(!$this->allowCreate()) {
            throw new ForbiddenException;
        }

        $id = Util::get($data, 'id');

        $schema = Search::getSchema();

        $search = SearchFinder::getById($id);
        foreach($data as $k=>$v) {
            if(!Util::exists($schema, $k)) {
                continue;
            }
            $search[$k] = $v;
        }
        $search->validate();

        $execution_time = (int) Util::get($data, 'execution_time', 0);
        if($execution_time == 0) {
            $execution_time = $_SERVER['REQUEST_TIME'];
        }

        $searchjob = new Search_Job();
        $searchjob['target_date'] = $execution_time;
        list($alerts, $errors, $ignorable) = $searchjob->_run(true, $search, true);
        $this->slog(SLog::AS_EXECUTE, $search['id']);
        return self::format($alerts, is_null($alerts), $errors);
    }

    public function stats($data) {
        if(!$this->allowRead()) {
            throw new ForbiddenException;
        }

        $id = Util::get($data, 'id');
        $model = SearchFinder::getById($id);
        if(!$model) {
            throw new NotFoundException;
        }

        $client = new ESClient;
        $data = [];
        $stats = [];

        $data['historical_alerts'] = $client->getAlertActivityCounts(self::RANGE, $id);

        $stats['Flap rate'] = $model['flap_rate'];

        // Get count of total alerts.
        list($sql, $vals) = AlertFinder::generateQuery(
            ['state', 'COUNT(*) as count'],
            ['search_id' => $id],
        null, null, [], ['state']);

        $ret = DB::query(implode(' ', $sql), $vals);
        $active = [0, 0];
        foreach($ret as $row) {
            $i = $row['state'] != Alert::ST_RES ? 0:1;
            $active[$i] += $row['count'];
        }
        $stats['Total'] = array_sum($active);
        $stats['Active'] = $active[0];

        // Get count of escalated alerts.
        $ret = AlertFinder::countByQuery([
            'search_id' => $id,
            'escalated' => 1
        ]);
        $stats['Escalated'] = is_null($ret) ? 0:(int)$ret;

        // Get count of resolved alerts
        list($sql, $vals) = AlertFinder::generateQuery(
            ['resolution', 'COUNT(*) as count'],
            ['search_id' => $id, 'state' => Alert::ST_RES],
        null, null, [], ['resolution']);

        $ret = DB::query(implode(' ', $sql), $vals);
        $groups = [0, 0, 0];
        foreach($ret as $row) {
            $groups[$row['resolution']] += $row['count'];
        }
        $stats['Resolved: Not an issue'] = $groups[0];
        $stats['Resolved: Action taken'] = $groups[1];
        $stats['Resolved: Too old'] = $groups[2];

        // Get timestamp of most recent alert.
        list($sql, $vals) = AlertFinder::generateQuery(
            ['MAX(`alert_date`) as date'],
            ['search_id' => $id]
        );

        $ret = DB::query(implode(' ', $sql), $vals, DB::VAL);
        $stats['Last alert'] = is_null($ret) ? 'N/A':Util::formatDateTime($ret);

        $stats['Last execution'] = $model['last_execution_date'] == 0 ? 'N/A':Util::formatDateTime($model['last_execution_date']);
        $stats['Last successful execution'] = $model['last_success_date'] == 0 ? 'N/A':Util::formatDateTime($model['last_success_date']);

        $data['stats'] = $stats;

        return $data;
    }
};