-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/episode sparklines #1386
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/episode sparklines #1386
Changes from 12 commits
fffd32a
1e807d4
c2237e2
d4fa1cf
caf92ad
65237da
d767717
2c4452c
7f056b4
5a68772
fc723ad
d6e5b62
84330c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,7 +17,12 @@ def index | |
| def show | ||
| authorize @podcast | ||
|
|
||
| @recently_published = @podcast.episodes.published.dropdate_desc.limit(3) | ||
| @recently_published_episodes = @podcast.episodes.published.dropdate_desc.limit(4) | ||
| @trend_episodes = @podcast.default_feed.episodes.published.dropdate_desc.where.not(first_rss_published_at: nil).offset(1).limit(4) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here i am pulling the episodes to be compared against in the trend calculation. offset by 1 because i don't want the most recent one. |
||
| @episode_trend_pairs = episode_trend_pairs(@recently_published_episodes, @trend_episodes) | ||
|
|
||
| # @recently_published is used for the prod branch | ||
| @recently_published = @recently_published_episodes[0..2] | ||
| @next_scheduled = @podcast.episodes.draft_or_scheduled.dropdate_asc.limit(3) | ||
|
|
||
| @metrics_jwt = prx_jwt | ||
|
|
@@ -189,4 +194,24 @@ def compose_message(podcast) | |
| def sub_escapes(text) | ||
| text.gsub(/[&<>]/, "&" => "&", "<" => "<", ">" => ">") | ||
| end | ||
|
|
||
| def episode_trend_pairs(episodes, trend_episodes) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. and here i realized that i didn't want to necessarily pair episode and trend episode 1:1. i still needed to check if an episode should have a trend number at all, and then count which
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since these are in separate turbo requests, you could just find which episode you should compare to in that other request? It avoids any kind of N+1, not attempting to pair them all upfront. And makes the outer request quicker. Episode.where(podcast_id: ep.podcast_id, first_rss_published_at: ..ep.first_rss_published_at).order(first_rss_published_at: :desc).first(Not a request for change in this PR, just something to think about ... optimize for the outer/first GET request).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. added to #1400 |
||
| paired_trend_episodes = [] | ||
|
|
||
| episodes.map.with_index do |ep, i| | ||
| if ep.in_default_feed? | ||
| paired_trend_episodes << trend_episodes[paired_trend_episodes.length] | ||
|
|
||
| { | ||
| episode: ep, | ||
| prev_episode: paired_trend_episodes.last | ||
| } | ||
| else | ||
| { | ||
| episode: ep, | ||
| prev_episode: nil | ||
| } | ||
| end | ||
| end | ||
| end | ||
| end | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { Controller } from "@hotwired/stimulus" | ||
| import { buildSparklineChart, destroyChart } from "util/apex" | ||
|
|
||
| export default class extends Controller { | ||
| static values = { | ||
| id: String, | ||
| downloads: Array, | ||
| } | ||
|
|
||
| static targets = ["chart"] | ||
|
|
||
| connect() { | ||
| const seriesData = this.downloadsValue.map((rollup) => { | ||
| return { | ||
| x: rollup.hour, | ||
| y: rollup.count, | ||
| } | ||
| }) | ||
| const series = [ | ||
| { | ||
| name: "Downloads", | ||
| data: seriesData, | ||
| }, | ||
| ] | ||
|
|
||
| const chart = buildSparklineChart(this.idValue, series, this.chartTarget) | ||
|
|
||
| chart.render() | ||
| } | ||
|
|
||
| disconnect() { | ||
| destroyChart(this.idValue) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| <%= turbo_frame_tag "episode_sparkline" do %> | ||
| <% trend = parse_trend(episode_trend) %> | ||
|
|
||
| <p class="d-flex align-items-center <%= trend[:color] if trend.present? %>"> | ||
| <span class="z-1"><%= trend[:percent] if trend.present? %></span> | ||
| <span class="material-icons ms-1 z-1" aria-hidden="true"><%= trend[:direction] if trend.present? %></span> | ||
| </p> | ||
| <div class="position-absolute top-50 start-50 translate-middle w-100 h-100 z-0" | ||
| data-controller="apex-sparkline" | ||
| data-apex-sparkline-id-value="<%= episode.guid %>" | ||
| data-apex-sparkline-downloads-value="<%= downloads.to_json %>"> | ||
| <div data-apex-sparkline-target="chart"></div> | ||
| </div> | ||
| <% end %> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,50 +1,8 @@ | ||
| <% chart_id = SecureRandom.uuid %> | ||
|
|
||
| <%= turbo_frame_tag "episodes" do %> | ||
| <%= form_with(url: url, method: :get, id: form_id, data: {controller: "click", click_debounce_value: 200}) do |form| %> | ||
| <div class="row d-flex flex-wrap gx-4 h-100"> | ||
| <div class="col metrics-chart" | ||
| data-controller="apex-episodes" | ||
| data-apex-episodes-id-value="<%= chart_id %>" | ||
| data-apex-episodes-selected-episodes-value="<%= episode_rollups.to_json %>" | ||
| data-apex-episodes-date-range-value="<%= date_range.to_json %>" | ||
| data-apex-episodes-interval-value="<%= interval %>"> | ||
| <div data-apex-episodes-target="chart"></div> | ||
| </div> | ||
| <div class="col align-items-center metrics-table" data-controller="apex-toggles"> | ||
| <table class="table table-sm table-striped shadow rounded"> | ||
| <thead class="table-primary"> | ||
| <tr> | ||
| <td><%= t(".table_header.episodes") %></td> | ||
| <td><%= t(".table_header.published_at") %></td> | ||
| <td><%= t(".table_header.downloads") %></td> | ||
| <td><%= t(".table_header.all_time") %></td> | ||
| </tr> | ||
| </thead> | ||
| <tbody> | ||
| <% episode_rollups.each_with_index do |er, i| %> | ||
| <% sum = sum_rollups(er[:rollups]) %> | ||
| <% total = sum_rollups(er[:totals]) %> | ||
| <% title = er[:episode][:title] %> | ||
| <tr class="align-items-center"> | ||
| <td class="d-flex align-items-center"> | ||
| <input id="episode<%= title %>" type="checkbox" class="form-check-input mt-0 me-2" checked="true" data-action="click->apex-toggles#toggleSeries" data-apex-toggles-series-param="<%= title %>" data-apex-toggles-id-param="<%= chart_id %>"> | ||
| <span class="material-icons me-2" aria-label="hidden" style="color: <%= er[:color] %>">circle</span> | ||
| <span class="text-nowrap overflow-hidden"><%= link_to title, episode_metrics_path(episode_id: er[:episode][:guid], date_start: date_start, date_end: date_end, interval: interval), data: {turbo_frame: "_top"} %></span> | ||
| </td> | ||
| <td><%= er[:episode][:published_at].strftime("%Y-%m-%d") %></td> | ||
| <td><% if sum %><%= sum %><% else %>—<% end %></td> | ||
| <td><% if total %><%= total %><% else %>—<% end %></td> | ||
| </tr> | ||
| <% end %> | ||
| </tbody> | ||
| </table> | ||
| </div> | ||
| </div> | ||
|
|
||
| <%= form.date_field :date_start, class: "d-none", data: {apex_nav_target: "startDate", action: "change->click#submit"}, value: date_start %> | ||
| <%= form.date_field :date_end, class: "d-none", data: {apex_nav_target: "endDate", action: "change->click#submit"}, value: date_end %> | ||
| <%= form.select :interval, interval_options, {selected: interval}, class: "d-none", data: {apex_nav_target: "interval", action: "change->click#submit", path: "episodes"} %> | ||
| <%= form.submit class: "d-none", data: {click_target: "submit"} %> | ||
| <div class="episode-card-info inside-card flex-fill px-3 py-2 position-relative"> | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. repurposing this partial for the published episode cards. |
||
| <p class="episode-card-date z-1"><%= local_date(episode.published_at, format: :short) %></p> | ||
| <div class="episode-card-inner inside-card"> | ||
| <h2 class="h5 episode-card-title m-0 z-1"><%= link_to episode.title, episode_path(episode) %></h2> | ||
| </div> | ||
| <%= turbo_frame_tag "episode_sparkline", src: episode_sparkline_podcast_metrics_path(podcast_id: podcast.id, episode_id: episode.guid, prev_episode_id: prev_episode.guid), loading: "lazy" do %> | ||
| <% end %> | ||
| <% end %> | ||
| </div> | ||
Uh oh!
There was an error while loading. Please reload this page.