Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
d659eba
Fixed Opensearch filters used in the backend API endpoint for dataset…
JoonLeeNIH Jun 30, 2025
a203b46
Merge pull request #33 from CBIIT/INS-1421
jonkiky Jul 2, 2025
48da758
Changed dataset text search from ORing the words to ANDing the words
JoonLeeNIH Sep 15, 2025
723dff6
Removed redundant array operations from building query for search terms
JoonLeeNIH Sep 15, 2025
19eed39
Removed commented line
JoonLeeNIH Sep 15, 2025
a5e16ab
Merge pull request #34 from CBIIT/INS-1448
amattu2 Sep 16, 2025
4e479f2
Updated filter query generation to use updated helper function
JoonLeeNIH Sep 18, 2025
e9c8fbf
Merge pull request #35 from CBIIT/filter-fix
amattu2 Sep 18, 2025
e5e3af9
Clarified how the sort clause is made in Opensearch query
JoonLeeNIH Nov 13, 2025
5991a84
Merge pull request #36 from CBIIT/INS-1466
amattu2 Nov 17, 2025
0d7e83f
Merge branch 'main' into gha
JoonLeeNIH Nov 17, 2025
14be926
Merge pull request #37 from CBIIT/gha
kiran1942 Nov 17, 2025
7db6899
Change default fail_on_trivy_scan to true
kiran1942 Nov 17, 2025
2fd4b72
Merge pull request #38 from CBIIT/kiran1942-patch-1
JoonLeeNIH Nov 17, 2025
30c5ed9
init: Testing workflows
amattu2 Dec 8, 2025
ab26884
init: Vitest
amattu2 Dec 8, 2025
45ae9b6
Initial plan
Copilot Dec 8, 2025
e65ff0c
3.2.0
amattu2 Dec 8, 2025
2120178
test: Add comprehensive unit tests for getSearchableText function
Copilot Dec 8, 2025
787891e
Merge pull request #40 from CBIIT/copilot/sub-pr-39
amattu2 Dec 8, 2025
1cf1872
Remove ESLint job
amattu2 Dec 8, 2025
0c8abf7
Add README tags
amattu2 Dec 8, 2025
fc845aa
fix: Wrong package name
amattu2 Dec 8, 2025
ce1a893
Merge pull request #39 from CBIIT/unit-testing
amattu2 Dec 10, 2025
da0dfd6
fix: CVE-2025-64756
amattu2 Dec 11, 2025
275cef1
Merge pull request #41 from CBIIT/CVE-fix
amattu2 Dec 12, 2025
5323b30
Merge remote-tracking branch 'origin/main' into sync-main-320
amattu2 Dec 18, 2025
cd684ce
Merge pull request #43 from CBIIT/sync-main-320
amattu2 Dec 18, 2025
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
11 changes: 11 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
### Overview

N/A

### Change Details (Specifics)

N/A

### Related Ticket(s)

N/A
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
type: boolean
description: fail the build if vulnerabilities are found
required: true
default: false
default: true
jobs:
build:
name: Build Image
Expand Down Expand Up @@ -102,4 +102,4 @@ jobs:
with:
status: ${{ job.status }}
steps: ${{ toJson(steps) }}
if: always()
if: always()
39 changes: 39 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Test

on:
workflow_dispatch:
push:
branches:
- "*.*.*"
- "main"
- "master"
pull_request:
branches:
- "*"

permissions:
contents: read

jobs:
test:
name: Test Changes
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22.x"
cache: "npm"

- name: Install Dependencies
run: npm install

- name: Run Vitest
run: npm run test:ci

- name: Coveralls GitHub Action
uses: coverallsapp/github-action@v2
continue-on-error: true
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
/build

# misc
coverage
.DS_Store
.env
.env.local
Expand Down
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
FROM node:22-alpine3.20 AS fnl_base_image
FROM node:22-alpine3.23 AS fnl_base_image

ENV PORT 8081
ENV NODE_ENV production

WORKDIR /usr/src/app

RUN npm install -g npm@latest

COPY package*.json ./

RUN npm ci --only=production
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# CCDC Backend
# C3DC Rest Backend

