Skip to content
Open
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
4b630cd
fix(notifications): use cms page url instead of url for method for links
xhyrom Apr 13, 2026
f736c89
feat: new activity types
xhyrom Apr 17, 2026
8334467
Merge branch 'main' into feat/new-activity-types
xhyrom Apr 24, 2026
3ce6e45
feat: mig
xhyrom Apr 26, 2026
7f7b4b1
refactor: cleanup
xhyrom Apr 26, 2026
fa11e90
Fix syncing previous responsible subject
luciajanikova Apr 24, 2026
2cf0659
Update SyncIssueActivityObjectToTriageJob
luciajanikova Apr 23, 2026
c3702de
Fix SyncIssueActivityObjectToTriageJob
luciajanikova Apr 26, 2026
9fc5ebc
Fix SyncIssueActivityObjectToTriageJob
luciajanikova Apr 26, 2026
84aa166
Fix SQL injection vulnerability in PgSearch::Faster fulltext_search
jsuchal Apr 28, 2026
7ff0c6f
fix(comments): sanitize and normalize html
xhyrom Dec 13, 2025
0877e3b
style: lint
xhyrom Dec 13, 2025
46a9ace
feat: add db migration for converting agent, responsible html comment…
xhyrom Dec 13, 2025
a1ceb2b
refactor: simplify
xhyrom Jan 10, 2026
fea4dd5
refactor: simplify
xhyrom Jan 10, 2026
86de8e0
refactor: simplify
xhyrom Jan 10, 2026
ff4be47
refactor: simplify
xhyrom Jan 10, 2026
546471b
refactor: remove unrelated changes
xhyrom Apr 13, 2026
2aee176
Fix CMS pages import
luciajanikova Apr 30, 2026
05a4243
Retry all jobs
crutch Mar 28, 2026
3baeede
Fix rubocop
crutch Mar 28, 2026
086bded
update osm client to new osm responses
celuchmarek May 5, 2026
3bb58b4
rm unused method
celuchmarek May 5, 2026
682a25d
Do not initialize omniauth_providers in development
crutch Mar 28, 2026
5de8324
Use postgres from docker
crutch Mar 28, 2026
6413574
Fill NOTIFICATION_SMTP_USERNAME in env sample
crutch Mar 28, 2026
43f5245
Use full connection string incl. database
crutch Mar 28, 2026
c0cd19e
Remove empty LEGACY_DB_URL from .env.sample
celuchmarek May 6, 2026
4101a65
Remove empty LEGACY_DB_URL from .env.test
celuchmarek May 6, 2026
83b6a4b
Add Múdre mestá to header
luciajanikova May 18, 2026
795fe2b
add postgis handling
celuchmarek May 22, 2026
11cba4b
rm old find_by_address methods
celuchmarek May 22, 2026
dd9c852
update
celuchmarek May 24, 2026
f961c06
use tmp path for the json
celuchmarek May 24, 2026
721ce5e
rm unique index on boundaries
celuchmarek May 24, 2026
7517d59
lint
celuchmarek May 24, 2026
55def4d
Sync previous RS only for issues in resolution process
luciajanikova May 25, 2026
485389c
Set backoffice owner & group for new backoffice tickets
luciajanikova May 25, 2026
8593d8d
use paging in zammad triage group listing
celuchmarek May 25, 2026
797e08e
Issue Updates without author
luciajanikova May 25, 2026
a1ec877
send categories in issue update
celuchmarek May 25, 2026
f2ecd50
parametrize user limits via ENV
celuchmarek Jun 9, 2026
c663b4d
incluse response status in fire webhook error message
celuchmarek Jun 9, 2026
f6232d6
allow all 2xx status codes for webhooks
celuchmarek Jun 10, 2026
ace01f5
raise response body if webhook failed
celuchmarek Jun 10, 2026
c308d79
Merge branch 'main' into feat/new-activity-types
xhyrom Jun 22, 2026
f661a70
feat: state change
xhyrom Jun 24, 2026
62a5619
feat: sent t oresponsible, in progress, waiting and rs change notific…
xhyrom Jun 27, 2026
a9b26b4
refactor: cleanup
xhyrom Jun 27, 2026
d4bd9ec
refactor: cleanup
xhyrom Jun 27, 2026
00b43e9
refactor: cleanup
xhyrom Jun 29, 2026
239e6ae
fix: add schema info
xhyrom Jul 8, 2026
ad27f1f
feat: use 'na' instead of right arrow
xhyrom Jul 8, 2026
ca1c393
Merge branch 'main' into feat/new-activity-types
xhyrom Jul 23, 2026
44f3430
fix: remove issue waiting from notification mailer
xhyrom Aug 11, 2026
a9cf2f8
Merge branch 'main' into feat/new-activity-types
xhyrom Sep 16, 2026
0c7dee2
feat: replace samospráva with zodpovedný subjekt
xhyrom Sep 30, 2026
d9c23d7
feat: shorter texts
xhyrom Oct 1, 2026
3864bef
feat: use arrow right, state unknown badge
xhyrom Oct 1, 2026
5cc185f
Merge remote-tracking branch 'origin/main' into feat/new-activity-types
xhyrom Oct 1, 2026
23a0f6f
test: notification stater
xhyrom Oct 8, 2026
93019a1
feat: history mig
xhyrom Oct 11, 2026
b84d063
feat: db mig
xhyrom Oct 11, 2026
45ea742
fix: mig and aktivita
xhyrom Oct 12, 2026
7f24c3f
refactor: cleanup
xhyrom Oct 12, 2026
e981942
refactor: cleanup
xhyrom Oct 12, 2026
ede802b
merge
xhyrom Oct 12, 2026
b37a000
style: lint
xhyrom Oct 12, 2026
d9600e8
feat: backfill issue state changes job
xhyrom Oct 17, 2026
c67a76a
feat: backfill issue history
xhyrom Oct 17, 2026
6b34ce8
Merge branch 'main' into feat/new-activity-types
xhyrom Oct 17, 2026
b4c6396
test: zammad tests
xhyrom Oct 17, 2026
f9f46c6
Merge branch 'main' into feat/new-activity-types
mirrec Oct 19, 2026
c8d9d6b
Update annotations
mirrec Oct 19, 2026
7eff555
test: rename backfill issue history test so it runs
mirrec Oct 19, 2026
359fd26
docs: describe state and RS history, new emails and backfill job
mirrec Oct 19, 2026
db18df2
fix: notify about the first responsible subject of an issue
mirrec Oct 19, 2026
ad20c18
Merge branch 'main' into feat/new-activity-types
mirrec Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion app/assets/stylesheets/application.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2474,6 +2474,76 @@ section.podnet .podnet-discussion .comment {
padding: 30px 20px;
word-break: break-word;
}
section.podnet .podnet-discussion .state-change-message {
align-items: center;
display: flex;
flex-wrap: wrap;
gap: 16px;
margin: 10px 0;
padding: 10px 20px;
}

