diff --git a/docs/api/metrics-api.md b/docs/api/metrics-api.md index e15e4b3dd6..da58a96e3e 100644 --- a/docs/api/metrics-api.md +++ b/docs/api/metrics-api.md @@ -178,6 +178,14 @@ a link to follow for more datails. } ``` +#### Query Params +You can also specify the query param: _strategyName_, which will return all applications +implementing the given strategy. + +`GET http://unleash.host.com/api/client/applications?strategyName=someStrategyName` + + + ### Application Details diff --git a/lib/db/app.js b/lib/db/app.js new file mode 100644 index 0000000000..72fc1bf848 --- /dev/null +++ b/lib/db/app.js @@ -0,0 +1,67 @@ +'use strict'; + +const express = require('express'); +const favicon = require('serve-favicon'); +const bodyParser = require('body-parser'); +const cookieParser = require('cookie-parser'); +const validator = require('express-validator'); +const responseTime = require('response-time'); +const log4js = require('log4js'); +const logger = require('./logger'); +const routes = require('./routes'); +const path = require('path'); +const errorHandler = require('errorhandler'); + +const { REQUEST_TIME } = require('./events'); + +module.exports = function (config) { + const app = express(); + + const baseUriPath = config.baseUriPath || ''; + const publicFolder = config.publicFolder; + + app.set('trust proxy'); + app.set('port', config.port); + app.locals.baseUriPath = baseUriPath; + app.use(cookieParser()); + + if (publicFolder) { + app.use(favicon(path.join(publicFolder, 'favicon.ico'))); + } + + app.use(responseTime((req, res, time) => { + const timingInfo = { path: req.path, method: req.method, statusCode: res.statusCode, time }; + config.eventBus.emit(REQUEST_TIME, timingInfo); + })); + + app.use(validator([])); + + if (publicFolder) { + app.use(baseUriPath, express.static(publicFolder)); + } + + app.use(bodyParser.json({ strict: false })); + + if (config.enableRequestLogger) { + app.use(log4js.connectLogger(logger, { + format: ':status :method :url :response-timems', + level: 'auto', // 3XX=WARN, 4xx/5xx=ERROR + })); + } + + // Setup API routes + const apiRouter = express.Router(); // eslint-disable-line new-cap + routes.createAPI(apiRouter, config); + app.use(`${baseUriPath}/api/`, apiRouter); + + // Setup deprecated routes + const router = express.Router(); // eslint-disable-line new-cap + routes.createLegacy(router, config); + app.use(baseUriPath, router); + + if (process.env.NODE_ENV !== 'production') { + app.use(errorHandler()); + } + + return app; +}; diff --git a/lib/db/client-strategy-store.js b/lib/db/client-strategy-store.js index 35287ffa52..68ae5d049e 100644 --- a/lib/db/client-strategy-store.js +++ b/lib/db/client-strategy-store.js @@ -56,7 +56,30 @@ class ClientStrategyStore { .where('app_name', appName) .from(TABLE) .map((row) => row.strategies) - .then(arr => arr[0]) + .then(arr => arr[0]); + } + + /** + * Could also be done in SQL: + * (not sure if it is faster though) + * + * SELECT app_name from ( + * SELECT app_name, json_array_elements(strategies)::text as strategyName from client_strategies + * ) as foo + * WHERE foo.strategyName = '"other"'; + */ + getAppsForStrategy (strategyName) { + return this.getAll() + .then(apps => apps + .filter(app => app.strategies.includes(strategyName)) + .map(app => app.appName)); + } + + getApplications () { + return this.db + .select('app_name') + .from(TABLE) + .map((row) => row.app_name); } }; diff --git a/lib/routes/metrics.js b/lib/routes/metrics.js index 81298ad910..1a575c94c4 100644 --- a/lib/routes/metrics.js +++ b/lib/routes/metrics.js @@ -100,9 +100,18 @@ module.exports = function (app, config) { }); app.get('/client/applications/', (req, res) => { - clientInstanceStore.getApplications() + const strategyName = req.query.strategyName; + let appsPromise; + + if (strategyName) { + appsPromise = clientStrategyStore.getAppsForStrategy(strategyName); + } else { + appsPromise = clientStrategyStore.getApplications(); + } + + appsPromise .then(apps => { - const applications = apps.map(({ appName }) => ({ + const applications = apps.map(appName => ({ appName, links: { appDetails: `/api/client/applications/${appName}`, diff --git a/test/e2e/metrics-api.test.js b/test/e2e/metrics-api.test.js index 3b90f1b643..9f3562c0df 100644 --- a/test/e2e/metrics-api.test.js +++ b/test/e2e/metrics-api.test.js @@ -92,7 +92,7 @@ test.serial('should get list of applications', async t => { .expect('Content-Type', /json/) .expect((res) => { t.true(res.status === 200); - t.true(res.body.applications.length === 2); + t.true(res.body.applications.length === 1); }) .then(destroy); }); diff --git a/test/unit/routes/fixtures/fake-client-strategy-store.js b/test/unit/routes/fixtures/fake-client-strategy-store.js index 2666aed691..f2ce28bc25 100644 --- a/test/unit/routes/fixtures/fake-client-strategy-store.js +++ b/test/unit/routes/fixtures/fake-client-strategy-store.js @@ -1,6 +1,14 @@ 'use strict'; -module.exports = () => ({ - insert: () => Promise.resolve(), - getAll: () => Promise.resolve([]) -}); +module.exports = () => { + const _instances = []; + + return { + insert: () => { + _instances.push(); + return Promise.resolve(); + }, + getAll: () => Promise.resolve(_instances), + getApplications: () => Promise.resolve([]), + }; +};