From 4fcb214ac186cc02344507b46bba9e45ea0b724a Mon Sep 17 00:00:00 2001 From: Vitali Pinchuk <146737590+vitPinchuk@users.noreply.github.com> Date: Fri, 7 Jun 2024 07:09:24 +0300 Subject: [PATCH] Update fields in items getting mixed up (#76) * fix: rss datasource mixed up * added new provisioning and data sources * should added for test names * test name updated * Return Data source * updated api.ts * CI updated * updated News data; added new tests * api.tts file refactoring * Formatting * Fix lint errors * Update CHANGELOG.md * Update README.md --------- Co-authored-by: Mikhail Volkov Co-authored-by: asimonok --- CHANGELOG.md | 3 +- README.md | 2 +- docker-compose.yml | 2 +- provisioning/dashboards/comparing.json | 457 ++++++++++++++++ provisioning/datasources/datasource.yaml | 20 + src/api/api.test.ts | 637 +++++++++++++++++------ src/api/api.ts | 228 ++++---- src/types.ts | 29 +- src/utils.test.ts | 109 ++++ src/utils.ts | 89 +++- 10 files changed, 1304 insertions(+), 272 deletions(-) create mode 100644 provisioning/dashboards/comparing.json create mode 100644 src/utils.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a7500..2c530d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # Change Log -## 4.1.0 (IN PROGRESS) +## 4.1.0 (2024-06-07) ### Features / Enhancements - Added tooltip to Feed URL (#73) - Added XML Server (#75) - Updated to Grafana 11.0 and dependencies (#72) +- Update fields in items getting mixed up (#76) ## 4.0.0 (2024-05-09) diff --git a/README.md b/README.md index 490e0bf..4fc30db 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![Dashboard](https://raw.githubusercontent.com/VolkovLabs/volkovlabs-rss-datasource/main/src/img/dashboard.png) -![Grafana](https://img.shields.io/badge/Grafana-11-orange) +![Grafana](https://img.shields.io/badge/Grafana-11.0-orange) ![CI](https://github.com/volkovlabs/volkovlabs-rss-datasource/workflows/CI/badge.svg) ![E2E](https://github.com/volkovlabs/volkovlabs-rss-datasource/workflows/E2E/badge.svg) [![codecov](https://codecov.io/gh/VolkovLabs/volkovlabs-rss-datasource/branch/main/graph/badge.svg?token=2W9VR0PG5N)](https://codecov.io/gh/VolkovLabs/volkovlabs-rss-datasource) diff --git a/docker-compose.yml b/docker-compose.yml index 35093ab..9210b5d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: environment: - GF_DEFAULT_APP_MODE=development - GF_DASHBOARDS_DEFAULT_HOME_DASHBOARD_PATH=/etc/grafana/provisioning/dashboards/panels.json - - GF_INSTALL_PLUGINS=marcusolsson-dynamictext-panel + - GF_INSTALL_PLUGINS=marcusolsson-dynamictext-panel,yesoreyeram-infinity-datasource volumes: - ./dist:/var/lib/grafana/plugins/volkovlabs-rss-datasource - ./provisioning:/etc/grafana/provisioning diff --git a/provisioning/dashboards/comparing.json b/provisioning/dashboards/comparing.json new file mode 100644 index 0000000..84a6036 --- /dev/null +++ b/provisioning/dashboards/comparing.json @@ -0,0 +1,457 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": 5, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "yesoreyeram-infinity-datasource", + "uid": "infinity1" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields.link}" + } + ] + }, + { + "id": "custom.width", + "value": 434 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "link" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Release Date" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeFromNow" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "contact" + }, + "properties": [ + { + "id": "custom.width", + "value": 192 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "availability" + }, + "properties": [ + { + "id": "custom.width", + "value": 203 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "custom.width", + "value": 514 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "tag" + }, + "properties": [ + { + "id": "noValue", + "value": "Blank" + } + ] + } + ] + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "cellHeight": "md", + "footer": { + "countRows": false, + "fields": "", + "reducer": ["sum"], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "pubDate" + } + ] + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "columns": [], + "datasource": { + "type": "yesoreyeram-infinity-datasource", + "uid": "789789897" + }, + "filters": [], + "format": "table", + "global_query_id": "", + "parser": "backend", + "refId": "A", + "root_selector": "rss.channel.item", + "source": "url", + "type": "xml", + "uql": "parse-xml", + "url": "https://grafana.com/docs/grafana-cloud/whats-new/index.xml", + "url_options": { + "data": "", + "method": "GET" + } + } + ], + "title": "Cloud What's New - Infinity", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "availability": true, + "content": true, + "description": true, + "documentationURL": true, + "guid": true, + "link": true, + "offering": true, + "pubDate": false, + "selfManagedEdition": true + }, + "includeByName": {}, + "indexByName": { + "availability": 0, + "content": 1, + "description": 2, + "documentationURL": 3, + "guid": 4, + "link": 5, + "offering": 6, + "pubDate": 9, + "selfManagedEdition": 10, + "tag": 8, + "title": 7 + }, + "renameByName": {} + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "volkovlabs-rss-datasource", + "uid": "rss1" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "cellOptions": { + "type": "auto" + }, + "filterable": true, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unitScale": true + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "title" + }, + "properties": [ + { + "id": "links", + "value": [ + { + "targetBlank": true, + "title": "", + "url": "${__data.fields.link}" + } + ] + }, + { + "id": "custom.width", + "value": 434 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "link" + }, + "properties": [ + { + "id": "custom.hidden", + "value": true + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Release Date" + }, + "properties": [ + { + "id": "unit", + "value": "dateTimeFromNow" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "contact" + }, + "properties": [ + { + "id": "custom.width", + "value": 192 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "availability" + }, + "properties": [ + { + "id": "custom.width", + "value": 203 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Title" + }, + "properties": [ + { + "id": "custom.width", + "value": 514 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "tag" + }, + "properties": [ + { + "id": "noValue", + "value": "Blank" + } + ] + } + ] + }, + "gridPos": { + "h": 16, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 2, + "options": { + "cellHeight": "md", + "footer": { + "countRows": false, + "fields": "", + "reducer": ["sum"], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "tag" + } + ] + }, + "pluginVersion": "10.3.1", + "targets": [ + { + "datasource": { + "type": "volkovlabs-rss-datasource", + "uid": "45645564" + }, + "feedType": "items", + "refId": "A" + } + ], + "title": "Cloud What's New - RSS Datasource", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "availability": true, + "content": true, + "description": true, + "documentationURL": true, + "guid": true, + "link": true, + "offering": true, + "pubDate": false, + "selfManagedEdition": true + }, + "includeByName": {}, + "indexByName": { + "availability": 7, + "content": 5, + "description": 6, + "documentationURL": 8, + "guid": 4, + "link": 2, + "offering": 9, + "pubDate": 3, + "selfManagedEdition": 10, + "tag": 1, + "title": 0 + }, + "renameByName": {} + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 39, + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now/y", + "to": "now" + }, + "timepicker": {}, + "timezone": "browser", + "title": "Comparing Datasource", + "uid": "bdmc5fb3z0074c", + "version": 20, + "weekStart": "" +} diff --git a/provisioning/datasources/datasource.yaml b/provisioning/datasources/datasource.yaml index cb9ce0e..9610cef 100644 --- a/provisioning/datasources/datasource.yaml +++ b/provisioning/datasources/datasource.yaml @@ -70,6 +70,15 @@ datasources: editable: true jsonData: feed: https://github.com/sonatype/nexus-public/releases.atom + - name: Grafana Cloud RSS + type: volkovlabs-rss-datasource + access: proxy + orgId: 1 + uid: rss1 + version: 1 + editable: true + jsonData: + feed: https://grafana.com/docs/grafana-cloud/whats-new/index.xml - name: Volkov Labs YouTube type: volkovlabs-rss-datasource access: proxy @@ -79,6 +88,17 @@ datasources: editable: true jsonData: feed: https://www.youtube.com/feeds/videos.xml?channel_id=UCQadniwbukI6ZmTN2oTTb-g + - name: Grafana Cloud Infinity + type: yesoreyeram-infinity-datasource + access: proxy + isDefault: false + orgId: 1 + uid: infinity1 + version: 1 + editable: true + jsonData: + url: https://grafana.com/docs/grafana-cloud/whats-new/index.xml + feedType: rss - name: XML Server RSS type: volkovlabs-rss-datasource access: proxy diff --git a/src/api/api.test.ts b/src/api/api.test.ts index 745f173..f8f02b9 100644 --- a/src/api/api.test.ts +++ b/src/api/api.test.ts @@ -134,34 +134,34 @@ describe('Api', () => { it('Should make getFeed request for Atom', async () => { xmlResponse.data = ` - - tag:status.redis.com,2005:/history - - - Redis Status - Incident History - 2021-12-26T15:23:56Z - - Redis - - - tag:status.redis.com,2005:Incident/8938651 - 2021-12-26T14:45:23Z - 2021-12-26T14:45:23Z - - Instance failure - <p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:45</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:38</var> UTC</small><br><strong>Investigating</strong> - We are experiencing instance failure on GCP/us-east1 , this may affect resources that have the following pattern in their endpoint: c16307.us-east1-mz</p> - <p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:45</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:38</var> UTC</small><br><strong>Investigating</strong> - We are experiencing instance failure on GCP/us-east1 , this may affect resources that have the following pattern in their endpoint: c16307.us-east1-mz</p> - - - tag:status.redis.com,2005:Incident/8899527 - 2021-12-21T01:37:14Z - 2021-12-21T01:37:14Z - - Scheduled Maintenance - <p><small>Dec <var data-var=\'date\'>21</var>, <var data-var=\'time\'>01:37</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>21</var>, <var data-var=\'time\'>01:01</var> UTC</small><br><strong>Monitoring</strong> - Service maintenance on AWS/us-west-2 applies to all resources with the following pattern in their endpoint : c2638.us-west-2-mz</p> - - `; + + tag:status.redis.com,2005:/history + + + Redis Status - Incident History + 2021-12-26T15:23:56Z + + Redis + + + tag:status.redis.com,2005:Incident/8938651 + 2021-12-26T14:45:23Z + 2021-12-26T14:45:23Z + + Instance failure + <p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:45</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:38</var> UTC</small><br><strong>Investigating</strong> - We are experiencing instance failure on GCP/us-east1 , this may affect resources that have the following pattern in their endpoint: c16307.us-east1-mz</p> + <p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:45</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:38</var> UTC</small><br><strong>Investigating</strong> - We are experiencing instance failure on GCP/us-east1 , this may affect resources that have the following pattern in their endpoint: c16307.us-east1-mz</p> + + + tag:status.redis.com,2005:Incident/8899527 + 2021-12-21T01:37:14Z + 2021-12-21T01:37:14Z + + Scheduled Maintenance + <p><small>Dec <var data-var=\'date\'>21</var>, <var data-var=\'time\'>01:37</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>21</var>, <var data-var=\'time\'>01:01</var> UTC</small><br><strong>Monitoring</strong> - Service maintenance on AWS/us-west-2 applies to all resources with the following pattern in their endpoint : c2638.us-west-2-mz</p> + + `; let result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ALL }, range); expect(result?.length).toEqual(2); @@ -175,34 +175,34 @@ describe('Api', () => { it('Should make getFeed request for Atom with Time Range', async () => { xmlResponse.data = ` - - tag:status.redis.com,2005:/history - - - Redis Status - Incident History - 2021-12-26T15:23:56Z - - Redis - - - tag:status.redis.com,2005:Incident/8938651 - 2021-12-26T14:45:23Z - 2021-12-26T14:45:23Z - - Instance failure - <p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:45</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:38</var> UTC</small><br><strong>Investigating</strong> - We are experiencing instance failure on GCP/us-east1 , this may affect resources that have the following pattern in their endpoint: c16307.us-east1-mz</p> - <p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:45</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:38</var> UTC</small><br><strong>Investigating</strong> - We are experiencing instance failure on GCP/us-east1 , this may affect resources that have the following pattern in their endpoint: c16307.us-east1-mz</p> - - - tag:status.redis.com,2005:Incident/8899527 - 2021-12-21T01:37:14Z - 2021-12-21T01:37:14Z - - Scheduled Maintenance - <p><small>Dec <var data-var=\'date\'>21</var>, <var data-var=\'time\'>01:37</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>21</var>, <var data-var=\'time\'>01:01</var> UTC</small><br><strong>Monitoring</strong> - Service maintenance on AWS/us-west-2 applies to all resources with the following pattern in their endpoint : c2638.us-west-2-mz</p> - - `; + + tag:status.redis.com,2005:/history + + + Redis Status - Incident History + 2021-12-26T15:23:56Z + + Redis + + + tag:status.redis.com,2005:Incident/8938651 + 2021-12-26T14:45:23Z + 2021-12-26T14:45:23Z + + Instance failure + <p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:45</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:38</var> UTC</small><br><strong>Investigating</strong> - We are experiencing instance failure on GCP/us-east1 , this may affect resources that have the following pattern in their endpoint: c16307.us-east1-mz</p> + <p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:45</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>26</var>, <var data-var=\'time\'>14:38</var> UTC</small><br><strong>Investigating</strong> - We are experiencing instance failure on GCP/us-east1 , this may affect resources that have the following pattern in their endpoint: c16307.us-east1-mz</p> + + + tag:status.redis.com,2005:Incident/8899527 + 2021-12-21T01:37:14Z + 2021-12-21T01:37:14Z + + Scheduled Maintenance + <p><small>Dec <var data-var=\'date\'>21</var>, <var data-var=\'time\'>01:37</var> UTC</small><br><strong>Resolved</strong> - This incident has been resolved.</p><p><small>Dec <var data-var=\'date\'>21</var>, <var data-var=\'time\'>01:01</var> UTC</small><br><strong>Monitoring</strong> - Service maintenance on AWS/us-west-2 applies to all resources with the following pattern in their endpoint : c2638.us-west-2-mz</p> + + `; let result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ALL, dateField: 'updated' }, range2000); expect(result?.length).toEqual(2); @@ -234,11 +234,11 @@ describe('Api', () => { */ it('Should handle getFeed request for Atom with title only', async () => { xmlResponse.data = ` - - Test - - `; + xmlns:atom="http://www.w3.org/2005/Atom"> + + Test + + `; const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ALL }, range); expect(result?.length).toEqual(1); @@ -249,16 +249,16 @@ describe('Api', () => { */ it('Should handle getFeed request for Atom with image and no src', async () => { xmlResponse.data = ` - - - Test.
]]>
-
- -
]]> - - - `; + xmlns:atom="http://www.w3.org/2005/Atom"> + + + Test.
]]>
+
+ +
]]> + + + `; const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ITEMS }, range); expect(result?.length).toEqual(1); @@ -269,24 +269,24 @@ describe('Api', () => { */ it('Should handle getFeed request for RSS 1.0', async () => { xmlResponse.data = ` - - - National Vulnerability Database - https://web.nvd.nist.gov/view/vuln/search - This feed contains the most recent CVE cyber vulnerabilities published within the National Vulnerability Database. - 2022-04-25T18:00:00Z - en-us - This material is not copywritten and may be freely used, however, attribution is requested. - - - CVE-2016-20014 - https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-20014 - In pam_tacplus.c in pam_tacplus before 1.4.1, pam_sm_acct_mgmt does not zero out the arep data structure. - 2022-04-21T04:15:09Z - - `; + + + National Vulnerability Database + https://web.nvd.nist.gov/view/vuln/search + This feed contains the most recent CVE cyber vulnerabilities published within the National Vulnerability Database. + 2022-04-25T18:00:00Z + en-us + This material is not copywritten and may be freely used, however, attribution is requested. + + + CVE-2016-20014 + https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2016-20014 + In pam_tacplus.c in pam_tacplus before 1.4.1, pam_sm_acct_mgmt does not zero out the arep data structure. + 2022-04-21T04:15:09Z + + `; const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ITEMS }, range); expect(result?.length).toEqual(1); @@ -298,24 +298,24 @@ describe('Api', () => { */ it('Should make getFeed request for GitHub Atom', async () => { xmlResponse.data = ` - - tag:github.com,2008:https://github.com/sonatype/nexus-public/releases - - - Release notes from nexus-public - 2022-03-29T22:38:42Z - - tag:github.com,2008:Repository/40029062/release-3.38.1-01 - 2022-03-30T09:02:42Z - - 3.38.1-01 - <p><a href="https://help.sonatype.com/repomanager3/product-information/release-notes/2022-release-notes/nexus-repository-3.38.0---3.38.1-release-notes" rel="nofollow">https://help.sonatype.com/repomanager3/product-information/release-notes/2022-release-notes/nexus-repository-3.38.0---3.38.1-release-notes</a></p> - - Blacktiger - - - - `; + + tag:github.com,2008:https://github.com/sonatype/nexus-public/releases + + + Release notes from nexus-public + 2022-03-29T22:38:42Z + + tag:github.com,2008:Repository/40029062/release-3.38.1-01 + 2022-03-30T09:02:42Z + + 3.38.1-01 + <p><a href="https://help.sonatype.com/repomanager3/product-information/release-notes/2022-release-notes/nexus-repository-3.38.0---3.38.1-release-notes" rel="nofollow">https://help.sonatype.com/repomanager3/product-information/release-notes/2022-release-notes/nexus-repository-3.38.0---3.38.1-release-notes</a></p> + + Blacktiger + + + + `; let result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ALL }, range); expect(result?.length).toEqual(2); @@ -352,69 +352,374 @@ describe('Api', () => { */ it('Should handle getFeed request for YouTube', async () => { xmlResponse.data = ` - - yt:channel:UCQadniwbukI6ZmTN2oTTb-g - UCQadniwbukI6ZmTN2oTTb-g - Volkov Labs - - - Volkov Labs - https://www.youtube.com/channel/UCQadniwbukI6ZmTN2oTTb-g - - 2022-01-18T15:17:13+00:00 - - yt:video:PaiGygHI-dA - PaiGygHI-dA - UCQadniwbukI6ZmTN2oTTb-g - Favorite sessions of Grafana Conference 2022 | GrafanaCONline 2022 | Grafana 9 - - - Volkov Labs - https://www.youtube.com/channel/UCQadniwbukI6ZmTN2oTTb-g - - 2022-06-20T18:10:53+00:00 - 2022-06-20T19:26:02+00:00 - - Favorite sessions of Grafana Conference 2022 | GrafanaCONline 2022 | Grafana 9 - - - ABOUT THIS VIDEO Grafa - - - - - - - - yt:video:RAxqS2hpWkg - RAxqS2hpWkg - UCQadniwbukI6ZmTN2oTTb-g - RSS/Atom Data Source for Grafana | News feed tutorial for Grafana Dashboard - - - Volkov Labs - https://www.youtube.com/channel/UCQadniwbukI6ZmTN2oTTb-g - - 2022-06-08T22:26:34+00:00 - 2022-06-09T00:10:05+00:00 - - RSS/Atom Data Source for Grafana | News feed tutorial for Grafana Dashboard - - - ABOUT THIS VIDEO In the m - - - - - - -`; + xmlns:media="http://search.yahoo.com/mrss/" + xmlns="http://www.w3.org/2005/Atom"> + + yt:channel:UCQadniwbukI6ZmTN2oTTb-g + UCQadniwbukI6ZmTN2oTTb-g + Volkov Labs + + + Volkov Labs + https://www.youtube.com/channel/UCQadniwbukI6ZmTN2oTTb-g + + 2022-01-18T15:17:13+00:00 + + yt:video:PaiGygHI-dA + PaiGygHI-dA + UCQadniwbukI6ZmTN2oTTb-g + Favorite sessions of Grafana Conference 2022 | GrafanaCONline 2022 | Grafana 9 + + + Volkov Labs + https://www.youtube.com/channel/UCQadniwbukI6ZmTN2oTTb-g + + 2022-06-20T18:10:53+00:00 + 2022-06-20T19:26:02+00:00 + + Favorite sessions of Grafana Conference 2022 | GrafanaCONline 2022 | Grafana 9 + + + ABOUT THIS VIDEO Grafa + + + + + + + + yt:video:RAxqS2hpWkg + RAxqS2hpWkg + UCQadniwbukI6ZmTN2oTTb-g + RSS/Atom Data Source for Grafana | News feed tutorial for Grafana Dashboard + + + Volkov Labs + https://www.youtube.com/channel/UCQadniwbukI6ZmTN2oTTb-g + + 2022-06-08T22:26:34+00:00 + 2022-06-09T00:10:05+00:00 + + RSS/Atom Data Source for Grafana | News feed tutorial for Grafana Dashboard + + + ABOUT THIS VIDEO In the m + + + + + + + `; const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ITEMS }, range); expect(result?.length).toEqual(1); expect(result[0].fields.length).toEqual(12); }); + + /** + * Source with different key count in item + */ + it('Should return null value if key doesn`t contain in element', async () => { + xmlResponse.data = ` + + + + RSS Tutorial + + Test.
]]>
+
+ +
]]> + + RSS Tutorial 2 + + + RSS Tag 2 + + + + `; + + const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ITEMS }, range); + + const tagField = result[0].fields.find((elem) => elem.name === 'tag'); + + expect(result?.length).toEqual(1); + expect(tagField?.values).toEqual([undefined, 'RSS Tag 2']); + }); + + /** + * Source with meta + */ + it('Should not return meta', async () => { + xmlResponse.data = ` + + + + + RSS Tutorial + + Test.
]]>
+
+ + +
]]> + + RSS Tutorial 2 + + + RSS Tag 2 + + + + `; + + const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ITEMS }, range); + + const metaField = result[0].fields.find((elem) => elem.name === 'meta'); + + expect(result?.length).toEqual(1); + expect(metaField).toBe(undefined); + }); + + /** + * Source with meta + */ + it('Should not repeat a property in meta', async () => { + xmlResponse.data = ` + + + + + RSS Tutorial + + Test.
]]>
+
+ + +
]]> + + RSS Tutorial 2 + + + RSS Tag 2 + + + + `; + + const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ITEMS }, range); + const ogImageField = result[0].fields.find((elem) => elem.name === 'og:image'); + + expect(result?.length).toEqual(1); + expect(ogImageField?.values.length).toEqual(2); + expect(ogImageField?.values).toEqual(['content', 'content']); + }); + + /** + * Source with difference meta property + */ + it('Should return correct properties for meta tag', async () => { + xmlResponse.data = ` + + W3Schools Home Page + https://www.w3schools.com + Free web building tutorials + + + RSS Tutorial + https://www.w3schools.com/xml/xml_rss.asp + New RSS tutorial on W3Schools + + + + XML Tutorial 1 + https://www.w3schools.com/xml + New XML tutorial on W3Schools + + + + XML Tutorial 4 + https://www.w3schools.com/xml + New XML tutorial on W3Schools + + + + `; + + const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ITEMS }, range); + + const images = result[0].fields.find((elem) => elem.name === 'og:image'); + const titles = result[0].fields.find((elem) => elem.name === 'og:title'); + + expect(result?.length).toEqual(1); + + /** + * Return data Equalwith items length + */ + expect(images?.values.length).toEqual(3); + expect(titles?.values.length).toEqual(3); + + expect(images?.values).toEqual(['image', 'image', null]); + expect(titles?.values).toEqual([null, null, 'title']); + }); + + /** + * Source with content:encoded + */ + it('Should return correct properties for content:encoded tags', async () => { + xmlResponse.data = ` + + W3Schools Home Page + https://www.w3schools.com + Free web building tutorials + + How product teams can manage their performance using Grafana, Prometheus, and Oracle metrics + + https://grafana.com/blog/2021/12/23/how-product-teams-can-manage-their-performance-using-grafana-prometheus-and-oracle-metrics/?utm_source=grafana_news&utm_medium=rss + Thu, 23 Dec 2021 00:00:00 +0000 + https://grafana.com/blog/2021/12/23/how-product-teams-can-manage-their-performance-using-grafana-prometheus-and-oracle-metrics/?utm_source=grafana_news&utm_medium=rss&src=in-prod&plcmt=rss + Ever known a project manager who thinks a task takes minutes when it really takes hours? One company has developed a helpful monitoring tool that not only helps project managers make more realistic estimates, but also helps product teams save time, increase efficiency, and improve their overall performance.\nAt ObservabilityCON 2020, Walter Ritzel Paixão Côrtes, a product designer at Dell, gave a presentation about a data-driven solution his team developed called Product Team Observability. + + + h4 Text

