Skip to content

Commit 37a3f3d

Browse files
committed
[sdk] drawer addition
1 parent ceccc7e commit 37a3f3d

File tree

4 files changed

+233
-4
lines changed

4 files changed

+233
-4
lines changed

plugins/sdk/api/api.js

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ const validOptions = [
3232
"bom_at",
3333
"bom_rqp",
3434
"bom_ra",
35-
"bom_d"
35+
"bom_d",
36+
"upcl", // user property cache. dart only
37+
"eb", // event blacklist dart only
38+
"upb", // user property blacklist dart only
39+
"sb", // segment blacklist dart only
40+
"esb" // event segment blacklist dart only
3641
];
3742

3843
plugins.register("/permissions/features", function(ob) {
@@ -221,6 +226,41 @@ plugins.register("/permissions/features", function(ob) {
221226
switch (paths[3]) {
222227
case 'update-parameter': validateUpdate(params, FEATURE_NAME, updateParameter);
223228
break;
229+
case 'upload': // POST /i/sdk-config/upload
230+
validateUpdate(params, FEATURE_NAME, function() {
231+
var uploadConfig = params.qstring.config;
232+
if (uploadConfig && typeof uploadConfig === "string") {
233+
try {
234+
uploadConfig = JSON.parse(uploadConfig);
235+
}
236+
catch (ex) {
237+
return common.returnMessage(params, 400, 'Invalid config format');
238+
}
239+
}
240+
if (!uploadConfig || typeof uploadConfig !== "object") {
241+
return common.returnMessage(params, 400, 'Config must be a valid object');
242+
}
243+
var configToSave = uploadConfig.c || uploadConfig;
244+
for (var key in configToSave) {
245+
if (validOptions.indexOf(key) === -1) {
246+
delete configToSave[key];
247+
}
248+
}
249+
common.outDb.collection('sdk_configs').updateOne(
250+
{_id: params.qstring.app_id + ""},
251+
{$set: {config: configToSave}},
252+
{upsert: true},
253+
function(err) {
254+
if (err) {
255+
common.returnMessage(params, 500, 'Error saving config to database');
256+
}
257+
else {
258+
common.returnOutput(params, {result: 'Success'});
259+
}
260+
}
261+
);
262+
});
263+
break;
224264
case 'update-enforcement':
225265
validateUpdate(params, FEATURE_NAME, function() {
226266
if (!params.app_id) {

plugins/sdk/frontend/public/javascripts/countly.views.js

Lines changed: 149 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/*global app, countlyVue, countlySDK, CV, countlyCommon, CountlyHelpers*/
1+
/*global app, countlyVue, countlySDK, CV, countlyCommon, CountlyHelpers, Vue*/
22
(function() {
33
var enable_logs = false;
44
var SC_VER = 2; // check/update sdk/api/api.js for this
@@ -103,6 +103,7 @@
103103

104104
var SDKConfigurationView = countlyVue.views.create({
105105
template: CV.T('/sdk/templates/config.html'),
106+
mixins: [countlyVue.mixins.hasDrawers('sdkSettings')],
106107
created: function() {
107108
var self = this;
108109
Promise.all([
@@ -409,6 +410,79 @@
409410
});
410411
},
411412
methods: {
413+
openSettingsDrawer: function() {
414+
this.openDrawer('sdkSettings', {});
415+
var self = this;
416+
this.$nextTick(function() {
417+
var config = Object.assign({}, self.$store.getters["countlySDK/sdk/all"]);
418+
if (typeof config.bom_rqp !== "undefined") {
419+
config.bom_rqp = config.bom_rqp / 100;
420+
}
421+
for (var key in config) { // remove internal
422+
if (self.configs[key] && self.configs[key].type === "preset") {
423+
delete config[key];
424+
}
425+
if (self.configs[key] && self.configs[key].enforced === false) {
426+
delete config[key];
427+
}
428+
}
429+
var obj = {v: SC_VER, t: Date.now(), c: config};
430+
var attempt = 0;
431+
var applyJson = function() {
432+
if (self.$refs.sdkSettingsDrawer && self.$refs.sdkSettingsDrawer.setRawJson) {
433+
self.$refs.sdkSettingsDrawer.setRawJson(JSON.stringify(obj, null, 2));
434+
}
435+
else if (attempt < 5) { // retry a few times if component not yet ready
436+
attempt += 1; setTimeout(applyJson, 150);
437+
}
438+
};
439+
applyJson();
440+
});
441+
},
442+
onRawSettingsSaved: function(updated) {
443+
var cfg = updated.c || updated;
444+
if (typeof cfg.bom_rqp !== 'undefined') {
445+
cfg.bom_rqp = Math.round(cfg.bom_rqp * 100); // revert to integer percentage
446+
}
447+
// derive enforcement from key presence
448+
var enforcement = {};
449+
for (var k in this.configs) {
450+
if (this.configs[k].type === 'preset') {
451+
continue;
452+
}
453+
enforcement[k] = Object.prototype.hasOwnProperty.call(cfg, k);
454+
}
455+
456+
// save config and enforcement
457+
var self = this;
458+
var saveConfigReq = CV.$.ajax({
459+
type: 'POST',
460+
url: countlyCommon.API_PARTS.data.w + '/sdk-config/upload',
461+
data: { app_id: countlyCommon.ACTIVE_APP_ID, config: JSON.stringify({c: cfg}) },
462+
dataType: 'json'
463+
});
464+
var saveEnforcementReq = CV.$.ajax({
465+
type: 'POST',
466+
url: countlyCommon.API_PARTS.data.w + '/sdk-config/update-enforcement',
467+
data: { app_id: countlyCommon.ACTIVE_APP_ID, enforcement: JSON.stringify(enforcement) },
468+
dataType: 'json'
469+
});
470+
Promise.all([saveConfigReq, saveEnforcementReq]).then(function() {
471+
for (var key in enforcement) { // reflect enforcement changes in UI
472+
if (self.configs[key]) {
473+
self.configs[key].enforced = enforcement[key];
474+
}
475+
}
476+
self.$store.dispatch('countlySDK/initialize').then(function() {
477+
self.$store.dispatch('countlySDK/initializeEnforcement').then(function() {
478+
self.diff = [];
479+
CountlyHelpers.notify({title: 'SDK Config', message: 'Configuration saved', type: 'success'});
480+
});
481+
});
482+
}).catch(function(xhr) {
483+
CountlyHelpers.alert((xhr && xhr.responseJSON && xhr.responseJSON.result) || 'Error saving configuration', 'red');
484+
});
485+
},
412486
onChange: function(key, value) {
413487
log("onChange", key, value);
414488
this.configs[key].value = value;
@@ -758,6 +832,80 @@
758832
}
759833
}
760834
});
835+
Vue.component("cly-sdk-settings-drawer", countlyVue.views.create({
836+
props: { controls: {type: Object} },
837+
data: function() {
838+
return { rawJson: '', jsonError: '', showInfo: true, original: '', diffKeys: [] };
839+
},
840+
computed: {
841+
isSaveDisabled: function() {
842+
return !!this.jsonError || this.rawJson.trim().length === 0 || this.rawJson === this.original;
843+
}
844+
},
845+
methods: {
846+
setRawJson: function(v) {
847+
this.rawJson = v; this.original = v; this.jsonError = ''; this.updateDiff();
848+
},
849+
formatJson: function() {
850+
try {
851+
this.rawJson = JSON.stringify(JSON.parse(this.rawJson), null, 2); this.jsonError = '';
852+
}
853+
catch (e) {
854+
this.jsonError = 'Invalid JSON: ' + e.message;
855+
}
856+
this.updateDiff();
857+
},
858+
onInput: function() {
859+
try {
860+
JSON.parse(this.rawJson); this.jsonError = '';
861+
}
862+
catch (e) {
863+
this.jsonError = e.message;
864+
}
865+
this.updateDiff();
866+
},
867+
updateDiff: function() {
868+
var diff = [];
869+
try {
870+
var current = JSON.parse(this.rawJson);
871+
var original = JSON.parse(this.original);
872+
var curC = current.c || current; var orgC = original.c || original;
873+
var keys = {}; Object.keys(curC).forEach(k=>{
874+
keys[k] = true;
875+
}); Object.keys(orgC).forEach(k=>{
876+
keys[k] = true;
877+
});
878+
Object.keys(keys).forEach(k=>{
879+
if (JSON.stringify(curC[k]) !== JSON.stringify(orgC[k])) {
880+
diff.push(k);
881+
}
882+
});
883+
}
884+
catch (e) {
885+
// Ignore JSON parse errors while user is typing
886+
}
887+
this.diffKeys = diff;
888+
},
889+
handleClose: function() {
890+
this.jsonError = '';
891+
},
892+
onSubmit: function() {
893+
if (this.jsonError) {
894+
return;
895+
}
896+
var parsed;
897+
try {
898+
parsed = JSON.parse(this.rawJson);
899+
}
900+
catch (e) {
901+
this.jsonError = e.message; return;
902+
}
903+
this.$emit('saved', parsed);
904+
this.original = this.rawJson;
905+
}
906+
},
907+
template: CV.T('/sdk/templates/settings_drawer.html')
908+
}));
761909
countlyVue.container.registerTab("/manage/sdk", {
762910
priority: 2,
763911
route: "#/manage/sdk/configurations",

plugins/sdk/frontend/public/templates/config.html

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
</el-button>
1515
</div>
1616
<div class="bu-level-item">
17-
<el-button @click="downloadConfig" type="default" size="small" icon="cly-icon-btn cly-icon-download">
18-
Download Config
17+
<el-button @click="openSettingsDrawer" type="default" size="small" icon="cly-icon-btn fas fa-cog">
18+
Settings
1919
</el-button>
2020
</div>
2121
</template>
@@ -156,4 +156,10 @@ <h3 v-if="group.label" class="bu-mb-4" data-test-id="sdk-control-label">
156156
</div>
157157
<cly-diff-helper :diff="diff" @discard="unpatch" @save="save"></cly-diff-helper>
158158
</cly-main>
159+
<cly-sdk-settings-drawer
160+
ref="sdkSettingsDrawer"
161+
:controls="drawers.sdkSettings"
162+
@saved="onRawSettingsSaved"
163+
@download-config="downloadConfig"
164+
></cly-sdk-settings-drawer>
159165
</div>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<cly-drawer ref="drawer" @submit="onSubmit" @close="handleClose" class="sdk-settings-drawer" v-bind="$props.controls"
2+
name="sdkSettings" title="SDK Behavior Settings" saveButtonLabel="Save" :size="5" :saveDisabled="isSaveDisabled">
3+
<template v-slot:default="drawerScope">
4+
<cly-form-step id="sdk-settings-step" style="margin-left: 32px; margin-right: 32px;">
5+
<div class="cly-vue-drawer-step__section bu-mb-5">
6+
<div class="bu-is-flex bu-is-align-items-center bu-mb-3" style="gap:8px; flex-wrap:wrap; justify-content:flex-end;">
7+
<el-button size="small" type="info" @click="formatJson" icon="fas fa-indent">Format JSON</el-button>
8+
<el-button size="small" @click="$emit('download-config')" icon="cly-icon-btn fas fa-download" :title="'Download configuration'" aria-label="Download configuration"></el-button>
9+
</div>
10+
<div v-if="showInfo" class="bu-mt-2 bu-has-text-grey">
11+
You can change or remove values of your configuration.
12+
</div>
13+
<div class="text-small text-heading bu-mb-1">Your current active configuration:</div>
14+
<el-input
15+
type="textarea"
16+
:class="{'is-error': jsonError}"
17+
v-model="rawJson"
18+
:autosize="{minRows: 18, maxRows: 30}"
19+
placeholder='{"c": { /* add keys here */ }}'
20+
@input="onInput"
21+
></el-input>
22+
<div v-if="!rawJson" class="bu-mt-2 bu-has-text-grey-light text-small">No current config loaded yet. It will appear here momentarily or you can paste JSON manually.</div>
23+
<div v-if="diffKeys.length" class="bu-mt-3">
24+
<div class="text-small text-heading">Changed Keys ({{diffKeys.length}})</div>
25+
<div class="bu-tags" style="gap:4px;flex-wrap:wrap;display:flex;">
26+
<span v-for="k in diffKeys" :key="k" class="bu-tag is-info is-light">{{k}}</span>
27+
</div>
28+
</div>
29+
<div v-if="jsonError" class="validation-error bu-mt-2">
30+
<cly-notification type="error" :text="jsonError"></cly-notification>
31+
</div>
32+
</div>
33+
</cly-form-step>
34+
</template>
35+
</cly-drawer>

0 commit comments

Comments
 (0)