diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2e67802f6..d4b50473f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -204,9 +204,10 @@ dev-overrides: VALUES_FILE: "dev.values.yaml" extends: - ".helm-overrides" + # TODO: temporarily pull values from library branch for refactored chart's values script: - | - git clone https://dev:${GITLAB_TOKEN}@gitlab.advana.boozallencsn.com/advana/gamechanger/gamechanger-ci-cd.git + git clone -b library https://dev:${GITLAB_TOKEN}@gitlab.advana.boozallencsn.com/advana/gamechanger/gamechanger-ci-cd.git mv gamechanger-ci-cd/${VALUES_FILE} "${HELM_UPGRADE_VALUES_FILE}" rm -rf gamechanger-ci-cd @@ -242,11 +243,11 @@ trigger: --set image.tag=${CI_COMMIT_SHA}-oci --set appVersion=${BUILD_VERSION} --set ingress.enabled=true - --set ingress.web.hostname=${HELM_RELEASE_NAME} - --set env.REACT_APP_BACKEND_URL=${DEV_URL} - --set env.REACT_APP_USER_TOKEN_ENDPOINT=${DEV_URL}/api/auth/token - --set env.REACT_APP_LOGIN_ROUTE=${DEV_URL}/login - --set env.APPROVED_API_CALLERS=${DEV_URL} + --set global.appName=${HELM_RELEASE_NAME} + --set deployment.web.container.gamechanger-web.env.REACT_APP_BACKEND_URL=${DEV_URL} + --set deployment.web.container.gamechanger-web.env.REACT_APP_USER_TOKEN_ENDPOINT=${DEV_URL}/api/auth/token + --set deployment.web.container.gamechanger-web.env.REACT_APP_LOGIN_ROUTE=${DEV_URL}/login + --set deployment.web.container.gamechanger-web.env.APPROVED_API_CALLERS=${DEV_URL} - if: $DEPLOY_TO_PERSONAL != "false" variables: UPSTREAM_COMMIT_BRANCH: ${CI_COMMIT_BRANCH} @@ -260,11 +261,11 @@ trigger: --set image.tag=${CI_COMMIT_SHA}-oci --set appVersion=${BUILD_VERSION} --set ingress.enabled=true - --set ingress.web.hostname=${HELM_RELEASE_NAME} - --set env.REACT_APP_BACKEND_URL=${DEV_URL} - --set env.REACT_APP_USER_TOKEN_ENDPOINT=${DEV_URL}/api/auth/token - --set env.REACT_APP_LOGIN_ROUTE=${DEV_URL}/login - --set env.APPROVED_API_CALLERS=${DEV_URL} + --set global.appName=${HELM_RELEASE_NAME} + --set deployment.web.container.gamechanger-web.env.REACT_APP_BACKEND_URL=${DEV_URL} + --set deployment.web.container.gamechanger-web.env.REACT_APP_USER_TOKEN_ENDPOINT=${DEV_URL}/api/auth/token + --set deployment.web.container.gamechanger-web.env.REACT_APP_LOGIN_ROUTE=${DEV_URL}/login + --set deployment.web.container.gamechanger-web.env.APPROVED_API_CALLERS=${DEV_URL} - if: $CI_COMMIT_BRANCH == "main" # trigger: # strategy: "depend" diff --git a/.gitlab/issue_templates/bugfix.md b/.gitlab/issue_templates/bugfix.md index cdad26e61..f86debb3c 100644 --- a/.gitlab/issue_templates/bugfix.md +++ b/.gitlab/issue_templates/bugfix.md @@ -2,7 +2,6 @@ - ## Steps to Reproduce the Bug @@ -11,5 +10,8 @@ +## Initial thoughts / Potential relevant files + + - \ No newline at end of file + diff --git a/.gitlab/issue_templates/cosmetic.md b/.gitlab/issue_templates/cosmetic.md new file mode 100644 index 000000000..55a41d7e0 --- /dev/null +++ b/.gitlab/issue_templates/cosmetic.md @@ -0,0 +1,13 @@ +## Description + + + +## Mockups (if applicable) + + + +## Initial thoughts / Potential relevant files + + + + diff --git a/.gitlab/issue_templates/feature.md b/.gitlab/issue_templates/feature.md index 970e01fca..1aee8eda0 100644 --- a/.gitlab/issue_templates/feature.md +++ b/.gitlab/issue_templates/feature.md @@ -1,11 +1,13 @@ ## Description - - + ## Mockups (if applicable) +## Initial thoughts / Potential relevant files + + - \ No newline at end of file + diff --git a/.gitlab/merge_request_templates/default_request.md b/.gitlab/merge_request_templates/default_request.md index 53332a49b..a753e5d35 100644 --- a/.gitlab/merge_request_templates/default_request.md +++ b/.gitlab/merge_request_templates/default_request.md @@ -12,6 +12,8 @@ ## Types of changes + +- [ ] User interface improvement (non-breaking change that that improves the UI cosmetically) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Optimization (provides speedup with no functional changes) @@ -23,6 +25,7 @@ ## Requester Checklist: + - [ ] Documentation updated @@ -33,7 +36,8 @@ - [ ] Dev tools or other infrastructure changed ## Reviewer Checklist: - + +Not all of these may be required/applicable. - [ ] Review the Changelog/Code - [ ] Test locally and aggressively diff --git a/Dockerfile.prod b/Dockerfile.prod index 6a2730e37..fe2d2afd8 100644 --- a/Dockerfile.prod +++ b/Dockerfile.prod @@ -97,7 +97,13 @@ RUN cd "${APP_BACKEND_DIR}" && \ echo "//${_registry_fqdn}/:_authToken=${NPM_AUTH_TOKEN}" > .npmrc && \ echo "@${NPM_PROFILE}:registry=${NPM_REGISTRY}" >> .npmrc \ )) && \ - npm install --production + npm install --production + +USER root + +RUN yum remove git -y + +USER "${APP_UID}":"${APP_GID}" USER root diff --git a/backend/.env.template b/backend/.env.template index f21c95b02..de3db9e03 100644 --- a/backend/.env.template +++ b/backend/.env.template @@ -22,15 +22,15 @@ GAMECHANGER_ELASTICSEARCH_SUGGEST_INDEX= GAMECHANGER_ELASTICSEARCH_ENTITIES_INDEX= GAMECHANGER_ELASTICSEARCH_HISTORY_INDEX= -EDA_ELASTICSEARCH_HOST= -EDA_ELASTICSEARCH_PORT= -EDA_ELASTICSEARCH_USER= -EDA_ELASTICSEARCH_PASSWORD= -EDA_ELASTICSEARCH_CA= -EDA_ELASTICSEARCH_CA_FILEPATH= -EDA_ELASTICSEARCH_INDEX= -EDA_ELASTICSEARCH_FILTER_PICKLIST_INDEX= -EDA_ELASTICSEARCH_PROTOCOL= +EDA_OPENSEARCH_HOST= +EDA_OPENSEARCH_PORT= +EDA_OPENSEARCH_USER= +EDA_OPENSEARCH_PASSWORD= +EDA_OPENSEARCH_CA= +EDA_OPENSEARCH_CA_FILEPATH= +EDA_OPENSEARCH_INDEX= +EDA_OPENSEARCH_FILTER_PICKLIST_INDEX= +EDA_OPENSEARCH_PROTOCOL= EDA_DATA_HOST= HERMES_ELASTICSEARCH_INDEX= diff --git a/backend/node_app/config/constants.js b/backend/node_app/config/constants.js index e916c1f1c..d9ce47cd4 100644 --- a/backend/node_app/config/constants.js +++ b/backend/node_app/config/constants.js @@ -135,14 +135,14 @@ module.exports = Object.freeze({ requestTimeout: 60000, }, EDA_ELASTIC_SEARCH_OPTS: { - protocol: process.env.EDA_ELASTICSEARCH_PROTOCOL || 'https', - host: process.env.EDA_ELASTICSEARCH_HOST || 'loclhost', - port: process.env.EDA_ELASTICSEARCH_PORT || '443', - user: process.env.EDA_ELASTICSEARCH_USER ? process.env.EDA_ELASTICSEARCH_USER : '', - password: process.env.EDA_ELASTICSEARCH_PASSWORD || 'password', - ca: process.env.EDA_ELASTICSEARCH_CA ? process.env.EDA_ELASTICSEARCH_CA.replace(/\\n/g, '\n') : '', - index: process.env.EDA_ELASTICSEARCH_INDEX || 'eda', - filterPicklistIndex: process.env.EDA_ELASTICSEARCH_FILTER_PICKLIST_INDEX || 'gc_eda_picklist', + protocol: process.env.EDA_OPENSEARCH_PROTOCOL || 'https', + host: process.env.EDA_OPENSEARCH_HOST || 'loclhost', + port: process.env.EDA_OPENSEARCH_PORT || '443', + user: process.env.EDA_OPENSEARCH_USER ? process.env.EDA_OPENSEARCH_USER : '', + password: process.env.EDA_OPENSEARCH_PASSWORD || 'password', + ca: process.env.EDA_OPENSEARCH_CA ? process.env.EDA_OPENSEARCH_CA.replace(/\\n/g, '\n') : '', + index: process.env.EDA_OPENSEARCH_INDEX || 'eda', + filterPicklistIndex: process.env.EDA_OPENSEARCH_FILTER_PICKLIST_INDEX || 'gc_eda_picklist', extSearchFields: ['*_eda_ext'], //['acomod_eda_ext','product_or_service_line_item_eda_ext'], extRetrieveFields: ['*_eda_ext'], // index: 'eda' diff --git a/backend/node_app/controllers/appStatsController.js b/backend/node_app/controllers/appStatsController.js index 7f1c7f865..685223de5 100644 --- a/backend/node_app/controllers/appStatsController.js +++ b/backend/node_app/controllers/appStatsController.js @@ -288,18 +288,20 @@ class AppStatsController { connection.query( ` select - b.name as document, - CONVERT_TZ(a.server_time,'UTC','EST') as documenttime - from - matomo_log_link_visit_action a, - matomo_log_action b - where - a.idaction_name = b.idaction - and b.name like 'PDFViewer%gamechanger' - and hex(a.idvisitor) in (?) - order by - documenttime desc - limit 10`, + b.name as document, + max(CONVERT_TZ(a.server_time,'UTC','EST')) as documenttime + from + matomo_log_link_visit_action a, + matomo_log_action b + where + a.idaction_name = b.idaction + and b.name like 'PDFViewer%gamechanger' + and hex(a.idvisitor) in (?) + group by + document + order by + documenttime desc + limit 10`, [userId], (error, results) => { if (error) { diff --git a/backend/node_app/controllers/textSuggestionController.js b/backend/node_app/controllers/textSuggestionController.js index fd45830a2..f3d3695c8 100644 --- a/backend/node_app/controllers/textSuggestionController.js +++ b/backend/node_app/controllers/textSuggestionController.js @@ -48,7 +48,19 @@ class TextSuggestionController { this.logger.error(message, 'JBVZKTP', userId); } if (corrected.length > 0 && esClientName !== 'eda') { + const clientObj = { + esClientName: esClientName, + esIndex: index, + }; + const originalText = req.body.searchText; req.body.searchText = corrected; + // If the auto-corrected text doesn't return any results, don't suggest it to the user. + const correctedResults = await this.searchUtility.documentSearch(null, req.body, clientObj, userId); + + if (!correctedResults.totalCount) { + req.body.searchText = originalText; + corrected = ''; + } } if (suggestionsFlag === true) { const data_presearch = await this.getPresearchSuggestion({ diff --git a/backend/node_app/modules/eda/edaSearchUtility.js b/backend/node_app/modules/eda/edaSearchUtility.js index ee068c4c8..c41c965f0 100644 --- a/backend/node_app/modules/eda/edaSearchUtility.js +++ b/backend/node_app/modules/eda/edaSearchUtility.js @@ -1502,7 +1502,7 @@ class EDASearchUtility { }); return { - track_total_hits: true, + track_total_hits: 10000, size: 10, _source: { includes: ['pagerank_r', 'kw_doc_score_r', 'orgs_rs', 'file_location_eda_ext'], diff --git a/backend/node_app/utils/searchUtility.js b/backend/node_app/utils/searchUtility.js index 378286eaf..48df481ea 100644 --- a/backend/node_app/utils/searchUtility.js +++ b/backend/node_app/utils/searchUtility.js @@ -646,6 +646,7 @@ class SearchUtility { : 'paragraphs.par_raw_text_t.gc_english'; const analyzer = this.isVerbatim(searchText) ? 'standard' : 'gc_english'; const plainQuery = this.isVerbatim(searchText) ? parsedQuery.replace(/["']/g, '') : parsedQuery; + const plainQueryTrimmed = plainQuery.trim(); let mainKeywords = this.remove_stopwords(plainQuery) .replace(/["']/gi, '') .replace(/ OR | AND /gi, ' ') @@ -715,7 +716,7 @@ class SearchUtility { fuzzy_max_expansions: 100, fuzziness: 'AUTO', analyzer, - boost: 0.5, + boost: 0.05, }, }, ], @@ -725,62 +726,85 @@ class SearchUtility { }, }, { - wildcard: { - keyw_5: { - value: `*${plainQuery}*`, - boost: 5, - }, - }, - }, - { - wildcard: { - 'display_title_s.search': { - value: `*${plainQuery}*`, - boost: 15, - case_insensitive: true, - }, - }, - }, - { - fuzzy: { - 'display_title_s.search': { - value: `${plainQuery.trim()}`, - fuzziness: 'AUTO', // https://www.elastic.co/guide/en/elasticsearch/reference/current/common-options.html#fuzziness - transpositions: false, // do not allow edits to include transpositions of adjacent characters (e.g., ba -> ab) - boost: 15, - }, + dis_max: { + queries: [ + { + wildcard: { + 'display_title_s.search': { + value: `*${plainQueryTrimmed}*`, + case_insensitive: true, + }, + }, + }, + { + fuzzy: { + 'display_title_s.search': { + value: `${plainQueryTrimmed}`, + fuzziness: 'AUTO', + transpositions: false, + }, + }, + }, + { + match_phrase: { + 'display_title_s.search': plainQueryTrimmed, + }, + }, + { + wildcard: { + 'filename.search': { + value: `*${plainQueryTrimmed}*`, + case_insensitive: true, + }, + }, + }, + { + wildcard: { + doc_num: { + value: `*${plainQueryTrimmed}*`, + }, + }, + }, + { + term: { + doc_num: { + value: plainQueryTrimmed, + }, + }, + }, + ], + boost: 150, }, }, { - wildcard: { - 'filename.search': { - value: `*${plainQuery}*`, - boost: 10, - case_insensitive: true, - }, + dis_max: { + queries: [ + { + wildcard: { + 'top_entities_t.search': { + value: `*${plainQueryTrimmed}*`, + }, + }, + }, + { + wildcard: { + keyw_5: { + value: `*${plainQueryTrimmed}*`, + }, + }, + }, + ], + boost: 5, }, }, { wildcard: { 'display_source_s.search': { - value: `*${plainQuery}*`, - boost: 4, - }, - }, - }, - { - wildcard: { - 'top_entities_t.search': { - value: `*${plainQuery}*`, - boost: 5, + value: `*${plainQueryTrimmed}*`, + boost: 10, }, }, }, - { - match_phrase: { - 'display_title_s.search': plainQuery, - }, - }, ], minimum_should_match: 1, diff --git a/backend/test/controllers/textSuggestionController.test.js b/backend/test/controllers/textSuggestionController.test.js index 878e162c0..0a1f8d639 100644 --- a/backend/test/controllers/textSuggestionController.test.js +++ b/backend/test/controllers/textSuggestionController.test.js @@ -364,6 +364,13 @@ describe('TextSuggestionController', function () { return []; } }, + documentSearch(body) { + if (body) { + return { test: 'test' }; + } else { + return {}; + } + }, }; it('should return parsed data', async () => { @@ -411,7 +418,7 @@ describe('TextSuggestionController', function () { await target.getTextSuggestion(req, res); const expected = { - autocorrect: ['navy'], + autocorrect: [], presearchTitle: [ 'H.R. 4402: To authorize the Secretary of the Navy to establish a surface danger zone over the Guam National Wildlife Refuge or any portion thereof to support the operation of a live-fire training range complex.', 'H.R. 3183: An Act To designate the facility of the United States Postal Service located at 13683 James Madison Highway in Palmyra, Virginia, as the U.S. Navy Seaman Dakota Kyle Rigsby Post Office.', diff --git a/backend/test/modules/policy/policyGraphHandler.test.js b/backend/test/modules/policy/policyGraphHandler.test.js index f33f29f0e..be272cc58 100644 --- a/backend/test/modules/policy/policyGraphHandler.test.js +++ b/backend/test/modules/policy/policyGraphHandler.test.js @@ -3026,184 +3026,25 @@ describe('PolicyGraphHandler', function () { try { const actual = await target.callFunctionHelper(req, 'test'); const expected = { - query: { - _source: { includes: ['pagerank_r', 'kw_doc_score_r', 'orgs_rs', 'topics_s'] }, - stored_fields: [ - 'filename', - 'title', - 'page_count', - 'doc_type', - 'doc_num', - 'ref_list', - 'id', - 'summary_30', - 'keyw_5', - 'p_text', - 'type', - 'p_page', - 'display_title_s', - 'display_org_s', - 'display_doc_type_s', - 'is_revoked_b', - 'access_timestamp_dt', - 'publication_date_dt', - 'crawler_used_s', - 'download_url_s', - 'source_page_url_s', - 'source_fqdn_s', - 'topics_s', - 'top_entities_t', - ], - from: 0, - size: 20, - aggregations: { - doc_type_aggs: { terms: { field: 'display_doc_type_s', size: 10000 } }, - doc_org_aggs: { terms: { field: 'display_org_s', size: 10000 } }, - }, - track_total_hits: true, - query: { - bool: { - must: [], - should: [ - { - nested: { - path: 'paragraphs', - inner_hits: { - _source: false, - stored_fields: ['paragraphs.page_num_i', 'paragraphs.par_raw_text_t'], - from: 0, - size: 100, - highlight: { - fields: { - 'paragraphs.par_raw_text_t': { - fragment_size: 270, - number_of_fragments: 1, - type: 'plain', - }, - 'paragraphs.par_raw_text_t.gc_english': { - fragment_size: 270, - number_of_fragments: 1, - type: 'plain', - }, - }, - fragmenter: 'span', - }, - }, - query: { - bool: { - should: [ - { - query_string: { - query: 'artificial intelligence', - default_field: 'paragraphs.par_raw_text_t.gc_english', - default_operator: 'and', - fuzzy_max_expansions: 100, - fuzziness: 'AUTO', - analyzer: 'gc_english', - boost: 0.5, - }, - }, - ], - }, - }, - score_mode: 'sum', - }, - }, - { wildcard: { keyw_5: { value: '*artificial intelligence*', boost: 5 } } }, - { - wildcard: { - 'display_title_s.search': { - value: '*artificial intelligence*', - boost: 15, - case_insensitive: true, - }, - }, - }, - { - fuzzy: { - 'display_title_s.search': { - value: 'artificial intelligence', - fuzziness: 'AUTO', - transpositions: false, - boost: 15, - }, - }, - }, - { - wildcard: { - 'filename.search': { - value: '*artificial intelligence*', - boost: 10, - case_insensitive: true, - }, - }, - }, - { - wildcard: { - 'display_source_s.search': { value: '*artificial intelligence*', boost: 4 }, - }, - }, - { - wildcard: { - 'top_entities_t.search': { value: '*artificial intelligence*', boost: 5 }, - }, - }, - { match_phrase: { 'display_title_s.search': 'artificial intelligence' } }, - { - query_string: { - fields: ['display_title_s.search'], - query: '*artificial* AND *intelligence*', - type: 'best_fields', - boost: 10, - analyzer: 'gc_english', - }, - }, - ], - minimum_should_match: 1, - filter: [{ term: { is_revoked_b: 'false' } }, { terms: { id: ['Test'] } }], - }, - }, - highlight: { - require_field_match: false, - fields: { - 'display_title_s.search': {}, - keyw_5: {}, - 'filename.search': {}, - 'display_source_s.search': {}, - top_entities_t: {}, - topics_s: {}, - }, - fragment_size: 10, - fragmenter: 'simple', - type: 'unified', - boundary_scanner: 'word', - }, - sort: [{ _score: { order: 'desc' } }, { _id: 'desc' }], - }, - totalCount: 1, + doc_orgs: [], + doc_types: [], docs: [ { - display_title_s: 'DoDI 5000.02T: Operation of the Defense Acquisition System', - display_org_s: 'Dept. of Defense', + access_timestamp_dt: '2021-07-13T23:23:37', crawler_used_s: 'dod_issuances', + current_as_of: 'TEST', + display_doc_type_s: 'Instruction', + display_org_s: 'Dept. of Defense', + display_title_s: 'DoDI 5000.02T: Operation of the Defense Acquisition System', doc_num: '5000.02T', - summary_30: '', - top_entities_t: [ - 'DoD', - 'Program', - 'DoD Instruction', - 'HSI', - 'the Defense Acquisition System', - ], - topics_s: ['acquisition', 'enclosure', 'instruction', 'cybersecurity', 'change enclosure'], doc_type: 'DoDI', - title: 'Operation of the Defense Acquisition System', - type: 'document', - keyw_5: 'dodi 02t, formal coordination, information technology, formal coordinatio, acquisition programs, usd pubs@osd, training plans, systems engineering, qualification criteria, national intelligence', + esIndex: 'gamechanger', filename: 'DoDI 5000.02T CH 10.pdf', - access_timestamp_dt: '2021-07-13T23:23:37', id: 'DoDI 5000.02T CH 10.pdf_0', - display_doc_type_s: 'Instruction', + keyw_5: 'dodi 02t, formal coordination, information technology, formal coordinatio, acquisition programs, usd pubs@osd, training plans, systems engineering, qualification criteria, national intelligence', + pageHits: [], + page_count: 27, + publication_date_dt: '2015-01-07T00:00:00', ref_list: [ 'DoD 5000.04-M', 'DoD 5015.02-STD', @@ -3270,17 +3111,198 @@ describe('PolicyGraphHandler', function () { 'OMBM M-04-16', 'OMBM M-05-25', ], - publication_date_dt: '2015-01-07T00:00:00', - page_count: 27, - pageHits: [], - esIndex: 'gamechanger', - current_as_of: 'TEST', + summary_30: '', + title: 'Operation of the Defense Acquisition System', + top_entities_t: [ + 'DoD', + 'Program', + 'DoD Instruction', + 'HSI', + 'the Defense Acquisition System', + ], + topics_s: ['acquisition', 'enclosure', 'instruction', 'cybersecurity', 'change enclosure'], + type: 'document', }, ], - doc_types: [], - doc_orgs: [], - searchTerms: ['artificial', 'intelligence'], expansionDict: null, + query: { + _source: { includes: ['pagerank_r', 'kw_doc_score_r', 'orgs_rs', 'topics_s'] }, + aggregations: { + doc_org_aggs: { terms: { field: 'display_org_s', size: 10000 } }, + doc_type_aggs: { terms: { field: 'display_doc_type_s', size: 10000 } }, + }, + from: 0, + highlight: { + boundary_scanner: 'word', + fields: { + 'display_source_s.search': {}, + 'display_title_s.search': {}, + 'filename.search': {}, + keyw_5: {}, + top_entities_t: {}, + topics_s: {}, + }, + fragment_size: 10, + fragmenter: 'simple', + require_field_match: false, + type: 'unified', + }, + query: { + bool: { + filter: [{ term: { is_revoked_b: 'false' } }, { terms: { id: ['Test'] } }], + minimum_should_match: 1, + must: [], + should: [ + { + nested: { + inner_hits: { + _source: false, + from: 0, + highlight: { + fields: { + 'paragraphs.par_raw_text_t': { + fragment_size: 270, + number_of_fragments: 1, + type: 'plain', + }, + 'paragraphs.par_raw_text_t.gc_english': { + fragment_size: 270, + number_of_fragments: 1, + type: 'plain', + }, + }, + fragmenter: 'span', + }, + size: 100, + stored_fields: ['paragraphs.page_num_i', 'paragraphs.par_raw_text_t'], + }, + path: 'paragraphs', + query: { + bool: { + should: [ + { + query_string: { + query: 'artificial intelligence', + default_field: 'paragraphs.par_raw_text_t.gc_english', + default_operator: 'and', + fuzzy_max_expansions: 100, + fuzziness: 'AUTO', + analyzer: 'gc_english', + boost: 0.05, + }, + }, + ], + }, + }, + score_mode: 'sum', + }, + }, + { + dis_max: { + boost: 150, + queries: [ + { + wildcard: { + 'display_title_s.search': { + value: '*artificial intelligence*', + case_insensitive: true, + }, + }, + }, + { + fuzzy: { + 'display_title_s.search': { + value: 'artificial intelligence', + fuzziness: 'AUTO', + transpositions: false, + }, + }, + }, + { + match_phrase: { + 'display_title_s.search': 'artificial intelligence', + }, + }, + { + wildcard: { + 'filename.search': { + value: '*artificial intelligence*', + case_insensitive: true, + }, + }, + }, + { wildcard: { doc_num: { value: '*artificial intelligence*' } } }, + { term: { doc_num: { value: 'artificial intelligence' } } }, + ], + }, + }, + { + dis_max: { + boost: 5, + queries: [ + { + wildcard: { + 'top_entities_t.search': { + value: '*artificial intelligence*', + }, + }, + }, + { wildcard: { keyw_5: { value: '*artificial intelligence*' } } }, + ], + }, + }, + { + wildcard: { + 'display_source_s.search': { + boost: 10, + value: '*artificial intelligence*', + }, + }, + }, + { + query_string: { + analyzer: 'gc_english', + boost: 10, + fields: ['display_title_s.search'], + query: '*artificial* AND *intelligence*', + type: 'best_fields', + }, + }, + ], + }, + }, + size: 20, + sort: [{ _score: { order: 'desc' } }, { _id: 'desc' }], + stored_fields: [ + 'filename', + 'title', + 'page_count', + 'doc_type', + 'doc_num', + 'ref_list', + 'id', + 'summary_30', + 'keyw_5', + 'p_text', + 'type', + 'p_page', + 'display_title_s', + 'display_org_s', + 'display_doc_type_s', + 'is_revoked_b', + 'access_timestamp_dt', + 'publication_date_dt', + 'crawler_used_s', + 'download_url_s', + 'source_page_url_s', + 'source_fqdn_s', + 'topics_s', + 'top_entities_t', + ], + track_total_hits: true, + }, + searchTerms: ['artificial', 'intelligence'], + totalCount: 1, }; assert.deepStrictEqual(actual, expected); diff --git a/backend/test/utils/searchUtility.test.js b/backend/test/utils/searchUtility.test.js index 74109de3a..11adc5eba 100644 --- a/backend/test/utils/searchUtility.test.js +++ b/backend/test/utils/searchUtility.test.js @@ -1293,64 +1293,38 @@ describe('SearchUtility', function () { typeFilterString: [], }); const expected = { - _source: { - includes: ['pagerank_r', 'kw_doc_score_r', 'orgs_rs', 'topics_s'], + _source: { includes: ['pagerank_r', 'kw_doc_score_r', 'orgs_rs', 'topics_s'] }, + aggregations: { + doc_org_aggs: { terms: { field: 'display_org_s', size: 10000 } }, + doc_type_aggs: { terms: { field: 'display_doc_type_s', size: 10000 } }, }, - stored_fields: [ - 'filename', - 'title', - 'page_count', - 'doc_type', - 'doc_num', - 'ref_list', - 'id', - 'summary_30', - 'keyw_5', - 'p_text', - 'type', - 'p_page', - 'display_title_s', - 'display_org_s', - 'display_doc_type_s', - 'is_revoked_b', - 'access_timestamp_dt', - 'publication_date_dt', - 'crawler_used_s', - 'download_url_s', - 'source_page_url_s', - 'source_fqdn_s', - 'topics_s', - 'top_entities_t', - ], from: 0, - size: 20, - aggregations: { - doc_type_aggs: { - terms: { - field: 'display_doc_type_s', - size: 10000, - }, - }, - doc_org_aggs: { - terms: { - field: 'display_org_s', - size: 10000, - }, + highlight: { + boundary_scanner: 'word', + fields: { + 'display_source_s.search': {}, + 'display_title_s.search': {}, + 'filename.search': {}, + keyw_5: {}, + top_entities_t: {}, + topics_s: {}, }, + fragment_size: 10, + fragmenter: 'simple', + require_field_match: false, + type: 'unified', }, - track_total_hits: true, query: { bool: { + filter: [{ term: { is_revoked_b: 'false' } }], + minimum_should_match: 1, must: [], should: [ { nested: { - path: 'paragraphs', inner_hits: { _source: false, - stored_fields: ['paragraphs.page_num_i', 'paragraphs.par_raw_text_t'], from: 0, - size: 100, highlight: { fields: { 'paragraphs.par_raw_text_t': { @@ -1366,7 +1340,10 @@ describe('SearchUtility', function () { }, fragmenter: 'span', }, + size: 100, + stored_fields: ['paragraphs.page_num_i', 'paragraphs.par_raw_text_t'], }, + path: 'paragraphs', query: { bool: { should: [ @@ -1378,7 +1355,7 @@ describe('SearchUtility', function () { fuzzy_max_expansions: 100, fuzziness: 'AUTO', analyzer: 'gc_english', - boost: 0.5, + boost: 0.05, }, }, ], @@ -1388,106 +1365,101 @@ describe('SearchUtility', function () { }, }, { - wildcard: { - keyw_5: { - value: '*artificial intelligence*', - boost: 5, - }, - }, - }, - { - wildcard: { - 'display_title_s.search': { - value: '*artificial intelligence*', - boost: 15, - case_insensitive: true, - }, - }, - }, - { - fuzzy: { - 'display_title_s.search': { - value: 'artificial intelligence', - fuzziness: 'AUTO', - transpositions: false, - boost: 15, - }, - }, - }, - { - wildcard: { - 'filename.search': { - value: '*artificial intelligence*', - boost: 10, - case_insensitive: true, - }, + dis_max: { + boost: 150, + queries: [ + { + wildcard: { + 'display_title_s.search': { + case_insensitive: true, + value: '*artificial intelligence*', + }, + }, + }, + { + fuzzy: { + 'display_title_s.search': { + fuzziness: 'AUTO', + transpositions: false, + value: 'artificial intelligence', + }, + }, + }, + { match_phrase: { 'display_title_s.search': 'artificial intelligence' } }, + { + wildcard: { + 'filename.search': { + case_insensitive: true, + value: '*artificial intelligence*', + }, + }, + }, + { wildcard: { doc_num: { value: '*artificial intelligence*' } } }, + { term: { doc_num: { value: 'artificial intelligence' } } }, + ], }, }, { - wildcard: { - 'display_source_s.search': { - value: '*artificial intelligence*', - boost: 4, - }, + dis_max: { + boost: 5, + queries: [ + { + wildcard: { + 'top_entities_t.search': { value: '*artificial intelligence*' }, + }, + }, + { wildcard: { keyw_5: { value: '*artificial intelligence*' } } }, + ], }, }, { wildcard: { - 'top_entities_t.search': { - value: '*artificial intelligence*', - boost: 5, - }, - }, - }, - { - match_phrase: { - 'display_title_s.search': 'artificial intelligence', + 'display_source_s.search': { boost: 10, value: '*artificial intelligence*' }, }, }, { query_string: { + analyzer: 'gc_english', + boost: 10, fields: ['display_title_s.search'], query: '*artificial* AND *intelligence*', type: 'best_fields', - boost: 10, - analyzer: 'gc_english', - }, - }, - ], - minimum_should_match: 1, - filter: [ - { - term: { - is_revoked_b: 'false', }, }, ], }, }, - highlight: { - require_field_match: false, - fields: { - 'display_title_s.search': {}, - keyw_5: {}, - 'filename.search': {}, - 'display_source_s.search': {}, - top_entities_t: {}, - topics_s: {}, - }, - fragment_size: 10, - fragmenter: 'simple', - type: 'unified', - boundary_scanner: 'word', - }, - sort: [ - { - _score: { - order: 'desc', - }, - }, - { _id: 'desc' }, + size: 20, + sort: [{ _score: { order: 'desc' } }, { _id: 'desc' }], + stored_fields: [ + 'filename', + 'title', + 'page_count', + 'doc_type', + 'doc_num', + 'ref_list', + 'id', + 'summary_30', + 'keyw_5', + 'p_text', + 'type', + 'p_page', + 'display_title_s', + 'display_org_s', + 'display_doc_type_s', + 'is_revoked_b', + 'access_timestamp_dt', + 'publication_date_dt', + 'crawler_used_s', + 'download_url_s', + 'source_page_url_s', + 'source_fqdn_s', + 'topics_s', + 'top_entities_t', ], + track_total_hits: true, }; + assert.deepStrictEqual(actual, expected); }); diff --git a/chart/Chart.lock b/chart/Chart.lock new file mode 100644 index 000000000..a5ca525ff --- /dev/null +++ b/chart/Chart.lock @@ -0,0 +1,6 @@ +dependencies: + - name: advana-library + repository: oci://092912502985.dkr.ecr.us-east-1.amazonaws.com/platform/charts + version: 1.0.2 +digest: sha256:01cdd204f42640bd6a23d2344ee4a82ab1c21ce09cbffd35f457fa5d027008c1 +generated: "2023-01-26T11:09:50.284308-08:00" diff --git a/chart/Chart.yaml b/chart/Chart.yaml index 1ddd0232e..8a2a37d55 100644 --- a/chart/Chart.yaml +++ b/chart/Chart.yaml @@ -4,5 +4,12 @@ description: A Helm chart for Kubernetes type: application version: 1.0.0 -appVersion: "1.0.0" +appVersion: "1.31.0.20230130" +dependencies: + - name: advana-library + version: 1.0.2 + # repository: https://gitlab.advana.boozallencsn.com/platform/charts/advana-library + repository: oci://092912502985.dkr.ecr.us-east-1.amazonaws.com/platform/charts + tags: + - library diff --git a/chart/charts/advana-library-1.0.2.tgz b/chart/charts/advana-library-1.0.2.tgz new file mode 100644 index 000000000..ce235b85d Binary files /dev/null and b/chart/charts/advana-library-1.0.2.tgz differ diff --git a/chart/deploy.sh b/chart/deploy.sh deleted file mode 100644 index e20f6505d..000000000 --- a/chart/deploy.sh +++ /dev/null @@ -1 +0,0 @@ -helm upgrade --install gamechanger-chart gamechanger-chart -f gamechanger-chart/values.yaml -f gamechanger-chart/values.dev.yaml \ No newline at end of file diff --git a/chart/templates/NOTES.txt b/chart/templates/NOTES.txt index 5f634f15c..72ca2f473 100644 --- a/chart/templates/NOTES.txt +++ b/chart/templates/NOTES.txt @@ -1,7 +1,11 @@ +{{ include "common.notes" $ }} +{{- range $deploymentName, $deploymentValues := $.Values.deployment }} +{{- range $typeName, $typeValues := $deploymentValues.service }} +{{- if eq ($typeName | toString ) "NodePort" }} 1. Get the application URL by running these commands: -{{- if .Values.service }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "chart.fullname" . }}) - export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + export NODE_PORT=$(kubectl get --namespace {{ $.Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "common.fullname" $ }}-{{ $deploymentName }}-{{ lower $typeName }}) + export NODE_IP=$(kubectl get nodes --namespace {{ $.Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT {{- end }} - +{{- end }} +{{- end }} diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl index d058e1ff6..39e3fd04d 100644 --- a/chart/templates/_helpers.tpl +++ b/chart/templates/_helpers.tpl @@ -77,6 +77,18 @@ Env Config Secret Name {{- printf "%s.%s" (include "chart.fullname" .) "env-config-secret" }} {{- end }} +{{- define "chart.envConfigSecretNameVault" }} +{{- printf "%s.%s" (include "chart.fullname" .) "env-config-secret-vault" }} +{{- end }} + +{{- define "chart.vaultKey" }} +{{- printf "%s/%s" (.Values.vaultKey) (include "chart.fullname" .) }} +{{- end }} + +{{- define "chart.vaultCertsKey" }} +{{- printf "%s/%s" (include "chart.vaultKey" .) "certs" }} +{{- end }} + {{/* File Config Secret Name @@ -96,7 +108,7 @@ Check File Config Defined One Time Job Name */}} {{- define "chart.oneTimeJobName" }} -{{- printf "%s.%s.%s" (include "chart.fullname" .) "one-time-job" (now | date "20060102-150405") }} +{{- printf "%s.%s" "job" (now | date "20060102-150405") }} {{- end }} {{/* @@ -135,6 +147,10 @@ Usage: {{- end }} {{- end -}} +#check if service type is defined +{{- define "app.isNodePort" }} +{{- if .Values.service.http.nodePort -}}{{- if .Values.service.https.nodePort -}}1{{- else -}}{{- end -}}{{- end -}} +{{- end }} {{/* Generate backend entry that is compatible with all Kubernetes API versions. @@ -220,8 +236,8 @@ Usage: {{- define "app.web.fqdn" -}} {{ $name := default (include "app.web.name" . | lower ) .Values.ingress.web.hostname }} -{{- if .Values.externalDomain -}} -{{- printf "%s.%s" $name .Values.externalDomain -}} +{{- if .Values.baseDomain -}} +{{- printf "%s.%s" $name .Values.baseDomain -}} {{- else -}} {{ printf "%s" $name }} {{- end -}} @@ -229,4 +245,4 @@ Usage: {{- define "app.web.name" -}} {{- printf "%s-web" (include "chart.fullname" .) -}} -{{- end -}} \ No newline at end of file +{{- end -}} diff --git a/chart/templates/deployment.yaml b/chart/templates/deployment.yaml index 57df8f764..07ee0b512 100644 --- a/chart/templates/deployment.yaml +++ b/chart/templates/deployment.yaml @@ -1,118 +1,2 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "chart.fullname" . }} - labels: - {{- include "chart.labels" . | nindent 4 }} - {{- with .Values.deploymentAnnotations }} - {{- toYaml . | nindent 8 }} - {{- end }} -spec: - replicas: {{ .Values.replicaCount }} - selector: - matchLabels: - {{- include "chart.selectorLabels" . | nindent 6 }} - template: - metadata: - annotations: - {{- /* checksum/ annotations are used here to force helm to redeploy when associated config values change */}} - checksum/env-config-secret: {{ include (print .Template.BasePath "/env_config_secret.yaml") . | sha256sum }} - {{- if (include "chart.isFileConfigDefined" .) }} - checksum/file-config-secret: {{ include (print .Template.BasePath "/file_config_secret.yaml") . | sha256sum }} - {{- end }} - {{- with .Values.podAnnotations -}}{{- toYaml . | nindent 8 }}{{- end }} - labels: - {{- include "chart.selectorLabels" . | nindent 8 }} - spec: - {{- with .Values.imagePullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - serviceAccountName: {{ include "chart.serviceAccountName" . }} - {{- with .Values.podSecurityContext }} - securityContext: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- if (include "chart.isFileConfigDefined" .) }} - volumes: - - name: file-config-vol - secret: - secretName: {{ include "chart.fileConfigSecretName" . | quote }} - {{- end }} - {{- if and .Values.scripts.runInitJob ( .Values.scripts.initJobScript | default "" | trim ) }} - initContainers: - - name: init-job - command: ["/bin/bash", "-c"] - args: - - | - {{- .Values.scripts.initJobScript | trim | nindent 16 }} - {{- with .Values.appContainerSecurityContext }} - securityContext: - {{- toYaml . | nindent 12 }} - {{- end }} - image: {{ include "chart.imageRepo" . | quote }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - {{- if (include "chart.isFileConfigDefined" .) }} - volumeMounts: - - name: file-config-vol - mountPath: {{ .Values.fileConfig.baseMountPath | quote }} - readOnly: true - {{- end }} - env: - - name: REACT_APP_VERSION - value: {{ default "1.0.0" .Chart.AppVersion | quote }} - - name: REACT_APP_VERSION - valueFrom: - secretKeyRef: - name: {{ include "chart.envConfigSecretName" . | quote }} - key: REACT_APP_VERSION - optional: true - envFrom: - - secretRef: - name: {{ include "chart.envConfigSecretName" . | quote }} - {{- end }} - containers: - - name: {{ .Chart.Name }} - {{- with .Values.startCommand }} - command: - {{- range . }} - {{- printf "- %s" (. | quote) | nindent 12 }} - {{- end -}} - {{- end }} - {{- if (include "chart.isFileConfigDefined" .) }} - volumeMounts: - - name: file-config-vol - mountPath: {{ .Values.fileConfig.baseMountPath | quote }} - readOnly: true - {{- end }} - {{- with .Values.appContainerSecurityContext }} - securityContext: - {{- toYaml . | nindent 12 }} - {{- end }} - image: {{ include "chart.imageRepo" . | quote }} - imagePullPolicy: {{ .Values.image.pullPolicy }} - env: - - name: REACT_APP_VERSION - value: {{ default "1.0.0" .Chart.AppVersion | quote }} - - name: REACT_APP_VERSION - valueFrom: - secretKeyRef: - name: {{ include "chart.envConfigSecretName" . | quote }} - key: REACT_APP_VERSION - optional: true - envFrom: - - secretRef: - name: {{ include "chart.envConfigSecretName" . | quote }} - {{- with .Values.service }} - ports: - {{- range (list .http .https) }} - - name: {{ .name }} - containerPort: {{ .targetPort }} - protocol: {{ .protocol }} - {{- end }} - {{- end }} - {{- with .Values.probes -}}{{- toYaml . | nindent 10}}{{- end }} - {{- with .Values.resources }} - resources: - {{- toYaml . | nindent 12 }} - {{- end }} +# see: https://gitlab.advana.boozallencsn.com/platform/charts/advana-library/-/blob/main/templates/_deployment.tpl +{{ include "common.deployment" $ }} diff --git a/chart/templates/env_config_secret.yaml b/chart/templates/env_config_secret.yaml deleted file mode 100644 index 8dd5f4df8..000000000 --- a/chart/templates/env_config_secret.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{- if .Values.env -}} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "chart.envConfigSecretName" . }} - labels: - {{- include "chart.labels" . | nindent 8 }} -data: - {{- range $key, $val := .Values.env }} - {{- if not (typeOf $val | eq "") -}} - {{- printf "%s: %s" $key ($val | toString | b64enc | quote) | nindent 4 }} - {{- end -}} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/chart/templates/externalsecret.yaml b/chart/templates/externalsecret.yaml new file mode 100644 index 000000000..7b7bf2db4 --- /dev/null +++ b/chart/templates/externalsecret.yaml @@ -0,0 +1,2 @@ +# see: https://gitlab.advana.boozallencsn.com/platform/charts/advana-library/-/blob/main/templates/_externalsecret.tpl +{{ include "common.externalsecret" $ }} diff --git a/chart/templates/file_config_secret.yaml b/chart/templates/file_config_secret.yaml deleted file mode 100644 index 726f1b694..000000000 --- a/chart/templates/file_config_secret.yaml +++ /dev/null @@ -1,14 +0,0 @@ -{{- if (include "chart.isFileConfigDefined" .) }} -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "chart.fileConfigSecretName" . }} - labels: - {{- include "chart.labels" . | nindent 8 }} -data: - {{- range $key, $val := .Values.fileConfig.files }} - {{- if not (typeOf $val | eq "") -}} - {{- printf "%s: %s" $key ($val | toString | b64enc | quote) | nindent 4 }} - {{- end -}} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/chart/templates/ingress.yaml b/chart/templates/ingress.yaml index c85dbe7a7..6ac233722 100644 --- a/chart/templates/ingress.yaml +++ b/chart/templates/ingress.yaml @@ -1,36 +1,2 @@ -{{- $fqdn := include "app.web.fqdn" . }} -{{- if eq .Values.ingress.controller "gce" }} - {{- $_ := set . "web_path" "/*" -}} -{{- else if eq .Values.ingress.controller "ncp" }} - {{- $_ := set . "web_path" "/.*" -}} -{{- else }} - {{- $_ := set . "web_path" "/" -}} -{{- end }} -{{- if eq .Values.ingress.enabled true }} -apiVersion: {{ template "chart.capabilities.ingress.apiVersion" . }} -kind: Ingress -metadata: - name: {{ include "chart.fullname" . }} - labels: {{- include "chart.labels" . | nindent 4 }} - annotations: - kubernetes.io/ingress.class: nginx - meta.helm.sh/release-name: {{ include "chart.fullname" . }} - meta.helm.sh/release-namespace: {{ include "chart.fullname" . }} - nginx.ingress.kubernetes.io/server-snippet: {{- .Values.ingress.serversnippets | toYaml | indent 4 }} -spec: - rules: - - host: {{ $fqdn | quote }} - http: - paths: - - backend: - service: - name: {{ include "chart.fullname" . }} - port: - number: 80 - path: / - pathType: ImplementationSpecific - tls: - - hosts: - - {{ $fqdn | quote }} - secretName: {{ .Values.ingress.web.tlsSecretName }} -{{- end }} +# see: https://gitlab.advana.boozallencsn.com/platform/charts/advana-library/-/blob/main/templates/_ingress.tpl +{{ include "common.ingress" $ }} diff --git a/chart/templates/job.yaml b/chart/templates/job.yaml new file mode 100644 index 000000000..8d462472a --- /dev/null +++ b/chart/templates/job.yaml @@ -0,0 +1,2 @@ +# see: https://gitlab.advana.boozallencsn.com/platform/charts/advana-library/-/blob/main/templates/_job.tpl +{{ include "common.job" $ }} diff --git a/chart/templates/one_time_job.yaml b/chart/templates/one_time_job.yaml index ebf2bb3da..e1f5343df 100644 --- a/chart/templates/one_time_job.yaml +++ b/chart/templates/one_time_job.yaml @@ -50,12 +50,6 @@ spec: env: - name: REACT_APP_VERSION value: {{ default "1.0.0" .Chart.AppVersion | quote }} - - name: REACT_APP_VERSION - valueFrom: - secretKeyRef: - name: {{ include "chart.envConfigSecretName" . | quote }} - key: REACT_APP_VERSION - optional: true envFrom: - secretRef: name: {{ include "chart.envConfigSecretName" . | quote }} diff --git a/chart/templates/secrets.yaml b/chart/templates/secrets.yaml new file mode 100644 index 000000000..d65a4929b --- /dev/null +++ b/chart/templates/secrets.yaml @@ -0,0 +1,5 @@ +# see: https://gitlab.advana.boozallencsn.com/platform/charts/advana-library/-/blob/main/templates/secret.tpl +{{ include "common.env.secret" $ }} +{{ include "common.volume.secret" $ }} +{{ include "common.tls.secret" $ }} +{{ include "common.dockerconfigjson.secret" $ }} \ No newline at end of file diff --git a/chart/templates/service.yaml b/chart/templates/service.yaml index 31cc48263..5c698dfb7 100644 --- a/chart/templates/service.yaml +++ b/chart/templates/service.yaml @@ -1,14 +1,2 @@ -{{- if .Values.service -}} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "chart.fullname" . }} - labels: - {{- include "chart.labels" . | nindent 4 }} -spec: - type: ClusterIP - ports: - {{- toYaml (list .Values.service.http .Values.service.https) | nindent 4 }} - selector: - {{- include "chart.selectorLabels" . | nindent 4 }} -{{- end }} +# see: https://gitlab.advana.boozallencsn.com/platform/charts/advana-library/-/blob/main/templates/_service.tpl +{{ include "common.service" $ }} diff --git a/chart/templates/serviceaccount.yaml b/chart/templates/serviceaccount.yaml index 26a57fa86..b2e9b3ea9 100644 --- a/chart/templates/serviceaccount.yaml +++ b/chart/templates/serviceaccount.yaml @@ -1,12 +1,2 @@ -{{- if .Values.serviceAccount.create -}} -apiVersion: v1 -kind: ServiceAccount -metadata: - name: {{ include "chart.serviceAccountName" . }} - labels: - {{- include "chart.labels" . | nindent 4 }} - {{- with .Values.serviceAccount.annotations }} - annotations: - {{- toYaml . | nindent 4 }} - {{- end }} -{{- end }} +# see: https://gitlab.advana.boozallencsn.com/platform/charts/advana-library/-/blob/main/templates/_serviceaccount.tpl +{{ include "common.serviceaccount" $ }} diff --git a/chart/values.yaml b/chart/values.yaml index 29965ea38..bb90043ac 100644 --- a/chart/values.yaml +++ b/chart/values.yaml @@ -1,21 +1,17 @@ -# DEFAULT VALUES +############################################################################### +## Global Values ## +############################################################################### -# replicaCount - = 1> -# Number of app pod replicas to launch in the load-balancing group. -# -# Tips: -# - when troubleshooting, set to 1 to avoid dealing with complications of load-balanced traffic -replicaCount: 1 +global: + awsRegion: us-east-1 + appName: gamechanger + # main domain for the application dev.advana.us for dev tests.advana.us for test + baseDomain: "dev.advana.us" # advana.example.local + registry: docker.io + imagePullSecrets: {} -# startCommand - -# Entrypoint/command to be executed by the main application pod. -# Will completely override any entrypoint/cmd baked into the container. -# -# Tips: -# - when troubleshooting, set to ["sleep", "infinity"] then exec into container and tweak/relaunch app as necessary -startCommand: - - node - - index.js +# DEFAULT VALUES +vaultKey: kv/aws-us-gov/default/prod # scripts - # BASH scripts that run during various points in the deployment @@ -39,37 +35,11 @@ scripts: echo "This job runs every time helm upgrade/install runs, unless explicitly disabled" echo "Can be disabled at will by setting scripts.runOneTimeJob=false" -# probes - -# Probes for determining pod/container health, as per ... -# https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ -# -# Tips: -# - if troubleshooting, set to empty object {}; to avoid k8s restarting container and blocking traffic to it -probes: {} - -# podAnnotations - additional pod annotations -podAnnotations: - iam.amazonaws.com/role: advana/k8s/s3.wildcard - -# service - -# Port details of the service used to open application pod to connections from outside. -# Note: must be of type NodePort and only http/https keys are supported. -# For syntax, refer to service.spec.ports[*] definitions of the NodePort service https://kubernetes.io/docs/concepts/services-networking/service/#nodeport -service: - http: - port: 80 - targetPort: 8990 - protocol: TCP - name: http - https: - port: 443 - targetPort: 8443 - protocol: TCP - name: https - # image - # Main app image details - also used to run all jobs defined in .Values.scripts image: + registry: 092912502985.dkr.ecr.us-east-1.amazonaws.com + repository: advana/gamechanger/gamechanger-web pullPolicy: Always tag: "latest" @@ -82,21 +52,98 @@ nameOverride: "" # Has no effect on helm-provided .Chart.Name variable. fullnameOverride: "" -# podSecurityContext - -# Sets security context for main application and job pods. -# This primarily comes into play when mounting files and volumes, but has other uses. -# For more on what goes in here, reference https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod -podSecurityContext: - fsGroup: 1001 +# deployment - +# Sets all configuration for the deployment resource yaml. +# +# TODO: done, tplify secret name for envFrom +deployment: + web: + autoscaling: + enabled: false + # replicaCount - = 1> + # Number of app pod replicas to launch in the load-balancing group. + # + # Tips: + # - when troubleshooting, set to 1 to avoid dealing with complications of load-balanced traffic + replicaCount: 1 + # podAnnotations - additional pod annotations + podAnnotations: + checksum/env-config-secret: 2660c5692715b67c1b913eda4cc32d3f5df69736a1dca4b92a9aa1fb0f2f10a0 + checksum/file-config-secret: a65095aa0d9e48d2e2280cfbd183ac6c1695ade9d5b6c6ad126c75f1815afc33 + iam.amazonaws.com/role: advana/k8s/s3.wildcard + serviceAccountName: "{{ .Release.Name}}-gamechanger-web" + initContainer: {} + container: + gamechanger-web: + securityContext: + allowPrivilegeEscalation: false + runAsGroup: 1001 + runAsUser: 1001 + # startCommand - + # Entrypoint/command to be executed by the main application pod. + # Will completely override any entrypoint/cmd baked into the container. + # + # Tips: + # - when troubleshooting, set to ["sleep", "infinity"] then exec into container and tweak/relaunch app as necessary + command: + - "node" + - "index.js" + envFrom: + - secretRef: # TODO: done, need to make mr to template + name: '{{ .Release.Name | trunc 63 | trimSuffix "-" }}-{{ .Chart.Name }}.env-config-secret' + env: + REACT_APP_VERSION: '{{ default "1.0.0" .Chart.AppVersion }}' + resources: + requests: + cpu: "1" + memory: 512Mi + + # probes - + # Probes for determining pod/container health, as per ... + # https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ + # + # Tips: + # - if troubleshooting, set to empty object {}; to avoid k8s restarting container and blocking traffic to it + livenessProbe: + httpGet: + path: /health + port: 8443 + scheme: HTTPS + timeoutSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: 8443 + scheme: HTTPS + timeoutSeconds: 10 + # service - + # Port details of the service used to open application pod to connections from outside. + # Note: must be of type NodePort and only http/https keys are supported. + # Only define service in the dev/test values. Advana-library chart will create another service if a different type is set in the default values.yaml + # For syntax, refer to service.spec.ports[*] definitions of the NodePort service https://kubernetes.io/docs/concepts/services-networking/service/#nodeport + # service: + # # Change this key between ClusterIP to NodePort as needed, making sure to comment out nodePort keys when using clusterIP. + # ClusterIP: #NodePort: + # ports: + # http: + # port: 80 + # protocol: TCP + # targetPort: 8990 + # # nodePort: 31007 + # https: + # port: 443 + # protocol: TCP + # targetPort: 8443 + # # nodePort: 31207 -# appContainerSecurityContext - +# podSecurityContext - # Security context for app and job containers. # It's useful to define this to match user-id and group-id of the application and its' files inside container # For more on what goes in here, reference https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container -appContainerSecurityContext: +podSecurityContext: runAsUser: 1001 runAsGroup: 1001 - allowPrivilegeEscalation: false + # allowPrivilegeEscalation: false # serviceAccount - # Config of service account to be used by the app and job pods @@ -105,94 +152,108 @@ serviceAccount: # create - - whether to attempt creating the service account create: true # name - - override with custom name. useful if there is a pre-existing service account you should be using - name: "" + name: "{{ .Release.Name }}-gamechanger-web" -# @param external DNS subdomain; used to build sub-components FQDNs and configure web CORS (if enabled) -## -externalDomain: "" #"dev.advana.boozallencsn.com" ingress: enabled: false - ## @param ingress.certManager Set to true to add the `kubernetes.io/tls-acme: "true"` annotation to gamechanger ingresses - ## - certManager: true - ## @param ingress.controller The ingress controller type. Currently supports `default`, `gce` and `ncp` - ## leave as `default` for most ingress controllers. - ## set to `gce` if using the GCE ingress controller - ## set to `ncp` if using the NCP (NSX-T Container Plugin) ingress controller - ## - controller: default - ingressClassName: "" - serversnippets: | + tls: true + className: nginx # set if default IngressClass is not configured + annotations: + ingress.kubernetes.io/ssl-redirect: "true" + ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" + nginx.ingress.kubernetes.io/proxy-body-size: "0" + nginx.ingress.kubernetes.io/server-snippet: | underscores_in_headers on; - web: - enabled: true - hostname: gamechanger - ## @param ingress.web.pathType Ingress path type - ## - pathType: ImplementationSpecific - ## @param ingress.web.annotations [object] Additional annotations for the Ingress resource. To enable certificate autogeneration, place here your cert-manager annotations. - ## Use this parameter to set the required annotations for cert-manager, see - ## ref: https://cert-manager.io/docs/usage/ingress/#supported-annotations - ## e.g: - ## annotations: - ## kubernetes.io/ingress.class: nginx - ## cert-manager.io/cluster-issuer: cluster-issuer-name - ## - annotations: - ingress.kubernetes.io/ssl-redirect: "true" - ingress.kubernetes.io/proxy-body-size: "0" - nginx.ingress.kubernetes.io/ssl-redirect: "true" - nginx.ingress.kubernetes.io/proxy-body-size: "0" - tls: true - tlsSecretName: "advana.us-wildcard-tls" - ## @param ingress.web.selfSigned Create a TLS secret for this ingress record using self-signed certificates generated by Helm - ## - selfSigned: false - ## @param ingress.web.extraHosts An array with additional hostname(s) to be covered with the ingress record - ## e.g: - ## extraHosts: - ## - name: web.gamechanger.domain - ## path: / - ## - extraHosts: [] - ## @param ingress.web.secrets Custom TLS certificates as secrets - ## NOTE: 'key' and 'certificate' are expected in PEM format - ## NOTE: 'name' should line up with a 'secretName' set further up - ## If it is not set and you're using cert-manager, this is unneeded, as it will create a secret for you with valid certificates - ## If it is not set and you're NOT using cert-manager either, self-signed certificates will be created valid for 365 days - ## It is also possible to create and manage the certificates outside of this helm chart - ## Please see README.md for more information - ## e.g: - ## secrets: - ## - name: web.gamechanger.domain-tls - ## key: |- - ## -----BEGIN RSA PRIVATE KEY----- - ## ... - ## -----END RSA PRIVATE KEY----- - ## certificate: |- - ## -----BEGIN CERTIFICATE----- - ## ... - ## -----END CERTIFICATE----- - ## - secrets: [] -# resources - -# Defines minimum and maximum cpu/memory requirements for each application pod (except one-time-job pods) -# For more on what goes in here, reference https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ -resources: - requests: - memory: "512Mi" - cpu: "1" - -# env - -# Map of environment variables to be defined for all containers (app & jobs) -# Keys correspond to env-var names (capitalization is important) and values are interpreted as strings -env: {} - -# fileConfig - -# Configuration for various text content meant to be exposed inside app containers as files rather than env vars -# This is useful for secrets and config files that are too long to be used as env vars (e.g. CA certificate bundles) -fileConfig: - # baseMountPath - - directory where the fileConfig.files will be exposed as actual files - baseMountPath: "/opt/app-root/src/secrets/" - # files - - files to be mounted in the baseMountPath. Key corresponds to filename, Value correspond to content. - files: {} + domain: "{{ $.Values.global.appName }}.{{ $.Values.global.baseDomain }}" + # hosts: + # "{{ $.Values.ingress.domain }}": + # paths: + # - path: / + # pathType: Prefix + # deploymentName: web-deployment + # serviceType: Nodeport + # serviceName: https + +############################################################################### +## Environment Specific Secret Values ## +############################################################################### + +secret: + ingress: + tls: + "{{ $.Values.ingress.domain }}": + # reference existing tls secret + secretName: advana.us-wildcard-tls + # or define new tls secret + # tls.crt: | + # -----BEGIN CERTIFICATE----- + # tls-crt + # -----END CERTIFICATE----- + # tls.key: | + # -----BEGIN RSA PRIVATE KEY----- + # tls-key + # -----END RSA PRIVATE KEY----- + + # public values that are stored as a secret object for convenience, remove if using values from vault + # put values from values.env here + env: + # secret - + # Map of environment variables to be defined for all containers (app & jobs) + # Keys correspond to env-var names (capitalization is important) and values are interpreted as strings + secret: + '{{ include "chart.envConfigSecretName" . }}': # TODO: done, how to use templated key as secret name + +secretstore: + "vault": + kind: ClusterSecretStore + externalsecret: + '{{ include "chart.envConfigSecretNameVault" . }}': # TODO: done, make MR to externalsecret template to tplify name + data: + - secretKey: TLS_KEY + remoteRef: + key: '{{ include "chart.vaultCertsKey" . }}' + property: key + - secretKey: TLS_CERT + remoteRef: + key: '{{ include "chart.vaultCertsKey" . }}' + property: cert + - secretKey: TLS_CA + remoteRef: + key: '{{ include "chart.vaultCertsKey" . }}' + property: ca + - secretKey: TLS_KEY_PASSPHRASE + remoteRef: + key: '{{ include "chart.vaultCertsKey" . }}' + property: passphrase + +# job - +# +# +job: + '{{ include "chart.oneTimeJobName" . }}': + podAnnotations: + checksum/secrets: '{{ include (print .Template.BasePath "/secrets.yaml") . | sha256sum }}' + restartPolicy: Never + container: + "init-scripts": + command: ["/bin/bash", "-c"] + args: + - | + {{- .Values.scripts.oneTimeJobScript | trim | nindent 16 }} + env: + REACT_APP_VERSION: '{{ default "1.0.0" .Chart.AppVersion }}' + envFrom: + - secretRef: + name: '{{ include "chart.envConfigSecretName" . }}' + +# volume - +# Configure which resources to attach to all pods as a volume +volume: + secret: + file-config-vol: + secretName: '{{ include "chart.fileConfigSecretName" . }}' + volumeMount: + mountPath: /opt/app-root/src/secrets/ + readOnly: true diff --git a/frontend/src/components/modules/default/defaultCardHandler.js b/frontend/src/components/modules/default/defaultCardHandler.js index 2aadf8028..684a20dcd 100644 --- a/frontend/src/components/modules/default/defaultCardHandler.js +++ b/frontend/src/components/modules/default/defaultCardHandler.js @@ -534,7 +534,7 @@ const getDisplayTitle = (item) => { return item.title; }; -export const clickFn = (filename, cloneName, searchText, sourceUrl, idx, pageNumber = 0) => { +export const clickFn = (filename, cloneName, searchText, sourceUrl, idx = null, pageNumber = 0) => { trackEvent( getTrackingNameForFactory(cloneName), 'CardInteraction', diff --git a/frontend/src/components/modules/eda/edaDocumentsComparisonTool.js b/frontend/src/components/modules/eda/edaDocumentsComparisonTool.js index 07ca31fdd..72a142483 100644 --- a/frontend/src/components/modules/eda/edaDocumentsComparisonTool.js +++ b/frontend/src/components/modules/eda/edaDocumentsComparisonTool.js @@ -174,9 +174,9 @@ const EDADocumentsComparisonTool = ({ if (state.runDocumentComparisonSearch) { setLoading(true); setCollapseKeys([]); - + let newParagraphs = paragraphs; if (paragraphs.length === 1 && paragraphs[0].text.length > 5500) { - setParagraphs(modifyParagraphs(paragraphs[0])); + newParagraphs = modifyParagraphs(paragraphs[0]); } const filters = { @@ -188,9 +188,8 @@ const EDADocumentsComparisonTool = ({ contractsOrMods, idvPIID, }; - gameChangerAPI - .compareDocumentPOST({ cloneName: state.cloneData.clone_name, paragraphs: paragraphs, filters }) + .compareDocumentPOST({ cloneName: state.cloneData.clone_name, paragraphs: newParagraphs, filters }) .then((resp) => { if (resp.data.docs.length <= 0) { setNoResults(true); @@ -212,11 +211,13 @@ const EDADocumentsComparisonTool = ({ } setReturnedDocs(resp.data.docs); setState(dispatch, { runDocumentComparisonSearch: false }); + setParagraphs(newParagraphs); setLoading(false); }) .catch(() => { setReturnedDocs([]); setState(dispatch, { runDocumentComparisonSearch: false }); + setParagraphs(newParagraphs); setLoading(false); console.log('server error'); }); @@ -1026,7 +1027,7 @@ const EDADocumentsComparisonTool = ({ handlePdfOnLoad( 'pdfViewer', 'viewerContainer', - compareDocument.filename, + compareDocument?.filename, 'PDF Viewer' ) } diff --git a/frontend/src/components/modules/jexnet/jexnetCardHandler.js b/frontend/src/components/modules/jexnet/jexnetCardHandler.js index 784f715b5..0808b5704 100644 --- a/frontend/src/components/modules/jexnet/jexnetCardHandler.js +++ b/frontend/src/components/modules/jexnet/jexnetCardHandler.js @@ -199,6 +199,8 @@ const cardHandler = { item.filename, state.cloneData.clone_name, state.searchText, + null, + null, page.pageNumber ); }} @@ -277,6 +279,8 @@ const cardHandler = { item.filename, state.cloneData.clone_name, state.searchText, + null, + null, page.pageNumber ); }} @@ -361,6 +365,8 @@ const cardHandler = { item.filename, state.cloneData.clone_name, state.searchText, + null, + null, page.pageNumber ); }} @@ -436,7 +442,7 @@ const cardHandler = { href={'#'} onClick={(e) => { e.preventDefault(); - clickFn(filename, cloneName, searchText, 0); + clickFn(filename, cloneName, searchText, null); }} > Open diff --git a/frontend/src/components/modules/policy/policyCardHandler.js b/frontend/src/components/modules/policy/policyCardHandler.js index f43894336..74e19d550 100644 --- a/frontend/src/components/modules/policy/policyCardHandler.js +++ b/frontend/src/components/modules/policy/policyCardHandler.js @@ -344,8 +344,8 @@ const StyledListViewFrontCardContent = styled.div` const StyledFrontCardContent = styled.div` font-family: 'Noto Sans'; - overflow: auto; font-size: ${CARD_FONT_SIZE}px; + height: 100%; .current-as-of-div { display: flex; @@ -359,17 +359,16 @@ const StyledFrontCardContent = styled.div` .hits-container { display: grid; grid-template-columns: 100px auto auto; - height: 100%; + height: calc(100% - 24px); .page-hits { min-width: 100px; - height: fit-content; - max-height: 150px; overflow: auto; border: 1px solid rgb(189, 189, 189); border-top: 0px; position: relative; z-index: 1; + height: 100%; .page-hit { display: flex; @@ -380,6 +379,7 @@ const StyledFrontCardContent = styled.div` border-top: 1px solid rgb(189, 189, 189); cursor: pointer; color: #386f94; + border-bottom: 1px solid rgb(189, 189, 189); span { font-size: ${CARD_FONT_SIZE}px; @@ -393,8 +393,13 @@ const StyledFrontCardContent = styled.div` } > .expanded-metadata { - overflow-wrap: anywhere; grid-column: 2 / 4; + height: 100%; + overflow: auto; + + .searchdemo-blockquote { + height: 100%; + } } } `; @@ -778,7 +783,16 @@ const getPublicationDate = (publication_date_dt) => { } }; -const CardHeaderHandler = ({ item, state, checkboxComponent, favoriteComponent, graphView, intelligentSearch }) => { +const CardHeaderHandler = ({ + item, + state, + checkboxComponent, + favoriteComponent, + graphView, + intelligentSearch, + idx, + page, +}) => { const [showDocIngestModal, setShowDocIngestModal] = useState(false); const displayTitle = getDisplayTitle(item); const isRevoked = item.is_revoked_b; @@ -792,9 +806,7 @@ const CardHeaderHandler = ({ item, state, checkboxComponent, favoriteComponent, const typeTextColor = getTypeTextColor(cardType); let { docTypeColor, docOrgColor } = getDocTypeStyles(displayType, displayOrg); - const publicationDate = getPublicationDate(item.publication_date_dt); - return ( undefined } @@ -1149,8 +1162,9 @@ const renderListViewPageHitsWithoutIntelligentSearch = ( item.filename, cloneName, searchText, - page.pageNumber, - item.download_url_s + item.download_url_s, + idx, + page.pageNumber ); }} > @@ -1262,8 +1276,9 @@ const renderListView = ( item.filename, cloneName, searchText, - page.pageNumber, - item.download_url_s + item.download_url_s, + idx, + page.pageNumber ); }} > @@ -1346,9 +1361,9 @@ const renderPageHit = (page, key, hoveredHit, setHoveredHit, item, state, docume item.filename, state.cloneData.clone_name, state.searchText, - page.pageNumber, item.download_url_s, - documentIdx + documentIdx, + page.pageNumber ); }} > @@ -1516,7 +1531,7 @@ const cardHandler = { href={'#'} onClick={(e) => { e.preventDefault(); - clickFn(filename, cloneName, searchText, 0, item.download_url_s); + clickFn(filename, cloneName, searchText, item.download_url_s, idx); }} > Open diff --git a/frontend/src/components/modules/policy/policyMainViewHandler.js b/frontend/src/components/modules/policy/policyMainViewHandler.js index 7ac167786..bbad34844 100644 --- a/frontend/src/components/modules/policy/policyMainViewHandler.js +++ b/frontend/src/components/modules/policy/policyMainViewHandler.js @@ -268,10 +268,15 @@ const handleLastOpened = async (last_opened_docs, state, dispatch, cancelToken, let cleanedDocs = []; let filteredPubs = []; - for (let doc of last_opened_docs) { - cleanedDocs.push(doc.document.split(' - ')[1].split('.pdf')[0]); - cleanedDocs = [...new Set(cleanedDocs)]; + // Extract filenames out of last_opened_docs, e.g.: + // 'Title 5 - Appendix' from 'PDFViewer - Title 5 - Appendix.pdf - gamechanger' + for (let { document } of last_opened_docs) { + let cleanedDoc = document.substring(document.indexOf('-') + 1, document.lastIndexOf('-')).trim(); + cleanedDoc = cleanedDoc.substring(0, cleanedDoc.lastIndexOf('.')); + cleanedDocs.push(cleanedDoc); } + cleanedDocs = [...new Set(cleanedDocs)]; + try { filteredPubs = createFilteredPubs(cleanedDocs); diff --git a/frontend/src/utils/gamechangerUtils.js b/frontend/src/utils/gamechangerUtils.js index 4ceb7d797..09c4d217a 100644 --- a/frontend/src/utils/gamechangerUtils.js +++ b/frontend/src/utils/gamechangerUtils.js @@ -481,6 +481,7 @@ export const orgColorMap = { 'Financial Mgmt. Reg': '#636363', Legislation: '#ffbf00', USSTRATCOM: '#00308f', // air force blue + 'United States Army Training and Doctrine Command': '#4b5320', // army green }; const linkColorMap = { diff --git a/generateCombinedEnv.sh b/generateCombinedEnv.sh index f6f65f036..f6da086c3 100755 --- a/generateCombinedEnv.sh +++ b/generateCombinedEnv.sh @@ -29,13 +29,13 @@ GAMECHANGER_ELASTICSEARCH_SUGGEST_INDEX="${GAMECHANGER_ELASTICSEARCH_SUGGEST_IND GAMECHANGER_ELASTICSEARCH_ENTITIES_INDEX="${GAMECHANGER_ELASTICSEARCH_ENTITIES_INDEX:-entities}" GAMECHANGER_ELASTICSEARCH_HISTORY_INDEX="${GAMECHANGER_ELASTICSEARCH_HISTORY_INDEX:-search_history}" -EDA_ELASTICSEARCH_HOST="${EDA_ELASTICSEARCH_HOST:-elasticsearch}" -EDA_ELASTICSEARCH_PORT="${EDA_ELASTICSEARCH_PORT:-9200}" -EDA_ELASTICSEARCH_USER="${EDA_ELASTICSEARCH_USER:-}" -EDA_ELASTICSEARCH_PASSWORD="${EDA_ELASTICSEARCH_PASSWORD:-}" -EDA_ELASTICSEARCH_CA_FILEPATH="${EDA_ELASTICSEARCH_CA_FILEPATH:-/etc/secrets/gamechanger.crt}" -EDA_ELASTICSEARCH_INDEX="${EDA_ELASTICSEARCH_INDEX:-gc_eda_2021_json}" -EDA_ELASTICSEARCH_PROTOCOL="${EDA_ELASTICSEARCH_PROTOCOL:-http}" +EDA_OPENSEARCH_HOST="${EDA_OPENSEARCH_HOST:-elasticsearch}" +EDA_OPENSEARCH_PORT="${EDA_OPENSEARCH_PORT:-9200}" +EDA_OPENSEARCH_USER="${EDA_OPENSEARCH_USER:-}" +EDA_OPENSEARCH_PASSWORD="${EDA_OPENSEARCH_PASSWORD:-}" +EDA_OPENSEARCH_CA_FILEPATH="${EDA_OPENSEARCH_CA_FILEPATH:-/etc/secrets/gamechanger.crt}" +EDA_OPENSEARCH_INDEX="${EDA_OPENSEARCH_INDEX:-gc_eda_2021_json}" +EDA_OPENSEARCH_PROTOCOL="${EDA_OPENSEARCH_PROTOCOL:-http}" EDA_DATA_HOST="${EDA_DATA_HOST:-}" HERMES_ELASTICSEARCH_INDEX="${HERMES_ELASTICSEARCH_INDEX:-hermes_test_1}"