bsw@11: execute.view{ bsw@11: module = "timeline", bsw@11: view = "_constants" bsw@11: } bsw@11: poelzi@144: local active_name = "" poelzi@145: local areas_ignored = {} bsw@11: local options_box_count = param.get("options_box_count", atom.number) or 1 bsw@11: if options_box_count > 10 then bsw@11: options_box_count = 10 bsw@11: end bsw@10: bsw@10: local function format_dow(dow) bsw@10: local dows = { bsw@10: _"Monday", bsw@10: _"Tuesday", bsw@10: _"Wednesday", bsw@10: _"Thursday", bsw@10: _"Friday", bsw@10: _"Saturday", bsw@10: _"Sunday" bsw@10: } bsw@10: return dows[dow+1] bsw@10: end bsw@11: slot.put_into("title", _"Timeline") bsw@10: bsw@11: slot.select("actions", function() bsw@11: local setting_key = "liquidfeedback_frontend_timeline_current_options" bsw@11: local setting = Setting:by_pk(app.session.member.id, setting_key) bsw@11: local current_options = "" bsw@11: if setting then bsw@11: current_options = setting.value bsw@11: end bsw@11: local setting_maps = app.session.member:get_setting_maps_by_key("timeline_filters") bsw@11: for i, setting_map in ipairs(setting_maps) do bsw@11: local active bsw@11: local options_string = setting_map.value bsw@11: local name = setting_map.subkey bsw@11: if options_string == current_options then bsw@11: active = true poelzi@144: active_name = name bsw@11: end bsw@11: ui.link{ bsw/jbe@19: image = { static = "icons/16/time.png" }, bsw/jbe@19: attr = { class = active and "action_active" or nil }, bsw/jbe@19: text = name, poelzi@145: form_attr = { class = "inline" }, bsw@11: module = 'timeline', bsw@11: action = 'update', bsw@11: params = { bsw@11: options_string = options_string bsw@11: }, bsw@11: } bsw@11: end bsw@11: if #setting_maps > 0 then bsw@11: ui.link{ bsw@11: content = function() bsw@11: ui.image{ static = "icons/16/wrench.png" } bsw@11: slot.put(_"Manage filter") bsw@11: end, bsw@11: module = "timeline", bsw@11: view = "list_filter", bsw@11: } bsw@11: end bsw@11: ui.link{ bsw@11: content = function() bsw@11: ui.image{ static = "icons/16/bullet_disk.png" } bsw@11: slot.put(_"Save current filter") bsw@11: end, bsw@11: module = "timeline", bsw@11: view = "save_filter", poelzi@144: params = { poelzi@144: current_name = active_name poelzi@144: }, bsw@11: attr = { bsw@11: onclick = "el=document.getElementById('timeline_save');el.checked=true;el.form.submit();return(false);" bsw@11: } bsw@11: } bsw@11: end) bsw@10: bsw@11: util.help("timeline.index", _"Timeline") bsw@10: bsw@10: ui.form{ bsw@10: module = "timeline", bsw@11: action = "update", bsw@10: content = function() bsw/jbe@19: ui.container{ bsw@11: bsw@11: content = function() bsw/jbe@19: bsw/jbe@19: ui.tag{ bsw/jbe@19: tag = "input", bsw/jbe@19: attr = { bsw/jbe@19: type = "radio", bsw/jbe@19: id = "timeline_search_last_24h", bsw/jbe@19: name = "search_from", bsw/jbe@19: value = "last_24h", bsw/jbe@19: checked = param.get("date") == "last_24h" and "checked" or nil bsw/jbe@19: }, bsw@11: } bsw/jbe@19: bsw/jbe@19: ui.tag{ bsw/jbe@19: tag = "label", bsw/jbe@19: attr = { bsw/jbe@19: ["for"] = "timeline_search_last_24h" bsw/jbe@19: }, bsw/jbe@19: content = " " .. _"last 24 hours" .. " " bsw/jbe@19: } bsw/jbe@19: bsw/jbe@19: ui.tag{ bsw/jbe@19: tag = "input", bsw/jbe@19: attr = { bsw/jbe@19: type = "radio", bsw/jbe@19: id = "timeline_search_from_date", bsw/jbe@19: name = "search_from", bsw/jbe@19: value = "date", bsw/jbe@19: checked = not (param.get("date") == "last_24h") and "checked" or nil bsw/jbe@19: }, bsw@11: } bsw@11: bsw/jbe@19: slot.put(" ") bsw/jbe@19: local current_date = param.get("date") bsw/jbe@19: if not current_date or #current_date == 0 or current_date == "last_24h" then bsw/jbe@19: current_date = tostring(db:query("select now()::date as date")[1].date) bsw/jbe@19: end bsw/jbe@19: ui.tag{ bsw/jbe@19: tag = "input", bsw/jbe@19: attr = { bsw/jbe@19: type = "text", bsw/jbe@19: id = "timeline_search_date", bsw/jbe@19: style = "width: 10em;", bsw/jbe@19: onchange = "this.form.submit();", bsw/jbe@19: onclick = "document.getElementById('timeline_search_from_date').checked = true;", bsw/jbe@19: name = "date", bsw/jbe@19: value = current_date bsw/jbe@19: }, bsw/jbe@19: content = function() end bsw/jbe@19: } bsw/jbe@19: bsw/jbe@19: ui.script{ static = "gregor.js/gregor.js" } poelzi@146: util.gregor("timeline_search_date", true) bsw/jbe@19: bsw/jbe@19: bsw/jbe@19: ui.link{ bsw/jbe@19: attr = { style = "margin-left: 1em; font-weight: bold;", onclick = "document.getElementById('timeline_search_date').form.submit();return(false);" }, bsw/jbe@19: content = function() bsw/jbe@19: ui.image{ bsw/jbe@19: attr = { style = "margin-right: 0.25em;" }, bsw/jbe@19: static = "icons/16/magnifier.png" bsw/jbe@19: } bsw/jbe@19: slot.put(_"Search") bsw/jbe@19: end, bsw/jbe@19: external = "#", bsw/jbe@19: } bsw/jbe@19: local show_options = param.get("show_options", atom.boolean) bsw/jbe@19: ui.link{ bsw/jbe@19: attr = { style = "margin-left: 1em;", onclick = "el=document.getElementById('timeline_show_options');el.checked=" .. tostring(not show_options) .. ";el.form.submit();return(false);" }, bsw/jbe@19: content = function() bsw/jbe@19: ui.image{ bsw/jbe@19: attr = { style = "margin-right: 0.25em;" }, bsw/jbe@19: static = "icons/16/text_list_bullets.png" bsw/jbe@19: } bsw/jbe@19: slot.put(not show_options and _"Show filter details" or _"Hide filter details") bsw/jbe@19: end, bsw/jbe@19: external = "#", bsw/jbe@19: } bsw@11: bsw/jbe@19: ui.field.boolean{ bsw/jbe@19: attr = { id = "timeline_show_options", style = "display: none;", onchange="this.form.submit();" }, bsw/jbe@19: name = "show_options", bsw/jbe@19: value = param.get("show_options", atom.boolean) bsw/jbe@19: } poelzi@144: ui.hidden_field{ name = "current_name", value = active_name } bsw/jbe@19: ui.field.boolean{ bsw/jbe@19: attr = { id = "timeline_save", style = "display: none;", onchange="this.form.submit();" }, bsw/jbe@19: name = "save", bsw/jbe@19: value = false bsw/jbe@19: } bsw/jbe@19: bsw/jbe@19: end bsw@10: } bsw@11: bsw@11: ui.container{ bsw@11: attr = { bsw@11: id = "timeline_options_boxes", bsw@11: class = "vertical", bsw@11: style = not param.get("show_options", atom.boolean) and "display: none;" or nil bsw@10: }, bsw@11: content = function() bsw@11: bsw@11: local function option_field(event_ident, filter_ident) bsw@11: local param_name bsw@11: if not filter_ident then bsw@11: param_name = "option_" .. event_ident bsw@11: else bsw@11: param_name = "option_" .. event_ident .. "_" .. filter_ident bsw@11: end bsw@11: local value = param.get(param_name, atom.boolean) bsw@11: ui.field.boolean{ bsw@11: attr = { id = param_name }, bsw@11: name = param_name, bsw@11: value = value, bsw@11: } bsw@11: end bsw@11: bsw@11: local function filter_option_fields(event_ident, filter_idents) bsw@11: bsw@11: for i, filter_ident in ipairs(filter_idents) do bsw@11: slot.put("") bsw@11: option_field(event_ident, filter_ident) bsw@11: slot.put("
") bsw@11: ui.tag{ bsw@11: attr = { ["for"] = "option_" .. event_ident .. "_" .. filter_ident }, bsw@11: tag = "label", bsw@11: content = filter_names[filter_ident] bsw@11: } bsw@11: slot.put("
") bsw@11: end bsw@11: bsw@11: end bsw@10: bsw@11: local event_groups = { bsw@11: { bsw@11: title = _"Issue events", bsw@11: event_idents = { bsw@11: "issue_created", bsw@11: "issue_canceled", bsw@11: "issue_accepted", bsw@11: "issue_half_frozen", bsw@11: "issue_finished_without_voting", bsw@11: "issue_voting_started", bsw@11: "issue_finished_after_voting", bsw@11: }, bsw@11: filter_idents = { bsw@11: "membership", bsw@11: "interested" bsw@11: } bsw@11: }, bsw@11: { bsw@11: title = _"Initiative events", bsw@11: event_idents = { bsw@11: "initiative_created", bsw@11: "initiative_revoked", bsw@11: "draft_created", bsw@11: "suggestion_created", bsw@11: }, bsw@11: filter_idents = { bsw@11: "membership", bsw@11: "interested", bsw@11: "supporter", bsw@11: "potential_supporter", bsw@11: "initiator" bsw@11: } bsw@11: } bsw@11: } bsw@11: bsw@11: slot.put("") bsw@11: bsw@11: for i_event_group, event_group in ipairs(event_groups) do bsw@11: slot.put("") bsw@11: slot.put("") bsw@11: slot.put("") bsw@11: local event_idents = event_group.event_idents bsw@11: for i, event_ident in ipairs(event_idents) do bsw@11: slot.put("") bsw@11: filter_option_fields(event_ident, event_group.filter_idents) bsw@11: slot.put("") bsw@11: end bsw@11: end bsw@11: bsw@11: slot.put("
") bsw@11: slot.put(event_group.title) bsw@11: slot.put("") bsw@11: slot.put(_"Show only events which match... (or associtated)") bsw@11: slot.put("
") bsw@11: option_field(event_ident) bsw@11: slot.put("
") bsw@11: ui.tag{ bsw@11: attr = { ["for"] = "option_" .. event_ident }, bsw@11: tag = "label", bsw@11: content = event_names[event_ident] bsw@11: } bsw@11: slot.put("
") bsw@11: poelzi@145: local areas = Area:new_selector():add_where("active='t'"):exec() poelzi@145: for i, area in ipairs(areas) do poelzi@145: if param.get("option_ignore_area_"..tostring(area.id)) then poelzi@145: areas_ignored[#areas_ignored+1] = area.id poelzi@145: end poelzi@145: end poelzi@145: poelzi@145: ui.multiselect{ poelzi@145: style = "checkbox", poelzi@145: selected_ids = areas_ignored, poelzi@145: container_attr = { class = "ignore_area_list" }, poelzi@145: container2_attr = { class = "ignore_area_item" }, poelzi@145: label = _"Ignore Areas", poelzi@145: name = "option_ignore_area[]", poelzi@145: foreign_records = areas, poelzi@145: foreign_id = "id", poelzi@145: foreign_name = "name" poelzi@145: } poelzi@145: bsw@11: end bsw@10: } bsw@10: end bsw@10: } bsw@10: bsw@10: local date = param.get("date") bsw/jbe@19: bsw@11: if not date or #date == 0 then bsw@10: date = "today" bsw@10: end bsw@11: bsw@11: local timeline_selector bsw@11: bsw@11: for event, event_name in pairs(event_names) do bsw@11: bsw@11: if param.get("option_" .. event, atom.boolean) then bsw@11: bsw@11: local tmp = Timeline:new_selector() bsw/jbe@19: if event == "draft_created" then bsw/jbe@19: tmp bsw/jbe@19: :reset_fields() bsw/jbe@19: :add_field("max(timeline.occurrence)", "occurrence") bsw/jbe@19: :add_field("timeline.event", nil, { "grouped" }) bsw/jbe@19: :add_field("timeline.issue_id", nil, { "grouped" }) bsw@41: :add_field("draft.initiative_id", nil, { "grouped" }) bsw/jbe@19: :add_field("max(timeline.draft_id)", "draft_id") bsw/jbe@19: :add_field("timeline.suggestion_id", nil, { "grouped" }) bsw/jbe@19: :add_field("COUNT(*)", "count") bsw/jbe@19: else bsw/jbe@19: tmp bsw/jbe@19: :add_field("1", "count") bsw/jbe@19: end bsw@11: bsw/jbe@19: if date == "last_24h" then bsw/jbe@19: tmp:add_where{ "occurrence > now() - '24 hours'::interval" } bsw/jbe@19: else poelzi@146: local start,stop = string.gmatch(date, "(%d+-%d+-%d+):(%d+-%d+-%d+)")() poelzi@146: if start and stop then poelzi@146: tmp:add_where{ "occurrence::date >= ?::date AND occurrence::date <= ?::date", start, stop } poelzi@146: else poelzi@146: local age = string.gmatch(date, "age:(.+)")() poelzi@146: if age then poelzi@146: tmp:add_where{ "occurrence >= now() - ?::interval", age } poelzi@146: else poelzi@146: local since = string.gmatch(date, "since:%s*(%d+-%d+-%d+)%s*")() poelzi@146: if since then poelzi@146: tmp:add_where{ "occurrence::date >= ?::date", since } poelzi@146: else poelzi@146: tmp:add_where{ "occurrence::date = ?::date", date } poelzi@146: end poelzi@146: end poelzi@146: end bsw/jbe@19: end bsw/jbe@19: tmp bsw/jbe@19: :left_join("draft", nil, "draft.id = timeline.draft_id") bsw/jbe@19: :left_join("suggestion", nil, "suggestion.id = timeline.suggestion_id") bsw/jbe@19: :left_join("initiative", nil, "initiative.id = timeline.initiative_id or initiative.id = draft.initiative_id or initiative.id = suggestion.initiative_id") bsw/jbe@19: :left_join("issue", nil, "issue.id = timeline.issue_id or issue.id = initiative.issue_id") bsw/jbe@19: :left_join("area", nil, "area.id = issue.area_id") bsw@11: bsw/jbe@19: :left_join("interest", "_interest", { "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id} ) bsw/jbe@19: :left_join("initiator", "_initiator", { "_initiator.initiative_id = initiative.id AND _initiator.member_id = ?", app.session.member.id} ) bsw/jbe@19: :left_join("membership", "_membership", { "_membership.area_id = area.id AND _membership.member_id = ?", app.session.member.id} ) bsw/jbe@19: :left_join("supporter", "_supporter", { "_supporter.initiative_id = initiative.id AND _supporter.member_id = ?", app.session.member.id} ) bsw/jbe@19: bsw/jbe@19: local group bsw/jbe@19: if event == "draft_created" then bsw/jbe@19: group = { "grouped" } poelzi@172: tmp:add_where{"EXISTS(SELECT 1 from draft as cdraft WHERE draft.initiative_id = cdraft.initiative_id AND cdraft.id != draft.id ORDER BY cdraft.id DESC)"} bsw/jbe@19: end bsw/jbe@19: bsw/jbe@19: tmp bsw/jbe@19: :add_field("(_interest.member_id NOTNULL)", "is_interested", group) bsw/jbe@19: :add_field("(_initiator.member_id NOTNULL)", "is_initiator", group) bsw/jbe@19: :add_field({"(_supporter.member_id NOTNULL) AND NOT EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_supporter", group) bsw/jbe@19: :add_field({"EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1)", app.session.member.id }, "is_potential_supporter", group) bsw/jbe@19: -- :left_join("member", nil, "member.id = timeline.member_id", group) bsw/jbe@19: poelzi@145: if #areas_ignored > 0 then poelzi@145: tmp:add_where{"area.id NOT IN ($)", areas_ignored} poelzi@145: end bsw@11: bsw@11: tmp:add_where{ "event = ?", event } bsw@11: bsw@11: local filters = {} bsw@11: if param.get("option_" .. event .. "_membership", atom.boolean) then bsw@11: filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR _membership.member_id NOTNULL" bsw@11: end bsw@11: bsw@11: if param.get("option_" .. event .. "_supporter", atom.boolean) then bsw@11: filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR ((_supporter.member_id NOTNULL) AND NOT EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1))" bsw@11: end bsw@11: bsw@11: if param.get("option_" .. event .. "_potential_supporter", atom.boolean) then bsw@11: filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR ((_supporter.member_id NOTNULL) AND EXISTS(SELECT NULL FROM opinion WHERE opinion.initiative_id = initiative.id AND opinion.member_id = ? AND ((opinion.degree = 2 AND NOT fulfilled) OR (opinion.degree = -2 AND fulfilled)) LIMIT 1))" bsw@11: end bsw@10: bsw@11: if param.get("option_" .. event .. "_interested", atom.boolean) then bsw@11: filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR _interest.member_id NOTNULL" bsw@11: end bsw@11: bsw@11: if param.get("option_" .. event .. "_initiator", atom.boolean) then bsw@11: filters[#filters+1] = "(timeline.initiative_id ISNULL AND timeline.issue_id ISNULL AND timeline.draft_id ISNULL AND timeline.suggestion_id ISNULL) OR _initiator.member_id NOTNULL" bsw@11: end bsw@11: bsw@11: if #filters > 0 then bsw@11: local filter_string = "(" .. table.concat(filters, ") OR (") .. ")" bsw/jbe@19: tmp:add_where{ filter_string, app.session.member.id, app.session.member.id } bsw@11: end bsw@11: bsw@11: if not timeline_selector then bsw@11: timeline_selector = tmp bsw@11: else bsw@11: timeline_selector:union_all(tmp) bsw@11: end bsw@11: end bsw@11: end bsw@11: bsw@11: if timeline_selector then bsw@11: bsw@11: local initiatives_per_page = param.get("initiatives_per_page", atom.number) bsw@11: bsw@11: local outer_timeline_selector = db:new_selector() bsw@11: outer_timeline_selector._class = Timeline bsw/jbe@19: outer_timeline_selector bsw/jbe@19: :add_field{ "timeline.*" } bsw/jbe@19: :from({"($)", { timeline_selector }}, "timeline" ) bsw/jbe@19: :add_order_by("occurrence DESC") bsw/jbe@19: bsw@11: slot.put("
") bsw@11: execute.view{ bsw@11: module = "timeline", bsw@11: view = "_list", bsw@11: params = { bsw@11: timeline_selector = outer_timeline_selector, bsw@11: per_page = param.get("per_page", atom.number), bsw@11: event_names = event_names, bsw@11: initiatives_per_page = initiatives_per_page bsw@11: } bsw@10: } bsw@11: bsw@11: else bsw@10: bsw@11: slot.put(_"No events selected to list") bsw@11: bsw@11: end