Skip to content

Commit 7ae661f

Browse files
Merge pull request #564 from PermanentOrg/PER-saving-or-viewing-tags-fails
Per saving or viewing tags fails
2 parents 99798f4 + 22184bb commit 7ae661f

File tree

10 files changed

+179
-107
lines changed

10 files changed

+179
-107
lines changed

src/app/core/services/tags/tags.service.spec.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ describe('TagsService', () => {
9696

9797
it('should only cache tags from the current archive', () => {
9898
const item = new RecordVO({
99-
TagVOs: [
99+
tags: [
100100
{ tagId: 1, name: 'testOne', archiveId: 1 },
101101
{ tagId: 2, name: 'testTwo', archiveId: 2 },
102102
],

src/app/core/services/tags/tags.service.ts

+31-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { Injectable } from '@angular/core';
33
import { TagVOData } from '@models/tag-vo';
44
import debug from 'debug';
5-
import { ItemVO } from '@models';
5+
import { FolderVO, ItemVO, RecordVO } from '@models';
66
import { AccountService } from '@shared/services/account/account.service';
77
import { orderBy, find } from 'lodash';
88
import { Subject } from 'rxjs';
@@ -53,21 +53,41 @@ export class TagsService {
5353
}
5454

5555
checkTagsOnItem(item: ItemVO) {
56-
if (!item.TagVOs?.length) {
56+
if (
57+
(item.isRecord && !item.tags?.length) ||
58+
(item.isFolder && !item.TagVOs?.length)
59+
) {
5760
return;
5861
}
5962

6063
let hasNew = false;
6164

62-
for (const itemTag of item.TagVOs) {
63-
if (
64-
!this.tags.has(itemTag.tagId) &&
65-
itemTag.name &&
66-
itemTag.archiveId === this.account.getArchive().archiveId
67-
) {
68-
this.tags.set(itemTag.tagId, itemTag);
69-
hasNew = true;
70-
this.debug('new tag seen %o', itemTag);
65+
if (item.isFolder) {
66+
for (const itemTag of item.TagVOs) {
67+
if (
68+
!this.tags.has(itemTag.tagId) &&
69+
itemTag.name &&
70+
itemTag.archiveId === this.account.getArchive().archiveId
71+
) {
72+
this.tags.set(itemTag.tagId, itemTag);
73+
hasNew = true;
74+
this.debug('new tag seen %o', itemTag);
75+
}
76+
}
77+
}
78+
79+
if (item.isRecord) {
80+
for (const itemTag of (item as RecordVO).tags) {
81+
if (
82+
!this.tags.has(itemTag.id) &&
83+
itemTag.name &&
84+
itemTag.archiveId === this.account.getArchive().archiveId
85+
) {
86+
const tagId = itemTag.tagId ?? itemTag.id;
87+
this.tags.set(tagId, { ...itemTag, tagId });
88+
hasNew = true;
89+
this.debug('new tag seen %o', itemTag);
90+
}
7191
}
7292
}
7393

src/app/file-browser/components/edit-tags/edit-tags.component.spec.ts

+78-64
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { async } from '@angular/core/testing';
33
import { Shallow } from 'shallow-render';
44
import { Observable, of } from 'rxjs';
55

6-
import { ItemVO, TagVOData, RecordVO } from '@models';
6+
import { ItemVO, TagVOData, RecordVO, FolderVO } from '@models';
77
import { ApiService } from '@shared/services/api/api.service';
88
import { DataService } from '@shared/services/data/data.service';
99
import { TagsService } from '@core/services/tags/tags.service';
@@ -38,12 +38,35 @@ const defaultTagList: TagVOData[] = [
3838
type: 'type.tag.metadata.customField',
3939
},
4040
];
41-
const defaultItem: ItemVO = new RecordVO({ TagVOs: defaultTagList });
41+
const defaultRecord = new RecordVO({
42+
tags: defaultTagList,
43+
isRecord: true,
44+
});
45+
46+
const defaultFolder = new FolderVO({
47+
TagVOs: defaultTagList,
48+
isFolder: true,
49+
});
4250

4351
describe('EditTagsComponent', () => {
4452
let shallow: Shallow<EditTagsComponent>;
45-
async function defaultRender(
46-
item: ItemVO = defaultItem,
53+
async function defaultRenderRecord(
54+
item: ItemVO = defaultRecord,
55+
tagType: TagType = 'keyword',
56+
) {
57+
return await shallow.render(
58+
`<pr-edit-tags [item]="item" [tagType]="tagType"></pr-edit-tags>`,
59+
{
60+
bind: {
61+
item: item,
62+
tagType: tagType,
63+
},
64+
},
65+
);
66+
}
67+
68+
async function defaultRenderFolder(
69+
item: ItemVO = defaultFolder,
4770
tagType: TagType = 'keyword',
4871
) {
4972
return await shallow.render(
@@ -81,14 +104,32 @@ describe('EditTagsComponent', () => {
81104
);
82105
}));
83106

84-
it('should create', async () => {
85-
const { element } = await defaultRender();
107+
it('should create record tags', async () => {
108+
const { element } = await defaultRenderRecord();
86109

87110
expect(element).not.toBeNull();
88111
});
89112

90-
it('should only show keywords in keyword mode', async () => {
91-
const { element } = await defaultRender();
113+
it('should create folder tags', async () => {
114+
const { element } = await defaultRenderFolder();
115+
116+
expect(element).not.toBeNull();
117+
});
118+
119+
it('should only show keywords in keyword mode for records', async () => {
120+
const { element, fixture } = await defaultRenderRecord();
121+
122+
element.componentInstance.itemTags = [
123+
{ name: 'tagOne' },
124+
{ name: 'tagTwo' },
125+
];
126+
127+
element.componentInstance.matchingTags = [
128+
{ name: 'tagOne' },
129+
{ name: 'tagTwo' },
130+
];
131+
132+
fixture.detectChanges();
92133

93134
expect(
94135
element.componentInstance.itemTags.find((tag) => tag.name === 'tagOne'),
@@ -135,65 +176,20 @@ describe('EditTagsComponent', () => {
135176
).not.toBeTruthy();
136177
});
137178

138-
it('should only show custom metadata in custom metadata mode', async () => {
139-
const { element } = await defaultRender(defaultItem, 'customMetadata');
140-
141-
expect(
142-
element.componentInstance.itemTags.find((tag) => tag.name === 'tagOne'),
143-
).not.toBeTruthy();
144-
145-
expect(
146-
element.componentInstance.itemTags.find((tag) => tag.name === 'tagTwo'),
147-
).not.toBeTruthy();
148-
149-
expect(
150-
element.componentInstance.itemTags.find(
151-
(tag) => tag.name === 'customField:customValueOne',
152-
),
153-
).toBeTruthy();
154-
155-
expect(
156-
element.componentInstance.itemTags.find(
157-
(tag) => tag.name === 'customField:customValueTwo',
158-
),
159-
).toBeTruthy();
160-
161-
expect(
162-
element.componentInstance.matchingTags.find(
163-
(tag) => tag.name === 'tagOne',
164-
),
165-
).not.toBeTruthy();
166-
167-
expect(
168-
element.componentInstance.matchingTags.find(
169-
(tag) => tag.name === 'tagTwo',
170-
),
171-
).not.toBeTruthy();
172-
173-
expect(
174-
element.componentInstance.matchingTags.find(
175-
(tag) => tag.name === 'customField:customValueOne',
176-
),
177-
).toBeTruthy();
178-
179-
expect(
180-
element.componentInstance.matchingTags.find(
181-
(tag) => tag.name === 'customField:customValueTwo',
182-
),
183-
).toBeTruthy();
184-
});
185-
186-
it('should not create custom metadata in keyword mode', async () => {
187-
const { element } = await defaultRender();
179+
it('should not create custom metadata in keyword mode for records', async () => {
180+
const { element } = await defaultRenderRecord();
188181
const tagCreateSpy = spyOn(element.componentInstance.api.tag, 'create');
189182
await element.componentInstance.onInputEnter('key:value');
190183

191184
expect(element.componentInstance.newTagInputError).toBeTruthy();
192185
expect(tagCreateSpy).not.toHaveBeenCalled();
193186
});
194187

195-
it('should not create keyword in custom metadata mode', async () => {
196-
const { element } = await defaultRender(defaultItem, 'customMetadata');
188+
it('should not create keyword in custom metadata mode for records', async () => {
189+
const { element } = await defaultRenderRecord(
190+
defaultRecord,
191+
'customMetadata',
192+
);
197193
const tagCreateSpy = spyOn(element.componentInstance.api.tag, 'create');
198194
await element.componentInstance.onInputEnter('keyword');
199195

@@ -202,7 +198,25 @@ describe('EditTagsComponent', () => {
202198
});
203199

204200
it('should highlight the correct tag on key down', async () => {
205-
const { fixture, element } = await defaultRender();
201+
const { fixture, element } = await defaultRenderFolder();
202+
203+
element.componentInstance.isEditing = true;
204+
205+
fixture.detectChanges();
206+
const tags = fixture.debugElement.queryAll(By.css('.edit-tag'));
207+
208+
const arrowKeyDown = new KeyboardEvent('keydown', { key: 'ArrowDown' });
209+
tags[0].nativeElement.dispatchEvent(arrowKeyDown);
210+
211+
fixture.detectChanges();
212+
213+
const focusedElement = document.activeElement as HTMLElement;
214+
215+
expect(focusedElement).toBe(tags[1].nativeElement);
216+
});
217+
218+
it('should highlight the correct tag for folders on key down', async () => {
219+
const { fixture, element } = await defaultRenderFolder();
206220

207221
element.componentInstance.isEditing = true;
208222

@@ -220,7 +234,7 @@ describe('EditTagsComponent', () => {
220234
});
221235

222236
it('should highlight the correct tag on key up', async () => {
223-
const { fixture, element } = await defaultRender();
237+
const { fixture, element } = await defaultRenderRecord();
224238

225239
element.componentInstance.isEditing = true;
226240

@@ -238,7 +252,7 @@ describe('EditTagsComponent', () => {
238252
});
239253

240254
it('should highlight the input on key up', async () => {
241-
const { fixture, element } = await defaultRender();
255+
const { fixture, element } = await defaultRenderRecord();
242256

243257
element.componentInstance.isEditing = true;
244258

@@ -258,7 +272,7 @@ describe('EditTagsComponent', () => {
258272
});
259273

260274
it('should open dialog when manage link is clicked', async () => {
261-
const { element, find, inject, fixture } = await defaultRender();
275+
const { element, find, inject, fixture } = await defaultRenderRecord();
262276
const dialogOpenSpy = inject(DialogCdkService);
263277

264278
element.componentInstance.isEditing = true;

src/app/file-browser/components/edit-tags/edit-tags.component.ts

+46-20
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
Inject,
1313
} from '@angular/core';
1414
import { TagsService } from '@core/services/tags/tags.service';
15-
import { ItemVO, TagVOData, TagLinkVOData, FolderVO } from '@models';
15+
import { ItemVO, TagVOData, TagLinkVOData, FolderVO, RecordVO } from '@models';
1616
import { DataService } from '@shared/services/data/data.service';
1717
import { Subject, Subscription } from 'rxjs';
1818
import {
@@ -81,9 +81,9 @@ export class EditTagsComponent
8181
private tagsService: TagsService,
8282
private message: MessageService,
8383
private api: ApiService,
84-
private dataService: DataService,
8584
private elementRef: ElementRef,
8685
private dialog: DialogCdkService,
86+
private dataService: DataService,
8787
) {
8888
this.subscriptions.push(
8989
this.tagsService.getTags$().subscribe((tags) => {
@@ -113,9 +113,15 @@ export class EditTagsComponent
113113
(tag) => !tag.type.includes('type.tag.metadata'),
114114
);
115115
} else {
116-
this.dialogTags = tags?.filter((tag) =>
117-
tag.type.includes('type.tag.metadata'),
118-
);
116+
this.dialogTags = tags
117+
?.filter((tag) => tag.type.includes('type.tag.metadata'))
118+
.map((tag) => ({
119+
id: tag.id,
120+
name: `${tag.name}:${
121+
tag.type.split('.')[tag.type.split.length - 1]
122+
}`,
123+
type: tag.type,
124+
}));
119125
}
120126
});
121127
}
@@ -162,7 +168,7 @@ export class EditTagsComponent
162168
this.onTagType(this.newTagName);
163169
}
164170

165-
async onTagClick(tag: TagVOData) {
171+
async onTagClick(tag) {
166172
const tagLink: TagLinkVOData = {};
167173
if (this.item instanceof FolderVO) {
168174
tagLink.refTable = 'folder';
@@ -174,10 +180,14 @@ export class EditTagsComponent
174180

175181
this.waiting = true;
176182
try {
177-
if (tag.tagId && this.itemTagsById.has(tag.tagId)) {
183+
if (
184+
(tag.tagId && this.itemTagsById.has(tag.tagId)) ||
185+
(tag.id && this.itemTagsById.has(tag.id))
186+
) {
178187
await this.api.tag.deleteTagLink(tag, tagLink);
179188
} else {
180189
await this.api.tag.create(tag, tagLink);
190+
await this.tagsService.refreshTags();
181191
}
182192
await this.dataService.fetchFullItems([this.item]);
183193
} catch (err) {
@@ -232,23 +242,39 @@ export class EditTagsComponent
232242

233243
this.itemTagsById.clear();
234244

235-
this.itemTags = this.filterTagsByType(
236-
(this.item?.TagVOs || [])
237-
.map((tag) => this.allTags?.find((t) => t.tagId === tag.tagId))
238-
.filter(
239-
// Filter out tags that are now null from deletion
240-
(tag) => tag?.name,
241-
),
242-
);
245+
if (this.item && this.item?.isFolder) {
246+
this.itemTags = this.filterTagsByType(
247+
(this.item?.TagVOs || [])
248+
.map((tag) => this.allTags?.find((t) => t.tagId === tag.tagId))
249+
.filter(
250+
// Filter out tags that are now null from deletion
251+
(tag) => tag?.name,
252+
),
253+
);
254+
}
243255

244-
if (!this.item?.TagVOs?.length) {
245-
return;
256+
if (this.item && this.item?.isRecord) {
257+
this.itemTags = this.filterTagsByType(
258+
(this.item?.tags || [])
259+
.map((tag) => this.allTags?.find((t) => t.tagId === +tag.id))
260+
.filter(
261+
// Filter out tags that are now null from deletion
262+
(tag) => tag?.name,
263+
),
264+
);
265+
}
266+
267+
if (Array.isArray(this.itemTags)) {
268+
for (const tag of this.itemTags) {
269+
this.itemTagsById.add(tag.tagId);
270+
}
246271
}
247272

248-
for (const tag of this.itemTags) {
249-
this.itemTagsById.add(tag.tagId);
273+
if (this.item) {
274+
this.tagsService.setItemTags(
275+
this.item.isFolder ? this.item.TagVOs : (this.item as RecordVO).tags,
276+
);
250277
}
251-
this.tagsService.setItemTags(this.item.TagVOs);
252278
}
253279

254280
onManageTagsClick() {

0 commit comments

Comments
 (0)