section.podnet .podnet-discussion .state-change-message .change-icon {
align-items: center;
background: variables.$blue-light;
border-radius: 50%;
display: flex;
height: 32px;
justify-content: center;
width: 32px;
flex-shrink: 0;

svg {
stroke: variables.$blue-dark;
width: 16px;
height: 16px;
}
}

section.podnet .podnet-discussion .state-change-message .content {
align-items: center;
display: flex;
flex: 1;
flex-wrap: wrap;
gap: 10px;
}

section.podnet .podnet-discussion .state-change-message .text {
align-items: center;
color: variables.$black-light;
display: flex;
flex-wrap: wrap;
font-family: variables.$general;
font-size: variables.em(16,16);
gap: 8px;
font-weight: variables.$medium;
}

section.podnet .podnet-discussion .state-change-message .state-badge {
border-radius: 12px;
color: variables.$black;
font-size: variables.em(14,16);
font-weight: variables.$semibold;
padding: 4px 12px;
}

section.podnet .podnet-discussion .state-change-message .arrow-right {
stroke: variables.$black-light;
width: 16px;
height: 16px;
}

section.podnet .podnet-discussion .state-change-message .date {
color: variables.$black-light;
font-size: variables.em(14,16);
margin-left: auto;
}

section.podnet .podnet-discussion .comment-con > *:has(.state-change-message) + * .comment,
section.podnet .podnet-discussion .comment-con > *:has(.state-change-message) + * .edit-comment-form {
border-top: 1px solid variables.$black;
}