When we created a Base64 image/PDF panel to display images for one of our projects, support for PDF documents was added as a feature. I am glad the panel is being used to display PDF documents stored in the databases like PostgreSQL.

]]>
+ https://volkovlabs.com/using-grafana-to-display-large-pdf-documents-weve-got-you-covered-4e654e8d4bce?source=rss----97b04264832a---4 +
+ + h4 Second text

When we created a Base64 image/PDF panel to display images for one of our projects, support for PDF documents was added as a feature. I am glad the panel is being used to display PDF documents stored in the databases like PostgreSQL.

]]>
+ https://volkovlabs.com/using-grafana-to-display-large-pdf-documents-weve-got-you-covered-4e654e8d4bce?source=rss----97b04264832a---4 +
+ + 123 +
]]> + + + https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png + Volkov Labs - Medium + https://volkovlabs.com?source=rss----97b04264832a---4 + + + + `; + + const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ITEMS }, range); + + const encodedBase = result[0].fields.find((elem) => elem.name === 'content:encoded'); + const encodedH4 = result[0].fields.find((elem) => elem.name === 'content:h4'); + const encodedImg = result[0].fields.find((elem) => elem.name === 'content:img'); + const encodedImgSrc = result[0].fields.find((elem) => elem.name === 'content:img-src'); + + expect(result?.length).toEqual(1); + + /** + * Return data Equalwith items length + */ + expect(encodedBase?.values.length).toEqual(4); + expect(encodedH4?.values.length).toEqual(4); + expect(encodedImg?.values.length).toEqual(4); + expect(encodedImgSrc?.values.length).toEqual(4); + + expect(encodedH4?.values).toEqual([undefined, 'h4 Text', 'h4 Second text', '']); + expect(encodedImg?.values).toEqual([ + undefined, + ``, + '', + '', + ]); + expect(encodedImgSrc?.values).toEqual([ + undefined, + `https://cdn-images-1.medium.com/max/1024/0*ZhXFfccuxa1PugIM`, + '', + '', + ]); + }); + + /** + * YouTube + */ + it('Should not repeat a property in media:group', async () => { + xmlResponse.data = ` + + yt:channel:UCQadniwbukI6ZmTN2oTTb-g + UCQadniwbukI6ZmTN2oTTb-g + Volkov Labs + + + Volkov Labs + https://www.youtube.com/channel/UCQadniwbukI6ZmTN2oTTb-g + + 2022-01-18T15:17:13+00:00 + + yt:video:PaiGygHI-dA + PaiGygHI-dA + UCQadniwbukI6ZmTN2oTTb-g + Favorite sessions of Grafana Conference 2022 | GrafanaCONline 2022 | Grafana 9 + + + Volkov Labs + https://www.youtube.com/channel/UCQadniwbukI6ZmTN2oTTb-g + + 2022-06-20T18:10:53+00:00 + 2022-06-20T19:26:02+00:00 + + Favorite sessions of Grafana Conference 2022 | GrafanaCONline 2022 | Grafana 9 + + + ABOUT THIS VIDEO Grafa + + + + + + + + yt:video:RAxqS2hpWkg + RAxqS2hpWkg + UCQadniwbukI6ZmTN2oTTb-g + RSS/Atom Data Source for Grafana | News feed tutorial for Grafana Dashboard + + + Volkov Labs + https://www.youtube.com/channel/UCQadniwbukI6ZmTN2oTTb-g + + 2022-06-08T22:26:34+00:00 + 2022-06-09T00:10:05+00:00 + + RSS/Atom Data Source for Grafana | News feed tutorial for Grafana Dashboard + + + ABOUT THIS VIDEO In the m + + + + + + + `; + + const result = await api.getFeed({ refId: 'A', feedType: FeedTypeValue.ITEMS }, range); + const thumbnail = result[0].fields.find((elem) => elem.name === 'media:group:media:thumbnail:url'); + const description = result[0].fields.find((elem) => elem.name === 'media:group:media:description'); + const content = result[0].fields.find((elem) => elem.name === 'media:group:media:content:url'); + + expect(thumbnail?.values.length).toEqual(2); + expect(description?.values.length).toEqual(2); + expect(content?.values.length).toEqual(2); + expect(thumbnail?.values).toEqual([ + 'https://i1.ytimg.com/vi/PaiGygHI-dA/hqdefault.jpg', + 'https://i3.ytimg.com/vi/RAxqS2hpWkg/hqdefault.jpg', + ]); + expect(description?.values).toEqual(['ABOUT THIS VIDEO Grafa', 'ABOUT THIS VIDEO In the m']); + expect(content?.values).toEqual([ + 'https://www.youtube.com/v/PaiGygHI-dA?version=3', + 'https://www.youtube.com/v/RAxqS2hpWkg?version=3', + ]); + }); }); }); diff --git a/src/api/api.ts b/src/api/api.ts index a76ba06..727bd8a 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,11 +1,12 @@ import { createDataFrame, DataFrame, DataSourceInstanceSettings, FieldType, TimeRange } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { XMLParser } from 'fast-xml-parser'; +import { get } from 'lodash'; import { lastValueFrom } from 'rxjs'; -import { ALWAYS_ARRAY, FeedTypeValue, ItemKey, MetaProperties } from '../constants'; +import { ALWAYS_ARRAY, FeedTypeValue, ItemKey } from '../constants'; import { DataItem, DataSourceOptions, FeedItems, Query } from '../types'; -import { isDateBetweenRange, setItem } from '../utils'; +import { getAllItemKeyConfigs, getUniqueAtomKeys, isDateBetweenRange, setItem } from '../utils'; /** * API @@ -120,10 +121,19 @@ export class Api { } /** - * Find all items + * Configure Keys + * Take all the unique keys in all items + */ + const channelKeys = getAllItemKeyConfigs(channel.item); + + /** + * Configure Items */ const items: FeedItems = {}; + /** + * Find all items + */ channel.item.forEach((item: DataItem) => { /** * Filter by specified Date field @@ -132,44 +142,54 @@ export class Api { return; } - Object.keys(item).forEach((key: string) => { - let value = item[key]; + Object.keys(channelKeys).forEach((key) => { + const keyConfig = channelKeys[key]; /** - * Parse Meta + * Check key with Accessor (keys for meta tag) */ - if (key === ItemKey.META && (value as Record)['@_property'] === MetaProperties.OG_IMAGE) { - key = MetaProperties.OG_IMAGE; - value = (value as Record)['@_content']; - } + if (!keyConfig.keyAccessor) { + let value = get(item, keyConfig.valueAccessor); - /** - * Parse Guid - */ - if (key === ItemKey.GUID && (value as Record)['#text']) { - value = (value as Record)['#text']; + /** + * Parse Encoded content for H4 and first Image + */ + if (key === ItemKey.CONTENT_H4 && value) { + const h4 = value.toString().match(/

