Background: wallpaperscraft
jonkilroy jkusa
Photos: AIatariel, polarstein, Lego
Web Apps
Web Services
Data Pipelines
2007
Photo: Lego Ideas
Photo: Lego Ideas
Part Num | Brick Type | Brick Color | Brick Count |
---|---|---|---|
3003 | 2x2 | Red | 13 |
3003 | 2x2 | Blue | 21 |
3001 | 4x2 | Red | 34 |
Dimension
Metric
Dimension
Dimension
{ "date":"2019-10-17", "event": brickPurchase ... },
{ "date":"2019-10-17", "event": brickPurchase ... }
{ "date":"2019-10-17", "brickCount": 2 ... }
{ "brickId": 3003, "type": "2x2" ... }
{ "brickId": 3003, "brickCount": 2 ... }
1x4 ▉▉▉▉▉▉▉▉▉▉ 2x2 ▉▉▉▉▉▉ 4x2 ▉▉▉▉ 1x2 ▉▉
Photo: Jon Kilroy
Photo: Lego
2015
Photo: Tom Nagy
Credit: Lego
Credit: Lego Snazzy
Credit: Richard Süselbeck
Credit: Sarah von Innerebner
Photo: Lego
Photo: https://i.ebayimg.com/images/g/~b4AAOSwcBJdJkkp/s-l1600.png
Photo: Lego
Photo: Lego
Lego === 'play well'
Legos + Denmark + EmberFest === 'It All Fits Together'
ember-power-select
ember-data
ember-cp-validations
ember-cli-clipboard
ember-ember-modal-dialog
ember-composable-helpers
ember-cli-flash
ember-concurrency
ember-tooltips
ember-c3
ember-font-awesome
ember-animated
ember-tooltips
vertical-collection
ember-gridstack
Photo: CC0 1.0
Photo: Lego
Photo: Maxx 3001
models
routes
components
services
controllers
templates
Addon
Is this Ember Engines?
Photo: Jon Kilroy
Photo: Lego
Ember App
Photo: Jon Kilroy
Engine A
Engine B
Engine C
Engine D
Team 1
Team 2
Photo: Jon Kilroy
Engine A
Engine B
Engine C
Engine D
Ember App
Photo: Jon Kilroy
Duplo A
Duplo B
Duplo C
Photo: Jon Kilroy
Duplo A
Duplo B
Duplo C
Duplo A
Duplo B
Duplo D
App One
Experiences
App Two
Experiences
Photo: Jon Kilroy
App One
App Two
Customizations
Duplo A
Duplo B
Duplo C
Duplo A
Duplo B
Duplo D
Photo: Jon Kilroy
Photo: Lego
Photo: letsbuilditagain.com
Photo: Loozrboy
Photo: Lego
Made with: mecabricks.com
Photo: The Brick Zombie
Core Addons
Reports
Dashboards
Directory
Alerts
Admin
Navi Reports
Navi Dashboards
Navi Core
Custom App Views & Experiences
Custom Experience A
Custom Experience B
Navi Reports
Navi Dashboards
Navi Core
Custom App Views & Experiences
Custom Experience A
Custom Experience B
Scheduled Reports
CSV Export
Dashboard Filters
PDF Export
Threshold Alerting
Report Visualization
Save Reports
Ad-hoc Reports
Share URL
API Query URL
Clone Report
Dashboards
Asset Organization
Scheduled Dashboards
Bulk Import
Report Templates
Print Views
Cardinality Aware Lookups
Metric Descriptions
Admin Controls
Report Conversion
Photo: CC0 1.0
Photo: Jon Kilroy
Abstraction
Flexibility
size
<NaviReportBuilder
@subComponent1="customSubComponent1"
@subComponent2="customSubComponent2"
@subComponent3="customSubComponent3"
@subComponent4="customSubComponent4"
@subComponent5="customSubComponent5"
@subComponent6="customSubComponent6"
@subComponent7="customSubComponent7"
@subComponent8="customSubComponent8"
@subComponent9="customSubComponent9"
@subComponent10="customSubComponent10"
@subComponent11="customSubComponent11"
@subComponent12="customSubComponent12"
@subComponent13="customSubComponent13"
@subComponent14="customSubComponent14"
@subComponent15="customSubComponent15"
...
onAction1={{action 'action1'}}
onAction2={{action 'action2'}}
onAction3={{action 'action3'}}
onAction4={{action 'action4'}}
onAction5={{action 'action5'}}
onAction6={{action 'action6'}}
onAction7={{action 'action7'}}
onAction8={{action 'action8'}}
onAction9={{action 'action9'}}
onAction10={{action 'action10'}}
...
as | yield1 yield2 yield3 yield4 yield5 yield6 yield7 yield8 |
>
...
</NaviReportBuilder>
Photo: Jon Kilroy
Photo: Michael Brennand-Wood
Build
Config
Route
Component
Services
Made with: mecabricks.com
bricks.io/bricks
bricks
route
bricks
controller
bricks
template
component tree
Made with: mecabricks.com
Photo: Lego
Build
Config
Route
Component
Services
Dependency Injection
Made with: mecabricks.com
Ember applications utilize the dependency injection ("DI") design pattern to declare and instantiate classes of objects and dependencies between them.
Photo: Big Partnership/PA Wire
Photo: LitFilmFest
Photo: The Lego Movie
Made with: mecabricks.com
1x2 Interface
2x2 Interface
Made with: mecabricks.com
Made with: mecabricks.com
Photo: Lego
Photo: Lego
//goodbricks/app/routes/bricks.js
import Route from '@ember/routing/route';
export default class Bricks extends Route {
model() {
return [1,2,3];
}
}
goodbricks/
|-- app
|-- routes
| |-- bricks.js
| |-- sets.js
|-- controllers
| |-- bricks.js
| |-- sets.js
--> goodbricks/routes/bricks
--> goodbricks/routes/sets
--> goodbricks/controllers/bricks
--> goodbricks/controllers/sets
https://github.com/amdjs/amdjs-api/wiki/AMD
Module ID
owner.lookup('route:bricks')
resolve('route:bricks')
hasRegistration('route:bricks')
goodbricks/routes/bricks
goodbricks
|-- app
|-- components
| |-- brick-card.js
| |-- brick-details.js
|-- helpers
|-- brick-name.js
ember-cli-clipboard
|-- app
|-- components
| |-- copy-button.js
goodbricks
|-- app
|-- components
| |-- brick-card.js --> goodbricks/components/brick-card
| |-- brick-details.js
| |-- copy-button.js
|-- helpers
|-- format-brick-name.js
--> goodbricks/components/brick-card
--> goodbricks/components/brick-details
--> goodbricks/components/copy-button
--> goodbricks/helpers/format-brick-name
Module ID
owner.lookup('component:copy-button')
goodbricks/components/copy-button
Photo: twoclevermoms.com
ember-power-select
ember-data
ember-cp-validations
ember-cli-clipboard
ember-ember-modal-dialog
ember-composable-helpers
ember-cli-flash
ember-concurrency
ember-tooltips
ember-c3
ember-font-awesome
ember-animated
ember-tooltips
vertical-collection
ember-gridstack
Photo: Lego
Photo: CC0 1.0
Part Num | Brick Type | Brick Color | Brick Count |
---|---|---|---|
3003 | 2x2 | Red | 13 |
3003 | 2x2 | Blue | 21 |
3001 | 4x2 | Red | 34 |
$ ember new goodbricks
$ ember install navi-reports
//goodbricks/app/router.js
import EmberRouter from '@ember/routing/router';
import config from './config/environment';
import { reportRoutes } from 'navi-reports/router';
const Router = EmberRouter.extend({
location: config.locationType,
rootURL: config.rootURL
});
Router.map(function() {
this.route('my-sets');
reportRoutes(this, {/* options */});
});
export default Router;
goodbricks
|-- app
|-- routes
|-- bricks.js
|-- sets.js
navi-reports
|-- app
|-- routes
| |-- navi-reports
|-- new.js
|-- report
|-- clone.js
|-- save-as.js
...
goodbricks
|-- app
|-- routes
|-- bricks.js
|-- sets.js
|-- navi-reports
|-- new.js
|-- report
|-- clone.js
|-- save-as.js
...
Photo: fllcasts
'use strict';
module.exports = function(environment) {
let ENV = {
modulePrefix: 'goodbricks',
environment,
rootURL: '/',
locationType: 'auto',
EmberENV: {
FEATURES: {
// Here you can enable experimental features on an ember canary build
// e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true
},
EXTEND_PROTOTYPES: {
// Prevent Ember Data from overriding Date.parse.
Date: false
}
},
APP: {
// Here you can pass flags/options to your application instance
// when it is created
},
};
...
return ENV;
};
//goodbricks/config/environment.js
getOwner(this).resolveRegistration('config:environment');
import config from 'ember-get-config'; //via addon
import config from 'goodbricks/config/environment';
module.exports = function(/* environment, appConfig */) {
return {
navi: {
dataSources: [/* {
name: 'example',
uri: 'https://data.naviapp.io/v1',
type: 'fili',
timeout: 60000
}*/],
predefinedIntervalRanges: {
day: ['P1D', 'P7D', 'P14D'],
month: ['current/next', 'P1M', 'P3M'],
year: ['current/next', 'P1Y', 'P2Y']
},
FEATURES: {
enableScheduling: false,
enablePDFExport: false,
enabledNotifyIfData: false
}
}
};
};
Core Settings
Refinements
Feature Flags
//navi-reports/config/environment.js
module.exports = function(/* environment, appConfig */) {
return {
...
navi: {
dataEpoch: '2019-01-01',
dataSources: [{
name: 'brickData',
uri: 'https://data.goodbricks.io/v1',
type: 'fili'
}],
FEATURES: {
enableScheduleReports: true
...
}
}
};
};
//goodbricks/config/environment.js
import config from 'ember-get-config';
const dataSources = config.navi.dataSources;
export default class NaviDataAdapter {
...
_buildURLPath(request, options) {
const { uri } = dataSources.find(d => d.name = options.name);
...
return `${uri}/${namespace}/${table}/${timeGrain}/${dimensions}`;
}
...
}
{{#if (feature-flag "enableScheduleReports")}}
<NaviExportReport>
</NaviIcon name="clock-o"> Schedule
</NaviExportReport>
{{/if}}
Photo: Cyril Byrne
Photo: Lego
Photo: Rebecca Alvy
What tables, metrics, dimensions do you have?
{
tables: [{
name: "bricks",
metrics: [...]
}],
metrics: [... ]
dimension: [...]
}
Photo: Lego
Made with: mecabricks.com
2x2
Interface
Made with: mecabricks.com
LEGO 3943v2
Made with: mecabricks.com
LEGO 4591
Made with: mecabricks.com
Photo: La Petite Brique
...
@service naviNofications;
@action
async saveReport(report) {
await report.save();
this.naviNotifications.add({
message: 'Report was successfully saved!',
type: 'success'
});
}
...
goodbricks/services/navi-notifications
Provider Usage
Interface
import Service from '@ember/service';
import { assert } from '@ember/debug';
export default class extends Service {
add(/* options */) {
assert("NaviNotifications must implement `add`");
}
clearMessages() {
assert("NaviNotifications must implement `clear`");
}
}
//navi-core/services/navi-base-notifications.js
Provider Interface
goodbricks
|-- app
|-- services
|-- navi-notifications.js
$ ember g service navi-notifications
goodbricks/services/navi-notifications
ember-cli-flash
ember-cli-notifications
...
import Notification from 'navi-core/services/navi-base-notifications';
export default class FlashMessageService extends Notification {
@service
notificationMessages; //ember-cli-notifications
add(options = {}) {
const clearDuration = TIMEOUTS[options.timeout];
const { message, type } = options;
return this.notificationMessages[type](message, {
autoClear: true,
clearDuration
});
}
clearMessages() {
this.notificationMessages.clearAll();
}
}
//goodbricks/app/services/navi-notifications.js
Concrete Provider
Photo: Lego
<span>
</NaviIcon "download"> Export
</span>
goodbricks/components/navi-icon
Provider Usage
goodbricks
|-- app
|-- components
| |-- navi-icon.js
|-- templates
|-- components
|-- navi-icon.hbs
$ ember g component navi-icon
goodbricks/components/navi-icon
import NaviIcon from 'navi-core/components/navi-icon';
const ICON_MAP = {
download: 'cloud-download'
...
};
export default class extends NaviIcon {
get normalizedName() {
return ICON_MAP[this.name];
}
}
{{!-- goodbricks/app/templates/components/navi-icon.hbs --}}
<i aria-hidden="true" class="d-icons d-{{this.normalizedName}}"></i>
//goodbricks/app/components/navi-icon.js
Concrete Provider
denali icons
font awesome
Photo: 1LittleCraftyCorner
display
configure
select
owner.lookup('route:bricks')
resolve('route:bricks')
hasRegistration('route:bricks')
goodbricks/routes/bricks
Module Registry
/* global require */
...
const TYPE = 'navi-visualization-manifest';
const REGEX = new RegExp(`^${modulePrefix}/${TYPE}/([a-z-]*)$`);
export default NaviVisualizationService extends Service {
...
all() {
const modules = Object.keys(require.entries); //all modules
const owner = getOwner(this);
return modules.filter(module => REGEX.test(module))
.map(vis => REGEX.exec(vis)[1])
.map(name => owner.lookup(`${TYPE}:${name}`));
}
});
$ember g navi-visualization lego-bar-chart
installing navi-visualization
create app/components/navi-visualizations/lego-bar-chart.js
create app/components/navi-visualization-config/lego-bar-chart.js
create app/navi-visualization-manifests/lego-bar-chart.js
create app/models/lego-bar-chart.js
create app/templates/components/navi-visualizations/lego-bar-chart.hbs
create app/templates/components/navi-visualization-config/lego-bar-chart.hbs
installing navi-visualization-test
create tests/integration/components/navi-visualizations/lego-bar-chart-test.js
create tests/integration/components/navi-visualization-config/lego-bar-chart-test.js
Made with: mecabricks.com
myaddon
|-- app
|-- components
|-- brick-card.js
goodbricks
|-- app
|-- components
|-- brick-card.js
Photo: Lego Ideas
goodbricks/components/navi-cell-renderers/dimension
goodbricks
|-- app
|-- components
|-- navi-cell-renderers
|-- dimension.js
$ ember g component navi-cell-renderers/dimension
goodbricks/components/navi-cell-renderers/dimension
{{#if (eq name "partNumber")}}
<div class="navi-cell-renderer--lego-dim__container">
<div>
<img src="{{legoImgBaseUrl}}/{{value}}.png" alt="brick">
</div>
<span>{{value}}</span>
</div>
{{else}}
<span>{{value}}</span>
{{/if}}
import CellComponent from 'navi-core/components/cell-renderers/dimension';
export default class extends CellComponent {
classNames = ['navi-cell-renderer--lego-dim'];
}
// goodbricks/app/components/navi-cell-renderers/dimension.js
{{!-- goodbricks/app/templates/components/cell-renderers/dimension.hbs --}}
Photo: Lego
Photo: dailytelegraph
Photo: Lego
Photo: Lego
Photo: brickmania
Photo: Lego
Photo: Sarah Goldschadt
Photo: Andy Baird
Abstractions
time
Photo: theprisonerandthepenguin.com
Photo: Jon Kilroy
Photo: Jon Kilroy
Photo: CC0 1.0
What about testing Duplos?
What about namespacing?
What about Embroider?
Photo: Jon Kilroy
Photo: Lego
<Navi::Component>
Photo: sugarbeecrafts.com