Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: glimmer migration #179

Merged
merged 1 commit into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,17 @@ It displays an administration panel to enable or disable feature flags.

### Within a template

The addon provides a component `{{feature-controls}}` to add in a template of your app.
The addon provides a component `<FeatureControls />` to add in a template of your app.
This component basically displays the table with actions buttons.

```hbs
{{feature-controls}}
<FeatureControls />
```

#### Options

```hbs
{{feature-controls showRefresh=false showReset=false}}
<FeatureControls @showRefresh={{false}} @showReset={{false}} />
```

- `showRefresh`: Show the refresh button, true by default
Expand Down
90 changes: 54 additions & 36 deletions addon/components/feature-controls.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,59 @@
import Component from '@ember/component'
import Component from '@glimmer/component'
import { get, set, action } from '@ember/object'
import { inject as service } from '@ember/service'
import { action, set } from '@ember/object'
import { camelize } from '@ember/string'
import { assign } from '@ember/polyfills'
import config from 'ember-get-config'
import windowUtil from 'ember-feature-controls/utils/window'
import { getOwner } from '@ember/application'

const { featureFlags, featureControls } = config
export default class FeatureControlsComponent extends Component {
@service features
@service featureControlsStorage

export default Component.extend({
tagName: '',
features: service(),
featureControlStorage: service(),
showRefresh: true,
showReset: true,
featureControls,
featureFlags,
get featureFlags() {
return this.args.featureFlags ? this.args.featureFlags : this._featureFlags
}

init() {
this._super(...arguments)
get featureControls() {
return this.args.featureControls
? this.args.featureControls
: this._featureControls
}

get showRefresh() {
return this.args.showRefresh ?? true
}

get showReset() {
return this.args.showReset ?? true
}

constructor() {
super(...arguments)
let { featureFlags, featureControls } =
getOwner(this).resolveRegistration('config:environment')
this._featureFlags = featureFlags
this._featureControls = featureControls
this.refresh()
},
}

_normalizeFlag(key) {
return this.features._normalizeFlag(key)
},
return this.features._normalizeFlag(key) || camelize(key)
}

// Refresh the state of the feature flags list component
refresh: action(function () {
@action
refresh() {
// Take the existing flags from the config and put them in a list of default values
let featureFlags = this.featureFlags
let defaults = {}
for (let key in featureFlags) {
defaults[this._normalizeFlag(key)] = featureFlags[key]
}
Object.keys(featureFlags).forEach(
(key) => (defaults[this._normalizeFlag(key)] = featureFlags[key])
)
// Model is a local copy of the list of flags register for features service, used to compute properties on the full list
let model = (this.features.flags || []).map((key) => {
let meta =
((featureControls && this.featureControls.metadata) || []).find(
((this.featureControls && this.featureControls.metadata) || []).find(
(obj) => {
return this._normalizeFlag(obj.key) === key
}
Expand All @@ -46,11 +63,11 @@ export default Component.extend({
}
let isFlagLS =
this.featureControls.useLocalStorage &&
this.get(`featureControlStorage.featuresLS.${key}`) !== undefined
get(this, `featureControlsStorage.featuresLS.${key}`) !== undefined
let featureFlag = {
key,
isEnabled: isFlagLS
? this.get(`featureControlStorage.featuresLS.${key}`)
? get(this, `featureControlsStorage.featuresLS.${key}`)
: this.features.isEnabled(key),
default: defaults[key] || false,
}
Expand All @@ -61,44 +78,45 @@ export default Component.extend({
'model',
model.filter((item) => item !== undefined)
)
}),
}

reset: action(function () {
@action
reset() {
// Reset the flags from the features service to the default value in the config
let featureFlags = this.featureFlags
Object.keys(featureFlags).forEach((key) => {
this.updateFeature(this._normalizeFlag(key), featureFlags[key])
})
// If we use local storage then we want to clear the stored data
if (this.featureControls.useLocalStorage) {
this.featureControlStorage.featuresLS.reset()
this.featureControlsStorage.featuresLS.reset()
}
}),
}

@action
updateFeature(key, isEnabled) {
if (isEnabled) {
this.features.enable(key)
} else {
this.features.disable(key)
}
// Update the local model accordingly
let model = this.model
let modelFlag = model.find((obj) => {
let modelFlag = this.model.find((obj) => {
return obj.key === key
})
if (modelFlag) {
set(modelFlag, 'isEnabled', isEnabled)
set(this, 'model', model)
if (modelFlag.reload) {
windowUtil.reload()
}
}
},
}

doToggleFeature: action(function (key, checkboxState) {
@action
doToggleFeature(key, checkboxState) {
this.updateFeature(key, !checkboxState)
if (this.featureControls.useLocalStorage) {
this.set(`featureControlStorage.featuresLS.${key}`, !checkboxState)
set(this, `featureControlsStorage.featuresLS.${key}`, !checkboxState)
}
}),
})
}
}
15 changes: 7 additions & 8 deletions addon/instance-initializers/load-feature-controls.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import config from 'ember-get-config'

const { featureControls } = config

export function initialize(appInstance) {
const features = appInstance.lookup('service:features')
const { featureControls } =
appInstance.resolveRegistration('config:environment')
if (featureControls && featureControls.useLocalStorage) {
const controlStorageService = appInstance.lookup(
'service:feature-control-storage'
const storageService = appInstance.lookup(
'service:feature-controls-storage'
)
// result of controlStorageService.get('featuresLS') is an ObjectProxy we need to use "content"
let { content: featureControls } = controlStorageService.get('featuresLS')
// result of storageService.get('featuresLS') is an ObjectProxy we need to use "content"
let { content: featureControls } = storageService.get('featuresLS')
if (featureControls) {
Object.keys(featureControls).forEach((flag) => {
if (features.get('flags').includes(flag)) {
console.log(`i ${flag} => ${featureControls[flag]}`)
featureControls[flag] ? features.enable(flag) : features.disable(flag)
}
})
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default } from 'ember-feature-controls/services/feature-control-storage'
export { default } from 'ember-feature-controls/services/feature-controls-storage'
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
"ember-cli-babel": "^7.26.6",
"ember-cli-htmlbars": "^6.0.0",
"ember-feature-flags": "^6.0.0",
"ember-get-config": "^0.3.0",
"ember-local-storage": "^2.0.1",
"ember-truth-helpers": "^3.0.0"
},
Expand All @@ -53,7 +52,7 @@
"@embroider/test-setup": "^0.47.1",
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"babel-eslint": "^10.1.0",
"babel-eslint": "^8.0.0",
"broccoli-asset-rev": "^3.0.0",
"ember-auto-import": "^2.2.0",
"ember-cli": "~3.28.1",
Expand Down
96 changes: 8 additions & 88 deletions tests/acceptance/change-flag-test.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,11 @@
import { module, test } from 'qunit'
GreatWizard marked this conversation as resolved.
Show resolved Hide resolved
import { visit, click } from '@ember/test-helpers'
import { setupApplicationTest } from 'ember-qunit'
import { _resetStorages } from 'ember-local-storage/helpers/storage'
import { initialize } from 'ember-feature-controls/instance-initializers/load-feature-controls'
import config from 'dummy/config/environment'
import resetStorages from 'ember-local-storage/test-support/reset-storage'
import windowUtil from 'ember-feature-controls/utils/window'

const baseConfig = config.featureControls

const originalWindowReload = windowUtil.reload

// Simulates the instructions done at page reload
const reloadPage = function (appInstance) {
const features = appInstance.lookup('service:features')
Object.keys(config.featureFlags).forEach((flag) => {
config.featureFlags[flag] ? features.enable(flag) : features.disable(flag)
})
initialize(appInstance)
}

module('Acceptance | change flag', function (hooks) {
setupApplicationTest(hooks)

Expand All @@ -28,9 +15,13 @@ module('Acceptance | change flag', function (hooks) {

hooks.afterEach(function () {
windowUtil.reload = originalWindowReload
config.featureControls = baseConfig
window.localStorage.clear()
_resetStorages()
if (window.localStorage) {
window.localStorage.clear()
}
if (window.sessionStorage) {
window.sessionStorage.clear()
}
resetStorages()
})

test('it initializes the app with bear on and bacon off', async function (assert) {
Expand Down Expand Up @@ -96,75 +87,4 @@ module('Acceptance | change flag', function (hooks) {
await visit('/__features')
await click('[data-test-checkbox-flag=showBacon]')
})

test('with localStorage | it persists the changes when loading another URL', async function (assert) {
config.featureControls.useLocalStorage = true
await visit('/__features')
await click('[data-test-checkbox-flag=showBacon]')
reloadPage(this.owner)
await visit('/')
assert.dom('img[alt="bear"]').exists()
assert.dom('img[alt="bacon"]').exists()
await visit('/__features')
await click('[data-test-checkbox-flag=showBear]')
reloadPage(this.owner)
await visit('/')
assert.dom('img[alt="bear"]').doesNotExist()
assert.dom('img[alt="bacon"]').exists()
})

test('with localStorage | it persists the flags when reloading after a refresh', async function (assert) {
config.featureControls.useLocalStorage = true
await visit('/__features')
await click('[data-test-checkbox-flag=showBacon]')
await click('[data-test-button-refresh]')
await visit('/')
reloadPage(this.owner)
await visit('/__features')
assert.dom('[data-test-label-flag=showBacon]').hasText('❗')
})

test('with localStorage | it persists the flags when reloading after a reset', async function (assert) {
config.featureControls.useLocalStorage = true
await visit('/__features')
await click('[data-test-checkbox-flag=showBear]')
await click('[data-test-checkbox-flag=showBacon]')
await click('[data-test-button-reset]')
await visit('/')
reloadPage(this.owner)
await visit('/__features')
assert.dom('[data-test-label-flag=showBear]').hasText('')
assert.dom('[data-test-label-flag=showBacon]').hasText('')
})

test("with localStorage | it won't load unknown flags", async function (assert) {
window.localStorage.setItem('storage:feature-controls', '{"fakeFlag":true}')
config.featureControls.useLocalStorage = true
await visit('/__features')
assert
.dom('[data-test-checkbox-flag=fakeFlag]')
.doesNotExist('fakeFlag is not registered')
})

test('without localStorage | it resets the changes when loading another URL', async function (assert) {
config.featureControls.useLocalStorage = false
await visit('/__features')
await click('[data-test-checkbox-flag=showBear]')
await click('[data-test-checkbox-flag=showBacon]')
reloadPage(this.owner)
await visit('/')
assert.dom('img[alt="bear"]').exists()
assert.dom('img[alt="bacon"]').doesNotExist()
})

test('without localStorage | it resets the flags when reloading after a refresh', async function (assert) {
config.featureControls.useLocalStorage = false
await visit('/__features')
await click('[data-test-checkbox-flag=showBacon]')
await click('[data-test-button-refresh]')
await visit('/')
reloadPage(this.owner)
await visit('/__features')
assert.dom('[data-test-label-flag=showBacon]').hasText('')
})
})
Loading