section.podnet .podnet-discussion .duplicate {
background: variables.$green-light;
}
Expand Down Expand Up @@ -3402,4 +3472,4 @@ footer .footer-nav-bottom {
@include meta.load-css("_90em.scss");

// 1600px
@include meta.load-css("_100em.scss");
@include meta.load-css("_100em.scss");
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module Notifications
class PublishIssueResponsibleSubjectChangedJob < ApplicationJob
def perform(issue, responsible_subject_id_change: [], notification_mailer: NotificationMailer)
return unless responsible_subject_id_change.present?

previous_rs = ResponsibleSubject.find(responsible_subject_id_change.first)

issue.subscriptions.active.each do |subscription|
next unless subscription.subscriber.email_notifiable?

notification_mailer.with(subscription: subscription).issue_responsible_subject_changed(previous_rs).deliver_later
end
end
end
end
4 changes: 4 additions & 0 deletions app/jobs/notifications/publish_issue_state_changed_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ def perform(issue, state_id_change: [], notification_mailer: NotificationMailer)
notification_mailer.with(subscription: subscription).issue_rejected.deliver_later
when "duplicate"
notification_mailer.with(subscription: subscription).issue_marked_as_duplicate.deliver_later
when "waiting"
notification_mailer.with(subscription: subscription).issue_waiting.deliver_later
when "in_progress"
notification_mailer.with(subscription: subscription).issue_in_progress.deliver_later
when "resolved"
notification_mailer.with(subscription: subscription).issue_resolved.deliver_later
when "unresolved"
Expand Down
14 changes: 14 additions & 0 deletions app/mailers/notification_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,24 @@ def issue_rejected
mail
end

def issue_in_progress
mail
end

def issue_marked_as_duplicate
mail
end

def issue_waiting
mail
end

def issue_responsible_subject_changed(previous_rs)
@previous_rs = previous_rs
@new_rs = @issue.responsible_subject
mail
end

def praise_accepted
mail
end
Expand Down
38 changes: 38 additions & 0 deletions app/models/issue.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ class Issue < ApplicationRecord
scope :resolution_process, -> { where.not(resolution_external_id: nil) }

before_save :recalculate_computed_fields

after_update :track_state_change
after_update :track_responsible_subject_change

after_update :notify_subscribers

def imported?
Expand Down Expand Up @@ -237,6 +241,38 @@ def reset_counters
save
end

def track_state_change
return unless saved_change_to_state_id?

previous_state_id, new_state_id = saved_change_to_state_id
previous_state = Issues::State.find_by(id: previous_state_id)
new_state = Issues::State.find_by(id: new_state_id)

return unless new_state

activity = Issues::StateChangeActivity.create!(issue: self)
Issues::StateChange.create!(
activity: activity,
previous_state: previous_state,
new_state: new_state
)
end

def track_responsible_subject_change
return unless saved_change_to_responsible_subject_id?

previous_rs_id, new_rs_id = saved_change_to_responsible_subject_id
previous_rs = ResponsibleSubject.find_by(id: previous_rs_id)
new_rs = ResponsibleSubject.find_by(id: new_rs_id)

activity = Issues::SystemResponsibleSubjectChangeActivity.create!(issue: self)
Issues::SystemResponsibleSubjectChange.create!(
activity: activity,
previous_responsible_subject: previous_rs,
new_responsible_subject: new_rs
)
end

def notify_subscribers
if issue_type == "praise"
return unless saved_change_to_state_id?
Expand All @@ -251,6 +287,8 @@ def notify_subscribers
Notifications::PublishIssueAcceptedJob.perform_later(self)
elsif saved_change_to_state_id?
Notifications::PublishIssueStateChangedJob.perform_later(self, state_id_change: saved_change_to_state_id)
elsif saved_change_to_responsible_subject_id?
Notifications::PublishIssueResponsibleSubjectChangedJob.perform_later(self, responsible_subject_id_change: saved_change_to_responsible_subject_id)
end
end
end
34 changes: 34 additions & 0 deletions app/models/issues/state_change.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# == Schema Information
#
# Table name: issues_state_changes
#
# id :bigint not null, primary key
# hidden :boolean default(FALSE), not null
# uuid :uuid not null
# created_at :datetime not null
# updated_at :datetime not null
# activity_id :bigint not null
# new_state_id :bigint not null
# previous_state_id :bigint
# triage_external_id :integer
#
class Issues::StateChange < ApplicationRecord
belongs_to :activity, class_name: "Issues::Activity", dependent: :destroy
belongs_to :previous_state, optional: true, class_name: "Issues::State"
belongs_to :new_state, class_name: "Issues::State"
delegate :issue, to: :activity

before_create -> { self.uuid = SecureRandom.uuid }

def author_display_name
"Odkaz pre starostu"
end

def triage_visible?
false
end

def visible?
!hidden
end
end
15 changes: 15 additions & 0 deletions app/models/issues/state_change_activity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# == Schema Information
#
# Table name: issues_activities
#
# id :bigint not null, primary key
# dislikes_count :integer default(0), not null
# likes_count :integer default(0), not null
# type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# issue_id :bigint not null
#
class Issues::StateChangeActivity < Issues::Activity
has_one :activity_object, class_name: "Issues::StateChange", foreign_key: :activity_id, dependent: :destroy
end
30 changes: 30 additions & 0 deletions app/models/issues/system_responsible_subject_change.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# == Schema Information
#
# Table name: issues_system_responsible_subject_changes
#
# id :bigint not null, primary key
# hidden :boolean default(FALSE), not null
# uuid :uuid not null
# created_at :datetime not null
# updated_at :datetime not null
# activity_id :bigint not null
# new_responsible_subject_id :bigint
# previous_responsible_subject_id :bigint
#
class Issues::SystemResponsibleSubjectChange < ApplicationRecord
Comment thread
xhyrom marked this conversation as resolved.
belongs_to :activity, class_name: "Issues::Activity", dependent: :destroy
belongs_to :previous_responsible_subject, optional: true, class_name: "::ResponsibleSubject"
belongs_to :new_responsible_subject, optional: true, class_name: "::ResponsibleSubject"

delegate :issue, to: :activity

before_create -> { self.uuid = SecureRandom.uuid }

def triage_visible?
false
end

def visible?
!hidden
end
end
15 changes: 15 additions & 0 deletions app/models/issues/system_responsible_subject_change_activity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# == Schema Information
#
# Table name: issues_activities
#
# id :bigint not null, primary key
# dislikes_count :integer default(0), not null
# likes_count :integer default(0), not null
# type :string not null
# created_at :datetime not null
# updated_at :datetime not null
# issue_id :bigint not null
#
class Issues::SystemResponsibleSubjectChangeActivity < Issues::Activity
Comment thread
xhyrom marked this conversation as resolved.
has_one :activity_object, class_name: "Issues::SystemResponsibleSubjectChange", foreign_key: :activity_id, dependent: :destroy
end
39 changes: 39 additions & 0 deletions app/views/issues/state_changes/_state_change.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<% change = state_change %>
<%= turbo_frame_tag change do %>
<div class="state-change-message" id="state_change_<%= change.id %>">

<div class="change-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 3 4 7l4 4"/>
<path d="M4 7h16"/>
<path d="M16 21l4-4-4-4"/>
<path d="M20 17H4"/>
</svg>
</div>

<div class="content">
<div class="text">
<span>Stav podnetu bol zmenený z</span>

<% if change.previous_state %>
<span class="state-badge state-<%= change.previous_state.name.parameterize %>">
<%= change.previous_state.name %>
</span>
<% else %>
<span class="state-badge state-unknown">neznámeho stavu</span>
<% end %>

<svg class="arrow-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>
Comment thread
xhyrom marked this conversation as resolved.

<span class="state-badge state-<%= change.new_state.name.parameterize %>">
<%= change.new_state.name %>
</span>
</div>
</div>

<div class="date"><%= l change.created_at.to_date %></div>
</div>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<% change = system_responsible_subject_change %>
<%= turbo_frame_tag change do %>
<div class="state-change-message" id="system_rs_change_<%= change.id %>">

<div class="change-icon">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="4" y="2" width="16" height="20" rx="2" ry="2"></rect>
<path d="M9 22v-4h6v4"></path>
<path d="M8 6h.01"></path>
<path d="M16 6h.01"></path>
<path d="M12 6h.01"></path>
<path d="M12 10h.01"></path>
<path d="M12 14h.01"></path>
<path d="M16 10h.01"></path>
<path d="M16 14h.01"></path>
<path d="M8 10h.01"></path>
<path d="M8 14h.01"></path>
</svg>
</div>

<div class="content">
<div class="text">
<span>Zodpovedný subjekt bol zmenený z</span>
Comment thread
xhyrom marked this conversation as resolved.
Outdated

<% if change.previous_responsible_subject %>
<span class="state-badge state-unknown" style="background-color: #f1f5f9; color: #475569;">
Comment thread
xhyrom marked this conversation as resolved.
Outdated
<%= change.previous_responsible_subject.subject_name %>
</span>
<% else %>
<span class="state-badge state-unknown">neznámeho subjektu</span>
<% end %>

<svg class="arrow-right" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M5 12h14"></path>
<path d="m12 5 7 7-7 7"></path>
</svg>
Comment thread
xhyrom marked this conversation as resolved.

<% if change.new_responsible_subject %>
<span class="state-badge state-unknown" style="background-color: #e2e8f0; color: #1e293b; font-weight: 600;">
<%= change.new_responsible_subject.subject_name %>
</span>
<% else %>
<span class="state-badge state-unknown">nikoho</span>
<% end %>
</div>
</div>

<div class="date"><%= l change.created_at.to_date %></div>
</div>
<% end %>
14 changes: 14 additions & 0 deletions app/views/notification_mailer/issue_in_progress.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<%= ops_title "Podnet je v riešení" %>
<p class="ops-email-paragraph">
<% if @issue.author == @user %>
Dobrý deň, samospráva začala aktívne pracovať na Vašom podnete <%= ops_issue_link @issue %>.
Comment thread
xhyrom marked this conversation as resolved.
Outdated
<% else %>
Dobrý deň, samospráva začala aktívne pracovať na podnete <%= ops_issue_link @issue %>, ktorý sledujete.
<% end %>
Detailné informácie o postupe prác nájdete v <b><%= link_to "detaile podnetu", issue_url(@issue), class: "ops-email-link", target: "_blank" %></b>.
</p>
<p class="ops-email-paragraph">
<%= ops_button "Reagovať na nový status", "#{issue_url(@issue)}#new_issue_activity" %>
</p>
<%= render "signature" %>
<%= render "unsubscribe" %>
Loading