Skip to content
This repository was archived by the owner on Dec 7, 2021. It is now read-only.

Commit 2f6ff66

Browse files
tbarlow12wbreza
authored andcommitted
Split out project functions and asset functions into respective services
1 parent 295dcad commit 2f6ff66

8 files changed

Lines changed: 281 additions & 199 deletions

File tree

src/common/localization/en-us.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,9 @@ export const english: IAppStrings = {
262262
},
263263
delete: {
264264
title: "Delete Tag",
265-
confirmation: "Are you sure you want to delete this tag? It will be deleted throughout all assets and any regions where this is the only tag will also be deleted",
266-
}
265+
confirmation: "Are you sure you want to delete this tag? It will be deleted throughout all assets \
266+
and any regions where this is the only tag will also be deleted",
267+
},
267268
},
268269
canvas: {
269270
removeAllRegions: {
@@ -275,7 +276,8 @@ export const english: IAppStrings = {
275276
enforceTaggedRegions: {
276277
title: "Invalid region(s) detected",
277278
// tslint:disable-next-line:max-line-length
278-
description: "1 or more regions have not been tagged. Ensure all regions are tagged before continuing to next asset.",
279+
description: "1 or more regions have not been tagged. Ensure all regions are tagged before \
280+
continuing to next asset.",
279281
},
280282
},
281283
},

src/common/localization/es-cl.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,12 +260,14 @@ export const spanish: IAppStrings = {
260260
},
261261
rename: {
262262
title: "Cambiar el nombre de la etiqueta",
263-
confirmation: "¿Está seguro que quiere cambiar el nombre de esta etiqueta? Será cambiada en todos los activos",
263+
confirmation: "¿Está seguro que quiere cambiar el nombre de esta etiqueta? \
264+
Será cambiada en todos los activos",
264265
},
265266
delete: {
266267
title: "Delete Tag",
267-
confirmation: "¿Está seguro que quiere borrar esta etiqueta? Será borrada en todos los activos y en las regiones donde esta etiqueta sea la única, la region también será borrada",
268-
}
268+
confirmation: "¿Está seguro que quiere borrar esta etiqueta? Será borrada en todos \
269+
los activos y en las regiones donde esta etiqueta sea la única, la region también será borrada",
270+
},
269271
},
270272
canvas: {
271273
removeAllRegions: {

src/common/strings.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ export interface IAppStrings {
262262
delete: {
263263
title: string;
264264
confirmation: string;
265-
}
265+
},
266266
}
267267
canvas: {
268268
removeAllRegions: {

src/react/components/pages/editorPage/editorPage.tsx

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import EditorSideBar from "./editorSideBar";
2929
import { EditorToolbar } from "./editorToolbar";
3030
import Alert from "../../common/alert/alert";
3131
import Confirm from "../../common/confirm/confirm";
32+
import ProjectService from "../../../../services/projectService";
3233
// tslint:disable-next-line:no-var-requires
3334
const tagColors = require("../../common/tagColors.json");
3435

@@ -228,7 +229,7 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
228229
</div>
229230
<div className="editor-page-right-sidebar">
230231
<TagInput
231-
tags={this.props.project.tags}
232+
tags={this.state.project.tags}
232233
lockedTags={this.state.lockedTags}
233234
selectedRegions={this.state.selectedRegions}
234235
onChange={this.onTagsChanged}
@@ -307,16 +308,38 @@ export default class EditorPage extends React.Component<IEditorPageProps, IEdito
307308
this.renameTagConfirm.current.open(tagName, newTagName);
308309
}
309310

310-
private onTagRenamed = (tagName: string, newTagName: string): void => {
311-
debugger;
311+
private onTagRenamed = async (tagName: string, newTagName: string): Promise<void> => {
312+
const { project, selectedAsset } = this.state;
313+
const assetService = new AssetService(project);
314+
const asset = await assetService.renameTag(project.assets, tagName, newTagName, selectedAsset);
315+
316+
const projectService = new ProjectService();
317+
const newProject = projectService.renameTag(project, tagName, newTagName);
318+
await this.props.actions.saveProject(newProject);
319+
320+
this.setState({
321+
selectedAsset: asset,
322+
project: newProject,
323+
});
312324
}
313325

314326
private confirmTagDeleted = (tagName: string): void => {
315327
this.deleteTagConfirm.current.open(tagName);
316328
}
317329

318-
private onTagDeleted = (tagName: string): void => {
319-
debugger;
330+
private onTagDeleted = async (tagName: string): Promise<void> => {
331+
const { project, selectedAsset } = this.state;
332+
const assetService = new AssetService(project);
333+
const asset = await assetService.deleteTag(project.assets, tagName, selectedAsset);
334+
335+
const projectService = new ProjectService();
336+
const newProject = projectService.deleteTag(project, tagName);
337+
await this.props.actions.saveProject(newProject);
338+
339+
this.setState({
340+
selectedAsset: asset,
341+
project: newProject,
342+
});
320343
}
321344

322345
private onCtrlTagClicked = (tag: ITag): void => {

src/services/assetService.test.ts

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { AssetService } from "./assetService";
2-
import { AssetType, IAssetMetadata, AssetState } from "../models/applicationState";
2+
import { AssetType, IAssetMetadata, AssetState, IAsset, IProject } from "../models/applicationState";
33
import MockFactory from "../common/mockFactory";
44
import { AssetProviderFactory, IAssetProvider } from "../providers/storage/assetProviderFactory";
55
import { StorageProviderFactory } from "../providers/storage/storageProviderFactory";
66
import { constants } from "../common/constants";
77
import { TFRecordsBuilder, FeatureType } from "../providers/export/tensorFlowRecords/tensorFlowBuilder";
88
import HtmlFileReader from "../common/htmlFileReader";
99
import { encodeFileURI } from "../common/utils";
10+
import _ from "lodash";
1011

1112
describe("Asset Service", () => {
1213
describe("Static Methods", () => {
@@ -323,4 +324,105 @@ describe("Asset Service", () => {
323324
expect(Math.floor(result.regions[1].points[1].y)).toEqual(800);
324325
});
325326
});
327+
328+
describe("Tag Update functions", () => {
329+
330+
function populateProjectAssets(project?: IProject, assetCount = 10) {
331+
if (!project) {
332+
project = MockFactory.createTestProject();
333+
}
334+
const assets = MockFactory.createTestAssets(assetCount);
335+
assets.forEach((asset) => {
336+
asset.state = AssetState.Tagged;
337+
});
338+
339+
project.assets = _.keyBy(assets, (asset) => asset.id);
340+
return project;
341+
}
342+
343+
it("Deletes tag from assets", async () => {
344+
const tag1 = "tag1";
345+
const tag2 = "tag2";
346+
const region = MockFactory.createTestRegion(undefined, [tag1, tag2]);
347+
const asset: IAsset = {
348+
...MockFactory.createTestAsset("1"),
349+
state: AssetState.Tagged,
350+
};
351+
const assetMetadata = MockFactory.createTestAssetMetadata(asset, [region]);
352+
AssetService.prototype.getAssetMetadata = jest.fn((asset: IAsset) => Promise.resolve(assetMetadata));
353+
354+
const saveMetadata = jest.fn();
355+
AssetService.prototype.save = saveMetadata;
356+
357+
const expectedAssetMetadata: IAssetMetadata = {
358+
...MockFactory.createTestAssetMetadata(
359+
asset,
360+
[
361+
{
362+
...region,
363+
tags: [tag2],
364+
},
365+
],
366+
),
367+
368+
};
369+
370+
const project = populateProjectAssets();
371+
const assetService = new AssetService(project);
372+
await assetService.deleteTag(project.assets, tag1, assetMetadata);
373+
expect(saveMetadata).toBeCalledWith(expectedAssetMetadata);
374+
});
375+
376+
it("Deletes empty regions after deleting only tag from region", async () => {
377+
const tag1 = "tag1";
378+
const region = MockFactory.createTestRegion(undefined, [tag1]);
379+
const asset: IAsset = {
380+
...MockFactory.createTestAsset("1"),
381+
state: AssetState.Tagged,
382+
};
383+
const assetMetadata = MockFactory.createTestAssetMetadata(asset, [region]);
384+
AssetService.prototype.getAssetMetadata = jest.fn((asset: IAsset) => Promise.resolve(assetMetadata));
385+
386+
const saveMetadata = jest.fn();
387+
AssetService.prototype.save = saveMetadata;
388+
389+
const expectedAssetMetadata: IAssetMetadata = MockFactory.createTestAssetMetadata(asset, []);
390+
const project = populateProjectAssets();
391+
const assetService = new AssetService(project);
392+
await assetService.deleteTag(project.assets, tag1, assetMetadata);
393+
expect(saveMetadata).toBeCalledWith(expectedAssetMetadata);
394+
});
395+
396+
it("Updates renamed tag within all assets", async () => {
397+
const tag1 = "tag1";
398+
const newTag = "tag2";
399+
const region = MockFactory.createTestRegion(undefined, [tag1]);
400+
const asset: IAsset = {
401+
...MockFactory.createTestAsset("1"),
402+
state: AssetState.Tagged,
403+
};
404+
const assetMetadata = MockFactory.createTestAssetMetadata(asset, [region]);
405+
AssetService.prototype.getAssetMetadata = jest.fn((asset: IAsset) => Promise.resolve(assetMetadata));
406+
407+
const saveMetadata = jest.fn();
408+
AssetService.prototype.save = saveMetadata;
409+
410+
const expectedAssetMetadata: IAssetMetadata = {
411+
...MockFactory.createTestAssetMetadata(
412+
asset,
413+
[
414+
{
415+
...region,
416+
tags: [newTag],
417+
},
418+
],
419+
),
420+
421+
};
422+
const project = populateProjectAssets();
423+
const assetService = new AssetService(project);
424+
await assetService.renameTag(project.assets, tag1, newTag, assetMetadata);
425+
expect(saveMetadata).toBeCalledWith(expectedAssetMetadata);
426+
});
427+
});
326428
});

src/services/assetService.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,105 @@ export class AssetService {
204204
}
205205
}
206206

207+
/**
208+
* Delete a tag from asset metadata files
209+
* @param assets The assets containing tag to delete
210+
* @param tagName Name of tag to delete
211+
* @param currentAsset Current asset being viewed. Makes changes and returns updated asset to avoid
212+
* needing to reload the asset in the editor page
213+
*/
214+
public async deleteTag(assets: {[id: string]: IAsset}, tagName: string,
215+
currentAsset?: IAssetMetadata): Promise<IAssetMetadata> {
216+
const transformer = (tags) => tags.filter((t) => t !== tagName);
217+
return await this.updateAssetTags(assets, tagName, transformer, currentAsset);
218+
}
219+
220+
/**
221+
* Rename a tag within asset metadata files
222+
* @param assets The assets containing tag to rename
223+
* @param tagName Name of tag to rename
224+
* @param currentAsset Current asset being viewed. Makes changes and returns updated asset to avoid
225+
* needing to reload the asset in the editor page
226+
*/
227+
public async renameTag(assets: {[id: string]: IAsset}, tagName: string, newTagName: string,
228+
currentAsset?: IAssetMetadata): Promise<IAssetMetadata> {
229+
const transformer = (tags) => tags.map((t) => (t === tagName) ? newTagName : t);
230+
return await this.updateAssetTags(assets, tagName, transformer, currentAsset);
231+
}
232+
233+
/**
234+
* Update tags within asset metadata files
235+
* @param assets The assets containing tags to update
236+
* @param tagName Name of tag to update within project
237+
* @param currentAsset Current asset being viewed. Makes changes and returns updated asset to avoid
238+
* needing to reload the asset in the editor page
239+
* @param transformer Function that accepts array of tags from a region and returns a modified array of tags
240+
*/
241+
private async updateAssetTags(
242+
assets: {[id: string]: IAsset},
243+
tagName: string,
244+
transformer: (tags: string[]) => string[],
245+
currentAsset?: IAssetMetadata): Promise<IAssetMetadata> {
246+
const assetKeys = Object.keys(assets);
247+
// Loop over assets and update if necessary
248+
for (const assetKey of assetKeys) {
249+
const asset = assets[assetKey];
250+
if (asset.state !== AssetState.Tagged) {
251+
return;
252+
}
253+
const assetMetadata = await this.getAssetMetadata(asset);
254+
const updatedAssetMetadata = this.updateTagInAssetMetadata(assetMetadata, tagName, transformer);
255+
if (updatedAssetMetadata) {
256+
await this.save(updatedAssetMetadata);
257+
}
258+
}
259+
if (currentAsset) {
260+
return this.updateTagInAssetMetadata(currentAsset, tagName, transformer);
261+
}
262+
/*
263+
TODO: Replace with async
264+
265+
For some reason in tests, the `forEachAsync` is not recognized as a function
266+
267+
await assetKeys.forEachAsync(async (assetKey) => {
268+
const asset = project.assets[assetKey];
269+
if (asset.state !== AssetState.Tagged) {
270+
return;
271+
}
272+
const assetMetadata = await assetService.getAssetMetadata(asset);
273+
const updatedAssetMetadata = this.updateTagInAssetMetadata(assetMetadata, tagName, transformer);
274+
if (updatedAssetMetadata) {
275+
await assetService.save(updatedAssetMetadata);
276+
}
277+
});
278+
279+
*/
280+
}
281+
282+
/**
283+
* Update tag within asset metadata object
284+
* @param assetMetadata Asset metadata to update
285+
* @param tagName Name of tag being updated
286+
* @param transformer Function that accepts array of tags from a region and returns a modified array of tags
287+
* @returns Modified asset metadata object or null if object does not need to be modified
288+
*/
289+
private updateTagInAssetMetadata(assetMetadata: IAssetMetadata, tagName: string,
290+
transformer: (tags: string[]) => string[]): IAssetMetadata {
291+
let foundTag = false;
292+
for (const region of assetMetadata.regions) {
293+
if (region.tags.find((t) => t === tagName)) {
294+
foundTag = true;
295+
region.tags = transformer(region.tags);
296+
}
297+
}
298+
if (foundTag) {
299+
assetMetadata.regions = assetMetadata.regions.filter((region) => region.tags.length > 0);
300+
assetMetadata.asset.state = (assetMetadata.regions.length) ? AssetState.Tagged : AssetState.Visited;
301+
return assetMetadata;
302+
}
303+
return null;
304+
}
305+
207306
private async getRegionsFromTFRecord(asset: IAsset): Promise<IRegion[]> {
208307
const objectArray = await this.getTFRecordMetadata(asset);
209308
const regions: IRegion[] = [];

0 commit comments

Comments
 (0)