diff --git a/calefaction/modules/campaigns/database.py b/calefaction/modules/campaigns/database.py index 2a66b75..b17e974 100644 --- a/calefaction/modules/campaigns/database.py +++ b/calefaction/modules/campaigns/database.py @@ -141,12 +141,23 @@ class CampaignDB: res = self._conn.execute(query, (campaign, operation)).fetchall() return tuple(res[0]) - def get_associated_kills(self, campaign, operation, limit=5, offset=0): + def get_associated_kills(self, campaign, operation, sort="new", limit=-1, + offset=0): """Return a list of kills associated with a campaign/operation. - Kills are returned as dictionaries most recent first, up to a limit. - Use -1 for no limit. + Kills are returned as dictionaries, up to a limit. Use -1 for no limit. + The sort should be "new" for most recent first, "old" for most recent + last, or "value" for most valuable first. """ + sortkeys = { + "new": "ok_killid DESC", + "old": "ok_killid ASC", + "value": "kill_value DESC, ok_killid DESC" + } + if sort in sortkeys: + sortkey = sortkeys[sort] + else: + raise ValueError(sort) if not isinstance(limit, int): raise ValueError(limit) if not isinstance(offset, int): @@ -160,8 +171,8 @@ class CampaignDB: FROM oper_kill JOIN kill ON ok_killid = kill_id WHERE ok_campaign = ? AND ok_operation = ? - ORDER BY ok_killid DESC LIMIT {} OFFSET {}""" - qform = query.format(limit, offset) + ORDER BY {} LIMIT {} OFFSET {}""" + qform = query.format(sortkey, limit, offset) res = self._conn.execute(qform, (campaign, operation)).fetchall() return [{ @@ -205,21 +216,34 @@ class CampaignDB: for char_id, types in chars.items() for type_id, (count, value) in types.items()]) - def get_associated_items(self, campaign, operation, limit=5, offset=0): + def get_associated_items(self, campaign, operation, sort="value", limit=-1, + offset=0): """Return a list of items associated with a campaign/operation. - Items are returned as 2-tuples of (item_type, item_count), most - valuable first, up to a limit. Use -1 for no limit. + Items are returned as 2-tuples of (item_type, item_count), up to a + limit. Use -1 for no limit. The sort should be "value" for most + valuable first (individual item value * quantity), "quantity" for + greatest quantity first, "price" for most valuable items first. """ + sortkeys = { + "value": "total_value DESC", + "quantity": "total_count DESC", + "price": "(total_value / total_count) DESC" + } + if sort in sortkeys: + sortkey = sortkeys[sort] + else: + raise ValueError(sort) if not isinstance(limit, int): raise ValueError(limit) if not isinstance(offset, int): raise ValueError(offset) - query = """SELECT oi_type, SUM(oi_count), TOTAL(oi_value) as total_val + query = """SELECT oi_type, SUM(oi_count) AS total_count, + TOTAL(oi_value) as total_value FROM oper_item WHERE oi_campaign = ? AND oi_operation = ? - GROUP BY oi_type ORDER BY total_val DESC LIMIT {} OFFSET {}""" - qform = query.format(limit, offset) + GROUP BY oi_type ORDER BY {} LIMIT {} OFFSET {}""" + qform = query.format(sortkey, limit, offset) res = self._conn.execute(qform, (campaign, operation)).fetchall() return [(type_id, count or 0, value) for type_id, count, value in res] diff --git a/calefaction/modules/campaigns/getters.py b/calefaction/modules/campaigns/getters.py index 5201375..ca0f841 100644 --- a/calefaction/modules/campaigns/getters.py +++ b/calefaction/modules/campaigns/getters.py @@ -55,18 +55,25 @@ def get_overview(cname, opname): "operation=%s", age, cname, opname) return g.campaign_db.get_overview(cname, opname) -def get_summary(cname, opname, limit=5): - """Return a sample fraction of results for the given campaign/operation.""" +def get_summary(cname, opname, sortby=None, limit=5): + """Return some details for the given campaign/operation.""" optype = config["campaigns"][cname]["operations"][opname]["type"] if optype == "killboard": - kills = g.campaign_db.get_associated_kills(cname, opname, limit=limit) - return kills, "killboard_recent" + sorts = ["new", "old", "value"] + renderer = "killboard_recent" + func = g.campaign_db.get_associated_kills elif optype == "collection": - items = g.campaign_db.get_associated_items(cname, opname, limit=limit) - return items, "collection_items" + sorts = ["value", "quantity", "price"] + renderer = "collection_items" + func = g.campaign_db.get_associated_items else: raise RuntimeError("Unknown operation type: %s" % optype) + if sortby not in sorts: + sortby = sorts[0] + data = func(cname, opname, sort=sortby, limit=limit) + return data, renderer + def get_unit(operation, num, primary=True): """Return the correct form of the unit tracked by the given operation.""" if not primary: diff --git a/calefaction/modules/campaigns/routes.py b/calefaction/modules/campaigns/routes.py index 5b98bbe..4fdc203 100644 --- a/calefaction/modules/campaigns/routes.py +++ b/calefaction/modules/campaigns/routes.py @@ -52,10 +52,11 @@ def operation(cname, opname): if opname not in campaign["operations"]: abort(404) operation = campaign["operations"][opname] + sortby = request.args.get("sort") enabled = cname in config["enabled"] and opname in campaign["enabled"] return render_template("campaigns/operation.mako", cname=cname, campaign=campaign, opname=opname, - operation=operation, enabled=enabled) + operation=operation, sortby=sortby, enabled=enabled) @blueprint.rroute("/settings/campaign", methods=["POST"]) def set_campaign(): diff --git a/static/campaigns.css b/static/campaigns.css index a557862..15faeb5 100644 --- a/static/campaigns.css +++ b/static/campaigns.css @@ -218,7 +218,36 @@ h2 .disabled-info { .operation.detail .detail-list li:not(:last-child)::after { margin-left: 0.25em; content: "/"; - color: #AAA; + color: #777; +} + +.change-sort { + display: inline-block; + margin: 0; + padding: 0; +} + +.change-sort li { + display: inline-block; +} + +.change-sort .cur { + font-weight: bold; +} + +.change-sort::before { + content: "["; + color: #777; +} + +.change-sort::after { + content: "]"; + color: #777; +} + +.change-sort li:not(:last-child)::after { + content: " |"; + color: #777; } .last-updated { diff --git a/templates/campaigns/operation.mako b/templates/campaigns/operation.mako index 9bbd143..459ca11 100644 --- a/templates/campaigns/operation.mako +++ b/templates/campaigns/operation.mako @@ -27,7 +27,7 @@ <% mod = g.config.modules.campaigns primary, secondary = mod.get_overview(cname, opname) - summary, renderer = mod.get_summary(cname, opname, limit=-1) + summary, renderer = mod.get_summary(cname, opname, sortby=sortby, limit=-1) klass = "big" if primary < 1000 else "medium" if primary < 1000000 else "small" punit = mod.get_unit(operation, primary) sunit = mod.get_unit(operation, secondary, primary=False) @@ -48,7 +48,7 @@ % if summary:
- ${render_summary(renderer, summary, detail=True)} + ${render_summary(renderer, summary, detail=True, sortby=sortby)}
% endif diff --git a/templates/campaigns/renderers.mako b/templates/campaigns/renderers.mako index 8b5f28a..eca9f13 100644 --- a/templates/campaigns/renderers.mako +++ b/templates/campaigns/renderers.mako @@ -95,9 +95,32 @@ -<%def name="_killboard_recent(summary, detail)"> +<%def name="_build_sort_changer(keys, descriptors, sortby)"> + % if keys: + + % endif + +<%def name="_killboard_recent(summary, detail, sortby)"> % if detail: + <% + descriptors = { + "new": "most recent first", + "old": "most recent last", + "value": "most valuable first" + } + if sortby not in descriptors: + sortby = "new" + %>

Kills:

+ ${_build_sort_changer(["new", "old", "value"], descriptors, sortby)} % else:
Most recent kills:
% endif @@ -113,9 +136,19 @@ -<%def name="_collection_items(summary, detail)"> +<%def name="_collection_items(summary, detail, sortby)"> % if detail: + <% + descriptors = { + "value": "most valuable first", + "quantity": "greatest quantity first", + "price": "most expensive first" + } + if sortby not in descriptors: + sortby = "value" + %>

Items:

+ ${_build_sort_changer(["value", "quantity", "price"], descriptors, sortby)} % else:
Top items:
% endif @@ -128,10 +161,10 @@ -<%def name="render_summary(renderer, summary, detail=False)"><% +<%def name="render_summary(renderer, summary, detail=False, sortby=None)"><% if renderer == "killboard_recent": - return _killboard_recent(summary, detail) + return _killboard_recent(summary, detail, sortby) if renderer == "collection_items": - return _collection_items(summary, detail) + return _collection_items(summary, detail, sortby) raise RuntimeError("Unknown renderer: %s" % renderer) %>