[![Test](https://github.com/CBIIT/INS-REST-WebService/actions/workflows/test.yml/badge.svg)](https://github.com/CBIIT/INS-REST-WebService/actions/workflows/test.yml)
[![Coverage Status](https://coveralls.io/repos/github/CBIIT/INS-REST-WebService/badge.svg?branch=copilot/sub-pr-39)](https://coveralls.io/github/CBIIT/INS-REST-WebService?branch=main)

## Table of Contents

Expand Down
59 changes: 24 additions & 35 deletions Services/queryGenerator.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,41 +169,30 @@ queryGenerator.getFiltersClause = (filters) => {
return clause;
}

queryGenerator.getTextSearchClause = (searchText) => {
const clause = {
'bool': {
'should': []
}
};
const strArr = searchText.trim().split(' ');
const result = strArr.map(
queryGenerator.getTextSearchConditions = (searchText) => {
const conditions = [];
const searchTerms = searchText.trim().split(' ').map(
term => term.trim()
).filter(
term => term.length > 2
);
const keywords = result.length === 0 ? '' : result.join(' ');

// No search terms, so return null
if (keywords == '') {
return null;
}

const termArr = keywords.split(' ').map((t) => t.trim());
const uniqueTermArr = termArr.filter((t, idx) => {
return termArr.indexOf(t) === idx;
const uniqueSearchTerms = searchTerms.filter((term, idx) => {
return searchTerms.indexOf(term) === idx;
});
uniqueTermArr.filter((term) => term.trim() != '').forEach((term) => {
let dsl = {};
let searchTerm = term.trim();

dsl.multi_match = {
'query': searchTerm,
'fields': DATASET_FIELDS.map((field) => `${field}.search`),
// Add a search condition for finding each term in any of the dataset fields
uniqueSearchTerms.forEach((term) => {
const dsl = {
'multi_match': {
'query': term,
'fields': DATASET_FIELDS.map((field) => `${field}.search`),
}
};
clause.bool.should.push(dsl);

conditions.push(dsl);
});

return clause;
return conditions;
};

queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) => {
Expand All @@ -214,7 +203,7 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) =
},
};
const filtersClause = queryGenerator.getFiltersClause(filters);
const textSearchClause = queryGenerator.getTextSearchClause(searchText);
const textSearchClause = queryGenerator.getTextSearchConditions(searchText);

body['_source'] = returnFields;

Expand All @@ -228,7 +217,7 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) =
}

if (textSearchClause != null) {
compoundQuery.bool.must.push(textSearchClause);
compoundQuery.bool.must = textSearchClause;
}

if (compoundQuery.bool.must.length > 0 || compoundQuery.bool.filter) {
Expand All @@ -244,10 +233,10 @@ queryGenerator.getSearchQueryV2 = (searchText, filters, options, returnFields) =
// body.aggs = agg;
// Add sort parameters
if (options?.sort) {
body.sort = [];
const tmp = {};
tmp[options.sort.k] = options.sort.v;
body.sort.push(tmp);
body.sort = []; // Initialize a list of sort clauses
const sortClause = {};
sortClause[options.sort.k] = options.sort.v; // In our API, "k" is the property name, and "v" is the direction
body.sort.push(sortClause);
}

body.highlight = {
Expand Down Expand Up @@ -305,17 +294,17 @@ queryGenerator.getDatasetFiltersQuery = (searchText, searchFilters, excludedFiel
([filterName]) => filterName != excludedField
)
));
const textSearchClause = queryGenerator.getTextSearchClause(searchText);
const textSearchClause = queryGenerator.getTextSearchConditions(searchText);

if (filtersClause != null) {
compoundQuery.bool['filter'] = filtersClause;
}

if (textSearchClause != null) {
compoundQuery.bool.must.push(textSearchClause);
compoundQuery.bool.must = textSearchClause;
}

if (compoundQuery.bool.must.length > 0) {
if (compoundQuery.bool.must.length > 0 || compoundQuery.bool.filter) {
body.query = compoundQuery;
}

Expand Down
79 changes: 79 additions & 0 deletions Utils/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { describe, it, expect } from 'vitest';
const utils = require('./index.js');

describe('getSearchableText', () => {
it('should return filtered text with terms longer than 2 characters', () => {
const result = utils.getSearchableText('hello world test');
expect(result).toBe('hello world test');
});

it('should filter out terms with 2 or fewer characters', () => {
const result = utils.getSearchableText('hello to a world');
expect(result).toBe('hello world');
});

it('should handle extra spaces between words', () => {
const result = utils.getSearchableText('hello world test');
expect(result).toBe('hello world test');
});

it('should handle leading and trailing spaces', () => {
const result = utils.getSearchableText(' hello world ');
expect(result).toBe('hello world');
});

it('should return empty string when all terms are 2 or fewer characters', () => {
const result = utils.getSearchableText('a to in at');
expect(result).toBe('');
});

it('should return empty string for empty input', () => {
const result = utils.getSearchableText('');
expect(result).toBe('');
});

it('should return empty string for whitespace only input', () => {
const result = utils.getSearchableText(' ');
expect(result).toBe('');
});

it('should handle single word longer than 2 characters', () => {
const result = utils.getSearchableText('test');
expect(result).toBe('test');
});

it('should filter out single word with 2 or fewer characters', () => {
const result = utils.getSearchableText('at');
expect(result).toBe('');
});

it('should handle mixed case text', () => {
const result = utils.getSearchableText('Hello WORLD Test');
expect(result).toBe('Hello WORLD Test');
});

it('should preserve special characters in terms longer than 2 characters', () => {
const result = utils.getSearchableText('test-123 data@2024');
expect(result).toBe('test-123 data@2024');
});

it('should handle terms with exactly 3 characters', () => {
const result = utils.getSearchableText('the cat dog');
expect(result).toBe('the cat dog');
});

it('should filter terms with exactly 2 characters', () => {
const result = utils.getSearchableText('in on at to my cat');
expect(result).toBe('cat');
});

it('should filter terms with exactly 1 character', () => {
const result = utils.getSearchableText('a b c test hello');
expect(result).toBe('test hello');
});

it('should handle complex mixed input', () => {
const result = utils.getSearchableText(' a hello to world in test ');
expect(result).toBe('hello world test');
});
});
Loading
Loading