2021-08-12 15:04:37 +02:00
|
|
|
import EventEmitter from 'events';
|
|
|
|
import NotFoundError from '../error/notfound-error';
|
|
|
|
import {
|
|
|
|
IClientApplication,
|
|
|
|
IClientApplicationsStore,
|
|
|
|
} from '../types/stores/client-applications-store';
|
|
|
|
import { Logger, LogProvider } from '../logger';
|
|
|
|
import { IApplicationQuery } from '../types/query';
|
2023-01-30 09:02:44 +01:00
|
|
|
import { Db } from './db';
|
2024-02-22 07:20:57 +01:00
|
|
|
import { IApplicationOverview } from '../features/metrics/instance/models';
|
2016-12-06 09:19:15 +01:00
|
|
|
|
2017-06-28 10:17:14 +02:00
|
|
|
const COLUMNS = [
|
|
|
|
'app_name',
|
|
|
|
'created_at',
|
2021-03-05 14:01:15 +01:00
|
|
|
'created_by',
|
2017-06-28 10:17:14 +02:00
|
|
|
'updated_at',
|
|
|
|
'description',
|
|
|
|
'strategies',
|
|
|
|
'url',
|
|
|
|
'color',
|
|
|
|
'icon',
|
|
|
|
];
|
2016-12-06 09:19:15 +01:00
|
|
|
const TABLE = 'client_applications';
|
|
|
|
|
2023-08-21 13:36:31 +02:00
|
|
|
const TABLE_USAGE = 'client_applications_usage';
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
const mapRow: (any) => IClientApplication = (row) => ({
|
2016-12-06 09:19:15 +01:00
|
|
|
appName: row.app_name,
|
|
|
|
createdAt: row.created_at,
|
|
|
|
updatedAt: row.updated_at,
|
|
|
|
description: row.description,
|
2021-04-09 11:16:06 +02:00
|
|
|
strategies: row.strategies || [],
|
2021-03-05 14:01:15 +01:00
|
|
|
createdBy: row.created_by,
|
2016-12-06 09:19:15 +01:00
|
|
|
url: row.url,
|
|
|
|
color: row.color,
|
|
|
|
icon: row.icon,
|
2021-08-12 15:04:37 +02:00
|
|
|
lastSeen: row.last_seen,
|
|
|
|
announced: row.announced,
|
2023-08-23 11:00:22 +02:00
|
|
|
project: row.project,
|
|
|
|
environment: row.environment,
|
2016-12-06 09:19:15 +01:00
|
|
|
});
|
|
|
|
|
2023-08-23 11:00:22 +02:00
|
|
|
const reduceRows = (rows: any[]): IClientApplication[] => {
|
|
|
|
const appsObj = rows.reduce((acc, row) => {
|
|
|
|
// extracting project and environment from usage table
|
|
|
|
const { project, environment } = row;
|
|
|
|
const existingApp = acc[row.app_name];
|
|
|
|
|
|
|
|
if (existingApp) {
|
|
|
|
const existingProject = existingApp.usage.find(
|
|
|
|
(usage) => usage.project === project,
|
|
|
|
);
|
|
|
|
|
|
|
|
if (existingProject) {
|
|
|
|
existingProject.environments.push(environment);
|
|
|
|
} else {
|
|
|
|
existingApp.usage.push({
|
|
|
|
project: project,
|
|
|
|
environments: [environment],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
acc[row.app_name] = {
|
|
|
|
...mapRow(row),
|
|
|
|
usage:
|
|
|
|
project && environment
|
|
|
|
? [{ project, environments: [environment] }]
|
|
|
|
: [],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, {});
|
|
|
|
|
|
|
|
return Object.values(appsObj);
|
|
|
|
};
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
const remapRow = (input) => {
|
2021-03-23 12:43:33 +01:00
|
|
|
const temp = {
|
|
|
|
app_name: input.appName,
|
|
|
|
updated_at: input.updatedAt || new Date(),
|
|
|
|
seen_at: input.lastSeen || new Date(),
|
|
|
|
description: input.description,
|
|
|
|
created_by: input.createdBy,
|
|
|
|
announced: input.announced,
|
|
|
|
url: input.url,
|
|
|
|
color: input.color,
|
|
|
|
icon: input.icon,
|
|
|
|
strategies: JSON.stringify(input.strategies),
|
|
|
|
};
|
2021-08-12 15:04:37 +02:00
|
|
|
Object.keys(temp).forEach((k) => {
|
2021-03-23 12:43:33 +01:00
|
|
|
if (temp[k] === undefined) {
|
|
|
|
// not using !temp[k] to allow false and null values to get through
|
|
|
|
delete temp[k];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return temp;
|
|
|
|
};
|
2016-12-06 09:19:15 +01:00
|
|
|
|
2023-08-21 13:36:31 +02:00
|
|
|
const remapUsageRow = (input) => {
|
|
|
|
return {
|
|
|
|
app_name: input.appName,
|
|
|
|
project: input.project || '*',
|
|
|
|
environment: input.environment || '*',
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
chore(deps): update dependency @biomejs/biome to v1.4.1 (#5709)
[![Mend
Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)
This PR contains the following updates:
| Package | Change | Age | Adoption | Passing | Confidence |
|---|---|---|---|---|---|
| [@biomejs/biome](https://biomejs.dev)
([source](https://togithub.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome))
| [`1.4.0` ->
`1.4.1`](https://renovatebot.com/diffs/npm/@biomejs%2fbiome/1.4.0/1.4.1)
|
[![age](https://developer.mend.io/api/mc/badges/age/npm/@biomejs%2fbiome/1.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![adoption](https://developer.mend.io/api/mc/badges/adoption/npm/@biomejs%2fbiome/1.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![passing](https://developer.mend.io/api/mc/badges/compatibility/npm/@biomejs%2fbiome/1.4.0/1.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
[![confidence](https://developer.mend.io/api/mc/badges/confidence/npm/@biomejs%2fbiome/1.4.0/1.4.1?slim=true)](https://docs.renovatebot.com/merge-confidence/)
|
---
### Release Notes
<details>
<summary>biomejs/biome (@​biomejs/biome)</summary>
###
[`v1.4.1`](https://togithub.com/biomejs/biome/blob/HEAD/CHANGELOG.md#141-2023-11-30)
[Compare
Source](https://togithub.com/biomejs/biome/compare/889593e3f983a6fec642d20eea3c7f94d58fc7e1...a88751306242058374575b9f511e3c22213032b6)
##### Editors
- Fix [#​933](https://togithub.com/biomejs/biome/issues/933). Some
files are properly ignored in the LSP too. E.g. `package.json`,
`tsconfig.json`, etc.
##### Formatter
##### Bug fixes
- Fix some accidental line breaks when printing array expressions within
arrow functions and other long lines
[#​917](https://togithub.com/biomejs/biome/pull/917). Contributed
by [@​faultyserver](https://togithub.com/faultyserver)
- Match Prettier's breaking strategy for `ArrowChain` layouts
[#​934](https://togithub.com/biomejs/biome/pull/934). Contributed
by [@​faultyserver](https://togithub.com/faultyserver)
- Fix double-printing of leading comments in arrow chain expressions
[#​951](https://togithub.com/biomejs/biome/pull/951). Contributed
by [@​faultyserver](https://togithub.com/faultyserver)
##### Linter
##### Bug fixes
- Fix [#​910](https://togithub.com/biomejs/biome/issues/910),
where the rule `noSvgWithoutTitle` should skip elements that have
`aria-hidden` attributes. Contributed by
[@​vasucp1207](https://togithub.com/vasucp1207)
##### New features
- Add [useForOf](https://biomejs.dev/linter/rules/use-for-of) rule.
The rule recommends a for-of loop when the loop index is only used to
read from an array that is being iterated.
Contributed by [@​victor-teles](https://togithub.com/victor-teles)
##### Enhancement
- Implements
[#​924](https://togithub.com/biomejs/biome/issues/924) and
[#​920](https://togithub.com/biomejs/biome/issues/920).
[noUselessElse](https://biomejs.dev/linter/rules/no-useless-else) now
ignores `else` clauses that follow at least one `if` statement that
doesn't break early. Contributed by
[@​Conaclos](https://togithub.com/Conaclos)
For example, the following code is no longer reported by the rule:
```js
function f(x) {
if (x < 0) {
// this `if` doesn't break early.
} else if (x > 0) {
return x;
} else {
// This `else` block was previously reported as useless.
}
}
```
##### Bug fixes
- Fix [#​918](https://togithub.com/biomejs/biome/issues/918),
[useSimpleNumberKeys](https://biomejs.dev/linter/rules/use-simple-number-keys)
no longer repports false positive on comments. Contributed by
[@​kalleep](https://togithub.com/kalleep)
- Fix [#​953](https://togithub.com/biomejs/biome/issues/953),
[noRedeclare](https://biomejs.dev/linter/rules/no-redeclare) no longer
reports type parameters with the same name in different mapped types as
redeclarations. Contributed by
[@​Conaclos](https://togithub.com/Conaclos)
- Fix [#​608](https://togithub.com/biomejs/biome/issues/608),
[useExhaustiveDependencies](https://biomejs.dev/linter/rules/use-exhaustive-dependencies)
no longer repports missing dependencies for React hooks without
dependency array. Contributed by
[@​kalleep](https://togithub.com/kalleep)
##### Parser
</details>
---
### Configuration
📅 **Schedule**: Branch creation - "after 7pm every weekday,before 5am
every weekday" in timezone Europe/Madrid, Automerge - At any time (no
schedule defined).
🚦 **Automerge**: Enabled.
♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the
rebase/retry checkbox.
🔕 **Ignore**: Close this PR and you won't be reminded about this update
again.
---
- [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check
this box
---
This PR has been generated by [Mend
Renovate](https://www.mend.io/free-developer-tools/renovate/). View
repository job log
[here](https://developer.mend.io/github/Unleash/unleash).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNy4xMDMuMSIsInVwZGF0ZWRJblZlciI6IjM3LjEyNy4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiJ9-->
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: David Leek <david@getunleash.io>
2024-01-10 10:11:49 +01:00
|
|
|
export default class ClientApplicationsStore
|
|
|
|
implements IClientApplicationsStore
|
|
|
|
{
|
2023-01-30 09:02:44 +01:00
|
|
|
private db: Db;
|
2021-08-12 15:04:37 +02:00
|
|
|
|
|
|
|
private logger: Logger;
|
|
|
|
|
2023-09-11 12:28:43 +02:00
|
|
|
constructor(db: Db, eventBus: EventEmitter, getLogger: LogProvider) {
|
2016-12-06 09:19:15 +01:00
|
|
|
this.db = db;
|
2021-08-12 15:04:37 +02:00
|
|
|
this.logger = getLogger('client-applications-store.ts');
|
2016-12-06 09:19:15 +01:00
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async upsert(details: Partial<IClientApplication>): Promise<void> {
|
2021-03-23 12:43:33 +01:00
|
|
|
const row = remapRow(details);
|
2021-08-12 15:04:37 +02:00
|
|
|
await this.db(TABLE).insert(row).onConflict('app_name').merge();
|
2023-08-21 13:36:31 +02:00
|
|
|
const usageRow = remapUsageRow(details);
|
|
|
|
await this.db(TABLE_USAGE)
|
|
|
|
.insert(usageRow)
|
|
|
|
.onConflict(['app_name', 'project', 'environment'])
|
|
|
|
.merge();
|
2016-12-06 09:19:15 +01:00
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async bulkUpsert(apps: Partial<IClientApplication>[]): Promise<void> {
|
2021-03-04 12:54:13 +01:00
|
|
|
const rows = apps.map(remapRow);
|
2023-08-21 13:36:31 +02:00
|
|
|
const usageRows = apps.map(remapUsageRow);
|
2021-08-12 15:04:37 +02:00
|
|
|
await this.db(TABLE).insert(rows).onConflict('app_name').merge();
|
2023-08-21 13:36:31 +02:00
|
|
|
await this.db(TABLE_USAGE)
|
|
|
|
.insert(usageRows)
|
|
|
|
.onConflict(['app_name', 'project', 'environment'])
|
|
|
|
.merge();
|
2016-12-06 09:19:15 +01:00
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async exists(appName: string): Promise<boolean> {
|
2021-03-04 12:54:13 +01:00
|
|
|
const result = await this.db.raw(
|
2021-08-12 15:04:37 +02:00
|
|
|
`SELECT EXISTS(SELECT 1 FROM ${TABLE} WHERE app_name = ?) AS present`,
|
2021-03-04 12:54:13 +01:00
|
|
|
[appName],
|
|
|
|
);
|
|
|
|
const { present } = result.rows[0];
|
|
|
|
return present;
|
2016-12-06 09:19:15 +01:00
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async getAll(): Promise<IClientApplication[]> {
|
2020-09-18 09:05:09 +02:00
|
|
|
const rows = await this.db
|
2017-11-02 09:23:38 +01:00
|
|
|
.select(COLUMNS)
|
|
|
|
.from(TABLE)
|
2020-09-18 09:05:09 +02:00
|
|
|
.orderBy('app_name', 'asc');
|
|
|
|
|
|
|
|
return rows.map(mapRow);
|
2016-12-09 17:30:12 +01:00
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async getApplication(appName: string): Promise<IClientApplication> {
|
2020-09-18 09:05:09 +02:00
|
|
|
const row = await this.db
|
2017-06-28 10:17:14 +02:00
|
|
|
.select(COLUMNS)
|
|
|
|
.where('app_name', appName)
|
|
|
|
.from(TABLE)
|
2020-09-18 09:05:09 +02:00
|
|
|
.first();
|
|
|
|
|
2021-01-06 13:25:25 +01:00
|
|
|
if (!row) {
|
|
|
|
throw new NotFoundError(`Could not find appName=${appName}`);
|
|
|
|
}
|
|
|
|
|
2020-09-18 09:05:09 +02:00
|
|
|
return mapRow(row);
|
2016-12-09 17:30:12 +01:00
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async deleteApplication(appName: string): Promise<void> {
|
|
|
|
return this.db(TABLE).where('app_name', appName).del();
|
2020-09-25 09:39:12 +02:00
|
|
|
}
|
|
|
|
|
2016-12-09 17:30:12 +01:00
|
|
|
/**
|
|
|
|
* 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"';
|
|
|
|
*/
|
2021-08-12 15:04:37 +02:00
|
|
|
async getAppsForStrategy(
|
|
|
|
query: IApplicationQuery,
|
|
|
|
): Promise<IClientApplication[]> {
|
2023-09-11 12:28:43 +02:00
|
|
|
const rows = await this.db
|
|
|
|
.select([
|
|
|
|
...COLUMNS.map((column) => `${TABLE}.${column}`),
|
|
|
|
'project',
|
|
|
|
'environment',
|
|
|
|
])
|
|
|
|
.from(TABLE)
|
|
|
|
.leftJoin(
|
|
|
|
TABLE_USAGE,
|
|
|
|
`${TABLE_USAGE}.app_name`,
|
|
|
|
`${TABLE}.app_name`,
|
|
|
|
);
|
|
|
|
const apps = reduceRows(rows);
|
2023-08-23 11:00:22 +02:00
|
|
|
|
2023-09-11 12:28:43 +02:00
|
|
|
if (query.strategyName) {
|
|
|
|
return apps.filter((app) =>
|
|
|
|
app.strategies.includes(query.strategyName),
|
|
|
|
);
|
2021-08-12 15:04:37 +02:00
|
|
|
}
|
2023-09-11 12:28:43 +02:00
|
|
|
return apps;
|
2016-12-09 17:30:12 +01:00
|
|
|
}
|
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async getUnannounced(): Promise<IClientApplication[]> {
|
2021-03-05 14:01:15 +01:00
|
|
|
const rows = await this.db(TABLE)
|
|
|
|
.select(COLUMNS)
|
|
|
|
.where('announced', false);
|
|
|
|
return rows.map(mapRow);
|
|
|
|
}
|
|
|
|
|
|
|
|
/** *
|
|
|
|
* Updates all rows that have announced = false to announced =true and returns the rows altered
|
|
|
|
* @return {[app]} - Apps that hadn't been announced
|
|
|
|
*/
|
2021-08-12 15:04:37 +02:00
|
|
|
async setUnannouncedToAnnounced(): Promise<IClientApplication[]> {
|
2021-03-05 14:01:15 +01:00
|
|
|
const rows = await this.db(TABLE)
|
|
|
|
.update({ announced: true })
|
|
|
|
.where('announced', false)
|
|
|
|
.whereNotNull('announced')
|
|
|
|
.returning(COLUMNS);
|
|
|
|
return rows.map(mapRow);
|
|
|
|
}
|
2016-12-06 09:19:15 +01:00
|
|
|
|
2021-08-12 15:04:37 +02:00
|
|
|
async delete(key: string): Promise<void> {
|
|
|
|
await this.db(TABLE).where('app_name', key).del();
|
|
|
|
}
|
|
|
|
|
|
|
|
async deleteAll(): Promise<void> {
|
|
|
|
await this.db(TABLE).del();
|
|
|
|
}
|
|
|
|
|
|
|
|
destroy(): void {}
|
|
|
|
|
|
|
|
async get(appName: string): Promise<IClientApplication> {
|
|
|
|
const row = await this.db
|
|
|
|
.select(COLUMNS)
|
|
|
|
.where('app_name', appName)
|
|
|
|
.from(TABLE)
|
|
|
|
.first();
|
|
|
|
|
|
|
|
if (!row) {
|
|
|
|
throw new NotFoundError(`Could not find appName=${appName}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return mapRow(row);
|
|
|
|
}
|
2024-02-22 07:20:57 +01:00
|
|
|
|
|
|
|
async getApplicationOverview(
|
|
|
|
appName: string,
|
|
|
|
): Promise<IApplicationOverview> {
|
|
|
|
const query = this.db
|
|
|
|
.select([
|
|
|
|
'f.project',
|
|
|
|
'cme.environment',
|
|
|
|
'cme.feature_name',
|
|
|
|
'ci.instance_id',
|
|
|
|
'ci.sdk_version',
|
|
|
|
'ci.last_seen',
|
|
|
|
])
|
|
|
|
.from({ a: 'client_applications' })
|
|
|
|
.leftJoin('client_metrics_env as cme', 'cme.app_name', 'a.app_name')
|
|
|
|
.leftJoin('features as f', 'cme.feature_name', 'f.name')
|
|
|
|
.leftJoin('client_instances as ci', function () {
|
|
|
|
this.on('ci.app_name', '=', 'cme.app_name').andOn(
|
|
|
|
'ci.environment',
|
|
|
|
'=',
|
|
|
|
'cme.environment',
|
|
|
|
);
|
|
|
|
})
|
|
|
|
.where('a.app_name', appName);
|
|
|
|
|
|
|
|
const rows = await query;
|
|
|
|
if (!rows.length) {
|
|
|
|
throw new NotFoundError(`Could not find appName=${appName}`);
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.mapApplicationOverviewData(rows);
|
|
|
|
}
|
|
|
|
|
|
|
|
mapApplicationOverviewData(rows: any[]): IApplicationOverview {
|
|
|
|
const featureCount = new Set(rows.map((row) => row.feature_name)).size;
|
|
|
|
|
|
|
|
const environments = rows.reduce((acc, row) => {
|
|
|
|
const { environment, instance_id, sdk_version, last_seen } = row;
|
|
|
|
let env = acc.find((e) => e.name === environment);
|
|
|
|
if (!env) {
|
|
|
|
env = {
|
|
|
|
name: environment,
|
|
|
|
instanceCount: 1,
|
|
|
|
sdks: sdk_version ? [sdk_version] : [],
|
|
|
|
lastSeen: last_seen,
|
|
|
|
uniqueInstanceIds: new Set([instance_id]),
|
|
|
|
};
|
|
|
|
acc.push(env);
|
|
|
|
} else {
|
|
|
|
env.uniqueInstanceIds.add(instance_id);
|
|
|
|
env.instanceCount = env.uniqueInstanceIds.size;
|
|
|
|
if (sdk_version && !env.sdks.includes(sdk_version)) {
|
|
|
|
env.sdks.push(sdk_version);
|
|
|
|
}
|
|
|
|
if (new Date(last_seen) > new Date(env.lastSeen)) {
|
|
|
|
env.lastSeen = last_seen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return acc;
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
environments.forEach((env) => {
|
|
|
|
delete env.uniqueInstanceIds;
|
|
|
|
env.sdks.sort();
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
projects: [
|
|
|
|
...new Set(
|
|
|
|
rows
|
|
|
|
.filter((row) => row.project != null)
|
|
|
|
.map((row) => row.project),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
featureCount,
|
|
|
|
environments,
|
|
|
|
};
|
|
|
|
}
|
2021-08-12 15:04:37 +02:00
|
|
|
}
|