Skip to content

Commit e679934

Browse files
committed
MMI-3336 Talkwalker Integration
Add DB migration 1.3.34 Publish tno-core:1.0.22
1 parent 13bc411 commit e679934

25 files changed

+9627
-49
lines changed

app/editor/.yarn/cache/tno-core-npm-1.0.21-f332bbbe0f-c96bb98def.zip renamed to app/editor/.yarn/cache/tno-core-npm-1.0.22-f9c5b0b4a6-c5ff4f7387.zip

2.02 MB
Binary file not shown.

app/editor/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
"redux-logger": "3.0.6",
6161
"styled-components": "6.1.11",
6262
"stylis": "4.3.2",
63-
"tno-core": "1.0.21"
63+
"tno-core": "1.0.22"
6464
},
6565
"devDependencies": {
6666
"@simbathesailor/use-what-changed": "2.0.0",

app/editor/src/features/admin/report-templates/templates/body/CustomReport.cshtml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
@{
77
var pageBreak = Settings.Sections.UsePageBreaks ? "page-break-after: always;" : "";
88
var utcOffset = ReportExtensions.GetUtcOffset(System.DateTime.Now, "Pacific Standard Time");
9-
var hasImages = Sections.Any(section => section.Value.SectionType == TNO.Entities.ReportSectionType.Image);
9+
var hasImages = Sections.Any(section => section.Value.SectionType == TNO.Entities.ReportSectionType.Image || section.Value.SectionType == TNO.Entities.ReportSectionType.Data);
1010
var subscriberAppUrl = @SubscriberAppUrl?.ToString();
1111
}
1212

@@ -46,7 +46,9 @@
4646
var sectionContent = section.Value.Content.ToArray();
4747

4848
if (!section.Value.IsEnabled) continue;
49-
if (sectionContent.Length == 0 && section.Value.Settings.HideEmpty) continue;
49+
if (sectionContent.Length == 0 && section.Value.Settings.HideEmpty
50+
&& section.Value.SectionType != TNO.Entities.ReportSectionType.Image
51+
&& section.Value.SectionType != TNO.Entities.ReportSectionType.Data) continue;
5052

5153
// Horizontal Chart is if this section and the next section is a Media Analytics chart.
5254
var horizontalCharts = section.Value.SectionType == ReportSectionType.MediaAnalytics && section.Value.Settings.Direction == "row"
@@ -356,7 +358,7 @@
356358
}
357359
else if (section.Value.SectionType == ReportSectionType.Data)
358360
{
359-
@* IMAGE SECTION *@
361+
@* DATA SECTION *@
360362
var alt = section.Value.Settings.Label;
361363
<div>
362364
@(section.Value.Data)
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
@using System
2+
@using System.Collections.Generic
3+
@using System.Dynamic
4+
@using System.Linq
5+
@using TNO.TemplateEngine
6+
7+
@{
8+
var items = Data as IEnumerable<dynamic>;
9+
var maxRows = 10;
10+
11+
if (items == null || !items.Any())
12+
{
13+
<p>No results available.</p>
14+
return;
15+
}
16+
17+
@functions
18+
{
19+
public int GetInt(dynamic item, string key)
20+
{
21+
var dict = item as IDictionary<string, object>;
22+
if (dict != null && dict.ContainsKey(key) && int.TryParse(dict[key]?.ToString(), out int value)) return value;
23+
return 0;
24+
}
25+
26+
public string? GetString(dynamic item, string key)
27+
{
28+
var dict = item as IDictionary<string, object>;
29+
if (dict != null && dict.ContainsKey(key)) return dict[key]?.ToString();
30+
return null;
31+
}
32+
33+
public dynamic[] CreateImages(
34+
dynamic[] images,
35+
bool[,] grid,
36+
int startIndex,
37+
int qty,
38+
int startCol = 0,
39+
int startRow = 0,
40+
int colSpan = 1,
41+
int rowSpan = 1)
42+
{
43+
var list = new List<dynamic>();
44+
for (int i = 0; i < qty && startIndex < images.Length; i++)
45+
{
46+
var image = images[startIndex] as IDictionary<string, object>;
47+
list.Add(new {
48+
StartCol = startCol,
49+
StartRow = startRow,
50+
ColSpan = colSpan,
51+
RowSpan = rowSpan,
52+
Width = $"{colSpan * 50}px",
53+
Height = $"{rowSpan * 50}px",
54+
Img = image,
55+
Engagement = ReportExtensions.FormatInteger(image["engagement"] as String, true),
56+
Url = image["url"] as String,
57+
});
58+
59+
// Identify which cells are occupied in the grid.
60+
for (int c = startCol; c < startCol + colSpan; c++)
61+
for (int r = startRow; r < startRow + rowSpan; r++)
62+
grid[c,r] = true;
63+
64+
startRow += rowSpan;
65+
startIndex++;
66+
}
67+
return list.ToArray();
68+
}
69+
}
70+
71+
// Sort images by engagement (descending)
72+
var images = items.OrderByDescending(i => GetInt(i, "engagement")).ToArray();
73+
74+
// Grid setup, configure the size per item per column.
75+
var sizePerCol = new int[] {5,4,4,3,3,2,2,1,1};
76+
bool[,] occupied = new bool[sizePerCol.Sum(), maxRows];
77+
var placedList = new List<dynamic>();
78+
79+
int startIndex = 0;
80+
int colIndex = 0;
81+
82+
for (int sizeIndex = 0; sizeIndex < sizePerCol.Length; sizeIndex++)
83+
{
84+
int size = sizePerCol[sizeIndex];
85+
int maxItems = maxRows / size;
86+
// Give the first item the addition space.
87+
var firstItemRowSpan = maxRows % size > 0 ? (maxRows - (size * maxItems)) + size : size;
88+
if (firstItemRowSpan != size)
89+
{
90+
placedList.AddRange(CreateImages(images, occupied, placedList.Count, 1, colIndex, 0, size, firstItemRowSpan));
91+
placedList.AddRange(CreateImages(images, occupied, placedList.Count, maxItems - 1, colIndex, firstItemRowSpan, size, size));
92+
}
93+
else
94+
placedList.AddRange(CreateImages(images, occupied, placedList.Count, maxItems, colIndex, 0, size, firstItemRowSpan));
95+
96+
colIndex += size;
97+
}
98+
}
99+
100+
<style>
101+
.image-table td {
102+
border: 2px solid;
103+
border-color: transparent;
104+
padding: 0px;
105+
vertical-align: top;
106+
overflow: hidden; /* Crop overflow */
107+
position: relative; /* For centering */
108+
}
109+
.image-table img {
110+
object-fit: cover; /* Crop to fill the cell(s) */
111+
object-position: center; /* Center the image */
112+
position: absolute;
113+
top: 50%;
114+
left: 50%;
115+
transform: translate(-50%, -50%); /* Center precisely */
116+
display: block;
117+
}
118+
.image-table {
119+
border-collapse: collapse;
120+
border: none;
121+
}
122+
</style>
123+
124+
<table class="image-table">
125+
@for (int r = 0; r < maxRows; r++)
126+
{
127+
<tr>
128+
@for (int c = 0; c < sizePerCol.Sum(); c++)
129+
{
130+
var placed = placedList.FirstOrDefault(p => p.StartRow == r && p.StartCol == c);
131+
if (placed != null)
132+
{
133+
<td rowspan="@placed.RowSpan" colspan="@placed.ColSpan" style="width: @placed.Width; height: @placed.Height;">
134+
<a href="@placed.Url" target="_blank">
135+
<img src="@GetString(placed.Img, "images.url")" alt="Engagement: @GetInt(placed.Img, "engagement")" style="width: @placed.Width; height: @placed.Height;" />
136+
<div style="color: white; position: absolute; background-color: rgba(0,0,0,0.5); width: 100%; bottom: 0; text-align: center;">@placed.Engagement</div>
137+
</a>
138+
</td>
139+
}
140+
else if (occupied[c, r])
141+
{
142+
<!-- Covered by span; output nothing -->
143+
}
144+
else
145+
{
146+
<td style="width: 50px; height: 50px;"></td> <!-- Empty cell -->
147+
}
148+
}
149+
</tr>
150+
}
151+
</table>
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
@using System
2+
@using System.Collections.Generic
3+
@using System.Dynamic
4+
@using System.Linq
5+
@using TNO.TemplateEngine
6+
7+
@{
8+
var items = Data as IEnumerable<dynamic>;
9+
10+
if (items == null || !items.Any())
11+
{
12+
<p>No results available.</p>
13+
return;
14+
}
15+
16+
@functions {
17+
public string? FormatMetric(IDictionary<string, object> dict, string key, string label)
18+
{
19+
if (dict.ContainsKey(key)
20+
&& !String.IsNullOrWhiteSpace(@dict[key] as string)
21+
&& int.TryParse(@dict[key] as string, out int value)
22+
&& value > 0)
23+
{
24+
return $"<span style=\"font-weight: 600;\">" + ReportExtensions.FormatInteger(dict[key] as String, true) + "</span> X Shares,";
25+
}
26+
return null;
27+
}
28+
}
29+
}
30+
31+
@if (items != null)
32+
{
33+
<table style="border-collapse: collapse; width: 100%; margin: 0 auto; font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #333333;">
34+
@foreach (var item in items)
35+
{
36+
var dict = item as IDictionary<string, object>;
37+
if (dict != null)
38+
{
39+
var sentimentStr = dict.ContainsKey("sentiment") ? dict["sentiment"] as String : null;
40+
int? sentiment = sentimentStr != null && int.TryParse(sentimentStr.Replace("'",""), out int sentimentValue) ? sentimentValue : null;
41+
var engagement = dict.ContainsKey("engagement") ? ReportExtensions.FormatInteger(dict["engagement"] as String, true) : null;
42+
var reach = dict.ContainsKey("reach") ? ReportExtensions.FormatInteger(dict["reach"] as String, true) : null;
43+
var author_image_url = dict.ContainsKey("extra_author_attributes.image_url") ? dict["extra_author_attributes.image_url"] as String : null;
44+
var title = dict.ContainsKey("title") ? dict["title"] as String : dict.ContainsKey("title_snippet") ? dict["title_snippet"] as String : null;
45+
var author_name = dict.ContainsKey("extra_author_attributes.name") ? dict["extra_author_attributes.name"] as String : null;
46+
var author_short_name = dict.ContainsKey("extra_author_attributes.short_name") ? dict["extra_author_attributes.short_name"] as String : null;
47+
var post_type = dict.ContainsKey("post_type") ? dict["post_type"] as String : null;
48+
var url = dict.ContainsKey("url") ? dict["url"] as String : null;
49+
var host_url = dict.ContainsKey("host_url") ? dict["host_url"] as String : null;
50+
var root_url = dict.ContainsKey("root_url") ? dict["root_url"] as String : null;
51+
var images_url = dict.ContainsKey("images.url") ? dict["images.url"] as String : null;
52+
var content = dict.ContainsKey("content") ? dict["content"] as String : null;
53+
var published = dict.ContainsKey("published") ? dict["published"] as String : null;
54+
var country = dict.ContainsKey("extra_source_attributes.world_data.country") ? dict["extra_source_attributes.world_data.country"] as String : null;
55+
var source_name = dict.ContainsKey("extra_source_attributes.name") ? dict["extra_source_attributes.name"] as String : null;
56+
57+
<tr style="height: 100%; width: 100%; box-sizing: border-box;">
58+
<td style="padding: 1rem; border-bottom: 1px solid #dddddd; width: 80px; height: 100%; box-sizing: border-box;">
59+
@if (!string.IsNullOrWhiteSpace(author_image_url))
60+
{
61+
<div style="width: 50px; height: 50px; border-radius: 50%; overflow: hidden; display: inline-block; text-align: center;">
62+
<a href="@root_url"><img src="@author_image_url" style="width: 100%; height:100%; object-fit: cover; display: block;"/></a>
63+
</div>
64+
}
65+
</td>
66+
<td style="padding: 1rem; border-bottom: 1px solid #dddddd; max-width: 65%; height: 100%; box-sizing: border-box;">
67+
<div style="width: 100%; height: 100%; box-sizing: border-box;">
68+
<div>
69+
@if (!string.IsNullOrWhiteSpace(title))
70+
{
71+
<span style="font-weight: 600; margin-right: 0.5rem;"><a href="@dict["url"]" style="color: rgb(52, 81, 153);">@title</a></span>
72+
}
73+
<span style="font-weight: 600; margin-right: 1rem; text-transform: capitalize;"><a href="@root_url" style="color: rgb(52, 81, 153);">@author_name</a></span>
74+
@if (!string.IsNullOrWhiteSpace(author_short_name))
75+
{
76+
<span style="margin-right: 0.5rem;"><a href="@root_url" style="color: rgb(52, 81, 153);">@@@author_short_name</a></span>
77+
}
78+
<span style="text-transform: lowercase; font-size: 0.85rem;">
79+
@if (post_type == "IMAGE")
80+
{
81+
@("shared an image")
82+
}
83+
else if (post_type == "LINK")
84+
{
85+
@("shared a link")
86+
}
87+
else if (post_type == "TEXT")
88+
{
89+
@("shared a post")
90+
}
91+
else
92+
{
93+
@("shared a " + post_type)
94+
}
95+
</span>
96+
</div>
97+
<div style="padding-top: 1rem;">
98+
<table style="border-collapse: collapse;">
99+
<tr>
100+
@if (!string.IsNullOrWhiteSpace(images_url))
101+
{
102+
<td style="padding: 1rem;"><a href="@url"><img src="@images_url" style="max-width: 300px;" /></a></td>
103+
}
104+
<td><p>@content</p></td>
105+
</tr>
106+
</table>
107+
</div>
108+
<div style="font-size: 0.85rem; padding-top: 1rem;">
109+
<a href="@url" style="color: rgb(52, 81, 153);">published on @published</a> | @country | <a href="@host_url">@source_name</a>
110+
</div>
111+
</div>
112+
</td>
113+
<td style="padding: 1rem; border-bottom: 1px solid #dddddd; width: 35%; height: 100%; box-sizing: border-box;">
114+
<div style="padding: 1rem; margin: 1rem; border-left: 1px solid #dddddd;">
115+
<table style="border-collapse: collapse;">
116+
<tr>
117+
<td style="padding-bottom: 1rem; text-align: center;">
118+
<div>@ReportExtensions.GetSentimentIcon(sentiment)</div>
119+
</td>
120+
</tr>
121+
<tr>
122+
<td style="font-weight: 600; color: rgb(52, 81, 153); text-align: center; vertical-align: top;">METRICS</td>
123+
<td style="text-align: left; vertical-align: top; padding: 0 1rem;">
124+
<div styles="">
125+
<table style="border-collapse: collapse;">
126+
<tr>
127+
<td>
128+
Engagement: <span style="font-weight: 600; color: rgb(52, 81, 153);">@engagement</span>
129+
</td>
130+
<td>
131+
Reach: <span style="font-weight: 600; color: rgb(52, 81, 153);">@reach</span>
132+
</td>
133+
</tr>
134+
</table>
135+
</div>
136+
<div style="font-size: 0.85rem; padding-top: 1rem;">
137+
@FormatMetric(dict, "article_extended_attributes.twitter_shares", "X Shares")
138+
@FormatMetric(dict, "article_extended_attributes.twitter_retweets", "X Reposts")
139+
@FormatMetric(dict, "article_extended_attributes.twitter_quote_tweets", "X Quotes")
140+
@FormatMetric(dict, "article_extended_attributes.twitter_replies", "X Replies")
141+
@FormatMetric(dict, "article_extended_attributes.twitter_likes", "X Likes")
142+
@FormatMetric(dict, "article_extended_attributes.twitter_followers", "X Followers")
143+
@FormatMetric(dict, "article_extended_attributes.twitter_impressions", "X Impressions")
144+
@FormatMetric(dict, "article_extended_attributes.twitter_video_views", "X Video Views")
145+
146+
@FormatMetric(dict, "article_extended_attributes.semrush_unique_visitors", "Semrush Traffic Rank")
147+
@FormatMetric(dict, "article_extended_attributes.semrush_pageviews", "Semrush Page Views")
148+
149+
@FormatMetric(dict, "article_extended_attributes.alexa_unique_visitors", "Alexa Unique Visitors")
150+
@FormatMetric(dict, "article_extended_attributes.alexa_pageviews", "Alexa Page Views")
151+
152+
@FormatMetric(dict, "article_extended_attributes.linkedin_shares", "LinkedIn Shares")
153+
@FormatMetric(dict, "article_extended_attributes.linkedin_impression", "LinkedIn Impressions")
154+
155+
@FormatMetric(dict, "article_extended_attributes.facebook_shares", "Facebook Shares")
156+
@FormatMetric(dict, "article_extended_attributes.facebook_reactions_total", "Facebook Reactions")
157+
@FormatMetric(dict, "article_extended_attributes.facebook_likes", "Facebook Likes")
158+
@FormatMetric(dict, "article_extended_attributes.facebook_reactions_haha", "Facebook HaHa")
159+
@FormatMetric(dict, "article_extended_attributes.facebook_reactions_angry", "Facebook Angry")
160+
@FormatMetric(dict, "article_extended_attributes.facebook_reactions_sad", "Facebook Sad")
161+
@FormatMetric(dict, "article_extended_attributes.facebook_reactions_love", "Facebook Love")
162+
@FormatMetric(dict, "article_extended_attributes.facebook_reactions_wow", "Facebook Wow")
163+
@FormatMetric(dict, "article_extended_attributes.facebook_followers", "Facebook Followers")
164+
@FormatMetric(dict, "article_extended_attributes.facebook_group_members", "Facebook Group Members")
165+
166+
@FormatMetric(dict, "article_extended_attributes.pinterest_likes", "Pinterest Likes")
167+
@FormatMetric(dict, "article_extended_attributes.pinterest_pins", "Pinterest Pins")
168+
@FormatMetric(dict, "article_extended_attributes.pinterest_repins", "Pinterest Repins")
169+
@FormatMetric(dict, "article_extended_attributes.pinterest_followers", "Pinterest Followers")
170+
171+
@FormatMetric(dict, "article_extended_attributes.youtube_views", "YouTube Views")
172+
@FormatMetric(dict, "article_extended_attributes.youtube_likes", "YouTube Likes")
173+
@FormatMetric(dict, "article_extended_attributes.youtube_dislikes", "YouTube Dislikes")
174+
175+
@FormatMetric(dict, "article_extended_attributes.instagram_likes", "Instagram Likes")
176+
@FormatMetric(dict, "article_extended_attributes.instagram_followers", "Instagram Followers")
177+
</div>
178+
</td>
179+
</tr>
180+
</table>
181+
</div>
182+
</td>
183+
</tr>
184+
}
185+
else
186+
{
187+
<div>Failed to parse data.</div>
188+
}
189+
}
190+
</table>
191+
}
192+
else
193+
{
194+
<div>No data found.</div>
195+
}

0 commit comments

Comments
 (0)