mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: Handle database connection errors with 500 (#725)
* feat: Handle database connection errors with 500 - If database goes away while unleash is running, unleash now stays running, but all api endpoints will return 500. - This includes our health endpoint, which allows k8s or similar orchestrators to decide what should be done, rather than Unleash terminating unexpectedly
This commit is contained in:
		
							parent
							
								
									f49b5084eb
								
							
						
					
					
						commit
						8bf4214ddb
					
				@ -23,13 +23,16 @@ class ClientMetricsDb {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async removeMetricsOlderThanOneHour() {
 | 
					    async removeMetricsOlderThanOneHour() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const rows = await this.db(TABLE)
 | 
					            const rows = await this.db(TABLE)
 | 
				
			||||||
                .whereRaw("created_at < now() - interval '1 hour'")
 | 
					                .whereRaw("created_at < now() - interval '1 hour'")
 | 
				
			||||||
                .del();
 | 
					                .del();
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (rows > 0) {
 | 
					            if (rows > 0) {
 | 
				
			||||||
                this.logger.debug(`Deleted ${rows} metrics`);
 | 
					                this.logger.debug(`Deleted ${rows} metrics`);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            this.logger.warn(`Error when deleting metrics ${e}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Insert new client metrics
 | 
					    // Insert new client metrics
 | 
				
			||||||
@ -39,26 +42,34 @@ class ClientMetricsDb {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // Used at startup to load all metrics last week into memory!
 | 
					    // Used at startup to load all metrics last week into memory!
 | 
				
			||||||
    async getMetricsLastHour() {
 | 
					    async getMetricsLastHour() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const result = await this.db
 | 
					            const result = await this.db
 | 
				
			||||||
                .select(METRICS_COLUMNS)
 | 
					                .select(METRICS_COLUMNS)
 | 
				
			||||||
                .from(TABLE)
 | 
					                .from(TABLE)
 | 
				
			||||||
                .limit(2000)
 | 
					                .limit(2000)
 | 
				
			||||||
                .whereRaw("created_at > now() - interval '1 hour'")
 | 
					                .whereRaw("created_at > now() - interval '1 hour'")
 | 
				
			||||||
                .orderBy('created_at', 'asc');
 | 
					                .orderBy('created_at', 'asc');
 | 
				
			||||||
 | 
					 | 
				
			||||||
            return result.map(mapRow);
 | 
					            return result.map(mapRow);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            this.logger.warn(`error when getting metrics last hour ${e}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Used to poll for new metrics
 | 
					    // Used to poll for new metrics
 | 
				
			||||||
    async getNewMetrics(lastKnownId) {
 | 
					    async getNewMetrics(lastKnownId) {
 | 
				
			||||||
        const result = await this.db
 | 
					        try {
 | 
				
			||||||
 | 
					            const res = await this.db
 | 
				
			||||||
                .select(METRICS_COLUMNS)
 | 
					                .select(METRICS_COLUMNS)
 | 
				
			||||||
                .from(TABLE)
 | 
					                .from(TABLE)
 | 
				
			||||||
                .limit(1000)
 | 
					                .limit(1000)
 | 
				
			||||||
                .where('id', '>', lastKnownId)
 | 
					                .where('id', '>', lastKnownId)
 | 
				
			||||||
                .orderBy('created_at', 'asc');
 | 
					                .orderBy('created_at', 'asc');
 | 
				
			||||||
 | 
					            return res.map(mapRow);
 | 
				
			||||||
        return result.map(mapRow);
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            this.logger.warn(`error when getting new metrics ${e}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    destroy() {
 | 
					    destroy() {
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ module.exports.createDb = function({ db, databaseSchema, getLogger }) {
 | 
				
			|||||||
        connection: db,
 | 
					        connection: db,
 | 
				
			||||||
        pool: db.pool,
 | 
					        pool: db.pool,
 | 
				
			||||||
        searchPath: databaseSchema,
 | 
					        searchPath: databaseSchema,
 | 
				
			||||||
 | 
					        asyncStackTraces: true,
 | 
				
			||||||
        log: {
 | 
					        log: {
 | 
				
			||||||
            debug: msg => logger.debug(msg),
 | 
					            debug: msg => logger.debug(msg),
 | 
				
			||||||
            info: msg => logger.info(msg),
 | 
					            info: msg => logger.info(msg),
 | 
				
			||||||
 | 
				
			|||||||
@ -13,12 +13,14 @@ const EVENT_COLUMNS = [
 | 
				
			|||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class EventStore extends EventEmitter {
 | 
					class EventStore extends EventEmitter {
 | 
				
			||||||
    constructor(db) {
 | 
					    constructor(db, getLogger) {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
        this.db = db;
 | 
					        this.db = db;
 | 
				
			||||||
 | 
					        this.logger = getLogger('lib/db/event-store.js');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async store(event) {
 | 
					    async store(event) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            await this.db('events').insert({
 | 
					            await this.db('events').insert({
 | 
				
			||||||
                type: event.type,
 | 
					                type: event.type,
 | 
				
			||||||
                created_by: event.createdBy, // eslint-disable-line
 | 
					                created_by: event.createdBy, // eslint-disable-line
 | 
				
			||||||
@ -26,9 +28,13 @@ class EventStore extends EventEmitter {
 | 
				
			|||||||
                tags: event.tags ? JSON.stringify(event.tags) : [],
 | 
					                tags: event.tags ? JSON.stringify(event.tags) : [],
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            process.nextTick(() => this.emit(event.type, event));
 | 
					            process.nextTick(() => this.emit(event.type, event));
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            this.logger.warn(`Failed to store event ${e}`);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getEvents() {
 | 
					    async getEvents() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const rows = await this.db
 | 
					            const rows = await this.db
 | 
				
			||||||
                .select(EVENT_COLUMNS)
 | 
					                .select(EVENT_COLUMNS)
 | 
				
			||||||
                .from('events')
 | 
					                .from('events')
 | 
				
			||||||
@ -36,9 +42,13 @@ class EventStore extends EventEmitter {
 | 
				
			|||||||
                .orderBy('created_at', 'desc');
 | 
					                .orderBy('created_at', 'desc');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return rows.map(this.rowToEvent);
 | 
					            return rows.map(this.rowToEvent);
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getEventsFilterByName(name) {
 | 
					    async getEventsFilterByName(name) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const rows = await this.db
 | 
					            const rows = await this.db
 | 
				
			||||||
                .select(EVENT_COLUMNS)
 | 
					                .select(EVENT_COLUMNS)
 | 
				
			||||||
                .from('events')
 | 
					                .from('events')
 | 
				
			||||||
@ -53,8 +63,10 @@ class EventStore extends EventEmitter {
 | 
				
			|||||||
                        .where({ type: DROP_FEATURES }),
 | 
					                        .where({ type: DROP_FEATURES }),
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
                .orderBy('created_at', 'desc');
 | 
					                .orderBy('created_at', 'desc');
 | 
				
			||||||
 | 
					 | 
				
			||||||
            return rows.map(this.rowToEvent);
 | 
					            return rows.map(this.rowToEvent);
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    rowToEvent(row) {
 | 
					    rowToEvent(row) {
 | 
				
			||||||
 | 
				
			|||||||
@ -57,7 +57,13 @@ class MetricsMonitor {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        async function collectFeatureToggleMetrics() {
 | 
					        async function collectFeatureToggleMetrics() {
 | 
				
			||||||
            featureTogglesTotal.reset();
 | 
					            featureTogglesTotal.reset();
 | 
				
			||||||
            const togglesCount = await featureToggleStore.count();
 | 
					            let togglesCount;
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
 | 
					                togglesCount = await featureToggleStore.count();
 | 
				
			||||||
 | 
					                // eslint-disable-next-line no-empty
 | 
				
			||||||
 | 
					            } catch (e) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            togglesCount = togglesCount || 0;
 | 
				
			||||||
            featureTogglesTotal.labels(version).set(togglesCount);
 | 
					            featureTogglesTotal.labels(version).set(togglesCount);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -105,14 +111,14 @@ class MetricsMonitor {
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.configureDbMetrics(stores, eventStore);
 | 
					        this.configureDbMetrics(stores, eventBus);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    stopMonitoring() {
 | 
					    stopMonitoring() {
 | 
				
			||||||
        clearInterval(this.timer);
 | 
					        clearInterval(this.timer);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    configureDbMetrics(stores, eventStore) {
 | 
					    configureDbMetrics(stores, eventBus) {
 | 
				
			||||||
        if (stores.db && stores.db.client) {
 | 
					        if (stores.db && stores.db.client) {
 | 
				
			||||||
            const dbPoolMin = new client.Gauge({
 | 
					            const dbPoolMin = new client.Gauge({
 | 
				
			||||||
                name: 'db_pool_min',
 | 
					                name: 'db_pool_min',
 | 
				
			||||||
@ -143,29 +149,31 @@ class MetricsMonitor {
 | 
				
			|||||||
                    'how many acquires are waiting for a resource to be released in DB pool',
 | 
					                    'how many acquires are waiting for a resource to be released in DB pool',
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            eventStore.on(DB_POOL_UPDATE, data => {
 | 
					            eventBus.on(DB_POOL_UPDATE, data => {
 | 
				
			||||||
                dbPoolFree.set(data.free);
 | 
					                dbPoolFree.set(data.free);
 | 
				
			||||||
                dbPoolUsed.set(data.used);
 | 
					                dbPoolUsed.set(data.used);
 | 
				
			||||||
                dbPoolPendingCreates.set(data.pendingCreates);
 | 
					                dbPoolPendingCreates.set(data.pendingCreates);
 | 
				
			||||||
                dbPoolPendingAcquires.set(data.pendingAcquires);
 | 
					                dbPoolPendingAcquires.set(data.pendingAcquires);
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.registerPoolMetrics(stores.db.client.pool, eventStore);
 | 
					            this.registerPoolMetrics(stores.db.client.pool, eventBus);
 | 
				
			||||||
            setInterval(
 | 
					            setInterval(
 | 
				
			||||||
                () =>
 | 
					                () => this.registerPoolMetrics(stores.db.client.pool, eventBus),
 | 
				
			||||||
                    this.registerPoolMetrics(stores.db.client.pool, eventStore),
 | 
					 | 
				
			||||||
                ONE_MINUTE,
 | 
					                ONE_MINUTE,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    registerPoolMetrics(pool, eventStore) {
 | 
					    registerPoolMetrics(pool, eventBus) {
 | 
				
			||||||
        eventStore.emit(DB_POOL_UPDATE, {
 | 
					        try {
 | 
				
			||||||
 | 
					            eventBus.emit(DB_POOL_UPDATE, {
 | 
				
			||||||
                used: pool.numUsed(),
 | 
					                used: pool.numUsed(),
 | 
				
			||||||
                free: pool.numFree(),
 | 
					                free: pool.numFree(),
 | 
				
			||||||
                pendingCreates: pool.numPendingCreates(),
 | 
					                pendingCreates: pool.numPendingCreates(),
 | 
				
			||||||
                pendingAcquires: pool.numPendingAcquires(),
 | 
					                pendingAcquires: pool.numPendingAcquires(),
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					            // eslint-disable-next-line no-empty
 | 
				
			||||||
 | 
					        } catch (e) {}
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -15,7 +15,7 @@ const monitor = createMetricsMonitor();
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
test.before(() => {
 | 
					test.before(() => {
 | 
				
			||||||
    const featureToggleStore = {
 | 
					    const featureToggleStore = {
 | 
				
			||||||
        count: () => 123,
 | 
					        count: async () => 123,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    const config = {
 | 
					    const config = {
 | 
				
			||||||
        serverMetrics: true,
 | 
					        serverMetrics: true,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { handleErrors } from './util';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Controller = require('../controller');
 | 
					const Controller = require('../controller');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const extractUser = require('../../extract-user');
 | 
					const extractUser = require('../../extract-user');
 | 
				
			||||||
@ -16,8 +18,12 @@ class ArchiveController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getArchivedFeatures(req, res) {
 | 
					    async getArchivedFeatures(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const features = await this.featureService.getArchivedFeatures();
 | 
					            const features = await this.featureService.getArchivedFeatures();
 | 
				
			||||||
            res.json({ features });
 | 
					            res.json({ features });
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async reviveFeatureToggle(req, res) {
 | 
					    async reviveFeatureToggle(req, res) {
 | 
				
			||||||
 | 
				
			|||||||
@ -34,10 +34,14 @@ class ContextController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getContextFields(req, res) {
 | 
					    async getContextFields(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const fields = await this.contextService.getAll();
 | 
					            const fields = await this.contextService.getAll();
 | 
				
			||||||
            res.status(200)
 | 
					            res.status(200)
 | 
				
			||||||
                .json(fields)
 | 
					                .json(fields)
 | 
				
			||||||
                .end();
 | 
					                .end();
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getContextField(req, res) {
 | 
					    async getContextField(req, res) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { handleErrors } from './util';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Controller = require('../controller');
 | 
					const Controller = require('../controller');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const eventDiffer = require('../../event-differ');
 | 
					const eventDiffer = require('../../event-differ');
 | 
				
			||||||
@ -15,14 +17,21 @@ class EventController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getEvents(req, res) {
 | 
					    async getEvents(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const events = await this.eventStore.getEvents();
 | 
					            const events = await this.eventStore.getEvents();
 | 
				
			||||||
            eventDiffer.addDiffs(events);
 | 
					            eventDiffer.addDiffs(events);
 | 
				
			||||||
            res.json({ version, events });
 | 
					            res.json({ version, events });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getEventsForToggle(req, res) {
 | 
					    async getEventsForToggle(req, res) {
 | 
				
			||||||
        const toggleName = req.params.name;
 | 
					        const toggleName = req.params.name;
 | 
				
			||||||
        const events = await this.eventStore.getEventsFilterByName(toggleName);
 | 
					        try {
 | 
				
			||||||
 | 
					            const events = await this.eventStore.getEventsFilterByName(
 | 
				
			||||||
 | 
					                toggleName,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (events) {
 | 
					            if (events) {
 | 
				
			||||||
                eventDiffer.addDiffs(events);
 | 
					                eventDiffer.addDiffs(events);
 | 
				
			||||||
@ -30,6 +39,9 @@ class EventController extends Controller {
 | 
				
			|||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                res.status(404).json({ error: 'Could not find events' });
 | 
					                res.status(404).json({ error: 'Could not find events' });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { handleErrors } from './util';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Controller = require('../controller');
 | 
					const Controller = require('../controller');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const version = 1;
 | 
					const version = 1;
 | 
				
			||||||
@ -14,8 +16,12 @@ class FeatureTypeController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getAllFeatureTypes(req, res) {
 | 
					    async getAllFeatureTypes(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const types = await this.featureTypeStore.getAll();
 | 
					            const types = await this.featureTypeStore.getAll();
 | 
				
			||||||
            res.json({ version, types });
 | 
					            res.json({ version, types });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -49,11 +49,15 @@ class FeatureController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getAllToggles(req, res) {
 | 
					    async getAllToggles(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const features = await this.featureService.getFeatures(
 | 
					            const features = await this.featureService.getFeatures(
 | 
				
			||||||
                req.query,
 | 
					                req.query,
 | 
				
			||||||
                fields,
 | 
					                fields,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            res.json({ version, features });
 | 
					            res.json({ version, features });
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getToggle(req, res) {
 | 
					    async getToggle(req, res) {
 | 
				
			||||||
@ -67,8 +71,14 @@ class FeatureController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async listTags(req, res) {
 | 
					    async listTags(req, res) {
 | 
				
			||||||
        const tags = await this.featureService.listTags(req.params.featureName);
 | 
					        try {
 | 
				
			||||||
 | 
					            const tags = await this.featureService.listTags(
 | 
				
			||||||
 | 
					                req.params.featureName,
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            res.json({ version, tags });
 | 
					            res.json({ version, tags });
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async addTag(req, res) {
 | 
					    async addTag(req, res) {
 | 
				
			||||||
@ -89,12 +99,16 @@ class FeatureController extends Controller {
 | 
				
			|||||||
    async removeTag(req, res) {
 | 
					    async removeTag(req, res) {
 | 
				
			||||||
        const { featureName, type, value } = req.params;
 | 
					        const { featureName, type, value } = req.params;
 | 
				
			||||||
        const userName = extractUser(req);
 | 
					        const userName = extractUser(req);
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            await this.featureService.removeTag(
 | 
					            await this.featureService.removeTag(
 | 
				
			||||||
                featureName,
 | 
					                featureName,
 | 
				
			||||||
                { type, value },
 | 
					                { type, value },
 | 
				
			||||||
                userName,
 | 
					                userName,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            res.status(200).end();
 | 
					            res.status(200).end();
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async validate(req, res) {
 | 
					    async validate(req, res) {
 | 
				
			||||||
 | 
				
			|||||||
@ -16,9 +16,9 @@ const {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const eventBus = new EventEmitter();
 | 
					const eventBus = new EventEmitter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getSetup() {
 | 
					function getSetup(databaseIsUp = true) {
 | 
				
			||||||
    const base = `/random${Math.round(Math.random() * 1000)}`;
 | 
					    const base = `/random${Math.round(Math.random() * 1000)}`;
 | 
				
			||||||
    const stores = store.createStores();
 | 
					    const stores = store.createStores(databaseIsUp);
 | 
				
			||||||
    const perms = permissions();
 | 
					    const perms = permissions();
 | 
				
			||||||
    const config = {
 | 
					    const config = {
 | 
				
			||||||
        baseUriPath: base,
 | 
					        baseUriPath: base,
 | 
				
			||||||
@ -614,3 +614,9 @@ test('Tags should be included in updated events', async t => {
 | 
				
			|||||||
    t.is(events[0].tags[0].type, 'simple');
 | 
					    t.is(events[0].tags[0].type, 'simple');
 | 
				
			||||||
    t.is(events[0].tags[0].value, 'tag');
 | 
					    t.is(events[0].tags[0].value, 'tag');
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('Trying to get features while database is down should yield 500', t => {
 | 
				
			||||||
 | 
					    t.plan(0);
 | 
				
			||||||
 | 
					    const { request, base } = getSetup(false);
 | 
				
			||||||
 | 
					    return request.get(`${base}/api/admin/features`).expect(500);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -28,21 +28,34 @@ class MetricsController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getSeenToggles(req, res) {
 | 
					    async getSeenToggles(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const seenAppToggles = await this.metrics.getAppsWithToggles();
 | 
					            const seenAppToggles = await this.metrics.getAppsWithToggles();
 | 
				
			||||||
            res.json(seenAppToggles);
 | 
					            res.json(seenAppToggles);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getSeenApps(req, res) {
 | 
					    async getSeenApps(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const seenApps = await this.metrics.getSeenApps();
 | 
					            const seenApps = await this.metrics.getSeenApps();
 | 
				
			||||||
            res.json(seenApps);
 | 
					            res.json(seenApps);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getFeatureToggles(req, res) {
 | 
					    async getFeatureToggles(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const toggles = await this.metrics.getTogglesMetrics();
 | 
					            const toggles = await this.metrics.getTogglesMetrics();
 | 
				
			||||||
            res.json(toggles);
 | 
					            res.json(toggles);
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getFeatureToggle(req, res) {
 | 
					    async getFeatureToggle(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const { name } = req.params;
 | 
					            const { name } = req.params;
 | 
				
			||||||
            const data = await this.metrics.getTogglesMetrics();
 | 
					            const data = await this.metrics.getTogglesMetrics();
 | 
				
			||||||
            const lastHour = data.lastHour[name] || {};
 | 
					            const lastHour = data.lastHour[name] || {};
 | 
				
			||||||
@ -51,6 +64,9 @@ class MetricsController extends Controller {
 | 
				
			|||||||
                lastHour,
 | 
					                lastHour,
 | 
				
			||||||
                lastMinute,
 | 
					                lastMinute,
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async deleteApplication(req, res) {
 | 
					    async deleteApplication(req, res) {
 | 
				
			||||||
 | 
				
			|||||||
@ -36,8 +36,12 @@ class StrategyController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getAllStratgies(req, res) {
 | 
					    async getAllStratgies(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const strategies = await this.strategyService.getStrategies();
 | 
					            const strategies = await this.strategyService.getStrategies();
 | 
				
			||||||
            res.json({ version, strategies });
 | 
					            res.json({ version, strategies });
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, err);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getStrategy(req, res) {
 | 
					    async getStrategy(req, res) {
 | 
				
			||||||
 | 
				
			|||||||
@ -16,10 +16,10 @@ const {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const eventBus = new EventEmitter();
 | 
					const eventBus = new EventEmitter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getSetup() {
 | 
					function getSetup(databaseIsUp = true) {
 | 
				
			||||||
    const base = `/random${Math.round(Math.random() * 1000)}`;
 | 
					    const base = `/random${Math.round(Math.random() * 1000)}`;
 | 
				
			||||||
    const perms = permissions();
 | 
					    const perms = permissions();
 | 
				
			||||||
    const stores = store.createStores();
 | 
					    const stores = store.createStores(databaseIsUp);
 | 
				
			||||||
    const config = {
 | 
					    const config = {
 | 
				
			||||||
        baseUriPath: base,
 | 
					        baseUriPath: base,
 | 
				
			||||||
        stores,
 | 
					        stores,
 | 
				
			||||||
@ -261,3 +261,9 @@ test(`deprecating 'default' strategy will yield 403`, t => {
 | 
				
			|||||||
        .set('Content-Type', 'application/json')
 | 
					        .set('Content-Type', 'application/json')
 | 
				
			||||||
        .expect(403);
 | 
					        .expect(403);
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('Getting strategies while database is down should yield 500', t => {
 | 
				
			||||||
 | 
					    t.plan(0);
 | 
				
			||||||
 | 
					    const { request, base } = getSetup(false);
 | 
				
			||||||
 | 
					    return request.get(`${base}/api/admin/strategies`).expect(500);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -22,8 +22,12 @@ class TagTypeController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getTagTypes(req, res) {
 | 
					    async getTagTypes(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const tagTypes = await this.tagTypeService.getAll();
 | 
					            const tagTypes = await this.tagTypeService.getAll();
 | 
				
			||||||
            res.json({ version, tagTypes });
 | 
					            res.json({ version, tagTypes });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async validate(req, res) {
 | 
					    async validate(req, res) {
 | 
				
			||||||
 | 
				
			|||||||
@ -22,13 +22,21 @@ class TagController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getTags(req, res) {
 | 
					    async getTags(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const tags = await this.tagService.getTags();
 | 
					            const tags = await this.tagService.getTags();
 | 
				
			||||||
            res.json({ version, tags });
 | 
					            res.json({ version, tags });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getTagsByType(req, res) {
 | 
					    async getTagsByType(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const tags = await this.tagService.getTagsByType(req.params.type);
 | 
					            const tags = await this.tagService.getTagsByType(req.params.type);
 | 
				
			||||||
            res.json({ version, tags });
 | 
					            res.json({ version, tags });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getTag(req, res) {
 | 
					    async getTag(req, res) {
 | 
				
			||||||
 | 
				
			|||||||
@ -12,9 +12,9 @@ const { UPDATE_FEATURE } = require('../../permissions');
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const eventBus = new EventEmitter();
 | 
					const eventBus = new EventEmitter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getSetup() {
 | 
					function getSetup(databaseIsUp = true) {
 | 
				
			||||||
    const base = `/random${Math.round(Math.random() * 1000)}`;
 | 
					    const base = `/random${Math.round(Math.random() * 1000)}`;
 | 
				
			||||||
    const stores = store.createStores();
 | 
					    const stores = store.createStores(databaseIsUp);
 | 
				
			||||||
    const perms = permissions();
 | 
					    const perms = permissions();
 | 
				
			||||||
    const config = {
 | 
					    const config = {
 | 
				
			||||||
        baseUriPath: base,
 | 
					        baseUriPath: base,
 | 
				
			||||||
@ -118,3 +118,9 @@ test('should be able to filter by type', t => {
 | 
				
			|||||||
            t.is(res.body.tags[0].value, 'TeamRed');
 | 
					            t.is(res.body.tags[0].value, 'TeamRed');
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('Getting tags while database is down should be a 500', t => {
 | 
				
			||||||
 | 
					    t.plan(0);
 | 
				
			||||||
 | 
					    const { request, base } = getSetup(false);
 | 
				
			||||||
 | 
					    return request.get(`${base}/api/admin/tags`).expect(500);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { handleErrors } from '../admin-api/util';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Controller = require('../controller');
 | 
					const Controller = require('../controller');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const version = 1;
 | 
					const version = 1;
 | 
				
			||||||
@ -23,11 +25,15 @@ class FeatureController extends Controller {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getAll(req, res) {
 | 
					    async getAll(req, res) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
            const features = await this.toggleService.getFeatures(
 | 
					            const features = await this.toggleService.getFeatures(
 | 
				
			||||||
                req.query,
 | 
					                req.query,
 | 
				
			||||||
                FEATURE_COLUMNS_CLIENT,
 | 
					                FEATURE_COLUMNS_CLIENT,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
            res.json({ version, features });
 | 
					            res.json({ version, features });
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					            handleErrors(res, this.logger, e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getFeatureToggle(req, res) {
 | 
					    async getFeatureToggle(req, res) {
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,15 @@
 | 
				
			|||||||
'use strict';
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = () => {
 | 
					module.exports = (databaseIsUp = true) => {
 | 
				
			||||||
    const _features = [];
 | 
					    const _features = [];
 | 
				
			||||||
    const _archive = [];
 | 
					    const _archive = [];
 | 
				
			||||||
    const _featureTags = {};
 | 
					    const _featureTags = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        getFeature: name => {
 | 
					        getFeature: name => {
 | 
				
			||||||
 | 
					            if (!databaseIsUp) {
 | 
				
			||||||
 | 
					                return Promise.reject(new Error('No database connection'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            const toggle = _features.find(f => f.name === name);
 | 
					            const toggle = _features.find(f => f.name === name);
 | 
				
			||||||
            if (toggle) {
 | 
					            if (toggle) {
 | 
				
			||||||
                return Promise.resolve(toggle);
 | 
					                return Promise.resolve(toggle);
 | 
				
			||||||
@ -63,6 +66,9 @@ module.exports = () => {
 | 
				
			|||||||
        },
 | 
					        },
 | 
				
			||||||
        importFeature: feat => Promise.resolve(_features.push(feat)),
 | 
					        importFeature: feat => Promise.resolve(_features.push(feat)),
 | 
				
			||||||
        getFeatures: query => {
 | 
					        getFeatures: query => {
 | 
				
			||||||
 | 
					            if (!databaseIsUp) {
 | 
				
			||||||
 | 
					                return Promise.reject(new Error('No database connection'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            if (query) {
 | 
					            if (query) {
 | 
				
			||||||
                const activeQueryKeys = Object.keys(query).filter(
 | 
					                const activeQueryKeys = Object.keys(query).filter(
 | 
				
			||||||
                    t => query[t],
 | 
					                    t => query[t],
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								src/test/fixtures/fake-strategies-store.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								src/test/fixtures/fake-strategies-store.js
									
									
									
									
										vendored
									
									
								
							@ -2,13 +2,18 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const NotFoundError = require('../../lib/error/notfound-error');
 | 
					const NotFoundError = require('../../lib/error/notfound-error');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = () => {
 | 
					module.exports = (databaseIsUp = true) => {
 | 
				
			||||||
    const _strategies = [
 | 
					    const _strategies = [
 | 
				
			||||||
        { name: 'default', editable: false, parameters: {}, deprecated: false },
 | 
					        { name: 'default', editable: false, parameters: {}, deprecated: false },
 | 
				
			||||||
    ];
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        getStrategies: () => Promise.resolve(_strategies),
 | 
					        getStrategies: () => {
 | 
				
			||||||
 | 
					            if (databaseIsUp) {
 | 
				
			||||||
 | 
					                return Promise.resolve(_strategies);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return Promise.reject(new Error('No database connection'));
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        getEditableStrategies: () =>
 | 
					        getEditableStrategies: () =>
 | 
				
			||||||
            Promise.resolve(_strategies.filter(s => s.editable)),
 | 
					            Promise.resolve(_strategies.filter(s => s.editable)),
 | 
				
			||||||
        getStrategy: name => {
 | 
					        getStrategy: name => {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										12
									
								
								src/test/fixtures/fake-tag-store.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								src/test/fixtures/fake-tag-store.js
									
									
									
									
										vendored
									
									
								
							@ -1,9 +1,12 @@
 | 
				
			|||||||
const NotFoundError = require('../../lib/error/notfound-error');
 | 
					const NotFoundError = require('../../lib/error/notfound-error');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = () => {
 | 
					module.exports = (databaseIsUp = true) => {
 | 
				
			||||||
    const _tags = [];
 | 
					    const _tags = [];
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        getTagsByType: type => {
 | 
					        getTagsByType: type => {
 | 
				
			||||||
 | 
					            if (!databaseIsUp) {
 | 
				
			||||||
 | 
					                return Promise.reject(new Error('No database connection'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            const tags = _tags.filter(t => t.type === type);
 | 
					            const tags = _tags.filter(t => t.type === type);
 | 
				
			||||||
            return Promise.resolve(tags);
 | 
					            return Promise.resolve(tags);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@ -18,7 +21,12 @@ module.exports = () => {
 | 
				
			|||||||
                1,
 | 
					                1,
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        getAll: () => Promise.resolve(_tags),
 | 
					        getAll: () => {
 | 
				
			||||||
 | 
					            if (!databaseIsUp) {
 | 
				
			||||||
 | 
					                return Promise.reject(new Error('No database connection'));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return Promise.resolve(_tags);
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        getTag: (type, value) => {
 | 
					        getTag: (type, value) => {
 | 
				
			||||||
            const tag = _tags.find(t => t.type === type && t.value === value);
 | 
					            const tag = _tags.find(t => t.type === type && t.value === value);
 | 
				
			||||||
            if (tag) {
 | 
					            if (tag) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										24
									
								
								src/test/fixtures/store.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								src/test/fixtures/store.js
									
									
									
									
										vendored
									
									
								
							@ -13,7 +13,7 @@ const settingStore = require('./fake-setting-store');
 | 
				
			|||||||
const addonStore = require('./fake-addon-store');
 | 
					const addonStore = require('./fake-addon-store');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    createStores: () => {
 | 
					    createStores: (databaseIsUp = true) => {
 | 
				
			||||||
        const db = {
 | 
					        const db = {
 | 
				
			||||||
            select: () => ({
 | 
					            select: () => ({
 | 
				
			||||||
                from: () => Promise.resolve(),
 | 
					                from: () => Promise.resolve(),
 | 
				
			||||||
@ -22,17 +22,17 @@ module.exports = {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					        return {
 | 
				
			||||||
            db,
 | 
					            db,
 | 
				
			||||||
            clientApplicationsStore: clientApplicationsStore(),
 | 
					            clientApplicationsStore: clientApplicationsStore(databaseIsUp),
 | 
				
			||||||
            clientMetricsStore: new ClientMetricsStore(),
 | 
					            clientMetricsStore: new ClientMetricsStore(databaseIsUp),
 | 
				
			||||||
            clientInstanceStore: clientInstanceStore(),
 | 
					            clientInstanceStore: clientInstanceStore(databaseIsUp),
 | 
				
			||||||
            featureToggleStore: featureToggleStore(),
 | 
					            featureToggleStore: featureToggleStore(databaseIsUp),
 | 
				
			||||||
            tagStore: tagStore(),
 | 
					            tagStore: tagStore(databaseIsUp),
 | 
				
			||||||
            tagTypeStore: tagTypeStore(),
 | 
					            tagTypeStore: tagTypeStore(databaseIsUp),
 | 
				
			||||||
            eventStore: new EventStore(),
 | 
					            eventStore: new EventStore(databaseIsUp),
 | 
				
			||||||
            strategyStore: strategyStore(),
 | 
					            strategyStore: strategyStore(databaseIsUp),
 | 
				
			||||||
            contextFieldStore: contextFieldStore(),
 | 
					            contextFieldStore: contextFieldStore(databaseIsUp),
 | 
				
			||||||
            settingStore: settingStore(),
 | 
					            settingStore: settingStore(databaseIsUp),
 | 
				
			||||||
            addonStore: addonStore(),
 | 
					            addonStore: addonStore(databaseIsUp),
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user