bsw@1045: local for_member = param.get ( "for_member", "table" ) bsw@1045: local for_unit = param.get ( "for_unit", "table" ) bsw@1045: local for_area = param.get ( "for_area", "table" ) bsw@1045: local for_issue = param.get ( "for_issue", "table" ) bsw@1045: local for_initiative = param.get ( "for_initiative", "table" ) bsw@1045: local for_sidebar = param.get("for_sidebar", atom.boolean) bsw@1045: local no_filter = param.get ( "no_filter", atom.boolean ) bsw@1045: local search = param.get ( "search" ) bsw@1045: bsw@1045: local limit = 25 bsw@1045: bsw@1145: local mode = request.get_param{ name = "mode" } or "issue" bsw@1045: bsw@1045: if for_initiative or for_issue or for_member then bsw@1045: mode = "timeline" bsw@1045: end bsw@1045: bsw@1045: local selector bsw@1045: bsw@1045: if search then bsw@1045: bsw@1045: selector = Issue:get_search_selector(search) bsw@1045: bsw@1045: bsw@1045: elseif mode == "timeline" then bsw@1045: bsw@1145: local event_max_id = request.get_param_strings()["event_max_id"] bsw@1045: bsw@1045: selector = Event:new_selector() bsw@1045: :add_order_by("event.id DESC") bsw@1045: :join("issue", nil, "issue.id = event.issue_id") bsw@1045: :add_field("now() - event.occurrence", "time_ago") bsw@1045: :limit(limit + 1) bsw@1045: bsw@1045: if event_max_id then bsw@1045: selector:add_where{ "event.id < ?", event_max_id } bsw@1045: end bsw@1045: bsw@1045: if for_member then bsw@1045: selector:add_where{ "event.member_id = ?", for_member.id } bsw@1045: end bsw@1045: bsw@1045: if for_initiative then bsw@1045: selector:add_where{ "event.initiative_id = ?", for_initiative.id } bsw@1045: end bsw@1045: bsw@1045: bsw@1045: elseif mode == "issue" then bsw@1045: bsw@1045: selector = Issue:new_selector() bsw@1045: bsw@1045: end bsw@1045: bsw@1045: if for_unit then bsw@1045: selector:join("area", nil, "area.id = issue.area_id") bsw@1045: selector:add_where{ "area.unit_id = ?", for_unit.id } bsw@1045: elseif for_area then bsw@1045: selector:add_where{ "issue.area_id = ?", for_area.id } bsw@1045: elseif for_issue then bsw@1045: selector:add_where{ "issue.id = ?", for_issue.id } bsw@1045: end bsw@1045: bsw@1045: if not search and app.session.member_id then bsw@1045: selector bsw@1045: :left_join("interest", "_interest", { bsw@1045: "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id bsw@1045: } ) bsw@1045: :add_field("(_interest.member_id NOTNULL)", "is_interested") bsw@1045: :left_join("delegating_interest_snapshot", "_delegating_interest", { [[ bsw@1045: _delegating_interest.issue_id = issue.id AND bsw@1045: _delegating_interest.member_id = ? AND bsw@1045: _delegating_interest.event = issue.latest_snapshot_event bsw@1045: ]], app.session.member.id } ) bsw@1045: :add_field("_delegating_interest.delegate_member_ids[1]", "is_interested_by_delegation_to_member_id") bsw@1045: :add_field("_delegating_interest.delegate_member_ids[array_upper(_delegating_interest.delegate_member_ids, 1)]", "is_interested_via_member_id") bsw@1045: :add_field("array_length(_delegating_interest.delegate_member_ids, 1)", "delegation_chain_length") bsw@1045: end bsw@1045: bsw@1145: local function doit() bsw@1045: bsw@1045: local last_event_id bsw@1045: bsw@1045: local items = selector:exec() bsw@1045: bsw@1045: if #items < 1 then bsw@1045: ui.section( function() bsw@1045: ui.sectionRow( function() bsw@1045: ui.heading{ level = 2, content = _"No results for this selection" } bsw@1045: end ) bsw@1045: end ) bsw@1045: return bsw@1045: end bsw@1045: bsw@1045: local row_class = "sectionRow" bsw@1045: if for_sidebar then bsw@1045: row_class = "sidebarRow" bsw@1045: end bsw@1045: bsw@1045: if mode == "timeline" then bsw@1045: local issues = items:load ( "issue" ) bsw@1045: local initiative = items:load ( "initiative" ) bsw@1045: items:load ( "suggestion" ) bsw@1045: items:load ( "member" ) bsw@1045: issues:load_everything_for_member_id ( app.session.member_id ) bsw@1045: initiative:load_everything_for_member_id ( app.session.member_id ) bsw@1045: elseif mode == "issue" then bsw@1045: items:load_everything_for_member_id ( app.session.member_id ) bsw@1045: end bsw@1045: bsw@1045: local class = "section" bsw@1045: if mode == "timeline" then bsw@1045: class = class .. " events" bsw@1045: elseif mode == "issue" then bsw@1045: class = class .. " issues" bsw@1045: end bsw@1045: bsw@1045: ui.container{ attr = { class = class }, content = function() bsw@1045: bsw@1045: local last_event_date bsw@1045: for i, item in ipairs(items) do bsw@1045: local event bsw@1045: local issue bsw@1045: if mode == "timeline" then bsw@1045: event = item bsw@1045: issue = item.issue bsw@1045: elseif mode == "issue" then bsw@1045: event = {} bsw@1045: issue = item bsw@1045: end bsw@1045: bsw@1045: last_event_id = event.id bsw@1045: bsw@1045: local class = "event " .. row_class bsw@1045: if event.suggestion_id then bsw@1045: class = class .. " suggestion" bsw@1045: end bsw@1045: bsw@1045: ui.container{ attr = { class = class }, content = function() bsw@1045: local event_name bsw@1045: local negative_event = false bsw@1045: bsw@1045: local days_ago_text bsw@1045: bsw@1045: if mode == "timeline" then bsw@1045: event_name = event.event_name bsw@1045: bsw@1045: if event.event == "issue_state_changed" then bsw@1045: if event.state == "discussion" then bsw@1045: event_name = _"Discussion started" bsw@1045: elseif event.state == "verification" then bsw@1045: event_name = _"Verification started" bsw@1045: elseif event.state == "voting" then bsw@1045: event_name = _"Voting started" bsw@1045: elseif event.state == "finished_with_winner" then bsw@1045: event_name = event.state_name bsw@1045: elseif event.state == "finished_without_winner" then bsw@1045: event_name = event.state_name bsw@1045: negative_event = true bsw@1045: else bsw@1045: event_name = event.state_name bsw@1045: negative_event = true bsw@1045: end bsw@1045: elseif event.event == "initiative_revoked" then bsw@1045: negative_event = true bsw@1045: end bsw@1045: bsw@1045: if event.time_ago == 0 then bsw@1045: days_ago_text = _("today at #{time}", { time = format.time(event.occurrence) }) bsw@1045: elseif event.time_ago == 1 then bsw@1045: days_ago_text = _("yesterday at #{time}", { time = format.time(event.occurrence) }) bsw@1045: else bsw@1045: days_ago_text = _("#{interval} ago", { interval = format.interval_text ( event.time_ago ) } ) bsw@1045: end bsw@1045: bsw@1045: elseif mode == "issue" then bsw@1045: event_name = issue.state_name bsw@1045: if issue.state_time_left:sub(1,1) ~= "-" then bsw@1045: days_ago_text = _( "#{interval} left", { bsw@1045: interval = format.interval_text ( issue.state_time_left ) bsw@1045: }) bsw@1045: elseif issue.closed then bsw@1045: days_ago_text = _( "#{interval} ago", { bsw@1045: interval = format.interval_text ( issue.closed_ago ) bsw@1045: }) bsw@1045: else bsw@1045: days_ago_text = _"phase ends soon" bsw@1045: end bsw@1045: if issue.closed and not issue.fully_frozen then bsw@1045: negative_event = true bsw@1045: end bsw@1045: if issue.state == "finished_without_winner" then bsw@1045: negative_event = true bsw@1045: end bsw@1045: if issue.state == "canceled_no_initiative_admitted" then bsw@1045: negative_event = true bsw@1045: end bsw@1045: if issue.state == "canceled_by_admin" then bsw@1045: negative_event = true bsw@1045: end bsw@1045: end bsw@1045: bsw@1045: local class= "event_info" bsw@1045: bsw@1045: if negative_event then bsw@1045: class = class .. " negative" bsw@1045: end bsw@1045: bsw@1045: if mode == "timeline" then bsw@1045: ui.container{ attr = { class = class }, content = function () bsw@1045: ui.tag { content = event_name } bsw@1045: slot.put ( " " ) bsw@1045: ui.tag{ attr = { class = "event_time" }, content = days_ago_text } bsw@1045: end } bsw@1045: end bsw@1045: bsw@1045: if not for_issue and not for_initiative then bsw@1045: ui.container{ attr = { class = "issue_context" }, content = function() bsw@1045: ui.link{ bsw@1045: module = "unit", view = "show", id = issue.area.unit_id, bsw@1045: attr = { class = "unit" }, text = issue.area.unit.name bsw@1045: } bsw@1045: slot.put ( " " ) bsw@1045: ui.link{ bsw@1045: module = "area", view = "show", id = issue.area_id, bsw@1045: attr = { class = "area" }, text = issue.area.name bsw@1045: } bsw@1045: slot.put ( " " ) bsw@1045: execute.view{ bsw@1045: module = "delegation", view = "_info", params = { bsw@1045: issue = issue, member = for_member bsw@1045: } bsw@1045: } bsw@1045: end } bsw@1045: ui.container{ attr = { class = "issue_info" }, content = function() bsw@1045: ui.link{ bsw@1045: attr = { class = "issue" }, bsw@1045: text = _("#{policy} ##{id}", { policy = issue.policy.name, id = issue.id }), bsw@1045: module = "issue", bsw@1045: view = "show", bsw@1045: id = issue.id bsw@1045: } bsw@1045: bsw@1045: end } bsw@1045: end bsw@1045: bsw@1045: if mode ~= "timeline" bsw@1045: or event.state == "finished_with_winner" bsw@1045: or event.state == "finished_without_winner" bsw@1045: then bsw@1045: local initiative = issue.initiatives[1] bsw@1045: if initiative then bsw@1045: util.initiative_pie(initiative) bsw@1045: end bsw@1045: end bsw@1045: bsw@1045: if mode == "issue" then bsw@1045: ui.container{ attr = { class = class }, content = function () bsw@1045: ui.tag { content = event_name } bsw@1045: slot.put ( " " ) bsw@1045: ui.tag{ attr = { class = "event_time" }, content = days_ago_text } bsw@1045: end } bsw@1045: elseif mode == "timeline" bsw@1045: and not for_issue bsw@1045: and event.event ~= "issue_state_changed" bsw@1045: then bsw@1045: slot.put("
") bsw@1045: end bsw@1045: bsw@1045: if event.suggestion_id then bsw@1045: ui.container{ attr = { class = "suggestion" }, content = function() bsw@1045: ui.link{ bsw@1045: text = format.string(event.suggestion.name, { bsw@1045: truncate_at = 160, truncate_suffix = true bsw@1045: }), bsw@1045: module = "initiative", view = "show", id = event.initiative.id, bsw@1045: params = { suggestion_id = event.suggestion_id }, bsw@1045: anchor = "s" .. event.suggestion_id bsw@1045: } bsw@1045: end } bsw@1045: end bsw@1045: bsw@1045: if not for_initiative and (not for_issue or event.initiative_id) then bsw@1045: bsw@1045: ui.container{ attr = { class = "initiative_list" }, content = function() bsw@1045: if event.initiative_id then bsw@1045: local initiative = event.initiative bsw@1045: bsw@1045: execute.view{ module = "initiative", view = "_list", params = { bsw@1045: issue = issue, bsw@1045: initiative = initiative, bsw@1045: for_event = mode == "timeline" and not (event.state == issue.state) bsw@1045: bsw@1045: } } bsw@1045: else bsw@1045: local initiatives = issue.initiatives bsw@1045: execute.view{ module = "initiative", view = "_list", params = { bsw@1045: issue = issue, bsw@1045: initiatives = initiatives, bsw@1045: for_event = mode == "timeline" and not (event.state == issue.state) bsw@1045: } } bsw@1045: end bsw@1045: end } bsw@1045: end bsw@1045: bsw@1045: end } bsw@1045: end bsw@1045: bsw@1045: if mode == "timeline" then bsw@1045: if for_sidebar then bsw@1045: ui.container { attr = { class = row_class }, content = function () bsw@1045: ui.link{ bsw@1045: attr = { class = "moreLink" }, bsw@1045: text = _"Show full history", bsw@1045: module = "initiative", view = "history", id = for_initiative.id bsw@1045: } bsw@1045: end } bsw@1045: elseif #items > limit then bsw@1045: ui.container { attr = { class = row_class }, content = function () bsw@1145: local params = request.get_param_strings() bsw@1045: ui.link{ bsw@1045: attr = { class = "moreLink" }, bsw@1045: text = _"Show older events", bsw@1045: module = request.get_module(), bsw@1045: view = request.get_view(), bsw@1045: id = for_unit and for_unit.id or for_area and for_area.id or for_issue and for_issue.id or for_member and for_member.id, bsw@1045: params = { bsw@1045: mode = "timeline", bsw@1045: event_max_id = last_event_id, bsw@1145: tab = params["tab"], bsw@1145: phase = params["phase"], bsw@1145: closed = params["closed"] bsw@1045: } bsw@1045: } bsw@1045: end } bsw@1045: elseif #items < 1 then bsw@1045: ui.container { attr = { class = row_class }, content = _"No more events available" } bsw@1045: end bsw@1045: end bsw@1045: bsw@1045: end } bsw@1045: bsw@1045: end bsw@1045: bsw@1045: bsw@1045: local filters = {} bsw@1045: bsw@1045: if not for_initiative and not for_issue and not no_filter then bsw@1152: bsw@1152: filters = execute.chunk{ bsw@1152: module = "issue", chunk = "_filters", params = { bsw@1045: for_events = mode == "timeline" and true or false, bsw@1045: member = app.session.member, bsw@1045: for_member = for_member, bsw@1045: state = for_state, bsw@1045: for_unit = for_unit and true or false, bsw@1045: for_area = for_area and true or false bsw@1045: }} bsw@1045: end bsw@1045: bsw@1045: filters.opened = true bsw@1045: filters.selector = selector bsw@1045: bsw@1045: if mode == "timeline" then bsw@1045: filters.content = doit bsw@1045: else bsw@1045: filters.content = function() bsw@1045: ui.paginate{ bsw@1045: selector = selector, bsw@1045: per_page = 25, bsw@1045: content = doit bsw@1045: } bsw@1045: end bsw@1045: end bsw@1045: bsw@1045: ui.filters(filters) bsw@1145: