@stryker-mutator/dashboard-data-access 0.20.2 → 0.20.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  import type { TableEntityQueryOptions } from '@azure/data-tables';
2
2
  import type { ModelClass } from './ModelClass.js';
3
3
  export declare class DashboardQuery<TModel, TPartitionKeyFields extends keyof TModel, TRowKeyFields extends keyof TModel> {
4
+ #private;
4
5
  protected ModelClass: ModelClass<TModel, TPartitionKeyFields, TRowKeyFields>;
5
- private readonly whereConditions;
6
6
  private constructor();
7
7
  whereRowKeyNotEquals(rowKey: Pick<TModel, TRowKeyFields>): DashboardQuery<TModel, TPartitionKeyFields, TRowKeyFields>;
8
8
  wherePartitionKeyEquals(partitionKey: Pick<TModel, TPartitionKeyFields>): DashboardQuery<TModel, TPartitionKeyFields, TRowKeyFields>;
@@ -2,24 +2,24 @@ import { odata } from '@azure/data-tables';
2
2
  import { encodeKey } from '../utils.js';
3
3
  export class DashboardQuery {
4
4
  ModelClass;
5
- whereConditions;
5
+ #whereConditions;
6
6
  constructor(ModelClass, whereConditions) {
7
7
  this.ModelClass = ModelClass;
8
- this.whereConditions = whereConditions;
8
+ this.#whereConditions = whereConditions;
9
9
  }
10
10
  whereRowKeyNotEquals(rowKey) {
11
11
  const whereCondition = odata `RowKey ne ${encodeKey(this.ModelClass.createRowKey(rowKey) || '')}`;
12
- return new DashboardQuery(this.ModelClass, [...this.whereConditions, whereCondition]);
12
+ return new DashboardQuery(this.ModelClass, [...this.#whereConditions, whereCondition]);
13
13
  }
14
14
  wherePartitionKeyEquals(partitionKey) {
15
15
  const whereCondition = odata `PartitionKey eq ${encodeKey(this.ModelClass.createPartitionKey(partitionKey))}`;
16
- return new DashboardQuery(this.ModelClass, [...this.whereConditions, whereCondition]);
16
+ return new DashboardQuery(this.ModelClass, [...this.#whereConditions, whereCondition]);
17
17
  }
18
18
  static create(ModelClass) {
19
19
  return new DashboardQuery(ModelClass, []);
20
20
  }
21
21
  build() {
22
- return this.whereConditions.reduce((tableQuery, whereCondition, index) => {
22
+ return this.#whereConditions.reduce((tableQuery, whereCondition, index) => {
23
23
  if (index === 0) {
24
24
  return { filter: whereCondition };
25
25
  }
@@ -6,7 +6,6 @@ import type * as schema from 'mutation-testing-report-schema';
6
6
  */
7
7
  export declare class MutationTestingResultMapper {
8
8
  #private;
9
- private static readonly CONTAINER_NAME;
10
9
  constructor(blobService?: BlobServiceClient);
11
10
  createStorageIfNotExists(): Promise<ContainerCreateIfNotExistsResponse>;
12
11
  insertOrReplace(id: ReportIdentifier, result: schema.MutationTestResult | null): Promise<void>;
@@ -9,10 +9,10 @@ const errCodes = Object.freeze({
9
9
  * The report json part of a mutation testing report is stored in blob storage
10
10
  */
11
11
  export class MutationTestingResultMapper {
12
- static CONTAINER_NAME = 'mutation-testing-report';
12
+ static #CONTAINER_NAME = 'mutation-testing-report';
13
13
  #containerClient;
14
14
  constructor(blobService = createBlobServiceClient()) {
15
- this.#containerClient = blobService.getContainerClient(MutationTestingResultMapper.CONTAINER_NAME);
15
+ this.#containerClient = blobService.getContainerClient(MutationTestingResultMapper.#CONTAINER_NAME);
16
16
  }
17
17
  createStorageIfNotExists() {
18
18
  return this.#containerClient.createIfNotExists({});
@@ -4,7 +4,6 @@ import type { Mapper, Result } from './Mapper.js';
4
4
  import type { ModelClass } from './ModelClass.js';
5
5
  export default class TableStorageMapper<TModel extends object, TPartitionKeyFields extends keyof TModel, TRowKeyFields extends keyof TModel> implements Mapper<TModel, TPartitionKeyFields, TRowKeyFields> {
6
6
  #private;
7
- private readonly ModelClass;
8
7
  constructor(ModelClass: ModelClass<TModel, TPartitionKeyFields, TRowKeyFields>, tableClient?: TableClient);
9
8
  createStorageIfNotExists(): Promise<void>;
10
9
  insertOrMerge(model: TModel): Promise<void>;
@@ -19,7 +18,5 @@ export default class TableStorageMapper<TModel extends object, TPartitionKeyFiel
19
18
  replace(model: TModel, etag: string): Promise<Result<TModel>>;
20
19
  insert(model: TModel): Promise<Result<TModel>>;
21
20
  delete(identity: Pick<TModel, TPartitionKeyFields | TRowKeyFields>): Promise<void>;
22
- private toModel;
23
- private toEntity;
24
21
  }
25
22
  //# sourceMappingURL=TableStorageMapper.d.ts.map
@@ -8,23 +8,23 @@ const errCodes = Object.freeze({
8
8
  RESOURCE_NOT_FOUND: 'ResourceNotFound',
9
9
  });
10
10
  export default class TableStorageMapper {
11
- ModelClass;
11
+ #ModelClass;
12
12
  #tableClient;
13
13
  constructor(ModelClass, tableClient = createTableClient(ModelClass.name)) {
14
- this.ModelClass = ModelClass;
14
+ this.#ModelClass = ModelClass;
15
15
  this.#tableClient = tableClient;
16
16
  }
17
17
  async createStorageIfNotExists() {
18
18
  await this.#tableClient.createTable();
19
19
  }
20
20
  async insertOrMerge(model) {
21
- const entity = this.toEntity(model);
21
+ const entity = this.#toEntity(model);
22
22
  await this.#tableClient.upsertEntity(entity, 'Merge');
23
23
  }
24
24
  async findOne(identity) {
25
25
  try {
26
- const result = await this.#tableClient.getEntity(encodeKey(this.ModelClass.createPartitionKey(identity)), encodeKey(this.ModelClass.createRowKey(identity) || ''));
27
- return this.toModel(result);
26
+ const result = await this.#tableClient.getEntity(encodeKey(this.#ModelClass.createPartitionKey(identity)), encodeKey(this.#ModelClass.createRowKey(identity) || ''));
27
+ return this.#toModel(result);
28
28
  }
29
29
  catch (err) {
30
30
  if (hasErrorCode(err, errCodes.RESOURCE_NOT_FOUND)) {
@@ -36,12 +36,12 @@ export default class TableStorageMapper {
36
36
  }
37
37
  }
38
38
  }
39
- async findAll(query = DashboardQuery.create(this.ModelClass)) {
39
+ async findAll(query = DashboardQuery.create(this.#ModelClass)) {
40
40
  const tableQuery = query.build();
41
41
  const entities = this.#tableClient.listEntities({ queryOptions: tableQuery });
42
42
  const results = [];
43
43
  for await (const entity of entities) {
44
- results.push(this.toModel(entity));
44
+ results.push(this.#toModel(entity));
45
45
  }
46
46
  return results;
47
47
  }
@@ -52,7 +52,7 @@ export default class TableStorageMapper {
52
52
  * @throws {OptimisticConcurrencyError}
53
53
  */
54
54
  async replace(model, etag) {
55
- const entity = this.toEntity(model);
55
+ const entity = this.#toEntity(model);
56
56
  try {
57
57
  const result = await this.#tableClient.updateEntity(entity, 'Replace', { etag });
58
58
  return { model, etag: result.etag };
@@ -67,7 +67,7 @@ export default class TableStorageMapper {
67
67
  }
68
68
  }
69
69
  async insert(model) {
70
- const entity = this.toEntity(model);
70
+ const entity = this.#toEntity(model);
71
71
  try {
72
72
  const result = await this.#tableClient.createEntity(entity);
73
73
  return { model, etag: result.etag };
@@ -83,7 +83,7 @@ export default class TableStorageMapper {
83
83
  }
84
84
  async delete(identity) {
85
85
  try {
86
- await this.#tableClient.deleteEntity(encodeKey(this.ModelClass.createPartitionKey(identity)), encodeKey(this.ModelClass.createRowKey(identity) || ''));
86
+ await this.#tableClient.deleteEntity(encodeKey(this.#ModelClass.createPartitionKey(identity)), encodeKey(this.#ModelClass.createRowKey(identity) || ''));
87
87
  }
88
88
  catch (err) {
89
89
  if (hasErrorCode(err, errCodes.RESOURCE_NOT_FOUND)) {
@@ -94,23 +94,23 @@ export default class TableStorageMapper {
94
94
  }
95
95
  }
96
96
  }
97
- toModel(entity) {
98
- const value = new this.ModelClass();
99
- this.ModelClass.identify(value, decodeKey(entity.partitionKey), decodeKey(entity.rowKey));
100
- this.ModelClass.persistedFields.forEach((field) => (value[field] = entity[field]));
97
+ #toModel(entity) {
98
+ const value = new this.#ModelClass();
99
+ this.#ModelClass.identify(value, decodeKey(entity.partitionKey), decodeKey(entity.rowKey));
100
+ this.#ModelClass.persistedFields.forEach((field) => (value[field] = entity[field]));
101
101
  return {
102
102
  etag: entity.etag,
103
103
  model: value,
104
104
  };
105
105
  }
106
- toEntity(entity) {
106
+ #toEntity(entity) {
107
107
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
108
  const data = {
109
- partitionKey: encodeKey(this.ModelClass.createPartitionKey(entity)),
110
- rowKey: encodeKey(this.ModelClass.createRowKey(entity) || ''),
109
+ partitionKey: encodeKey(this.#ModelClass.createPartitionKey(entity)),
110
+ rowKey: encodeKey(this.#ModelClass.createRowKey(entity) || ''),
111
111
  };
112
112
  // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
113
- this.ModelClass.persistedFields.forEach((field) => (data[field] = entity[field]));
113
+ this.#ModelClass.persistedFields.forEach((field) => (data[field] = entity[field]));
114
114
  return data;
115
115
  }
116
116
  }
@@ -27,8 +27,8 @@ export class MutationTestingReport {
27
27
  }
28
28
  static identify(entity, partitionKeyValue, rowKeyValue) {
29
29
  const versionSplit = partitionKeyValue.lastIndexOf('/');
30
- entity.projectName = partitionKeyValue.substr(0, versionSplit);
31
- entity.version = partitionKeyValue.substr(versionSplit + 1);
30
+ entity.projectName = partitionKeyValue.substring(0, versionSplit);
31
+ entity.version = partitionKeyValue.substring(versionSplit + 1);
32
32
  entity.moduleName = rowKeyValue;
33
33
  }
34
34
  static persistedFields = ['mutationScore'];
@@ -3,17 +3,11 @@ import type { MutationTestResult } from 'mutation-testing-report-schema';
3
3
  import type { MutationTestingReportMapper } from '../mappers/index.js';
4
4
  import { MutationTestingResultMapper } from '../mappers/MutationTestingResultMapper.js';
5
5
  export declare class MutationTestingReportService {
6
- private readonly resultMapper;
7
- private readonly mutationScoreMapper;
6
+ #private;
8
7
  constructor(resultMapper?: MutationTestingResultMapper, mutationScoreMapper?: MutationTestingReportMapper);
9
8
  createStorageIfNotExists(): Promise<void>;
10
9
  saveReport(id: ReportIdentifier, result: MutationScoreOnlyResult | MutationTestResult, logger: Logger): Promise<void>;
11
- private aggregateProjectReport;
12
- private deleteModules;
13
- private tryAggregateProjectReport;
14
10
  findOne(id: ReportIdentifier): Promise<Report | null>;
15
11
  delete(id: ReportIdentifier, logger: Logger): Promise<void>;
16
- private insertOrMergeReport;
17
- private calculateMutationScore;
18
12
  }
19
13
  //# sourceMappingURL=MutationTestingReportService.d.ts.map
@@ -8,45 +8,45 @@ function moduleHasResult(tuple) {
8
8
  return !!tuple[1];
9
9
  }
10
10
  export class MutationTestingReportService {
11
- resultMapper;
12
- mutationScoreMapper;
13
- constructor(resultMapper = new MutationTestingResultMapper(), mutationScoreMapper = createMutationTestingReportMapper()) {
14
- this.resultMapper = resultMapper;
15
- this.mutationScoreMapper = mutationScoreMapper;
11
+ #resultMapper;
12
+ #mutationScoreMapper;
13
+ constructor(resultMapper, mutationScoreMapper) {
14
+ this.#resultMapper = resultMapper ?? new MutationTestingResultMapper();
15
+ this.#mutationScoreMapper = mutationScoreMapper ?? createMutationTestingReportMapper();
16
16
  }
17
17
  async createStorageIfNotExists() {
18
- await this.resultMapper.createStorageIfNotExists();
19
- await this.mutationScoreMapper.createStorageIfNotExists();
18
+ await this.#resultMapper.createStorageIfNotExists();
19
+ await this.#mutationScoreMapper.createStorageIfNotExists();
20
20
  }
21
21
  async saveReport(id, result, logger) {
22
- const mutationScore = this.calculateMutationScore(result);
23
- await this.insertOrMergeReport(id, {
22
+ const mutationScore = this.#calculateMutationScore(result);
23
+ await this.#insertOrMergeReport(id, {
24
24
  ...id,
25
25
  mutationScore,
26
26
  }, isMutationTestResult(result) ? result : null);
27
27
  if (isMutationTestResult(result) && id.moduleName) {
28
- await this.aggregateProjectReport(id.projectName, id.version, logger);
28
+ await this.#aggregateProjectReport(id.projectName, id.version, logger);
29
29
  }
30
30
  }
31
- async aggregateProjectReport(projectName, version, logger) {
31
+ async #aggregateProjectReport(projectName, version, logger) {
32
32
  const id = {
33
33
  projectName,
34
34
  version,
35
35
  moduleName: undefined,
36
36
  };
37
- while (!(await this.tryAggregateProjectReport(id))) {
37
+ while (!(await this.#tryAggregateProjectReport(id))) {
38
38
  logger.log({
39
39
  message: `Optimistic concurrency exception occurred while trying to aggregate the report ${JSON.stringify(id)}, retrying...`,
40
40
  });
41
41
  }
42
42
  }
43
- async deleteModules(projectName, version) {
43
+ async #deleteModules(projectName, version) {
44
44
  const id = {
45
45
  projectName,
46
46
  version,
47
47
  moduleName: undefined,
48
48
  };
49
- const modules = await this.mutationScoreMapper.findAll(DashboardQuery.create(MutationTestingReport)
49
+ const modules = await this.#mutationScoreMapper.findAll(DashboardQuery.create(MutationTestingReport)
50
50
  .wherePartitionKeyEquals(id)
51
51
  .whereRowKeyNotEquals({ moduleName: undefined }));
52
52
  await Promise.all(modules.map(async (module) => {
@@ -55,28 +55,31 @@ export class MutationTestingReportService {
55
55
  version,
56
56
  moduleName: module.model.moduleName,
57
57
  };
58
- return Promise.all([this.resultMapper.delete(id), this.mutationScoreMapper.delete(id)]);
58
+ return Promise.all([this.#resultMapper.delete(id), this.#mutationScoreMapper.delete(id)]);
59
59
  }));
60
60
  }
61
- async tryAggregateProjectReport(id) {
62
- const projectMutationScoreModel = await this.mutationScoreMapper.findOne(id);
63
- const moduleScoreResults = await this.mutationScoreMapper.findAll(DashboardQuery.create(MutationTestingReport)
64
- .wherePartitionKeyEquals(id)
65
- .whereRowKeyNotEquals({ moduleName: undefined }));
66
- const resultsByModule = Object.fromEntries((await Promise.all(moduleScoreResults.map(async (score) => [score.model.moduleName, await this.resultMapper.findOne(score.model)]))).filter(moduleHasResult));
61
+ async #tryAggregateProjectReport(id) {
62
+ const [projectMutationScoreModel, resultsByModule] = await Promise.all([
63
+ this.#mutationScoreMapper.findOne(id),
64
+ this.#mutationScoreMapper
65
+ .findAll(DashboardQuery.create(MutationTestingReport)
66
+ .wherePartitionKeyEquals(id)
67
+ .whereRowKeyNotEquals({ moduleName: undefined }))
68
+ .then(async (moduleScoreResults) => Object.fromEntries((await Promise.all(moduleScoreResults.map(async (score) => [score.model.moduleName, await this.#resultMapper.findOne(score.model)]))).filter(moduleHasResult))),
69
+ ]);
67
70
  if (Object.keys(resultsByModule).length) {
68
71
  const projectResult = aggregateResultsByModule(resultsByModule);
69
72
  const projectReport = {
70
73
  ...id,
71
- mutationScore: this.calculateMutationScore(projectResult),
74
+ mutationScore: this.#calculateMutationScore(projectResult),
72
75
  };
73
76
  try {
74
- await this.resultMapper.insertOrReplace(id, projectResult);
77
+ await this.#resultMapper.insertOrReplace(id, projectResult);
75
78
  if (projectMutationScoreModel) {
76
- await this.mutationScoreMapper.replace(projectReport, projectMutationScoreModel.etag);
79
+ await this.#mutationScoreMapper.replace(projectReport, projectMutationScoreModel.etag);
77
80
  }
78
81
  else {
79
- await this.mutationScoreMapper.insert(projectReport);
82
+ await this.#mutationScoreMapper.insert(projectReport);
80
83
  }
81
84
  }
82
85
  catch (err) {
@@ -92,8 +95,8 @@ export class MutationTestingReportService {
92
95
  }
93
96
  async findOne(id) {
94
97
  const [reportEntity, result] = await Promise.all([
95
- this.mutationScoreMapper.findOne(id),
96
- this.resultMapper.findOne(id),
98
+ this.#mutationScoreMapper.findOne(id),
99
+ this.#resultMapper.findOne(id),
97
100
  ]);
98
101
  if (reportEntity) {
99
102
  if (result) {
@@ -112,18 +115,21 @@ export class MutationTestingReportService {
112
115
  }
113
116
  }
114
117
  async delete(id, logger) {
115
- await Promise.all([this.resultMapper.delete(id), this.mutationScoreMapper.delete(id)]);
118
+ await Promise.all([this.#resultMapper.delete(id), this.#mutationScoreMapper.delete(id)]);
116
119
  if (id.moduleName) {
117
- await this.aggregateProjectReport(id.projectName, id.version, logger);
120
+ await this.#aggregateProjectReport(id.projectName, id.version, logger);
118
121
  }
119
122
  else {
120
- await this.deleteModules(id.projectName, id.version);
123
+ await this.#deleteModules(id.projectName, id.version);
121
124
  }
122
125
  }
123
- async insertOrMergeReport(id, report, result) {
124
- await Promise.all([this.resultMapper.insertOrReplace(id, result), this.mutationScoreMapper.insertOrMerge(report)]);
126
+ async #insertOrMergeReport(id, report, result) {
127
+ await Promise.all([
128
+ this.#resultMapper.insertOrReplace(id, result),
129
+ this.#mutationScoreMapper.insertOrMerge(report),
130
+ ]);
125
131
  }
126
- calculateMutationScore(result) {
132
+ #calculateMutationScore(result) {
127
133
  if (isMutationTestResult(result)) {
128
134
  return calculateMetrics(result.files).metrics.mutationScore;
129
135
  }
@@ -3,7 +3,6 @@ import type { ReportIdentifier } from '@stryker-mutator/dashboard-common';
3
3
  import type { MutantResult } from 'mutation-testing-report-schema';
4
4
  export declare class RealTimeMutantsBlobService {
5
5
  #private;
6
- private static readonly CONTAINER_NAME;
7
6
  constructor(blobService?: BlobServiceClient);
8
7
  createStorageIfNotExists(): Promise<void>;
9
8
  createReport(id: ReportIdentifier): Promise<void>;
@@ -3,10 +3,10 @@ import { createBlobServiceClient } from './BlobServiceClient.js';
3
3
  // To make resource delete themselves automatically, this should be managed from within Azure:
4
4
  // https://learn.microsoft.com/en-us/azure/storage/blobs/lifecycle-management-overview?tabs=azure-portal
5
5
  export class RealTimeMutantsBlobService {
6
- static CONTAINER_NAME = 'real-time-mutant-results';
6
+ static #CONTAINER_NAME = 'real-time-mutant-results';
7
7
  #containerClient;
8
8
  constructor(blobService = createBlobServiceClient()) {
9
- this.#containerClient = blobService.getContainerClient(RealTimeMutantsBlobService.CONTAINER_NAME);
9
+ this.#containerClient = blobService.getContainerClient(RealTimeMutantsBlobService.#CONTAINER_NAME);
10
10
  }
11
11
  async createStorageIfNotExists() {
12
12
  await this.#containerClient.createIfNotExists({});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stryker-mutator/dashboard-data-access",
3
- "version": "0.20.2",
3
+ "version": "0.20.4",
4
4
  "type": "module",
5
5
  "description": "This package contains the data access layer of the stryker dashboard application.",
6
6
  "scripts": {
@@ -25,12 +25,12 @@
25
25
  },
26
26
  "license": "Apache-2.0",
27
27
  "dependencies": {
28
- "@azure/core-rest-pipeline": "1.22.2",
28
+ "@azure/core-rest-pipeline": "1.23.0",
29
29
  "@azure/data-tables": "13.3.2",
30
- "@azure/storage-blob": "12.29.1",
31
- "@stryker-mutator/dashboard-common": "0.20.2",
32
- "mutation-testing-metrics": "3.7.0",
33
- "mutation-testing-report-schema": "3.7.0"
30
+ "@azure/storage-blob": "12.31.0",
31
+ "@stryker-mutator/dashboard-common": "0.20.4",
32
+ "mutation-testing-metrics": "3.7.3",
33
+ "mutation-testing-report-schema": "3.7.3"
34
34
  },
35
- "gitHead": "76d369d14d473c784634168a2d3547c68b2e571e"
35
+ "gitHead": "48e1e0ee49fb77ce3130d3a98eb14a9dfd1e75c8"
36
36
  }