stitch-ui
Version:
512 lines (471 loc) • 13.8 kB
JavaScript
import { List, Record, OrderedMap } from "immutable";
import JSONState from "../../models/JSONState";
import { TYPE_ARRAY, TYPE_OBJECT, TYPE_UNTYPED } from "./constants";
const PERM_KEYS = ["read", "write", "valid"];
const DIRTY = "dirty";
export class Filter extends Record({
when: new JSONState({}),
match: new JSONState({})
}) {
static fromRaw(obj) {
return new Filter({
when: JSONState.fromRaw(obj.when),
match: JSONState.fromRaw(obj.matchExpression)
});
}
get dirty() {
return this.when.dirty || this.match.dirty;
}
parse() {
return this.set("when", this.when.parseInput()).set(
"match",
this.match.parseInput()
);
}
toRaw() {
return { when: this.when.data, matchExpression: this.match.data };
}
}
export class PermissionsState extends Record({
read: new JSONState(),
write: new JSONState(),
valid: new JSONState()
}) {
hasError() {
return !!(this.read.error || this.write.error || this.valid.error);
}
get dirty() {
return this.read.dirty || this.write.dirty || this.valid.dirty;
}
static fromRaw(rawPerm) {
return new PermissionsState({
read: JSONState.fromRaw(rawPerm.read),
write: JSONState.fromRaw(rawPerm.write),
valid: JSONState.fromRaw(rawPerm.valid)
});
}
parse() {
const out = {};
PERM_KEYS.forEach(key => {
if (this[key].input) {
out[key] = this[key].parseInput();
}
});
return new PermissionsState(out);
}
toRaw() {
const out = {};
if (this.read.data !== undefined) {
out.read = this.read.data;
}
if (this.write.data !== undefined) {
out.write = this.write.data;
}
if (this.valid.data !== undefined) {
out.valid = this.valid.data;
}
return out;
}
}
export class FieldRule extends Record({
path: "",
dottedFullPath: "",
permissions: new PermissionsState({}),
dirty: false,
fields: null,
elements: null,
errors: {},
hasError: false,
hasChildError: false,
otherFields: null, // TODO
newFieldPath: null // TODO
}) {
static fromRaw(parentPath, path, rule) {
let dottedFullPath = path;
if (parentPath) {
dottedFullPath = `${parentPath}.${path}`;
}
let childMap = null;
if (Object.prototype.hasOwnProperty.call(rule, "fields")) {
childMap = new OrderedMap(
Object.keys(rule.fields || {}).map(field => {
const fieldRule = rule.fields[field];
const childObj = FieldRule.fromRaw(
dottedFullPath || "",
field,
fieldRule
);
return [field, childObj];
})
);
}
let elements = null;
if (rule.elements) {
elements = PermissionsState.fromRaw({
read: rule.elements.read,
write: rule.elements.write,
valid: rule.elements.valid
});
}
let otherFields = null;
if (rule.otherFields) {
otherFields = PermissionsState.fromRaw({
read: rule.otherFields.read,
write: rule.otherFields.write,
valid: rule.otherFields.valid
});
}
const perm = PermissionsState.fromRaw({
read: rule.read,
write: rule.write,
valid: rule.valid
});
const newFieldRule = new FieldRule({
path,
dottedFullPath,
fields: childMap,
elements,
otherFields,
permissions: perm
});
return newFieldRule;
}
getFieldType() {
if (this.dottedFullPath === null) {
// must always treat the root as an object.
return TYPE_OBJECT;
}
let fieldType = TYPE_UNTYPED;
if (this.elements) {
fieldType = TYPE_ARRAY;
} else if (this.fields) {
fieldType = TYPE_OBJECT;
}
return fieldType;
}
toRaw() {
const raw = this.permissions.toRaw();
if (this.elements) {
raw.elements = this.elements.toRaw();
}
if (this.fields) {
raw.fields = this.fields.map(v => v.toRaw()).toJS();
}
if (this.otherFields) {
raw.otherFields = this.otherFields.toRaw();
}
return raw;
}
parseInputs() {
let updated = this.asMutable();
let hasError = false;
let hasChildError = false;
const permParts = [
{ key: "permissions", child: false },
{ key: "elements", child: true },
{ key: "otherFields", child: true }
];
for (let i = 0; i < permParts.length; i += 1) {
const permPart = permParts[i];
if (this.get(permPart.key)) {
const permState = this.get(permPart.key);
const newPerms = permState.parse();
updated = updated.set(permPart.key, newPerms);
if (permPart.child) {
hasChildError = hasChildError || newPerms.hasError();
} else {
hasError = hasError || newPerms.hasError();
}
}
}
let parsedFields;
if (this.fields) {
parsedFields = this.fields.map(v => {
const out = v.parseInputs();
return out;
});
updated = updated.set("fields", parsedFields);
parsedFields.forEach(v => {
hasChildError = hasChildError || v.hasError || v.hasChildError;
});
}
updated = updated
.set("hasError", hasError)
.set("hasChildError", hasChildError);
return updated.asImmutable();
}
setOtherFieldsEnabled(dottedFullPath, value) {
const obj = value ? PermissionsState.fromRaw({}) : null;
if (dottedFullPath === this.dottedFullPath) {
return this.set("otherFields", obj).set(DIRTY, true);
}
let fieldRule = this.getFieldRuleAtPath(dottedFullPath);
fieldRule = fieldRule.set("otherFields", obj);
return this.setFieldRuleAtPath(dottedFullPath, fieldRule).set(DIRTY, true);
}
changeNewFieldInput(dottedFullPath, value) {
if (dottedFullPath === this.dottedFullPath) {
return this.set("newFieldPath", value);
}
let fieldRule = this.getFieldRuleAtPath(dottedFullPath);
fieldRule = fieldRule.set("newFieldPath", value);
return this.setFieldRuleAtPath(dottedFullPath, fieldRule);
}
setFieldType(dottedFullPath, fieldType) {
let fieldRule = this.getFieldRuleAtPath(dottedFullPath);
if (fieldType === TYPE_ARRAY) {
fieldRule = fieldRule
.set("elements", PermissionsState.fromRaw({}))
.set("fields", null)
.set(DIRTY, true);
} else if (fieldType === TYPE_OBJECT) {
fieldRule = fieldRule
.set("fields", new OrderedMap())
.set("elements", null)
.set(DIRTY, true);
} else if (fieldType === TYPE_UNTYPED) {
fieldRule = fieldRule
.set("elements", null)
.set("fields", null)
.set(DIRTY, true);
}
return this.setFieldRuleAtPath(dottedFullPath, fieldRule);
}
setFieldRuleAtPath(dottedFullPath, fieldRule) {
if (dottedFullPath === this.dottedFullPath) {
return fieldRule;
}
const fieldParts = dottedFullPath
.split(".")
.map(part => ["fields", part])
.reduce((a, b) => a.concat(b));
return this.setIn(fieldParts, fieldRule);
}
getFieldRuleAtPath(dottedFullPath) {
if (dottedFullPath === this.dottedFullPath) {
return this;
}
const fieldParts = dottedFullPath
.split(".")
.map(part => ["fields", part])
.reduce((a, b) => a.concat(b));
return this.getIn(fieldParts);
}
discardPermissionsInput(dottedFullPath, editingElements, editingOther, perm) {
let permKey = "permissions";
if (editingElements) {
permKey = "elements";
} else if (editingOther) {
permKey = "otherFields";
}
if (dottedFullPath === this.dottedFullPath) {
return this.updateIn([permKey, perm], v => v.discardChanges());
}
const fieldPartsBase = dottedFullPath
.split(".")
.map(part => ["fields", part])
.reduce((a, b) => a.concat(b));
return this.updateIn(fieldPartsBase.concat([permKey, perm]), v =>
v.discardChanges()
);
}
updatePermissionsInput(
dottedFullPath,
editingElements,
editingOther,
perm,
value
) {
let permKey = "permissions";
if (editingElements) {
permKey = "elements";
} else if (editingOther) {
permKey = "otherFields";
}
if (dottedFullPath === this.dottedFullPath) {
return this.setIn([permKey, perm, "input"], value)
.setIn([permKey, perm, DIRTY], true)
.set(DIRTY, true);
}
const fieldPartsBase = dottedFullPath
.split(".")
.map(part => ["fields", part])
.reduce((a, b) => a.concat(b));
return this.set(DIRTY, true)
.setIn(fieldPartsBase.concat([permKey, perm, "input"]), value)
.setIn(fieldPartsBase.concat([permKey, perm, DIRTY]), true)
.setIn(fieldPartsBase.concat([DIRTY]), true);
}
removeField(path) {
const fieldParts = path
.split(".")
.map(part => ["fields", part])
.reduce((a, b) => a.concat(b));
return this.set(DIRTY, true).deleteIn(fieldParts);
}
addField(path, field) {
const newFieldRule = new FieldRule({
path: field,
dirty: true,
dottedFullPath: path ? `${path}.${field}` : field,
fields: null,
permissions: PermissionsState.fromRaw({}),
elements: null
});
if (path === this.dottedFullPath) {
return this.set(
"fields",
(this.fields || new OrderedMap()).set(field, newFieldRule)
);
}
const fieldParts = path
.split(".")
.map(part => ["fields", part])
.reduce((a, b) => a.concat(b))
.concat(["fields", field]);
return this.set(DIRTY, true).setIn(fieldParts, newFieldRule);
}
}
export class MongoDBServiceRule extends Record({
_id: "",
namespace: "",
fieldRules: new FieldRule({}),
dirty: false,
filters: List()
}) {
constructor(rawRule) {
super({
_id: rawRule._id,
namespace: rawRule.namespace,
fieldRules: FieldRule.fromRaw(null, null, rawRule),
filters: List(rawRule.filters).map(Filter.fromRaw)
});
}
changeNewFieldInput(dottedFullPath, value) {
return this.update("fieldRules", f =>
f.changeNewFieldInput(dottedFullPath, value)
);
}
setOtherFieldsEnabled(path, enabled) {
return this.update("fieldRules", f =>
f.setOtherFieldsEnabled(path, enabled)
);
}
parseFieldInputs() {
return this.update("fieldRules", f => f.parseInputs());
}
get filtersHasError() {
return this.filters.filter(x => !!x.match.error || !!x.when.error).size > 0;
}
addField(path, field) {
return this.update("fieldRules", f => f.addField(path, field)).set(
DIRTY,
true
);
}
removeField(path) {
return this.update("fieldRules", f => f.removeField(path)).set(DIRTY, true);
}
setFieldType(path, fieldType) {
return this.update("fieldRules", f => f.setFieldType(path, fieldType)).set(
DIRTY,
true
);
}
// Given a path, returns an object of the form:
// { read: <FieldRule>, write: <FieldRule>, valid: <FieldRule> }
// where each FieldRule specified is the one that is shadowing the permission property for that key.
// If a permission is not shadowed, its key will be undefined.
getShadows(path, elements, other) {
const out = {};
const fieldParts = path ? path.split(".") : [];
let current = this.fieldRules;
const keyNames = ["read", "write", "valid"];
while (current && Object.keys(out).length < 3) {
if (current.dottedFullPath === path && !elements && !other) {
// we're looking at the given path, so stop.
break;
}
// eslint-disable-next-line no-loop-func
keyNames.forEach(key => {
if (
!Object.prototype.hasOwnProperty.call(out, key) &&
current.permissions.get(key) &&
(current.permissions.get(key).input || "").length > 0
) {
out[key] = current;
}
});
const nextfield = fieldParts.shift();
if (nextfield === undefined) {
break;
}
current = current.fields ? current.fields.get(nextfield) : null;
}
return out;
}
parseFilters() {
return this.set("filters", this.filters.map(x => x.parse()));
}
addFilter() {
return this.set(
"filters",
this.filters.push(Filter.fromRaw({ matchExpression: {}, when: {} }))
).set(DIRTY, true);
}
removeFilter(index) {
return this.set("filters", this.filters.remove(index)).set(DIRTY, true);
}
discardFilterChanges(index) {
return this.updateIn(["filters", index, "when"], v =>
v.discardChanges()
).updateIn(["filters", index, "match"], v => v.discardChanges());
}
setFilterWhenInput(index, value) {
return this.setIn(["filters", index, "when", "input"], value)
.setIn(["filters", index, "when", DIRTY], true)
.set(DIRTY, true);
}
setFilterMatchInput(index, value) {
return this.setIn(["filters", index, "match", "input"], value)
.setIn(["filters", index, "match", DIRTY], true)
.set(DIRTY, true);
}
discardPermissionsInput(dottedFullPath, editingElements, editingOther, perm) {
return this.update("fieldRules", f =>
f.discardPermissionsInput(
dottedFullPath,
editingElements,
editingOther,
perm
)
);
}
updatePermissionsInput(
dottedFullPath,
editingElements,
editingOther,
perm,
value
) {
return this.update("fieldRules", f =>
f.updatePermissionsInput(
dottedFullPath,
editingElements,
editingOther,
perm,
value
)
);
}
toRawRule() {
let rawRule = {
_id: this._id,
namespace: this.namespace,
filters: this.filters.map(x => x.toRaw()).toArray()
};
const fieldRuleRaw = this.fieldRules.toRaw();
rawRule = Object.assign(rawRule, fieldRuleRaw);
return rawRule;
}
}