liquid_feedback_frontend

annotate app/main/timeline/index.lua @ 159:5d797c6706d5

implement quorum display

show the initiative quorum as a small 1px line in bargraph
allow to update your support on the diff page
better linked title in diff page
show absolute quorum numbers in detail pages of issue and initiative
author Daniel Poelzleithner <poelzi@poelzi.org>
date Sat Oct 09 03:42:48 2010 +0200 (2010-10-09)
parents 77260f05fd4b
children 165f4bd02cf3
rev   line source
bsw@11 1 execute.view{
bsw@11 2 module = "timeline",
bsw@11 3 view = "_constants"
bsw@11 4 }
bsw@11 5
poelzi@144 6 local active_name = ""
poelzi@145 7 local areas_ignored = {}
bsw@11 8 local options_box_count = param.get("options_box_count", atom.number) or 1
bsw@11 9 if options_box_count > 10 then
bsw@11 10 options_box_count = 10
bsw@11 11 end
bsw@10 12
bsw@10 13 local function format_dow(dow)
bsw@10 14 local dows = {
bsw@10 15 _"Monday",
bsw@10 16 _"Tuesday",
bsw@10 17 _"Wednesday",
bsw@10 18 _"Thursday",
bsw@10 19 _"Friday",
bsw@10 20 _"Saturday",
bsw@10 21 _"Sunday"
bsw@10 22 }
bsw@10 23 return dows[dow+1]
bsw@10 24 end
bsw@11 25 slot.put_into("title", _"Timeline")
bsw@10 26
bsw@11 27 slot.select("actions", function()
bsw@11 28 local setting_key = "liquidfeedback_frontend_timeline_current_options"
bsw@11 29 local setting = Setting:by_pk(app.session.member.id, setting_key)
bsw@11 30 local current_options = ""
bsw@11 31 if setting then
bsw@11 32 current_options = setting.value
bsw@11 33 end
bsw@11 34 local setting_maps = app.session.member:get_setting_maps_by_key("timeline_filters")
bsw@11 35 for i, setting_map in ipairs(setting_maps) do
bsw@11 36 local active
bsw@11 37 local options_string = setting_map.value
bsw@11 38 local name = setting_map.subkey
bsw@11 39 if options_string == current_options then
bsw@11 40 active = true
poelzi@144 41 active_name = name
bsw@11 42 end
bsw@11 43 ui.link{
bsw/jbe@19 44 image = { static = "icons/16/time.png" },
bsw/jbe@19 45 attr = { class = active and "action_active" or nil },
bsw/jbe@19 46 text = name,
poelzi@145 47 form_attr = { class = "inline" },
bsw@11 48 module = 'timeline',
bsw@11 49 action = 'update',
bsw@11 50 params = {
bsw@11 51 options_string = options_string
bsw@11 52 },
bsw@11 53 }
bsw@11 54 end
bsw@11 55 if #setting_maps > 0 then
bsw@11 56 ui.link{
bsw@11 57 content = function()
bsw@11 58 ui.image{ static = "icons/16/wrench.png" }
bsw@11 59 slot.put(_"Manage filter")
bsw@11 60 end,
bsw@11 61 module = "timeline",
bsw@11 62 view = "list_filter",
bsw@11 63 }
bsw@11 64 end
bsw@11 65 ui.link{
bsw@11 66 content = function()
bsw@11 67 ui.image{ static = "icons/16/bullet_disk.png" }
bsw@11 68 slot.put(_"Save current filter")
bsw@11 69 end,
bsw@11 70 module = "timeline",
bsw@11 71 view = "save_filter",
poelzi@144 72 params = {
poelzi@144 73 current_name = active_name
poelzi@144 74 },
bsw@11 75 attr = {
bsw@11 76 onclick = "el=document.getElementById('timeline_save');el.checked=true;el.form.submit();return(false);"
bsw@11 77 }
bsw@11 78 }
bsw@11 79 end)
bsw@10 80
bsw@11 81 util.help("timeline.index", _"Timeline")
bsw@10 82
bsw@10 83 ui.form{
bsw@10 84 module = "timeline",
bsw@11 85 action = "update",
bsw@10 86 content = function()
bsw/jbe@19 87 ui.container{
bsw@11 88
bsw@11 89 content = function()
bsw/jbe@19 90
bsw/jbe@19 91 ui.tag{
bsw/jbe@19 92 tag = "input",
bsw/jbe@19 93 attr = {
bsw/jbe@19 94 type = "radio",
bsw/jbe@19 95 id = "timeline_search_last_24h",
bsw/jbe@19 96 name = "search_from",
bsw/jbe@19 97 value = "last_24h",
bsw/jbe@19 98 checked = param.get("date") == "last_24h" and "checked" or nil
bsw/jbe@19 99 },
bsw@11 100 }
bsw/jbe@19 101
bsw/jbe@19 102 ui.tag{
bsw/jbe@19 103 tag = "label",
bsw/jbe@19 104 attr = {
bsw/jbe@19 105 ["for"] = "timeline_search_last_24h"
bsw/jbe@19 106 },
bsw/jbe@19 107 content = " " .. _"last 24 hours" .. " "
bsw/jbe@19 108 }
bsw/jbe@19 109
bsw/jbe@19 110 ui.tag{
bsw/jbe@19 111 tag = "input",
bsw/jbe@19 112 attr = {
bsw/jbe@19 113 type = "radio",
bsw/jbe@19 114 id = "timeline_search_from_date",
bsw/jbe@19 115 name = "search_from",
bsw/jbe@19 116 value = "date",
bsw/jbe@19 117 checked = not (param.get("date") == "last_24h") and "checked" or nil
bsw/jbe@19 118 },
bsw@11 119 }
bsw@11 120
bsw/jbe@19 121 slot.put(" ")
bsw/jbe@19 122 local current_date = param.get("date")
bsw/jbe@19 123 if not current_date or #current_date == 0 or current_date == "last_24h" then
bsw/jbe@19 124 current_date = tostring(db:query("select now()::date as date")[1].date)
bsw/jbe@19 125 end
bsw/jbe@19 126 ui.tag{
bsw/jbe@19 127 tag = "input",
bsw/jbe@19 128 attr = {
bsw/jbe@19 129 type = "text",
bsw/jbe@19 130 id = "timeline_search_date",
bsw/jbe@19 131 style = "width: 10em;",
bsw/jbe@19 132 onchange = "this.form.submit();",
bsw/jbe@19 133 onclick = "document.getElementById('timeline_search_from_date').checked = true;",
bsw/jbe@19 134 name = "date",
bsw/jbe@19 135 value = current_date
bsw/jbe@19 136 },
bsw/jbe@19 137 content = function() end
bsw/jbe@19 138 }
bsw/jbe@19 139
bsw/jbe@19 140 ui.script{ static = "gregor.js/gregor.js" }
poelzi@146 141 util.gregor("timeline_search_date", true)
bsw/jbe@19 142
bsw/jbe@19 143
bsw/jbe@19 144 ui.link{
bsw/jbe@19 145 attr = { style = "margin-left: 1em; font-weight: bold;", onclick = "document.getElementById('timeline_search_date').form.submit();return(false);" },
bsw/jbe@19 146 content = function()
bsw/jbe@19 147 ui.image{
bsw/jbe@19 148 attr = { style = "margin-right: 0.25em;" },
bsw/jbe@19 149 static = "icons/16/magnifier.png"
bsw/jbe@19 150 }
bsw/jbe@19 151 slot.put(_"Search")
bsw/jbe@19 152 end,
bsw/jbe@19 153 external = "#",
bsw/jbe@19 154 }
bsw/jbe@19 155 local show_options = param.get("show_options", atom.boolean)
bsw/jbe@19 156 ui.link{
bsw/jbe@19 157 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 158 content = function()
bsw/jbe@19 159 ui.image{
bsw/jbe@19 160 attr = { style = "margin-right: 0.25em;" },
bsw/jbe@19 161 static = "icons/16/text_list_bullets.png"
bsw/jbe@19 162 }
bsw/jbe@19 163 slot.put(not show_options and _"Show filter details" or _"Hide filter details")
bsw/jbe@19 164 end,
bsw/jbe@19 165 external = "#",
bsw/jbe@19 166 }
bsw@11 167
bsw/jbe@19 168 ui.field.boolean{
bsw/jbe@19 169 attr = { id = "timeline_show_options", style = "display: none;", onchange="this.form.submit();" },
bsw/jbe@19 170 name = "show_options",
bsw/jbe@19 171 value = param.get("show_options", atom.boolean)
bsw/jbe@19 172 }
poelzi@144 173 ui.hidden_field{ name = "current_name", value = active_name }
bsw/jbe@19 174 ui.field.boolean{
bsw/jbe@19 175 attr = { id = "timeline_save", style = "display: none;", onchange="this.form.submit();" },
bsw/jbe@19 176 name = "save",
bsw/jbe@19 177 value = false
bsw/jbe@19 178 }
bsw/jbe@19 179
bsw/jbe@19 180 end
bsw@10 181 }
bsw@11 182
bsw@11 183 ui.container{
bsw@11 184 attr = {
bsw@11 185 id = "timeline_options_boxes",
bsw@11 186 class = "vertical",
bsw@11 187 style = not param.get("show_options", atom.boolean) and "display: none;" or nil
bsw@10 188 },
bsw@11 189 content = function()
bsw@11 190
bsw@11 191 local function option_field(event_ident, filter_ident)
bsw@11 192 local param_name
bsw@11 193 if not filter_ident then
bsw@11 194 param_name = "option_" .. event_ident
bsw@11 195 else
bsw@11 196 param_name = "option_" .. event_ident .. "_" .. filter_ident
bsw@11 197 end
bsw@11 198 local value = param.get(param_name, atom.boolean)
bsw@11 199 ui.field.boolean{
bsw@11 200 attr = { id = param_name },
bsw@11 201 name = param_name,
bsw@11 202 value = value,
bsw@11 203 }
bsw@11 204 end
bsw@11 205
bsw@11 206 local function filter_option_fields(event_ident, filter_idents)
bsw@11 207
bsw@11 208 for i, filter_ident in ipairs(filter_idents) do
bsw@11 209 slot.put("<td>")
bsw@11 210 option_field(event_ident, filter_ident)
bsw@11 211 slot.put("</td><td><div class='ui_field_label label_right'>")
bsw@11 212 ui.tag{
bsw@11 213 attr = { ["for"] = "option_" .. event_ident .. "_" .. filter_ident },
bsw@11 214 tag = "label",
bsw@11 215 content = filter_names[filter_ident]
bsw@11 216 }
bsw@11 217 slot.put("</div></td>")
bsw@11 218 end
bsw@11 219
bsw@11 220 end
bsw@10 221
bsw@11 222 local event_groups = {
bsw@11 223 {
bsw@11 224 title = _"Issue events",
bsw@11 225 event_idents = {
bsw@11 226 "issue_created",
bsw@11 227 "issue_canceled",
bsw@11 228 "issue_accepted",
bsw@11 229 "issue_half_frozen",
bsw@11 230 "issue_finished_without_voting",
bsw@11 231 "issue_voting_started",
bsw@11 232 "issue_finished_after_voting",
bsw@11 233 },
bsw@11 234 filter_idents = {
bsw@11 235 "membership",
bsw@11 236 "interested"
bsw@11 237 }
bsw@11 238 },
bsw@11 239 {
bsw@11 240 title = _"Initiative events",
bsw@11 241 event_idents = {
bsw@11 242 "initiative_created",
bsw@11 243 "initiative_revoked",
bsw@11 244 "draft_created",
bsw@11 245 "suggestion_created",
bsw@11 246 },
bsw@11 247 filter_idents = {
bsw@11 248 "membership",
bsw@11 249 "interested",
bsw@11 250 "supporter",
bsw@11 251 "potential_supporter",
bsw@11 252 "initiator"
bsw@11 253 }
bsw@11 254 }
bsw@11 255 }
bsw@11 256
bsw@11 257 slot.put("<table>")
bsw@11 258
bsw@11 259 for i_event_group, event_group in ipairs(event_groups) do
bsw@11 260 slot.put("<tr>")
bsw@11 261 slot.put("<th colspan='2'>")
bsw@11 262 slot.put(event_group.title)
bsw@11 263 slot.put("</th><th colspan='10'>")
bsw@11 264 slot.put(_"Show only events which match... (or associtated)")
bsw@11 265 slot.put("</th>")
bsw@11 266 slot.put("</tr>")
bsw@11 267 local event_idents = event_group.event_idents
bsw@11 268 for i, event_ident in ipairs(event_idents) do
bsw@11 269 slot.put("<tr><td>")
bsw@11 270 option_field(event_ident)
bsw@11 271 slot.put("</td><td><div class='ui_field_label label_right'>")
bsw@11 272 ui.tag{
bsw@11 273 attr = { ["for"] = "option_" .. event_ident },
bsw@11 274 tag = "label",
bsw@11 275 content = event_names[event_ident]
bsw@11 276 }
bsw@11 277 slot.put("</div></td>")
bsw@11 278 filter_option_fields(event_ident, event_group.filter_idents)
bsw@11 279 slot.put("</tr>")
bsw@11 280 end
bsw@11 281 end
bsw@11 282
bsw@11 283 slot.put("</table>")
bsw@11 284
poelzi@145 285 local areas = Area:new_selector():add_where("active='t'"):exec()
poelzi@145 286 for i, area in ipairs(areas) do
poelzi@145 287 if param.get("option_ignore_area_"..tostring(area.id)) then
poelzi@145 288 areas_ignored[#areas_ignored+1] = area.id
poelzi@145 289 end
poelzi@145 290 end
poelzi@145 291
poelzi@145 292 ui.multiselect{
poelzi@145 293 style = "checkbox",
poelzi@145 294 selected_ids = areas_ignored,
poelzi@145 295 container_attr = { class = "ignore_area_list" },
poelzi@145 296 container2_attr = { class = "ignore_area_item" },
poelzi@145 297 label = _"Ignore Areas",
poelzi@145 298 name = "option_ignore_area[]",
poelzi@145 299 foreign_records = areas,
poelzi@145 300 foreign_id = "id",
poelzi@145 301 foreign_name = "name"
poelzi@145 302 }
poelzi@145 303
bsw@11 304 end
bsw@10 305 }
bsw@10 306 end
bsw@10 307 }
bsw@10 308
bsw@10 309 local date = param.get("date")
bsw/jbe@19 310
bsw@11 311 if not date or #date == 0 then
bsw@10 312 date = "today"
bsw@10 313 end
bsw@11 314
bsw@11 315 local timeline_selector
bsw@11 316
bsw@11 317 for event, event_name in pairs(event_names) do
bsw@11 318
bsw@11 319 if param.get("option_" .. event, atom.boolean) then
bsw@11 320
bsw@11 321 local tmp = Timeline:new_selector()
bsw/jbe@19 322 if event == "draft_created" then
bsw/jbe@19 323 tmp
bsw/jbe@19 324 :reset_fields()
bsw/jbe@19 325 :add_field("max(timeline.occurrence)", "occurrence")
bsw/jbe@19 326 :add_field("timeline.event", nil, { "grouped" })
bsw/jbe@19 327 :add_field("timeline.issue_id", nil, { "grouped" })
bsw@41 328 :add_field("draft.initiative_id", nil, { "grouped" })
bsw/jbe@19 329 :add_field("max(timeline.draft_id)", "draft_id")
bsw/jbe@19 330 :add_field("timeline.suggestion_id", nil, { "grouped" })
bsw/jbe@19 331 :add_field("COUNT(*)", "count")
bsw/jbe@19 332 else
bsw/jbe@19 333 tmp
bsw/jbe@19 334 :add_field("1", "count")
bsw/jbe@19 335 end
bsw@11 336
bsw/jbe@19 337 if date == "last_24h" then
bsw/jbe@19 338 tmp:add_where{ "occurrence > now() - '24 hours'::interval" }
bsw/jbe@19 339 else
poelzi@146 340 local start,stop = string.gmatch(date, "(%d+-%d+-%d+):(%d+-%d+-%d+)")()
poelzi@146 341 if start and stop then
poelzi@146 342 tmp:add_where{ "occurrence::date >= ?::date AND occurrence::date <= ?::date", start, stop }
poelzi@146 343 else
poelzi@146 344 local age = string.gmatch(date, "age:(.+)")()
poelzi@146 345 if age then
poelzi@146 346 tmp:add_where{ "occurrence >= now() - ?::interval", age }
poelzi@146 347 else
poelzi@146 348 local since = string.gmatch(date, "since:%s*(%d+-%d+-%d+)%s*")()
poelzi@146 349 if since then
poelzi@146 350 tmp:add_where{ "occurrence::date >= ?::date", since }
poelzi@146 351 else
poelzi@146 352 tmp:add_where{ "occurrence::date = ?::date", date }
poelzi@146 353 end
poelzi@146 354 end
poelzi@146 355 end
bsw/jbe@19 356 end
bsw/jbe@19 357 tmp
bsw/jbe@19 358 :left_join("draft", nil, "draft.id = timeline.draft_id")
bsw/jbe@19 359 :left_join("suggestion", nil, "suggestion.id = timeline.suggestion_id")
bsw/jbe@19 360 :left_join("initiative", nil, "initiative.id = timeline.initiative_id or initiative.id = draft.initiative_id or initiative.id = suggestion.initiative_id")
bsw/jbe@19 361 :left_join("issue", nil, "issue.id = timeline.issue_id or issue.id = initiative.issue_id")
bsw/jbe@19 362 :left_join("area", nil, "area.id = issue.area_id")
bsw@11 363
bsw/jbe@19 364 :left_join("interest", "_interest", { "_interest.issue_id = issue.id AND _interest.member_id = ?", app.session.member.id} )
bsw/jbe@19 365 :left_join("initiator", "_initiator", { "_initiator.initiative_id = initiative.id AND _initiator.member_id = ?", app.session.member.id} )
bsw/jbe@19 366 :left_join("membership", "_membership", { "_membership.area_id = area.id AND _membership.member_id = ?", app.session.member.id} )
bsw/jbe@19 367 :left_join("supporter", "_supporter", { "_supporter.initiative_id = initiative.id AND _supporter.member_id = ?", app.session.member.id} )
bsw/jbe@19 368
bsw/jbe@19 369 local group
bsw/jbe@19 370 if event == "draft_created" then
bsw/jbe@19 371 group = { "grouped" }
bsw/jbe@19 372 end
bsw/jbe@19 373
bsw/jbe@19 374 tmp
bsw/jbe@19 375 :add_field("(_interest.member_id NOTNULL)", "is_interested", group)
bsw/jbe@19 376 :add_field("(_initiator.member_id NOTNULL)", "is_initiator", group)
bsw/jbe@19 377 :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 378 :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 379 -- :left_join("member", nil, "member.id = timeline.member_id", group)
bsw/jbe@19 380
poelzi@145 381 if #areas_ignored > 0 then
poelzi@145 382 tmp:add_where{"area.id NOT IN ($)", areas_ignored}
poelzi@145 383 end
bsw@11 384
bsw@11 385 tmp:add_where{ "event = ?", event }
bsw@11 386
bsw@11 387 local filters = {}
bsw@11 388 if param.get("option_" .. event .. "_membership", atom.boolean) then
bsw@11 389 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 390 end
bsw@11 391
bsw@11 392 if param.get("option_" .. event .. "_supporter", atom.boolean) then
bsw@11 393 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 394 end
bsw@11 395
bsw@11 396 if param.get("option_" .. event .. "_potential_supporter", atom.boolean) then
bsw@11 397 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 398 end
bsw@10 399
bsw@11 400 if param.get("option_" .. event .. "_interested", atom.boolean) then
bsw@11 401 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 402 end
bsw@11 403
bsw@11 404 if param.get("option_" .. event .. "_initiator", atom.boolean) then
bsw@11 405 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 406 end
bsw@11 407
bsw@11 408 if #filters > 0 then
bsw@11 409 local filter_string = "(" .. table.concat(filters, ") OR (") .. ")"
bsw/jbe@19 410 tmp:add_where{ filter_string, app.session.member.id, app.session.member.id }
bsw@11 411 end
bsw@11 412
bsw@11 413 if not timeline_selector then
bsw@11 414 timeline_selector = tmp
bsw@11 415 else
bsw@11 416 timeline_selector:union_all(tmp)
bsw@11 417 end
bsw@11 418 end
bsw@11 419 end
bsw@11 420
bsw@11 421 if timeline_selector then
bsw@11 422
bsw@11 423 local initiatives_per_page = param.get("initiatives_per_page", atom.number)
bsw@11 424
bsw@11 425 local outer_timeline_selector = db:new_selector()
bsw@11 426 outer_timeline_selector._class = Timeline
bsw/jbe@19 427 outer_timeline_selector
bsw/jbe@19 428 :add_field{ "timeline.*" }
bsw/jbe@19 429 :from({"($)", { timeline_selector }}, "timeline" )
bsw/jbe@19 430 :add_order_by("occurrence DESC")
bsw/jbe@19 431
bsw@11 432 slot.put("<br />")
bsw@11 433 execute.view{
bsw@11 434 module = "timeline",
bsw@11 435 view = "_list",
bsw@11 436 params = {
bsw@11 437 timeline_selector = outer_timeline_selector,
bsw@11 438 per_page = param.get("per_page", atom.number),
bsw@11 439 event_names = event_names,
bsw@11 440 initiatives_per_page = initiatives_per_page
bsw@11 441 }
bsw@10 442 }
bsw@11 443
bsw@11 444 else
bsw@10 445
bsw@11 446 slot.put(_"No events selected to list")
bsw@11 447
bsw@11 448 end

Impressum / About Us