diff --git a/app/components/report/-icon.js b/app/components/report/-icon.js new file mode 100644 index 00000000..47986526 --- /dev/null +++ b/app/components/report/-icon.js @@ -0,0 +1,5 @@ +import Component from '@ember/component'; + +export default Component.extend({ + tagName: '', +}); diff --git a/app/components/report/date-selector.js b/app/components/report/date-selector.js new file mode 100644 index 00000000..8995ef63 --- /dev/null +++ b/app/components/report/date-selector.js @@ -0,0 +1,47 @@ +import Component from '@ember/component'; +import InitValueMixin from 'fortnight/mixins/init-value'; +import ActionMixin from 'fortnight/mixins/action-mixin'; +import moment from 'moment'; +import { observer } from '@ember/object'; + +export default Component.extend(ActionMixin, InitValueMixin, { + center: null, + format: 'MMM Do, YYYY', + + disabled: false, + + init() { + this._super(...arguments); + const endDate = this.get('range.end'); + this.initValue('center', endDate ? moment(endDate) : moment()); + this.setDropdownLabel(); + }, + + hasRangeChanged: observer('range.{start,end}', function() { + const { start, end } = this.get('range'); + if (start && end) this.setDropdownLabel(); + }), + + setDropdownLabel() { + const { start, end } = this.get('range'); + const format = this.get('format'); + if (!start || !end) return 'Select Date Range'; + const label = `${moment(start).format(format)} - ${moment(end).format(format)}`; + this.set('dropdownLabel', label); + }, + + actions: { + setRange(dropdown, value) { + if (!this.get('disabled')) { + const range = value.moment; + this.set('range', range); + const { start, end } = range; + if (start && end) { + this.setDropdownLabel(); + dropdown.actions.close(); + this.sendEventAction('onchange', range); + } + } + } + }, +}); diff --git a/app/components/report/model-filter.js b/app/components/report/model-filter.js new file mode 100644 index 00000000..d3ebda28 --- /dev/null +++ b/app/components/report/model-filter.js @@ -0,0 +1,17 @@ +import Component from '@ember/component'; +import { inject } from '@ember/service'; +import { computed } from '@ember/object'; + +export default Component.extend({ + autocomplete: inject(), + classNames: ['form-group', 'col-sm-6', 'col-md-4'], + id: computed('modelName', function() { + return `model-filter-${this.get('modelName')}`; + }), + label: null, + actions: { + search(phrase) { + return this.get('autocomplete').query(this.get('modelName'), phrase); + } + } +}); diff --git a/app/controllers/manage/reports.js b/app/controllers/manage/reports.js new file mode 100644 index 00000000..67c216d8 --- /dev/null +++ b/app/controllers/manage/reports.js @@ -0,0 +1,76 @@ +import Controller from '@ember/controller'; +import moment from 'moment'; +import { inject } from '@ember/service'; +import ActionMixin from 'fortnight/mixins/action-mixin'; +import query from 'fortnight/gql/queries/report/list'; +import { computed } from '@ember/object'; + +export default Controller.extend(ActionMixin, { + apollo: inject(), + start: null, + end: null, + + init() { + this._super(...arguments); + this.set('start', moment().startOf('week')); + this.set('end', moment().endOf('week')); + this.setProperties({ + publishers: [], + topics: [], + placements: [], + advertisers: [], + campaigns: [], + }); + }, + + downloadInput: computed('start', 'end', 'publishers', 'topics', 'placements', 'advertisers', 'campaigns', function() { + return JSON.stringify(this.getInput()); + }), + + getInput() { + return { + start: moment(this.get('start')).valueOf(), + end: moment(this.get('end')).valueOf(), + publisherIds: this.get('publishers').map(v => v.id), + topicIds: this.get('topics').map(v => v.id), + placementIds: this.get('placements').map(v => v.id), + advertiserIds: this.get('advertisers').map(v => v.id), + campaignIds: this.get('campaigns').map(v => v.id), + }; + }, + + async execute() { + this.startAction(); + const variables = { input: this.getInput() }; + + try { + const response = await this.get('apollo').watchQuery({ query, variables, fetchPolicy: 'network-only' }, 'report'); + this.set('model', response); + } catch (e) { + this.get('graphErrors').show(e); + } finally { + this.endAction(); + } + }, + + actions: { + setDates({ start, end }) { + this.setProperties({ start, end }); + }, + reset() { + this.setProperties({ + start: moment().startOf('week'), + end: moment().endOf('week'), + publishers: [], + topics: [], + placements: [], + advertisers: [], + campaigns: [], + }); + this.execute(); + }, + update() { + this.execute(); + }, + } +}); diff --git a/app/gql/queries/report/list.graphql b/app/gql/queries/report/list.graphql new file mode 100644 index 00000000..6fd313b5 --- /dev/null +++ b/app/gql/queries/report/list.graphql @@ -0,0 +1,33 @@ +query ReportingList($input: ReportQueryInput!) { + report(input: $input) { + rows { + publisher { + id + name + } + topic { + id + name + } + placement { + id + name + } + advertiser { + id + name + } + campaign { + id + name + } + creative { + id + title + } + impressions + clicks + ctr + } + } +} diff --git a/app/router.js b/app/router.js index 29e69e11..bf538959 100644 --- a/app/router.js +++ b/app/router.js @@ -9,6 +9,7 @@ const Router = EmberRouter.extend({ Router.map(function() { this.route('manage', { path: '' }, function() { this.route('account'); + this.route('reports'); this.route('advertiser', function() { this.route('create'); diff --git a/app/routes/manage/reports.js b/app/routes/manage/reports.js new file mode 100644 index 00000000..9288abe1 --- /dev/null +++ b/app/routes/manage/reports.js @@ -0,0 +1,25 @@ +import Route from '@ember/routing/route'; +import moment from 'moment'; +import RouteQueryManager from 'ember-apollo-client/mixins/route-query-manager'; +import query from 'fortnight/gql/queries/report/list'; + +export default Route.extend(RouteQueryManager, { + /** + * + * @param {object} params + */ + async model() { + const start = moment().startOf('week').valueOf(); + const end = moment().endOf('week').valueOf(); + + const input = { start, end }; + const variables = { input }; + + try { + const response = await this.get('apollo').watchQuery({ query, variables, fetchPolicy: 'network-only' }, 'report'); + return response; + } catch (e) { + this.get('graphErrors').show(e); + } + }, +}); diff --git a/app/templates/components/report/-icon.hbs b/app/templates/components/report/-icon.hbs new file mode 100644 index 00000000..14f641b2 --- /dev/null +++ b/app/templates/components/report/-icon.hbs @@ -0,0 +1 @@ +{{entypo-icon "bar-graph"}} diff --git a/app/templates/components/report/date-selector.hbs b/app/templates/components/report/date-selector.hbs new file mode 100644 index 00000000..e3b50d80 --- /dev/null +++ b/app/templates/components/report/date-selector.hbs @@ -0,0 +1,30 @@ +{{#basic-dropdown as |dropdown|}} + + + {{#dropdown.content}} +
| Publisher | +Topic | +Placement | +Advertiser | +Campaign | +Creative | +Impressions | +Clicks | +CTR | +
|---|---|---|---|---|---|---|---|---|
| + {{#link-to "manage.publishers.edit" item.publisher.id}} + {{item.publisher.name}} + {{/link-to}} + | ++ {{#link-to "manage.topics.edit" item.topic.id}} + {{item.topic.name}} + {{/link-to}} + | ++ {{#link-to "manage.placements.edit" item.placement.id}} + {{item.placement.name}} + {{/link-to}} + | ++ {{#link-to "manage.advertisers.edit" item.advertiser.id}} + {{item.advertiser.name}} + {{/link-to}} + | ++ {{#link-to "manage.campaigns.edit" item.campaign.id}} + {{item.campaign.name}} + {{/link-to}} + | ++ {{#link-to + "manage.campaigns.edit.creatives.edit.index" + item.campaign.id + item.creative.id + }} + {{item.creative.title}} + {{/link-to}} + | +{{number-format item.impressions "0a"}} | +{{number-format item.clicks "0a"}} | +{{number-format item.ctr "0.00%"}} | +
| No results |