Skip to content

Commit fb2bb9b

Browse files
committed
MMI-3249 Update Syndication Service
1 parent d818a9d commit fb2bb9b

File tree

3 files changed

+109
-17
lines changed

3 files changed

+109
-17
lines changed

app/editor/src/features/admin/ingests/configurations/Newspaper.tsx

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export const Newspaper: React.FC = (props) => {
2828
const sources = lookups.sources.map((s) => new OptionItem(s.name, s.code, !s.isEnabled));
2929
const source = sources.find((t) => t.value === values.configuration.defaultSource);
3030

31-
const initialItems = () => {
31+
const initialItems = React.useCallback(() => {
3232
const data: any[] = [];
3333
let id = 0;
3434
const keyValues = values.configuration.sources?.split('&') ?? [];
@@ -37,24 +37,27 @@ export const Newspaper: React.FC = (props) => {
3737
});
3838
data.push({ id: ++id, name: '', source: '' });
3939
return data;
40-
};
40+
}, [values.configuration.sources]);
4141

4242
const [items, setItems] = React.useState<{ id: number; name: string; source: string }[]>(
4343
initialItems(),
4444
);
4545

46-
const updateItems = (updatedItems: any[]) => {
47-
if (updatedItems.filter((x) => !x.name && !x.source).length === 0)
48-
updatedItems.push({ id: updatedItems.length + 1, name: '', source: '' });
49-
setItems(updatedItems);
50-
setFieldValue(
51-
'configuration.sources',
52-
updatedItems
53-
.filter((t) => !!t.name)
54-
.map((item) => `${item.name?.trim()}=${item.source?.trim()}`)
55-
.join('&'),
56-
);
57-
};
46+
const updateItems = React.useCallback(
47+
(updatedItems: any[]) => {
48+
if (updatedItems.filter((x) => !x.name && !x.source).length === 0)
49+
updatedItems.push({ id: updatedItems.length + 1, name: '', source: '' });
50+
setItems(updatedItems);
51+
setFieldValue(
52+
'configuration.sources',
53+
updatedItems
54+
.filter((t) => !!t.name)
55+
.map((item) => `${item.name?.trim()}=${item.source?.trim()}`)
56+
.join('&'),
57+
);
58+
},
59+
[setFieldValue],
60+
);
5861

5962
const handleRemove = async (id: number) => {
6063
const item = items.find((i) => i.id === id);
@@ -283,7 +286,7 @@ export const Newspaper: React.FC = (props) => {
283286
/>
284287
</Col>
285288
</Row>
286-
<b>Sources</b>
289+
<b>Source Mapping</b>
287290
<FlexboxTable
288291
rowId="id"
289292
data={items}

app/editor/src/features/admin/ingests/configurations/Syndication.tsx

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,67 @@
11
import { useFormikContext } from 'formik';
22
import React from 'react';
3-
import { FormikCheckbox, FormikSelect, FormikText, IIngestModel } from 'tno-core';
3+
import { FlexboxTable, FormikCheckbox, FormikSelect, FormikText, IIngestModel } from 'tno-core';
44

55
import { TimeZones } from './constants';
6+
import { columns } from './constants/columns';
67
import { ImportContent } from './ImportContent';
78
import * as styled from './styled';
89

910
export const Syndication: React.FC = (props) => {
1011
const { values, setFieldValue } = useFormikContext<IIngestModel>();
1112
const timeZone = TimeZones.find((t) => t.value === values.configuration.timeZone);
1213

14+
const initialItems = React.useCallback(() => {
15+
const data: any[] = [];
16+
let id = 0;
17+
const keyValues = values.configuration.sources?.split('&') ?? [];
18+
keyValues.forEach((x: string) => {
19+
data.push({ id: ++id, name: x.split('=')[0], source: x.split('=')[1] });
20+
});
21+
data.push({ id: ++id, name: '', source: '' });
22+
return data;
23+
}, [values.configuration.sources]);
24+
25+
const [items, setItems] = React.useState<{ id: number; name: string; source: string }[]>(
26+
initialItems(),
27+
);
28+
29+
const updateItems = React.useCallback(
30+
(updatedItems: any[]) => {
31+
if (updatedItems.filter((x) => !x.name && !x.source).length === 0)
32+
updatedItems.push({ id: updatedItems.length + 1, name: '', source: '' });
33+
setItems(updatedItems);
34+
setFieldValue(
35+
'configuration.sources',
36+
updatedItems
37+
.filter((t) => !!t.name)
38+
.map((item) => `${item.name?.trim()}=${item.source?.trim()}`)
39+
.join('&'),
40+
);
41+
},
42+
[setFieldValue],
43+
);
44+
45+
const handleRemove = async (id: number) => {
46+
const item = items.find((i) => i.id === id);
47+
if (!item) return;
48+
49+
const updatedItems = items.filter((i) => i.id !== id);
50+
updateItems(updatedItems);
51+
};
52+
53+
const onChange = (event: any, cell: any, isSource = true) => {
54+
const pointer = event.target.selectionStart;
55+
window.requestAnimationFrame(() => {
56+
event.target.selectionEnd = pointer;
57+
});
58+
const updatedItem = isSource
59+
? { ...cell.original, source: event.target.value }
60+
: { ...cell.original, name: event.target.value };
61+
const updatedItems = items.map((item) => (item.id === cell.original.id ? updatedItem : item));
62+
updateItems(updatedItems);
63+
};
64+
1365
return (
1466
<styled.IngestType>
1567
<ImportContent />
@@ -46,6 +98,15 @@ export const Syndication: React.FC = (props) => {
4698
setFieldValue('configuration.fetchContent', e.currentTarget.checked);
4799
}}
48100
/>
101+
<b>Source Mapping</b>
102+
<FlexboxTable
103+
rowId="id"
104+
data={items}
105+
columns={columns(handleRemove, onChange)}
106+
showSort={true}
107+
showActive={false}
108+
pagingEnabled={false}
109+
/>
49110
</styled.IngestType>
50111
);
51112
};

services/net/syndication/SyndicationAction.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,34 @@ private async Task<string> GetContentAsync(IIngestActionManager manager, Uri url
246246
}
247247
}
248248

