UNPKG

stitch-ui

Version:

512 lines (471 loc) 13.8 kB
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; } }