Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions libraries/api-client/src/cells/cellsApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1914,9 +1914,9 @@ describe('CellsAPI', () => {
);
});

it('filters by tags when provided', async () => {
it('creates a Should metadata filter for a single tag', async () => {
const searchPhrase = 'test';
const tags = ['tag1', 'tag2'];
const tags = ['tag1'];
const mockResponse: RestNodeCollection = {
Nodes: [
{
Expand All @@ -1938,7 +1938,7 @@ describe('CellsAPI', () => {
Status: {
Deleted: 'Not',
},
Metadata: [{Namespace: 'usermeta-tags', Term: JSON.stringify(tags.join(','))}],
Metadata: [{Namespace: 'usermeta-tags', Term: 'tag1', Operation: 'Should'}],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pull request description says single-tag search is unaffected. But the actual request changes from:

{
  Namespace: USER_META_TAGS_NAMESPACE,
  Term: this.transformTagsToJson(tags),
}

to

  Namespace: USER_META_TAGS_NAMESPACE,
  Term: tag,
  Operation: 'Should',
}

That changes both the term representation and the explicit operation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, "unaffected" was too loose. What I meant is that a single-tag search still returns only the tagged files with the given tag (See the demo)

},
Flags: ['WithPreSignedURLs'],
Limit: '10',
Expand All @@ -1947,6 +1947,34 @@ describe('CellsAPI', () => {
expect(result).toEqual(mockResponse);
});

it('creates one Should metadata filter per selected tag', async () => {
const searchPhrase = 'test';
const tags = ['tag1', 'tag2'];
const mockResponse: RestNodeCollection = {Nodes: []} as RestNodeCollection;

mockNodeServiceApi.lookup.mockResolvedValueOnce(createMockResponse(mockResponse));

await cellsAPI.searchNodes({phrase: searchPhrase, tags});

expect(mockNodeServiceApi.lookup).toHaveBeenCalledWith({
Scope: {Root: {Path: ''}, Recursive: true},
Filters: {
Text: {SearchIn: 'BaseName', Term: searchPhrase},
Type: 'UNKNOWN',
Status: {
Deleted: 'Not',
},
Metadata: [
{Namespace: 'usermeta-tags', Term: 'tag1', Operation: 'Should'},
{Namespace: 'usermeta-tags', Term: 'tag2', Operation: 'Should'},
],
},
Flags: ['WithPreSignedURLs'],
Limit: '10',
Offset: '0',
});
});

it('handles empty tags array', async () => {
const searchPhrase = 'test';
const tags: string[] = [];
Expand Down Expand Up @@ -2249,7 +2277,7 @@ describe('CellsAPI', () => {
Type: 'UNKNOWN',
Status: {Deleted: 'Not', HasPublicLink: true},
Metadata: [
{Namespace: 'usermeta-tags', Term: JSON.stringify(tags.join(','))},
{Namespace: 'usermeta-tags', Term: 'important', Operation: 'Should'},

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only verifies the serialized request shape. It does not verify that the selected tag still restricts the backend result when it is combined with MIME and creator filters.

Because the tag uses Should while the other metadata filters use Must, could we add a backend-level regression test for the combined case? It should verify that a file matching the MIME type and creator, but none of the selected tags, is not returned.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I created a follow up task to add this test case into cells-integration-tests https://wearezeta.atlassian.net/browse/WPB-27344

{Namespace: 'mime', Term: 'image/*', Operation: 'Must'},
{Namespace: 'usermeta-owner-uuid', Term: JSON.stringify(creatorId), Operation: 'Must'},
],
Expand Down
17 changes: 14 additions & 3 deletions libraries/api-client/src/cells/cellsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import {AxiosHeaders} from 'axios';
import {
NodeServiceApi,
LookupFilterMetaFilter,
RestLookupRequest,
RestCreateCheckResponse,
RestDeleteVersionResponse,
Expand Down Expand Up @@ -55,6 +56,17 @@ const USER_META_TAGS_NAMESPACE = 'usermeta-tags';
const USER_META_OWNER_UUID_NAMESPACE = 'usermeta-owner-uuid';
const MIME_NAMESPACE = 'mime';

// Each selected tag is sent as its own metadata filter with the `Should` operation so the
// backend applies OR semantics across tags (a node matching any selected tag is returned).
// Matches the iOS client shape in WireMessaging RestAPI.swift.
function createTagMetadataFilters(tags: string[]): LookupFilterMetaFilter[] {
return tags.map(tag => ({
Namespace: USER_META_TAGS_NAMESPACE,
Term: tag,
Operation: 'Should',
}));
}

// TODO: remove the apiKey (from pydio and s3) once the Pydio backend has fully support for the auth with the Wire's access token
// If it's passed we use it to authenticate, instead of the access token
interface CellsConfig {
Expand Down Expand Up @@ -450,6 +462,7 @@ export class CellsAPI {
throw new Error(CONFIGURATION_ERROR);
}

const tagMetadataFilters = createTagMetadataFilters(tags ?? []);
const mimeOp: 'Should' | 'Must' = mimeTypes !== undefined && mimeTypes.length > 1 ? 'Should' : 'Must';
const creatorOp: 'Should' | 'Must' = creatorIds !== undefined && creatorIds.length > 1 ? 'Should' : 'Must';

Expand All @@ -468,9 +481,7 @@ export class CellsAPI {
...(hasPublicLink !== undefined ? {HasPublicLink: hasPublicLink} : {}),
},
Metadata: [
...(tags !== undefined && tags.length > 0
? [{Namespace: USER_META_TAGS_NAMESPACE, Term: this.transformTagsToJson(tags)}]
: []),
...tagMetadataFilters,
...(mimeTypes?.map(term => ({Namespace: MIME_NAMESPACE, Term: term, Operation: mimeOp})) ?? []),
...(creatorIds?.map(term => ({
Namespace: USER_META_OWNER_UUID_NAMESPACE,
Expand Down
Loading