249+
/// <summary>
250+
/// If the ingest has source mappings configured, find a match and apply the source to the item.
251+
/// </summary>
252+
/// <param name="ingest"></param>
253+
/// <param name="item"></param>
254+
/// <returns></returns>
255+
/// <exception cref="InvalidOperationException"></exception>
256+
private string DetermineSource(IngestModel ingest, SyndicationItem item)
257+
{
258+
KeyValuePair<string, string>[] sourceValues = ingest.Configuration.GetDictionaryJsonValue<string>("sources")?.Split('&').Select(v =>
259+
{
260+
var keyValue = v.Split('=');
261+
if (keyValue.Length > 1) return new KeyValuePair<string, string>(keyValue[0], keyValue[1]);
262+
return new KeyValuePair<string, string>(keyValue[0], keyValue[0]);
263+
}).ToArray() ?? [];
264+
265+
var defaultSource = ingest.Source?.Code ?? throw new InvalidOperationException($"Ingest '{ingest.Name}' is missing source code.");
266+
var itemSource = item.SourceFeed?.Title?.Text;
267+
268+
// Find a match if possible
269+
if (!String.IsNullOrEmpty(itemSource))
270+
{
271+
var sourceMatch = sourceValues.FirstOrDefault(s => s.Key.Equals(itemSource, StringComparison.InvariantCultureIgnoreCase)).Value;
272+
if (!String.IsNullOrWhiteSpace(sourceMatch)) return sourceMatch;
273+
}
274+
return defaultSource;
275+
}
276+
249277
/// <summary>
250278
/// Create a SourceContent object that can be sent to Kafka.
251279
/// </summary>
@@ -255,7 +283,7 @@ private async Task<string> GetContentAsync(IIngestActionManager manager, Uri url
255283
private SourceContent CreateSourceContent(IngestModel ingest, SyndicationItem item)
256284
{
257285
var (title, summary, body) = HandleInvalidEncoding(item);
258-
var source = ingest.Source?.Code ?? throw new InvalidOperationException($"Ingest '{ingest.Name}' is missing source code.");
286+
var source = DetermineSource(ingest, item);
259287
var contentType = ingest.IngestType?.ContentType ?? throw new InvalidOperationException($"Ingest '{ingest.Name}' is missing ingest content type.");
260288
var publishedOn = item.PublishDate.UtcDateTime != DateTime.MinValue ? item.PublishDate.UtcDateTime : (DateTime?)null;
261289
var uid = Runners.BaseService.GetContentHash(source, title, publishedOn);

0 commit comments

Comments
 (0)