(.*?)<\/h4>/); + value = h4?.length ? h4[1] : ''; + } + + if (key === ItemKey.CONTENT_IMG && value) { + const figure = value.toString().match(/
(.*?)<\/figure>/); + value = figure?.length ? figure[1] : ''; + } + + if (key === ItemKey.CONTENT_IMG_SRC && value) { + const figure = value.toString().match(/
(.*?)<\/figure>/); + const img = figure?.length ? figure[1].match(//) : null; + value = img?.length ? img[1] : ''; + } + + setItem(items, key, value as string); + return; } /** - * Parse Encoded content for H4 and first Image + * Get key for item */ - if (key === ItemKey.CONTENT_ENCODED) { - const h4 = value.toString().match(/

(.*?)<\/h4>/); - const figure = value.toString().match(/
(.*?)<\/figure>/); - - setItem(items, ItemKey.CONTENT_H4, h4?.length ? h4[1] : ''); + const itemKey = get(item, keyConfig.keyAccessor); + if (key === itemKey) { /** - * Extract image and source + * Set value for key */ - if (figure?.length) { - setItem(items, ItemKey.CONTENT_IMG, figure[1]); - const img = figure[1].match(//); - setItem(items, ItemKey.CONTENT_IMG_SRC, img?.length ? img[1] : ''); - } + setItem(items, key, get(item, keyConfig.valueAccessor) as string); + } else { + /** + * Set null + */ + setItem(items, key, null); } - - setItem(items, key, value as string); }); }); @@ -230,10 +250,19 @@ export class Api { } /** - * Find all entries + * Configure entries Keys + * Take all the unique keys in all entries + */ + const entriesKeys: FeedItems = getUniqueAtomKeys(feed.entry); + + /** + * Configure entries */ const entries: FeedItems = {}; + /** + * Find all entries + */ feed.entry.forEach((entry: DataItem) => { /** * Filter by specified Date field @@ -242,91 +271,98 @@ export class Api { return; } - Object.keys(entry).forEach((key: string) => { + Object.keys(entriesKeys).forEach((key: string) => { let value = entry[key]; /** - * Link - */ - if (key === ItemKey.LINK && (value as Record)['@_href']) { - value = (value as Record)['@_href']; - } - - /** - * Content - */ - if (key === ItemKey.CONTENT && (value as Record)['#text']) { - value = (value as Record)['#text']; - } - - /** - * Summary - */ - if (key === ItemKey.SUMMARY && (value as Record)['#text']) { - value = (value as Record)['#text']; - } - - /** - * Author + * If Entry doesn`t contain key set key with null value to avoid mixed up */ - if (key === ItemKey.AUTHOR && (value as Record)['name']) { - value = (value as Record)['name']; - } + if (!value) { + setItem(entries, key, null); + } else { + /** + * Link + */ + if (key === ItemKey.LINK && (value as Record)['@_href']) { + value = (value as Record)['@_href']; + } - /** - * Thumbnail - */ - if (key === ItemKey.MEDIA_THUMBNAIL && (value as Record)['@_url']) { - value = (value as Record)['@_url']; - } + /** + * Content + */ + if (key === ItemKey.CONTENT && (value as Record)['#text']) { + value = (value as Record)['#text']; + } - /** - * Media Group - */ - if (key === ItemKey.MEDIA_GROUP) { - const mediaGroup: Record = value as Record; + /** + * Summary + */ + if (key === ItemKey.SUMMARY && (value as Record)['#text']) { + value = (value as Record)['#text']; + } /** - * Thumbnail URL + * Author */ - if ( - mediaGroup[ItemKey.MEDIA_THUMBNAIL] && - (mediaGroup[ItemKey.MEDIA_THUMBNAIL] as Record)['@_url'] - ) { - setItem( - entries, - `${ItemKey.MEDIA_GROUP}:${ItemKey.MEDIA_THUMBNAIL}:url`, - (mediaGroup[ItemKey.MEDIA_THUMBNAIL] as Record)['@_url'] as string - ); + if (key === ItemKey.AUTHOR && (value as Record)['name']) { + value = (value as Record)['name']; } /** - * Content URL + * Thumbnail */ - if ( - mediaGroup[ItemKey.MEDIA_CONTENT] && - (mediaGroup[ItemKey.MEDIA_CONTENT] as Record)['@_url'] - ) { - setItem( - entries, - `${ItemKey.MEDIA_GROUP}:${ItemKey.MEDIA_CONTENT}:url`, - (mediaGroup[ItemKey.MEDIA_CONTENT] as Record)['@_url'] as string - ); + if (key === ItemKey.MEDIA_THUMBNAIL && (value as Record)['@_url']) { + value = (value as Record)['@_url']; } /** - * Description + * Media Group */ - if (mediaGroup[ItemKey.MEDIA_DESCRIPTION]) { - setItem( - entries, - `${ItemKey.MEDIA_GROUP}:${ItemKey.MEDIA_DESCRIPTION}`, - mediaGroup[ItemKey.MEDIA_DESCRIPTION] as string - ); + if (key === ItemKey.MEDIA_GROUP) { + const mediaGroup: Record = value as Record; + + /** + * Thumbnail URL + */ + if ( + mediaGroup[ItemKey.MEDIA_THUMBNAIL] && + (mediaGroup[ItemKey.MEDIA_THUMBNAIL] as Record)['@_url'] + ) { + setItem( + entries, + `${ItemKey.MEDIA_GROUP}:${ItemKey.MEDIA_THUMBNAIL}:url`, + (mediaGroup[ItemKey.MEDIA_THUMBNAIL] as Record)['@_url'] as string + ); + } + + /** + * Content URL + */ + if ( + mediaGroup[ItemKey.MEDIA_CONTENT] && + (mediaGroup[ItemKey.MEDIA_CONTENT] as Record)['@_url'] + ) { + setItem( + entries, + `${ItemKey.MEDIA_GROUP}:${ItemKey.MEDIA_CONTENT}:url`, + (mediaGroup[ItemKey.MEDIA_CONTENT] as Record)['@_url'] as string + ); + } + + /** + * Description + */ + if (mediaGroup[ItemKey.MEDIA_DESCRIPTION]) { + setItem( + entries, + `${ItemKey.MEDIA_GROUP}:${ItemKey.MEDIA_DESCRIPTION}`, + mediaGroup[ItemKey.MEDIA_DESCRIPTION] as string + ); + } } - } - setItem(entries, key, value as string); + setItem(entries, key, value as string); + } }); }); diff --git a/src/types.ts b/src/types.ts index fd789cb..71e8b8f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -44,11 +44,11 @@ export interface DataSourceOptions extends DataSourceJsonData { */ export interface FeedItems { /** - * Mapping of ID to an array of strings representing the items + * Mapping of ID to an array of strings or strings and null * - * @type {Record} + * @type {Record} */ - [id: string]: string[]; + [id: string]: Array; } /** @@ -58,7 +58,26 @@ export interface DataItem { /** * Data Item * - * @type {[key: string]: string | Record} + * @type {[key: string]: string | Record } */ - [key: string]: string | Record; + [key: string]: string | Record | Record; +} + +/** + * Key Config + */ +export interface KeyConfig { + /** + * Key Accessor + * + * @type {string} + */ + keyAccessor?: string; + + /** + * Value Accessor + * + * @type {string} + */ + valueAccessor: string; } diff --git a/src/utils.test.ts b/src/utils.test.ts new file mode 100644 index 0000000..c5e2a0e --- /dev/null +++ b/src/utils.test.ts @@ -0,0 +1,109 @@ +import { getAllItemKeyConfigs, getUniqueAtomKeys } from './utils'; + +describe('utils', () => { + describe('getUniqueAtomKeys', () => { + it('Should return an empty object for an empty input array', () => { + const result = getUniqueAtomKeys([]); + expect(result).toEqual({}); + }); + + it('Should create a unique key object for a non-empty array', () => { + const items = [ + { name: 'John', age: 30 }, + { name: 'Jane', email: 'jane@example.com' }, + { name: 'Bob', age: 25, email: 'bob@example.com' }, + ]; + + const result = getUniqueAtomKeys(items as any); + expect(result).toEqual({ + name: [], + age: [], + email: [], + }); + }); + + it('Should handle duplicate keys in the array', () => { + const items = [ + { name: 'John', age: 30 }, + { name: 'Jane', age: 35 }, + { name: 'John', email: 'john@example.com' }, + ]; + + const result = getUniqueAtomKeys(items as any); + expect(result).toEqual({ + name: [], + age: [], + email: [], + }); + }); + }); + + describe('getAllItemKeyConfigs', () => { + it('Should return an empty object for an empty input array', () => { + const result = getAllItemKeyConfigs([]); + expect(result).toEqual({}); + }); + + it('should return the correct unique channel keys', () => { + const items = [ + { + title: 'Item 1', + description: 'Description 1', + guid: { + '#text': 'guid-1', + }, + meta: { + '@_property': 'og:title', + '@_content': 'Title', + }, + }, + { + title: 'Item 2', + description: 'Description 2', + 'content:encoded': '

Content 1

', + guid: { + '#text': 'guid-2', + }, + meta: { + '@_property': 'og:image', + '@_content': 'src image', + }, + }, + ] as any; + + const expectedResult = { + title: { + valueAccessor: 'title', + }, + description: { + valueAccessor: 'description', + }, + guid: { + valueAccessor: 'guid.#text', + }, + 'og:image': { + keyAccessor: 'meta.@_property', + valueAccessor: 'meta.@_content', + }, + 'og:title': { + keyAccessor: 'meta.@_property', + valueAccessor: 'meta.@_content', + }, + 'content:encoded': { + valueAccessor: 'content:encoded', + }, + 'content:h4': { + valueAccessor: 'content:encoded', + }, + 'content:img': { + valueAccessor: 'content:encoded', + }, + 'content:img-src': { + valueAccessor: 'content:encoded', + }, + }; + + expect(getAllItemKeyConfigs(items)).toEqual(expectedResult); + }); + }); +}); diff --git a/src/utils.ts b/src/utils.ts index e2b0685..0d9af60 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,12 @@ import { TimeRange } from '@grafana/data'; -import { FeedItems } from 'types'; +import { get } from 'lodash'; +import { DataItem, FeedItems, KeyConfig } from 'types'; + +import { ItemKey } from './constants'; /** * Set or added Item values */ -export const setItem = (items: FeedItems, key: string, value: string) => { +export const setItem = (items: FeedItems, key: string, value: string | null) => { items[key] ? items[key].push(value) : (items[key] = [value]); }; @@ -27,3 +30,85 @@ export const isDateBetweenRange = (value: string, range: TimeRange): boolean => return true; }; + +/** + * Create Unique Key Object + * Linear pass of items and forming an object with unique keys based on an array of objects + */ +export const getUniqueAtomKeys = (items: DataItem[]) => { + return items.reduce((result: Record, obj) => { + Object.keys(obj).forEach((key) => { + if (!result[key]) { + result[key] = []; + } + }); + return result; + }, {}); +}; + +/** + * Get all item key configs + * @param items + */ +export const getAllItemKeyConfigs = (items: DataItem[]): Record => { + const result: Record = {}; + + /** + * Go through all the objects in the array + */ + for (const item of items) { + /** + * Handle other keys + */ + for (const key in item) { + if (key !== ItemKey.META && key !== ItemKey.GUID && !result[key]) { + result[key] = { + valueAccessor: key, + }; + } + + /** + * If the object has a guid key + */ + if (key === ItemKey.GUID) { + result[key] = { + valueAccessor: 'guid.#text', + }; + } + + /** + * If the object has a content:encoded key + */ + if (key === ItemKey.CONTENT_ENCODED) { + const contentEncodedKeys = [ItemKey.CONTENT_H4, ItemKey.CONTENT_IMG, ItemKey.CONTENT_IMG_SRC]; + + contentEncodedKeys.forEach((contentKey) => { + if (!result[contentKey]) { + result[contentKey] = { + valueAccessor: key, + }; + } + }); + } + + /** + * If the object has a “meta” key + */ + if (key === ItemKey.META) { + const property = get(item.meta, '@_property'); + + /** + * If the “meta” key has not yet been added to the resulting object + */ + if (property && !result[property]) { + result[property] = { + keyAccessor: 'meta.@_property', + valueAccessor: 'meta.@_content', + }; + } + } + } + } + + return result; +};