merge conflicts
This commit is contained in:
parent
60013d3d3b
commit
6ab3003106
|
@ -1,7 +1,8 @@
|
||||||
/* Budibase Component Styles */
|
/* Budibase Component Styles */
|
||||||
.header {
|
.header {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: #999;
|
color: #000333;
|
||||||
|
opacity: 0.4;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
@ -58,7 +59,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
padding: 0 7px 0 3px;
|
padding: 0 7px 0 3px;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
margin: 5px 0;
|
margin: 5px 20px 5px 0px;
|
||||||
border-radius: 0 5px 5px 0;
|
border-radius: 0 5px 5px 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -68,7 +69,7 @@
|
||||||
|
|
||||||
.budibase__nav-item.selected {
|
.budibase__nav-item.selected {
|
||||||
color: var(--button-text);
|
color: var(--button-text);
|
||||||
background: var(--background-button) !important;
|
background: #fafafa !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.budibase__nav-item:hover {
|
.budibase__nav-item:hover {
|
||||||
|
@ -82,7 +83,7 @@
|
||||||
border: 1px solid #DBDBDB;
|
border: 1px solid #DBDBDB;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
letter-spacing: 0.7px;
|
letter-spacing: 0.7px;
|
||||||
color: #163057;
|
color: #000333;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
padding-left: 5px;
|
padding-left: 5px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
export let hidden = false
|
export let hidden = false
|
||||||
export let primary = true
|
export let primary = true
|
||||||
|
export let cancel = false
|
||||||
export let alert = false
|
export let alert = false
|
||||||
export let warning = false
|
export let warning = false
|
||||||
</script>
|
</script>
|
||||||
|
@ -12,6 +13,7 @@
|
||||||
class:hidden
|
class:hidden
|
||||||
class:primary
|
class:primary
|
||||||
class:alert
|
class:alert
|
||||||
|
class:cancel
|
||||||
class:warning
|
class:warning
|
||||||
{disabled}>
|
{disabled}>
|
||||||
<slot />
|
<slot />
|
||||||
|
@ -19,8 +21,8 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.primary {
|
.primary {
|
||||||
color: #0055ff;
|
color: #ffffff;
|
||||||
background: rgb(54, 133, 249, 0.1);
|
background: #0055ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert {
|
.alert {
|
||||||
|
@ -28,17 +30,23 @@
|
||||||
background: rgba(255, 0, 31, 0.1);
|
background: rgba(255, 0, 31, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cancel {
|
||||||
|
color: var(--secondary40);
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
border: none;
|
border: none;
|
||||||
min-width: 120px;
|
padding: 10px 20px;
|
||||||
height: 45px;
|
height: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover {
|
.button:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:disabled {
|
.button:disabled {
|
||||||
|
|
|
@ -8,9 +8,8 @@
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.root {
|
.root {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-auto-flow: column;
|
flex-direction: row;
|
||||||
grid-gap: 5px;
|
justify-content: space-between;
|
||||||
width: 50%;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -41,13 +41,26 @@
|
||||||
<h4 class="budibase__title--4">{title}</h4>
|
<h4 class="budibase__title--4">{title}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-modal-body">
|
<div class="uk-modal-body">
|
||||||
<slot>{body}</slot>
|
<slot class="rows">{body}</slot>
|
||||||
</div>
|
</div>
|
||||||
<div class="uk-modal-footer">
|
<div class="uk-modal-footer">
|
||||||
<ButtonGroup>
|
<ButtonGroup>
|
||||||
|
<ActionButton cancel on:click={cancel}>{cancelText}</ActionButton>
|
||||||
<ActionButton primary on:click={ok}>{okText}</ActionButton>
|
<ActionButton primary on:click={ok}>{okText}</ActionButton>
|
||||||
<ActionButton alert on:click={cancel}>{cancelText}</ActionButton>
|
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.uk-modal-footer {
|
||||||
|
background: var(--lightslate);
|
||||||
|
}
|
||||||
|
|
||||||
|
.uk-modal-dialog {
|
||||||
|
width: 400px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
|
@ -8,8 +8,6 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
background: rgba(249, 249, 249, 1);
|
|
||||||
|
|
||||||
min-width: 1.8rem;
|
min-width: 1.8rem;
|
||||||
min-height: 1.8rem;
|
min-height: 1.8rem;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
@ -20,6 +18,6 @@
|
||||||
|
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: rgba(22, 48, 87, 1);
|
color: var(--secondary100);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -41,11 +41,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
height: 35px;
|
height: 40px;
|
||||||
display: block;
|
display: block;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 400;
|
||||||
color: #163057;
|
color: #000333;
|
||||||
padding: 0 2.6em 0em 1.4em;
|
padding: 0 2.6em 0em 1.4em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -54,8 +54,7 @@
|
||||||
-moz-appearance: none;
|
-moz-appearance: none;
|
||||||
-webkit-appearance: none;
|
-webkit-appearance: none;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background: #fff;
|
background: var(--lightslate);
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-top: 1px solid #ccc;
|
border-top: 1px solid #ccc;
|
||||||
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
@import "./budibase.css";
|
@import "./budibase.css";
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--primary100: #173157FF;
|
--primary100: #0055ff;
|
||||||
--primary75: #454CA0BF;
|
--primary80: rgba(0, 85, 255, 0.8);
|
||||||
--primary50: #454CA080;
|
--primary60: #rgba(0, 85, 255, 0.6);
|
||||||
--primary25: #454CA040;
|
--primary40: #rgba(0, 85, 255, 0.4);
|
||||||
--primary10: #454CA01A;
|
--primary20: #rgba(0, 85, 255, 0.2);
|
||||||
--primary5: #454ca00c;
|
--primary10: #rgba(0, 85, 255, 0.1);
|
||||||
--primarydark: #3F448A;
|
--primary5: #rgba(0, 85, 255, 0.05);
|
||||||
|
--primarydark: #0044cc;
|
||||||
|
|
||||||
--secondary100:#828fa5;
|
--secondary100:#000333;
|
||||||
--secondary75: #162B4DBF;
|
--secondary80: rgba(0, 3, 51, 0.8);
|
||||||
--secondary50: #162B4D80;
|
--secondary60: rgba(0, 3, 51, 0.6);
|
||||||
--secondary25: #162B4D40;
|
--secondary40: rgba(0, 3, 51, 0.4);
|
||||||
--secondary10: #162B4D1A;
|
--secondary20: rgba(0, 3, 51, 0.2);
|
||||||
--secondary5:#fff;
|
--secondary10: rgba(0, 3, 51, 0.1);
|
||||||
--secondarydark: #3F448A;
|
--secondary5: rgba(0, 3, 51, 0.05);
|
||||||
|
--secondarydark: #00021a;
|
||||||
|
|
||||||
--tertiary: #F2F5F7;
|
--tertiary: #F2F5F7;
|
||||||
|
|
||||||
|
@ -35,8 +37,8 @@
|
||||||
|
|
||||||
--white: #FFFFFF;
|
--white: #FFFFFF;
|
||||||
--darkslate: #1a202c;
|
--darkslate: #1a202c;
|
||||||
--slate: #a0aec0;
|
--slate: #d8d8d8;
|
||||||
--lightslate: #f7fafc;
|
--lightslate: #f9f9f9;
|
||||||
|
|
||||||
--borderradius: 2px;
|
--borderradius: 2px;
|
||||||
--borderradiusall: 2px 2px 2px 2px;
|
--borderradiusall: 2px 2px 2px 2px;
|
||||||
|
@ -55,13 +57,13 @@
|
||||||
--quotation: var(--fontnormal) "italics" var(--darkslate) 16pt;
|
--quotation: var(--fontnormal) "italics" var(--darkslate) 16pt;
|
||||||
--smallheavybodytext: var(--fontbold) "regular" var(--secondary100) 14pt;
|
--smallheavybodytext: var(--fontbold) "regular" var(--secondary100) 14pt;
|
||||||
|
|
||||||
--background-button: #e6eeff;
|
--background-button: #f9f9f9;
|
||||||
--button-text: #0055ff;
|
--button-text: #0055ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
font-family: var(--fontnormal);
|
font-family: var(--fontnormal);
|
||||||
color: var(--secondary100);
|
color: var(--secondary80);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height:100%;
|
height:100%;
|
||||||
|
@ -83,7 +85,7 @@ h2 {
|
||||||
h3 {
|
h3 {
|
||||||
font-family: var(--fontbold);
|
font-family: var(--fontbold);
|
||||||
font-size: 24pt;
|
font-size: 24pt;
|
||||||
color: var(--darkslate);
|
color: var(--secondary60);
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
|
|
|
@ -49,7 +49,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: var(--secondary5);
|
background-color: var(--white);
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-group-header {
|
.nav-group-header {
|
||||||
|
|
|
@ -162,38 +162,49 @@
|
||||||
.component-props-container {
|
.component-props-container {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
overflow-y: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style: none;
|
list-style: none;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
li {
|
li {
|
||||||
margin-right: 20px;
|
|
||||||
background: none;
|
background: none;
|
||||||
border-radius: 5px;
|
border-radius: 3px;
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 48px;
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
li button {
|
li button {
|
||||||
width: 100%;
|
width: 48px;
|
||||||
height: 100%;
|
height: 48px;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 3px;
|
||||||
padding: 12px;
|
padding: 7px;
|
||||||
outline: none;
|
outline: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
li:nth-last-child(1) {
|
||||||
|
margin-right: 0px;
|
||||||
|
background: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
color: var(--button-text);
|
color: var(--button-text);
|
||||||
background: var(--background-button) !important;
|
background: #f9f9f9 !important;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-indicator {
|
.button-indicator {
|
||||||
|
|
|
@ -239,12 +239,12 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 1px solid #ebebeb;
|
border: 1px solid #d8d8d8;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
color: #163057;
|
color: #000333;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
@ -256,11 +256,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.component > .name {
|
.component > .name {
|
||||||
color: #163057;
|
color: #000333;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: bold;
|
opacity: 0.8;
|
||||||
opacity: 0.6;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
|
@ -279,12 +278,11 @@
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
color: rgba(22, 48, 87, 0.6);
|
color:var(--secondary80);
|
||||||
}
|
}
|
||||||
|
|
||||||
.preset-menu > span {
|
.preset-menu > span {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: bold;
|
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
height: 35px;
|
height: 35px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-weight: normal;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item button {
|
.item button {
|
||||||
|
|
|
@ -54,14 +54,15 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 2rem 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switcher {
|
.switcher {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 25px;
|
margin-bottom: 20px;
|
||||||
padding: 0 1.5rem;
|
padding: 0 20px 20px;
|
||||||
|
border-bottom: 1px solid #d8d8d8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.switcher > button {
|
.switcher > button {
|
||||||
|
@ -70,21 +71,21 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-size: 14px;
|
||||||
font-size: 0.85rem;
|
font-weight: 400;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: #999;
|
color: var(--secondary60);
|
||||||
background-color: rgba(0, 0, 0, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.switcher > .selected {
|
.switcher > .selected {
|
||||||
color: #333;
|
color: var(--secondary100);
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.panel {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
height: 0px;
|
height: 0px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0 1.5rem 1.5rem 1.5rem;
|
padding: 0 20px 40px 20px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -136,7 +136,8 @@
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #8997ab;
|
color: #000333;
|
||||||
|
opacity: 0.6;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,16 +145,16 @@
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #163057;
|
color: #000333;
|
||||||
opacity: 0.3;
|
opacity: 0.4;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 400;
|
||||||
color: #163057;
|
color: #000333;
|
||||||
opacity: 0.6;
|
opacity: 0.8;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,26 +51,26 @@
|
||||||
<style>
|
<style>
|
||||||
.selected {
|
.selected {
|
||||||
color: var(--button-text);
|
color: var(--button-text);
|
||||||
background: var(--background-button);
|
background: #f9f9f9;
|
||||||
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 3px;
|
||||||
background: rgba(249, 249, 249, 1);
|
|
||||||
|
|
||||||
min-width: 1.6rem;
|
min-width: 1.6rem;
|
||||||
min-height: 1.6rem;
|
min-height: 1.6rem;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: rgba(22, 48, 87, 1);
|
color: #000333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputs {
|
.inputs {
|
||||||
|
|
|
@ -126,3 +126,36 @@
|
||||||
|
|
||||||
</ConfirmDialog>
|
</ConfirmDialog>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
.uk-margin {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uk-form-controls {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uk-form-label {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--secondary80);
|
||||||
|
}
|
||||||
|
|
||||||
|
.uk-input {
|
||||||
|
height: 40px !important;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uk-select {
|
||||||
|
height: 40px !important;
|
||||||
|
font-weight: 500px;
|
||||||
|
color: var(--secondary60);
|
||||||
|
border: 1px solid var(--slate);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@
|
||||||
.root {
|
.root {
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: var(--secondary50);
|
color: #000333;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
position: relative;
|
position: relative;
|
||||||
padding-left: 1.8rem;
|
padding-left: 1.8rem;
|
||||||
|
@ -79,6 +79,6 @@
|
||||||
.icon {
|
.icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 14px;
|
width: 14px;
|
||||||
color: #333;
|
color: #000333;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -34,14 +34,15 @@
|
||||||
grid-template-rows: 1fr;
|
grid-template-rows: 1fr;
|
||||||
grid-template-columns: 70px 1fr;
|
grid-template-columns: 70px 1fr;
|
||||||
grid-gap: 10px;
|
grid-gap: 10px;
|
||||||
|
align-items: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
h5 {
|
h5 {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-weight: 700;
|
font-weight: 400;
|
||||||
color: #163057;
|
color: #000333;
|
||||||
opacity: 0.6;
|
opacity: 0.8;
|
||||||
padding-top: 12px;
|
padding-top: 12px;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,9 +91,7 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: none;
|
outline: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 5px;
|
border-radius: 3px;
|
||||||
background: rgba(249, 249, 249, 1);
|
|
||||||
|
|
||||||
font-size: 1.6rem;
|
font-size: 1.6rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: rgba(22, 48, 87, 1);
|
color: rgba(22, 48, 87, 1);
|
||||||
|
|
|
@ -43,7 +43,9 @@
|
||||||
<div class="pages-list-container">
|
<div class="pages-list-container">
|
||||||
<div class="nav-header">
|
<div class="nav-header">
|
||||||
<span class="navigator-title">Navigator</span>
|
<span class="navigator-title">Navigator</span>
|
||||||
<span class="components-nav-header">Pages</span>
|
<div class="border-line" />
|
||||||
|
|
||||||
|
<span class="components-nav-page">Pages</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="nav-items-container">
|
<div class="nav-items-container">
|
||||||
|
@ -108,17 +110,28 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 275px 1fr 275px;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
background: #fafafa;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (min-width: 1800px) {
|
||||||
.root {
|
.root {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 290px 1fr 350px;
|
grid-template-columns: 300px 1fr 300px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #fafafa;
|
background: #fafafa;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ui-nav {
|
.ui-nav {
|
||||||
grid-column: 1;
|
grid-column: 1;
|
||||||
background-color: var(--secondary5);
|
background-color: var(--white);
|
||||||
height: calc(100vh - 49px);
|
height: calc(100vh - 49px);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -136,29 +149,34 @@
|
||||||
|
|
||||||
.components-pane {
|
.components-pane {
|
||||||
grid-column: 3;
|
grid-column: 3;
|
||||||
background-color: var(--secondary5);
|
background-color: var(--white);
|
||||||
min-height: 0px;
|
min-height: 0px;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.components-nav-header {
|
.components-nav-page {
|
||||||
font-size: 0.75rem;
|
font-size: 12px;
|
||||||
color: #999;
|
color: #000333;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin-top: 1rem;
|
padding-left: 20px;
|
||||||
font-weight: 500;
|
margin-top: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-group-header {
|
.components-nav-header {
|
||||||
font-size: 0.9rem;
|
font-size: 12px;
|
||||||
padding-left: 1rem;
|
color: #000333;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-header {
|
.nav-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
margin-top: 1.5rem;
|
margin-top: 20px;
|
||||||
padding: 0 1.8rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-items-container {
|
.nav-items-container {
|
||||||
|
@ -167,7 +185,7 @@
|
||||||
|
|
||||||
.nav-group-header {
|
.nav-group-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 1.5rem 0 0 1.8rem;
|
padding: 0px 20px 0px 20px;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
@ -200,20 +218,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.navigator-title {
|
.navigator-title {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--secondary100);
|
||||||
|
font-weight: 500;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-weight: 400;
|
padding: 0 20px 20px 20px;
|
||||||
color: #999;
|
line-height: 1rem !important;
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.border-line {
|
.border-line {
|
||||||
border-bottom: 1px solid #ddd;
|
border-bottom: 1px solid #d8d8d8;
|
||||||
margin-top: 1.5rem;
|
|
||||||
width: calc(100% + 1.5rem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.components-list-container {
|
.components-list-container {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0 30px 0 0;
|
padding: 20px 0px 0 0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { setCleanupFunc } from "../transactions/setCleanupFunc"
|
||||||
|
|
||||||
|
export const cloneApp = (app, mergeWith) => {
|
||||||
|
const newApp = { ...app }
|
||||||
|
Object.assign(newApp, mergeWith)
|
||||||
|
setCleanupFunc(newApp)
|
||||||
|
return newApp
|
||||||
|
}
|
|
@ -21,22 +21,29 @@ export const initialiseData = async (
|
||||||
applicationDefinition,
|
applicationDefinition,
|
||||||
accessLevels
|
accessLevels
|
||||||
) => {
|
) => {
|
||||||
await datastore.createFolder(configFolder)
|
if (!await datastore.exists(configFolder))
|
||||||
await datastore.createJson(appDefinitionFile, applicationDefinition)
|
await datastore.createFolder(configFolder)
|
||||||
|
|
||||||
|
if (!await datastore.exists(appDefinitionFile))
|
||||||
|
await datastore.createJson(appDefinitionFile, applicationDefinition)
|
||||||
|
|
||||||
await initialiseRootCollections(datastore, applicationDefinition.hierarchy)
|
await initialiseRootCollections(datastore, applicationDefinition.hierarchy)
|
||||||
await initialiseRootIndexes(datastore, applicationDefinition.hierarchy)
|
await initialiseRootIndexes(datastore, applicationDefinition.hierarchy)
|
||||||
|
|
||||||
await datastore.createFolder(TRANSACTIONS_FOLDER)
|
if (!await datastore.exists(TRANSACTIONS_FOLDER))
|
||||||
|
await datastore.createFolder(TRANSACTIONS_FOLDER)
|
||||||
|
|
||||||
await datastore.createFolder(AUTH_FOLDER)
|
if (!await datastore.exists(AUTH_FOLDER))
|
||||||
|
await datastore.createFolder(AUTH_FOLDER)
|
||||||
|
|
||||||
await datastore.createJson(USERS_LIST_FILE, [])
|
if (!await datastore.exists(USERS_LIST_FILE))
|
||||||
|
await datastore.createJson(USERS_LIST_FILE, [])
|
||||||
|
|
||||||
await datastore.createJson(
|
if (!await datastore.exists(ACCESS_LEVELS_FILE))
|
||||||
ACCESS_LEVELS_FILE,
|
await datastore.createJson(
|
||||||
accessLevels ? accessLevels : { version: 0, levels: [] }
|
ACCESS_LEVELS_FILE,
|
||||||
)
|
accessLevels ? accessLevels : { version: 0, levels: [] }
|
||||||
|
)
|
||||||
|
|
||||||
await initialiseRootSingleRecords(datastore, applicationDefinition.hierarchy)
|
await initialiseRootSingleRecords(datastore, applicationDefinition.hierarchy)
|
||||||
}
|
}
|
||||||
|
@ -64,6 +71,7 @@ const initialiseRootSingleRecords = async (datastore, hierarchy) => {
|
||||||
const singleRecords = $(flathierarchy, [filter(isSingleRecord)])
|
const singleRecords = $(flathierarchy, [filter(isSingleRecord)])
|
||||||
|
|
||||||
for (let record of singleRecords) {
|
for (let record of singleRecords) {
|
||||||
|
if (await datastore.exists(record.nodeKey())) continue
|
||||||
await datastore.createFolder(record.nodeKey())
|
await datastore.createFolder(record.nodeKey())
|
||||||
const result = _getNew(record, "")
|
const result = _getNew(record, "")
|
||||||
await _save(app, result)
|
await _save(app, result)
|
||||||
|
|
|
@ -7,7 +7,7 @@ import getActionsApi from "./actionsApi"
|
||||||
import { setupDatastore, createEventAggregator } from "./appInitialise"
|
import { setupDatastore, createEventAggregator } from "./appInitialise"
|
||||||
import { initialiseActions } from "./actionsApi/initialise"
|
import { initialiseActions } from "./actionsApi/initialise"
|
||||||
import { isSomething, crypto } from "./common"
|
import { isSomething, crypto } from "./common"
|
||||||
import { cleanup } from "./transactions/cleanup"
|
import { setCleanupFunc } from "./transactions/setCleanupFunc"
|
||||||
import { generateFullPermissions } from "./authApi/generateFullPermissions"
|
import { generateFullPermissions } from "./authApi/generateFullPermissions"
|
||||||
import { getApplicationDefinition } from "./templateApi/getApplicationDefinition"
|
import { getApplicationDefinition } from "./templateApi/getApplicationDefinition"
|
||||||
import common from "./common"
|
import common from "./common"
|
||||||
|
@ -40,9 +40,7 @@ export const getAppApis = async (
|
||||||
|
|
||||||
const templateApi = getTemplateApi(app)
|
const templateApi = getTemplateApi(app)
|
||||||
|
|
||||||
app.cleanupTransactions = isSomething(cleanupTransactions)
|
setCleanupFunc(app, cleanupTransactions)
|
||||||
? cleanupTransactions
|
|
||||||
: async () => await cleanup(app)
|
|
||||||
|
|
||||||
app.getEpochTime = isSomething(getEpochTime)
|
app.getEpochTime = isSomething(getEpochTime)
|
||||||
? getEpochTime
|
? getEpochTime
|
||||||
|
|
|
@ -6,8 +6,10 @@ import {
|
||||||
getNode,
|
getNode,
|
||||||
isIndex,
|
isIndex,
|
||||||
isRecord,
|
isRecord,
|
||||||
|
getActualKeyOfParent,
|
||||||
getAllowedRecordNodesForIndex,
|
getAllowedRecordNodesForIndex,
|
||||||
fieldReversesReferenceToIndex,
|
fieldReversesReferenceToIndex,
|
||||||
|
isTopLevelIndex,
|
||||||
} from "../templateApi/hierarchy"
|
} from "../templateApi/hierarchy"
|
||||||
import { joinKey, apiWrapper, events, $ } from "../common"
|
import { joinKey, apiWrapper, events, $ } from "../common"
|
||||||
import {
|
import {
|
||||||
|
@ -16,6 +18,8 @@ import {
|
||||||
} from "../transactions/create"
|
} from "../transactions/create"
|
||||||
import { permission } from "../authApi/permissions"
|
import { permission } from "../authApi/permissions"
|
||||||
import { BadRequestError } from "../common/errors"
|
import { BadRequestError } from "../common/errors"
|
||||||
|
import { initialiseIndex } from "../indexing/initialiseIndex"
|
||||||
|
import { getRecordInfo } from "../recordApi/recordInfo"
|
||||||
|
|
||||||
/** rebuilds an index
|
/** rebuilds an index
|
||||||
* @param {object} app - the application container
|
* @param {object} app - the application container
|
||||||
|
@ -32,7 +36,7 @@ export const buildIndex = app => async indexNodeKey =>
|
||||||
indexNodeKey
|
indexNodeKey
|
||||||
)
|
)
|
||||||
|
|
||||||
const _buildIndex = async (app, indexNodeKey) => {
|
export const _buildIndex = async (app, indexNodeKey) => {
|
||||||
const indexNode = getNode(app.hierarchy, indexNodeKey)
|
const indexNode = getNode(app.hierarchy, indexNodeKey)
|
||||||
|
|
||||||
await createBuildIndexFolder(app.datastore, indexNodeKey)
|
await createBuildIndexFolder(app.datastore, indexNodeKey)
|
||||||
|
@ -89,12 +93,6 @@ const buildReverseReferenceIndex = async (app, indexNode) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
const getAllowedParentCollectionNodes = (hierarchy, indexNode) => $(getAllowedRecordNodesForIndex(hierarchy, indexNode), [
|
|
||||||
map(n => n.parent()),
|
|
||||||
]);
|
|
||||||
*/
|
|
||||||
|
|
||||||
const buildHeirarchalIndex = async (app, indexNode) => {
|
const buildHeirarchalIndex = async (app, indexNode) => {
|
||||||
let recordCount = 0
|
let recordCount = 0
|
||||||
|
|
||||||
|
@ -139,77 +137,8 @@ const buildHeirarchalIndex = async (app, indexNode) => {
|
||||||
return recordCount
|
return recordCount
|
||||||
}
|
}
|
||||||
|
|
||||||
// const chooseChildRecordNodeByKey = (collectionNode, recordId) => find(c => recordId.startsWith(c.nodeId))(collectionNode.children);
|
|
||||||
|
|
||||||
const recordNodeApplies = indexNode => recordNode =>
|
const recordNodeApplies = indexNode => recordNode =>
|
||||||
includes(recordNode.nodeId)(indexNode.allowedRecordNodeIds)
|
includes(recordNode.nodeId)(indexNode.allowedRecordNodeIds)
|
||||||
|
|
||||||
/*
|
|
||||||
const hasApplicableDecendant = (hierarchy, ancestorNode, indexNode) => $(hierarchy, [
|
|
||||||
getFlattenedHierarchy,
|
|
||||||
filter(
|
|
||||||
allTrue(
|
|
||||||
isRecord,
|
|
||||||
isDecendant(ancestorNode),
|
|
||||||
recordNodeApplies(indexNode),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
const applyAllDecendantRecords = async (app, collection_Key_or_NodeKey,
|
|
||||||
indexNode, indexKey, currentIndexedData,
|
|
||||||
currentIndexedDataKey, recordCount = 0) => {
|
|
||||||
const collectionNode = getCollectionNodeByKeyOrNodeKey(
|
|
||||||
app.hierarchy,
|
|
||||||
collection_Key_or_NodeKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
const allIdsIterator = await getAllIdsIterator(app)(collection_Key_or_NodeKey);
|
|
||||||
|
|
||||||
|
|
||||||
const createTransactionsForIds = async (collectionKey, allIds) => {
|
|
||||||
for (const recordId of allIds) {
|
|
||||||
const recordKey = joinKey(collectionKey, recordId);
|
|
||||||
|
|
||||||
const recordNode = chooseChildRecordNodeByKey(
|
|
||||||
collectionNode,
|
|
||||||
recordId,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (recordNodeApplies(indexNode)(recordNode)) {
|
|
||||||
await transactionForBuildIndex(
|
|
||||||
app, indexNode.nodeKey(),
|
|
||||||
recordKey, recordCount,
|
|
||||||
);
|
|
||||||
recordCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasApplicableDecendant(app.hierarchy, recordNode, indexNode)) {
|
|
||||||
for (const childCollectionNode of recordNode.children) {
|
|
||||||
recordCount = await applyAllDecendantRecords(
|
|
||||||
app,
|
|
||||||
joinKey(recordKey, childCollectionNode.collectionName),
|
|
||||||
indexNode, indexKey, currentIndexedData,
|
|
||||||
currentIndexedDataKey, recordCount,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let allIds = await allIdsIterator();
|
|
||||||
while (allIds.done === false) {
|
|
||||||
await createTransactionsForIds(
|
|
||||||
allIds.result.collectionKey,
|
|
||||||
allIds.result.ids,
|
|
||||||
);
|
|
||||||
allIds = await allIdsIterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
return recordCount;
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default buildIndex
|
export default buildIndex
|
||||||
|
|
|
@ -33,7 +33,7 @@ const defaultOptions = {
|
||||||
searchPhrase: null,
|
searchPhrase: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const _listItems = async (app, indexKey, options = defaultOptions) => {
|
export const _listItems = async (app, indexKey, options = defaultOptions) => {
|
||||||
const { searchPhrase, rangeStartParams, rangeEndParams } = $({}, [
|
const { searchPhrase, rangeStartParams, rangeEndParams } = $({}, [
|
||||||
merge(options),
|
merge(options),
|
||||||
merge(defaultOptions),
|
merge(defaultOptions),
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { flatten, orderBy, filter, isUndefined } from "lodash/fp"
|
||||||
import {
|
import {
|
||||||
getFlattenedHierarchy,
|
getFlattenedHierarchy,
|
||||||
getCollectionNodeByKeyOrNodeKey,
|
getCollectionNodeByKeyOrNodeKey,
|
||||||
|
getNodeByKeyOrNodeKey,
|
||||||
isCollectionRecord,
|
isCollectionRecord,
|
||||||
isAncestor,
|
isAncestor,
|
||||||
} from "../templateApi/hierarchy"
|
} from "../templateApi/hierarchy"
|
||||||
|
@ -60,7 +61,7 @@ export const getAllIdsIterator = app => async collection_Key_or_NodeKey => {
|
||||||
const recordNode = getCollectionNodeByKeyOrNodeKey(
|
const recordNode = getCollectionNodeByKeyOrNodeKey(
|
||||||
app.hierarchy,
|
app.hierarchy,
|
||||||
collection_Key_or_NodeKey
|
collection_Key_or_NodeKey
|
||||||
)
|
) || getNodeByKeyOrNodeKey(app.hierarchy, collection_Key_or_NodeKey)
|
||||||
|
|
||||||
const getAllIdsIteratorForCollectionKey = async (
|
const getAllIdsIteratorForCollectionKey = async (
|
||||||
recordNode,
|
recordNode,
|
||||||
|
|
|
@ -9,11 +9,19 @@ import {
|
||||||
export const initialiseIndex = async (datastore, dir, index) => {
|
export const initialiseIndex = async (datastore, dir, index) => {
|
||||||
const indexDir = joinKey(dir, index.name)
|
const indexDir = joinKey(dir, index.name)
|
||||||
|
|
||||||
await datastore.createFolder(indexDir)
|
let newDir = false
|
||||||
|
if (!await datastore.exists(indexDir)) {
|
||||||
|
await datastore.createFolder(indexDir)
|
||||||
|
newDir = true
|
||||||
|
}
|
||||||
|
|
||||||
if (isShardedIndex(index)) {
|
if (isShardedIndex(index)) {
|
||||||
await datastore.createFile(getShardMapKey(indexDir), "[]")
|
const shardFile = getShardMapKey(indexDir)
|
||||||
|
if (newDir || !await datastore.exists(shardFile))
|
||||||
|
await datastore.createFile(shardFile, "[]")
|
||||||
} else {
|
} else {
|
||||||
await createIndexFile(datastore, getUnshardedIndexDataKey(indexDir), index)
|
const indexFile = getUnshardedIndexDataKey(indexDir)
|
||||||
|
if (newDir || !await datastore.exists(indexFile))
|
||||||
|
await createIndexFile(datastore, indexFile, index)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ import { promiseReadableStream } from "./promiseReadableStream"
|
||||||
import { createIndexFile } from "./sharding"
|
import { createIndexFile } from "./sharding"
|
||||||
import { generateSchema } from "./indexSchemaCreator"
|
import { generateSchema } from "./indexSchemaCreator"
|
||||||
import { getIndexReader, CONTINUE_READING_RECORDS } from "./serializer"
|
import { getIndexReader, CONTINUE_READING_RECORDS } from "./serializer"
|
||||||
|
import { getAllowedRecordNodesForIndex, getRecordNodeId } from "../templateApi/hierarchy"
|
||||||
|
import { $ } from "../common"
|
||||||
|
import { filter, includes, find } from "lodash/fp"
|
||||||
|
|
||||||
export const readIndex = async (
|
export const readIndex = async (
|
||||||
hierarchy,
|
hierarchy,
|
||||||
|
@ -11,8 +14,10 @@ export const readIndex = async (
|
||||||
indexedDataKey
|
indexedDataKey
|
||||||
) => {
|
) => {
|
||||||
const records = []
|
const records = []
|
||||||
|
const getType = typeLoader(index, hierarchy)
|
||||||
const doRead = iterateIndex(
|
const doRead = iterateIndex(
|
||||||
async item => {
|
async item => {
|
||||||
|
item.type = getType(item.key)
|
||||||
records.push(item)
|
records.push(item)
|
||||||
return CONTINUE_READING_RECORDS
|
return CONTINUE_READING_RECORDS
|
||||||
},
|
},
|
||||||
|
@ -31,8 +36,10 @@ export const searchIndex = async (
|
||||||
) => {
|
) => {
|
||||||
const records = []
|
const records = []
|
||||||
const schema = generateSchema(hierarchy, index)
|
const schema = generateSchema(hierarchy, index)
|
||||||
|
const getType = typeLoader(index, hierarchy)
|
||||||
const doRead = iterateIndex(
|
const doRead = iterateIndex(
|
||||||
async item => {
|
async item => {
|
||||||
|
item.type = getType(item.key)
|
||||||
const idx = lunr(function() {
|
const idx = lunr(function() {
|
||||||
this.ref("key")
|
this.ref("key")
|
||||||
for (const field of schema) {
|
for (const field of schema) {
|
||||||
|
@ -76,3 +83,8 @@ export const iterateIndex = (onGetItem, getFinalResult) => async (
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const typeLoader = (index, hierarchy) => {
|
||||||
|
const allowedNodes = getAllowedRecordNodesForIndex(hierarchy, index)
|
||||||
|
return key => find(n => getRecordNodeId(key) === n.nodeId)(allowedNodes).name
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
import { isString, flatten, map, filter } from "lodash/fp"
|
||||||
|
import { initialiseChildCollections } from "../collectionApi/initialise"
|
||||||
|
import { _loadFromInfo } from "./load"
|
||||||
|
import { $ } from "../common"
|
||||||
|
import {
|
||||||
|
getFlattenedHierarchy,
|
||||||
|
isRecord,
|
||||||
|
getNode,
|
||||||
|
isTopLevelRecord,
|
||||||
|
fieldReversesReferenceToNode,
|
||||||
|
} from "../templateApi/hierarchy"
|
||||||
|
import { initialiseIndex } from "../indexing/initialiseIndex"
|
||||||
|
import { getRecordInfo } from "./recordInfo"
|
||||||
|
|
||||||
|
export const initialiseChildren = async (app, recordInfoOrKey) => {
|
||||||
|
const recordInfo = isString(recordInfoOrKey)
|
||||||
|
? getRecordInfo(app.hierarchy, recordInfoOrKey)
|
||||||
|
: recordInfoOrKey
|
||||||
|
await initialiseReverseReferenceIndexes(app, recordInfo)
|
||||||
|
await initialiseAncestorIndexes(app, recordInfo)
|
||||||
|
await initialiseChildCollections(app, recordInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialiseChildrenForNode = async (app, recordNode) => {
|
||||||
|
|
||||||
|
if (isTopLevelRecord(recordNode)) {
|
||||||
|
await initialiseChildren(
|
||||||
|
app, recordNode.nodeKey())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const iterate = await getAllIdsIterator(app)(recordNode.parent().nodeKey())
|
||||||
|
let iterateResult = await iterate()
|
||||||
|
while (!iterateResult.done) {
|
||||||
|
const { result } = iterateResult
|
||||||
|
for (const id of result.ids) {
|
||||||
|
const initialisingRecordKey = joinKey(
|
||||||
|
result.collectionKey, id)
|
||||||
|
await initialiseChildren(app, initialisingRecordKey)
|
||||||
|
}
|
||||||
|
iterateResult = await iterate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialiseAncestorIndexes = async (app, recordInfo) => {
|
||||||
|
for (const index of recordInfo.recordNode.indexes) {
|
||||||
|
const indexKey = recordInfo.child(index.name)
|
||||||
|
if (!(await app.datastore.exists(indexKey))) {
|
||||||
|
await initialiseIndex(app.datastore, recordInfo.dir, index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialiseReverseReferenceIndexes = async (app, recordInfo) => {
|
||||||
|
const indexNodes = $(
|
||||||
|
fieldsThatReferenceThisRecord(app, recordInfo.recordNode),
|
||||||
|
[
|
||||||
|
map(f =>
|
||||||
|
$(f.typeOptions.reverseIndexNodeKeys, [
|
||||||
|
map(n => getNode(app.hierarchy, n)),
|
||||||
|
])
|
||||||
|
),
|
||||||
|
flatten,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
for (const indexNode of indexNodes) {
|
||||||
|
await initialiseIndex(app.datastore, recordInfo.dir, indexNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fieldsThatReferenceThisRecord = (app, recordNode) =>
|
||||||
|
$(app.hierarchy, [
|
||||||
|
getFlattenedHierarchy,
|
||||||
|
filter(isRecord),
|
||||||
|
map(n => n.fields),
|
||||||
|
flatten,
|
||||||
|
filter(fieldReversesReferenceToNode(recordNode)),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { cloneDeep, take, takeRight, flatten, map, filter } from "lodash/fp"
|
import { cloneDeep, take, takeRight, flatten, map, filter } from "lodash/fp"
|
||||||
import { initialiseChildCollections } from "../collectionApi/initialise"
|
|
||||||
import { validate } from "./validate"
|
import { validate } from "./validate"
|
||||||
import { _loadFromInfo } from "./load"
|
import { _loadFromInfo } from "./load"
|
||||||
import { apiWrapper, events, $, joinKey } from "../common"
|
import { apiWrapper, events, $, joinKey } from "../common"
|
||||||
|
@ -17,6 +16,7 @@ import { permission } from "../authApi/permissions"
|
||||||
import { initialiseIndex } from "../indexing/initialiseIndex"
|
import { initialiseIndex } from "../indexing/initialiseIndex"
|
||||||
import { BadRequestError } from "../common/errors"
|
import { BadRequestError } from "../common/errors"
|
||||||
import { getRecordInfo } from "./recordInfo"
|
import { getRecordInfo } from "./recordInfo"
|
||||||
|
import { initialiseChildren } from "./initialiseChildren"
|
||||||
|
|
||||||
export const save = app => async (record, context) =>
|
export const save = app => async (record, context) =>
|
||||||
apiWrapper(
|
apiWrapper(
|
||||||
|
@ -59,9 +59,7 @@ export const _save = async (app, record, context, skipValidation = false) => {
|
||||||
await createRecordFolderPath(app.datastore, pathInfo)
|
await createRecordFolderPath(app.datastore, pathInfo)
|
||||||
await app.datastore.createFolder(files)
|
await app.datastore.createFolder(files)
|
||||||
await app.datastore.createJson(recordJson, recordClone)
|
await app.datastore.createJson(recordJson, recordClone)
|
||||||
await initialiseReverseReferenceIndexes(app, recordInfo)
|
await initialiseChildren(app, recordInfo)
|
||||||
await initialiseAncestorIndexes(app, recordInfo)
|
|
||||||
await initialiseChildCollections(app, recordInfo)
|
|
||||||
await app.publish(events.recordApi.save.onRecordCreated, {
|
await app.publish(events.recordApi.save.onRecordCreated, {
|
||||||
record: recordClone,
|
record: recordClone,
|
||||||
})
|
})
|
||||||
|
@ -87,42 +85,6 @@ export const _save = async (app, record, context, skipValidation = false) => {
|
||||||
return returnedClone
|
return returnedClone
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialiseAncestorIndexes = async (app, recordInfo) => {
|
|
||||||
for (const index of recordInfo.recordNode.indexes) {
|
|
||||||
const indexKey = recordInfo.child(index.name)
|
|
||||||
if (!(await app.datastore.exists(indexKey))) {
|
|
||||||
await initialiseIndex(app.datastore, recordInfo.dir, index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialiseReverseReferenceIndexes = async (app, recordInfo) => {
|
|
||||||
const indexNodes = $(
|
|
||||||
fieldsThatReferenceThisRecord(app, recordInfo.recordNode),
|
|
||||||
[
|
|
||||||
map(f =>
|
|
||||||
$(f.typeOptions.reverseIndexNodeKeys, [
|
|
||||||
map(n => getNode(app.hierarchy, n)),
|
|
||||||
])
|
|
||||||
),
|
|
||||||
flatten,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
for (const indexNode of indexNodes) {
|
|
||||||
await initialiseIndex(app.datastore, recordInfo.dir, indexNode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldsThatReferenceThisRecord = (app, recordNode) =>
|
|
||||||
$(app.hierarchy, [
|
|
||||||
getFlattenedHierarchy,
|
|
||||||
filter(isRecord),
|
|
||||||
map(n => n.fields),
|
|
||||||
flatten,
|
|
||||||
filter(fieldReversesReferenceToNode(recordNode)),
|
|
||||||
])
|
|
||||||
|
|
||||||
const createRecordFolderPath = async (datastore, pathInfo) => {
|
const createRecordFolderPath = async (datastore, pathInfo) => {
|
||||||
const recursiveCreateFolder = async (
|
const recursiveCreateFolder = async (
|
||||||
subdirs,
|
subdirs,
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
import {
|
||||||
|
findRoot,
|
||||||
|
getFlattenedHierarchy,
|
||||||
|
fieldReversesReferenceToIndex,
|
||||||
|
isRecord
|
||||||
|
} from "./hierarchy"
|
||||||
|
import { $ } from "../common"
|
||||||
|
import { map, filter, reduce } from "lodash/fp"
|
||||||
|
|
||||||
|
export const canDeleteIndex = indexNode => {
|
||||||
|
const flatHierarchy = $(indexNode, [
|
||||||
|
findRoot,
|
||||||
|
getFlattenedHierarchy
|
||||||
|
])
|
||||||
|
|
||||||
|
const reverseIndexes = $(flatHierarchy,[
|
||||||
|
filter(isRecord),
|
||||||
|
reduce((obj, r) => {
|
||||||
|
for (let field of r.fields) {
|
||||||
|
if (fieldReversesReferenceToIndex(indexNode)(field)) {
|
||||||
|
obj.push({ ...field, record:r })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
},[]),
|
||||||
|
map(f => `field ${f.name} on record ${f.record.name} uses this index as a reference`)
|
||||||
|
])
|
||||||
|
|
||||||
|
const lookupIndexes = $(flatHierarchy,[
|
||||||
|
filter(isRecord),
|
||||||
|
reduce((obj, r) => {
|
||||||
|
for (let field of r.fields) {
|
||||||
|
if (field.type === "reference"
|
||||||
|
&& field.typeOptions.indexNodeKey === indexNode.nodeKey()) {
|
||||||
|
obj.push({ ...field, record:r })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return obj
|
||||||
|
},[]),
|
||||||
|
map(f => `field ${f.name} on record ${f.record.name} uses this index as a lookup`)
|
||||||
|
])
|
||||||
|
|
||||||
|
const errors = [
|
||||||
|
...reverseIndexes,
|
||||||
|
...lookupIndexes
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
canDelete: errors.length === 0,
|
||||||
|
errors
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
import {
|
||||||
|
findRoot,
|
||||||
|
getFlattenedHierarchy,
|
||||||
|
fieldReversesReferenceToIndex,
|
||||||
|
isRecord,
|
||||||
|
isAncestorIndex,
|
||||||
|
isAncestor
|
||||||
|
} from "./hierarchy"
|
||||||
|
import { $ } from "../common"
|
||||||
|
import { map, filter, includes } from "lodash/fp"
|
||||||
|
|
||||||
|
export const canDeleteRecord = recordNode => {
|
||||||
|
const flatHierarchy = $(recordNode, [
|
||||||
|
findRoot,
|
||||||
|
getFlattenedHierarchy
|
||||||
|
])
|
||||||
|
|
||||||
|
const ancestors = $(flatHierarchy, [
|
||||||
|
filter(isAncestor(recordNode))
|
||||||
|
])
|
||||||
|
|
||||||
|
const belongsToAncestor = i =>
|
||||||
|
ancestors.includes(i.parent())
|
||||||
|
|
||||||
|
|
||||||
|
const errorsForNode = node => {
|
||||||
|
const errorsThisNode = $(flatHierarchy, [
|
||||||
|
filter(i => isAncestorIndex(i)
|
||||||
|
&& belongsToAncestor(i)
|
||||||
|
&& includes(node.nodeId)(i.allowedRecordNodeIds)),
|
||||||
|
map(i => `index ${i.name} indexes this record. Please remove the record from allowedRecordIds, or delete the index`)
|
||||||
|
])
|
||||||
|
|
||||||
|
for (let child of node.children) {
|
||||||
|
for (let err of errorsForNode(child)) {
|
||||||
|
errorsThisNode.push(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorsThisNode
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorsForNode(recordNode)
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { getAllIdsIterator } from "../indexing/allIds"
|
||||||
|
import { getRecordInfo } from "../recordApi/recordInfo"
|
||||||
|
import { isTopLevelIndex, getParentKey, getLastPartInKey } from "./hierarchy"
|
||||||
|
import { safeKey, joinKey } from "../common"
|
||||||
|
|
||||||
|
export const deleteAllIndexFilesForNode = async (app, indexNode) => {
|
||||||
|
|
||||||
|
if (isTopLevelIndex(indexNode)) {
|
||||||
|
await app.datastore.deleteFolder(indexNode.nodeKey())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const iterate = await getAllIdsIterator(app)(indexNode.parent().nodeKey())
|
||||||
|
let iterateResult = await iterate()
|
||||||
|
while (!iterateResult.done) {
|
||||||
|
const { result } = iterateResult
|
||||||
|
for (const id of result.ids) {
|
||||||
|
const deletingIndexKey = joinKey(
|
||||||
|
result.collectionKey, id, indexNode.name)
|
||||||
|
await deleteIndexFolder(app, deletingIndexKey)
|
||||||
|
}
|
||||||
|
iterateResult = await iterate()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteIndexFolder = async (app, indexKey) => {
|
||||||
|
indexKey = safeKey(indexKey)
|
||||||
|
const indexName = getLastPartInKey(indexKey)
|
||||||
|
const parentRecordKey = getParentKey(indexKey)
|
||||||
|
const recordInfo = getRecordInfo(app.hierarchy, parentRecordKey)
|
||||||
|
await app.datastore.deleteFolder(
|
||||||
|
joinKey(recordInfo.dir, indexName))
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
import { getAllIdsIterator } from "../indexing/allIds"
|
||||||
|
import { getCollectionDir } from "../recordApi/recordInfo"
|
||||||
|
import { isTopLevelRecord, getCollectionKey } from "./hierarchy"
|
||||||
|
import { safeKey, joinKey } from "../common"
|
||||||
|
|
||||||
|
export const deleteAllRecordsForNode = async (app, recordNode) => {
|
||||||
|
|
||||||
|
if (isTopLevelRecord(recordNode)) {
|
||||||
|
await deleteRecordCollection(
|
||||||
|
app, recordNode.collectionName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const iterate = await getAllIdsIterator(app)(recordNode.parent().nodeKey())
|
||||||
|
let iterateResult = await iterate()
|
||||||
|
while (!iterateResult.done) {
|
||||||
|
const { result } = iterateResult
|
||||||
|
for (const id of result.ids) {
|
||||||
|
const deletingCollectionKey = joinKey(
|
||||||
|
result.collectionKey, id, recordNode.collectionName)
|
||||||
|
await deleteRecordCollection(app, deletingCollectionKey)
|
||||||
|
}
|
||||||
|
iterateResult = await iterate()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteRecordCollection = async (app, collectionKey) => {
|
||||||
|
collectionKey = safeKey(collectionKey)
|
||||||
|
await app.datastore.deleteFolder(
|
||||||
|
getCollectionDir(app.hierarchy, collectionKey))
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { getFlattenedHierarchy, isRecord, isIndex, isAncestor } from "./hierarchy"
|
import { getFlattenedHierarchy, isRecord, isIndex, isAncestor } from "./hierarchy"
|
||||||
import { $, none } from "../common"
|
import { $, none } from "../common"
|
||||||
import { map, filter, some, find } from "lodash/fp"
|
import { map, filter, some, find, difference } from "lodash/fp"
|
||||||
|
|
||||||
export const HierarchyChangeTypes = {
|
export const HierarchyChangeTypes = {
|
||||||
recordCreated: "Record Created",
|
recordCreated: "Record Created",
|
||||||
|
@ -10,7 +10,7 @@ export const HierarchyChangeTypes = {
|
||||||
recordEstimatedRecordTypeChanged: "Record's Estimated Record Count Changed",
|
recordEstimatedRecordTypeChanged: "Record's Estimated Record Count Changed",
|
||||||
indexCreated: "Index Created",
|
indexCreated: "Index Created",
|
||||||
indexDeleted: "Index Deleted",
|
indexDeleted: "Index Deleted",
|
||||||
indexChanged: "index Changed",
|
indexChanged: "Index Changed",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const diffHierarchy = (oldHierarchy, newHierarchy) => {
|
export const diffHierarchy = (oldHierarchy, newHierarchy) => {
|
||||||
|
@ -123,7 +123,7 @@ const findDeletedIndexes = (oldHierarchyFlat, newHierarchyFlat, deletedRecords)
|
||||||
|
|
||||||
const findUpdatedIndexes = (oldHierarchyFlat, newHierarchyFlat) =>
|
const findUpdatedIndexes = (oldHierarchyFlat, newHierarchyFlat) =>
|
||||||
$(oldHierarchyFlat, [
|
$(oldHierarchyFlat, [
|
||||||
filter(isRecord),
|
filter(isIndex),
|
||||||
filter(nodeExistsIn(newHierarchyFlat)),
|
filter(nodeExistsIn(newHierarchyFlat)),
|
||||||
filter(nodeChanged(newHierarchyFlat, indexHasChanged)),
|
filter(nodeChanged(newHierarchyFlat, indexHasChanged)),
|
||||||
map(n => changeItem(
|
map(n => changeItem(
|
||||||
|
@ -150,6 +150,7 @@ const indexHasChanged = (_new, old) =>
|
||||||
_new.map !== old.map
|
_new.map !== old.map
|
||||||
|| _new.filter !== old.filter
|
|| _new.filter !== old.filter
|
||||||
|| _new.getShardName !== old.getShardName
|
|| _new.getShardName !== old.getShardName
|
||||||
|
|| difference(_new.allowedRecordNodeIds)(old.allowedRecordNodeIds).length > 0
|
||||||
|
|
||||||
const isFieldSame = f1 => f2 =>
|
const isFieldSame = f1 => f2 =>
|
||||||
f1.name === f2.name && f1.type === f2.type
|
f1.name === f2.name && f1.type === f2.type
|
||||||
|
|
|
@ -191,6 +191,22 @@ export const getAllowedRecordNodesForIndex = (appHierarchy, indexNode) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getDependantIndexes = (hierarchy, recordNode) => {
|
||||||
|
const allIndexes = $(hierarchy, [ getFlattenedHierarchy, filter(isIndex)])
|
||||||
|
|
||||||
|
const allowedAncestors = $(allIndexes, [
|
||||||
|
filter(isAncestorIndex),
|
||||||
|
filter(i => recordNodeIsAllowed(i)(recordNode)),
|
||||||
|
])
|
||||||
|
|
||||||
|
const allowedReference = $(allIndexes, [
|
||||||
|
filter(isReferenceIndex),
|
||||||
|
filter(i => some(fieldReversesReferenceToIndex(i))(recordNode.fields))
|
||||||
|
])
|
||||||
|
|
||||||
|
return [...allowedAncestors, ...allowedReference]
|
||||||
|
}
|
||||||
|
|
||||||
export const getNodeFromNodeKeyHash = hierarchy => hash =>
|
export const getNodeFromNodeKeyHash = hierarchy => hash =>
|
||||||
$(hierarchy, [
|
$(hierarchy, [
|
||||||
getFlattenedHierarchy,
|
getFlattenedHierarchy,
|
||||||
|
@ -206,13 +222,19 @@ export const isaggregateGroup = node =>
|
||||||
export const isShardedIndex = node =>
|
export const isShardedIndex = node =>
|
||||||
isIndex(node) && isNonEmptyString(node.getShardName)
|
isIndex(node) && isNonEmptyString(node.getShardName)
|
||||||
export const isRoot = node => isSomething(node) && node.isRoot()
|
export const isRoot = node => isSomething(node) && node.isRoot()
|
||||||
|
export const findRoot = node => isRoot(node) ? node : findRoot(node.parent())
|
||||||
export const isDecendantOfARecord = hasMatchingAncestor(isRecord)
|
export const isDecendantOfARecord = hasMatchingAncestor(isRecord)
|
||||||
export const isGlobalIndex = node => isIndex(node) && isRoot(node.parent())
|
export const isGlobalIndex = node => isIndex(node) && isRoot(node.parent())
|
||||||
export const isReferenceIndex = node =>
|
export const isReferenceIndex = node =>
|
||||||
isIndex(node) && node.indexType === indexTypes.reference
|
isIndex(node) && node.indexType === indexTypes.reference
|
||||||
export const isAncestorIndex = node =>
|
export const isAncestorIndex = node =>
|
||||||
isIndex(node) && node.indexType === indexTypes.ancestor
|
isIndex(node) && node.indexType === indexTypes.ancestor
|
||||||
|
export const isTopLevelRecord = node => isRoot(node.parent()) && isRecord(node)
|
||||||
|
export const isTopLevelIndex = node => isRoot(node.parent()) && isIndex(node)
|
||||||
|
export const getCollectionKey = recordKey => $(recordKey, [
|
||||||
|
splitKey,
|
||||||
|
parts => joinKey(parts.slice(0, parts.length - 1))
|
||||||
|
])
|
||||||
export const fieldReversesReferenceToNode = node => field =>
|
export const fieldReversesReferenceToNode = node => field =>
|
||||||
field.type === "reference" &&
|
field.type === "reference" &&
|
||||||
intersection(field.typeOptions.reverseIndexNodeKeys)(
|
intersection(field.typeOptions.reverseIndexNodeKeys)(
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { saveApplicationHierarchy } from "./saveApplicationHierarchy"
|
||||||
import { saveActionsAndTriggers } from "./saveActionsAndTriggers"
|
import { saveActionsAndTriggers } from "./saveActionsAndTriggers"
|
||||||
import { all } from "../types"
|
import { all } from "../types"
|
||||||
import { getBehaviourSources } from "./getBehaviourSources"
|
import { getBehaviourSources } from "./getBehaviourSources"
|
||||||
|
import { upgradeData } from "./upgradeData"
|
||||||
|
|
||||||
const api = app => ({
|
const api = app => ({
|
||||||
getApplicationDefinition: getApplicationDefinition(app.datastore),
|
getApplicationDefinition: getApplicationDefinition(app.datastore),
|
||||||
|
@ -57,6 +58,7 @@ const api = app => ({
|
||||||
validateNode,
|
validateNode,
|
||||||
validateAll,
|
validateAll,
|
||||||
validateTriggers,
|
validateTriggers,
|
||||||
|
upgradeData: upgradeData(app)
|
||||||
})
|
})
|
||||||
|
|
||||||
export const getTemplateApi = app => api(app)
|
export const getTemplateApi = app => api(app)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { getAllIdsIterator } from "../indexing/allIds"
|
||||||
|
import { getRecordInfo } from "../recordApi/recordInfo"
|
||||||
|
import { isTopLevelIndex } from "./hierarchy"
|
||||||
|
import { joinKey } from "../common"
|
||||||
|
import { initialiseIndex } from "../indexing/initialiseIndex"
|
||||||
|
|
||||||
|
export const initialiseNewIndex = async (app, indexNode) => {
|
||||||
|
|
||||||
|
if (isTopLevelIndex(indexNode)) {
|
||||||
|
await initialiseIndex(app.datastore, "/", indexNode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const iterate = await getAllIdsIterator(app)(indexNode.parent().nodeKey())
|
||||||
|
let iterateResult = await iterate()
|
||||||
|
while (!iterateResult.done) {
|
||||||
|
const { result } = iterateResult
|
||||||
|
for (const id of result.ids) {
|
||||||
|
const recordKey = joinKey(result.collectionKey, id)
|
||||||
|
await initialiseIndex(
|
||||||
|
app.datastore,
|
||||||
|
getRecordInfo(app.hierarchy, recordKey).dir,
|
||||||
|
indexNode)
|
||||||
|
}
|
||||||
|
iterateResult = await iterate()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,17 +1,194 @@
|
||||||
/*
|
import { diffHierarchy, HierarchyChangeTypes } from "./diffHierarchy"
|
||||||
const changeActions = {
|
import { $, switchCase } from "../common"
|
||||||
rebuildIndex: indexNodeKey => ({
|
import {
|
||||||
type: "rebuildIndex",
|
differenceBy,
|
||||||
indexNodeKey,
|
isEqual,
|
||||||
}),
|
some,
|
||||||
reshardRecords: recordNodeKey => ({
|
map,
|
||||||
type: "reshardRecords",
|
filter,
|
||||||
recordNodeKey,
|
uniqBy,
|
||||||
}),
|
flatten
|
||||||
deleteRecords: recordNodeKey => ({
|
} from "lodash/fp"
|
||||||
type: "reshardRecords",
|
import {
|
||||||
recordNodeKey,
|
findRoot,
|
||||||
}),
|
getDependantIndexes,
|
||||||
renameRecord
|
isTopLevelRecord,
|
||||||
|
isAncestorIndex
|
||||||
|
} from "./hierarchy"
|
||||||
|
import { generateSchema } from "../indexing/indexSchemaCreator"
|
||||||
|
import { _buildIndex } from "../indexApi/buildIndex"
|
||||||
|
import { constructHierarchy } from "./createNodes"
|
||||||
|
import { deleteAllRecordsForNode } from "./deleteAllRecordsForNode"
|
||||||
|
import { deleteAllIndexFilesForNode } from "./deleteAllIndexFilesForNode"
|
||||||
|
import { cloneApp } from "../appInitialise/cloneApp"
|
||||||
|
import { initialiseData } from "../appInitialise/initialiseData"
|
||||||
|
import { initialiseChildrenForNode } from "../recordApi/initialiseChildren"
|
||||||
|
import { initialiseNewIndex } from "./initialiseNewIndex"
|
||||||
|
import { saveApplicationHierarchy } from "../templateApi/saveApplicationHierarchy"
|
||||||
|
|
||||||
|
export const upgradeData = app => async newHierarchy => {
|
||||||
|
const diff = diffHierarchy(app.hierarchy, newHierarchy)
|
||||||
|
const changeActions = gatherChangeActions(diff)
|
||||||
|
|
||||||
|
if (changeActions.length === 0) return
|
||||||
|
|
||||||
|
newHierarchy = constructHierarchy(newHierarchy)
|
||||||
|
const newApp = newHierarchy && cloneApp(app, {
|
||||||
|
hierarchy: newHierarchy
|
||||||
|
})
|
||||||
|
await doUpgrade(app, newApp, changeActions)
|
||||||
|
await saveApplicationHierarchy(newApp)(newHierarchy)
|
||||||
|
}
|
||||||
|
|
||||||
|
const gatherChangeActions = (diff) =>
|
||||||
|
$(diff, [
|
||||||
|
map(actionForChange),
|
||||||
|
flatten,
|
||||||
|
uniqBy(a => a.compareKey)
|
||||||
|
])
|
||||||
|
|
||||||
|
const doUpgrade = async (oldApp, newApp, changeActions) => {
|
||||||
|
for(let action of changeActions) {
|
||||||
|
await action.run(oldApp, newApp, action.diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionForChange = diff =>
|
||||||
|
switchCase(
|
||||||
|
|
||||||
|
[isChangeType(HierarchyChangeTypes.recordCreated), recordCreatedAction],
|
||||||
|
|
||||||
|
[isChangeType(HierarchyChangeTypes.recordDeleted), deleteRecordsAction],
|
||||||
|
|
||||||
|
[
|
||||||
|
isChangeType(HierarchyChangeTypes.recordFieldsChanged),
|
||||||
|
rebuildAffectedIndexesAction
|
||||||
|
],
|
||||||
|
|
||||||
|
[isChangeType(HierarchyChangeTypes.recordRenamed), renameRecordAction],
|
||||||
|
|
||||||
|
[
|
||||||
|
isChangeType(HierarchyChangeTypes.recordEstimatedRecordTypeChanged),
|
||||||
|
reshardRecordsAction
|
||||||
|
],
|
||||||
|
|
||||||
|
[isChangeType(HierarchyChangeTypes.indexCreated), newIndexAction],
|
||||||
|
|
||||||
|
[isChangeType(HierarchyChangeTypes.indexDeleted), deleteIndexAction],
|
||||||
|
|
||||||
|
[isChangeType(HierarchyChangeTypes.indexChanged), rebuildIndexAction],
|
||||||
|
|
||||||
|
)(diff)
|
||||||
|
|
||||||
|
|
||||||
|
const isChangeType = changeType => change =>
|
||||||
|
change.type === changeType
|
||||||
|
|
||||||
|
const action = (diff, compareKey, run) => ({
|
||||||
|
diff,
|
||||||
|
compareKey,
|
||||||
|
run,
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
const reshardRecordsAction = diff =>
|
||||||
|
[action(diff, `reshardRecords-${diff.oldNode.nodeKey()}`, runReshardRecords)]
|
||||||
|
|
||||||
|
const rebuildIndexAction = diff =>
|
||||||
|
[action(diff, `rebuildIndex-${diff.newNode.nodeKey()}`, runRebuildIndex)]
|
||||||
|
|
||||||
|
const newIndexAction = diff => {
|
||||||
|
if (isAncestorIndex(diff.newNode)) {
|
||||||
|
return [action(diff, `rebuildIndex-${diff.newNode.nodeKey()}`, runRebuildIndex)]
|
||||||
|
} else {
|
||||||
|
return [action(diff, `newIndex-${diff.newNode.nodeKey()}`, runNewIndex)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteIndexAction = diff =>
|
||||||
|
[action(diff, `deleteIndex-${diff.oldNode.nodeKey()}`, runDeleteIndex)]
|
||||||
|
|
||||||
|
const deleteRecordsAction = diff =>
|
||||||
|
[action(diff, `deleteRecords-${diff.oldNode.nodeKey()}`, runDeleteRecords)]
|
||||||
|
|
||||||
|
const renameRecordAction = diff =>
|
||||||
|
[action(diff, `renameRecords-${diff.oldNode.nodeKey()}`, runRenameRecord)]
|
||||||
|
|
||||||
|
const recordCreatedAction = diff => {
|
||||||
|
if (isTopLevelRecord(diff.newNode)) {
|
||||||
|
return [action(diff, `initialiseRoot`, runInitialiseRoot)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [action(diff, `initialiseChildRecord-${diff.newNode.nodeKey()}`, runInitialiseChildRecord)]
|
||||||
|
}
|
||||||
|
|
||||||
|
const rebuildAffectedIndexesAction = diff =>{
|
||||||
|
const newHierarchy = findRoot(diff.newNode)
|
||||||
|
const oldHierarchy = findRoot(diff.oldNode)
|
||||||
|
const indexes = getDependantIndexes(newHierarchy, diff.newNode)
|
||||||
|
|
||||||
|
const changedFields = (() => {
|
||||||
|
const addedFields = differenceBy(f => f.name)
|
||||||
|
(diff.oldNode.fields)
|
||||||
|
(diff.newNode.fields)
|
||||||
|
|
||||||
|
const removedFields = differenceBy(f => f.name)
|
||||||
|
(diff.newNode.fields)
|
||||||
|
(diff.oldNode.fields)
|
||||||
|
|
||||||
|
return map(f => f.name)([...addedFields, ...removedFields])
|
||||||
|
})()
|
||||||
|
|
||||||
|
const isIndexAffected = i => {
|
||||||
|
if (!isEqual(
|
||||||
|
generateSchema(oldHierarchy, i),
|
||||||
|
generateSchema(newHierarchy, i))) return true
|
||||||
|
|
||||||
|
if (some(f => indexes.filter.indexOf(`record.${f}`) > -1)(changedFields))
|
||||||
|
return true
|
||||||
|
|
||||||
|
if (some(f => indexes.getShardName.indexOf(`record.${f}`) > -1)(changedFields))
|
||||||
|
return true
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return $(indexes, [
|
||||||
|
filter(isIndexAffected),
|
||||||
|
map(i => action({ newNode:i }, `rebuildIndex-${i.nodeKey()}`, runRebuildIndex))
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
const runReshardRecords = async change => {
|
||||||
|
throw new Error("Resharding of records is not supported yet")
|
||||||
|
}
|
||||||
|
|
||||||
|
const runRebuildIndex = async (_, newApp, diff) => {
|
||||||
|
await _buildIndex(newApp, diff.newNode.nodeKey())
|
||||||
|
}
|
||||||
|
|
||||||
|
const runDeleteIndex = async (oldApp, _, diff) => {
|
||||||
|
await deleteAllIndexFilesForNode(oldApp, diff.oldNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
const runDeleteRecords = async (oldApp, _, diff) => {
|
||||||
|
await deleteAllRecordsForNode(oldApp, diff.oldNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
const runNewIndex = async (_, newApp, diff) => {
|
||||||
|
await initialiseNewIndex(newApp, diff.newNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
const runRenameRecord = change => {
|
||||||
|
/*
|
||||||
|
Going to disllow this in the builder. once a collection key is set... its done
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
const runInitialiseRoot = async (_, newApp) => {
|
||||||
|
await initialiseData(newApp.datastore, newApp)
|
||||||
|
}
|
||||||
|
|
||||||
|
const runInitialiseChildRecord = async (_, newApp, diff) => {
|
||||||
|
await initialiseChildrenForNode(newApp.datastore, diff.newNode)
|
||||||
}
|
}
|
||||||
*/
|
|
|
@ -14,8 +14,10 @@ export const cleanup = async app => {
|
||||||
const lock = await getTransactionLock(app)
|
const lock = await getTransactionLock(app)
|
||||||
if (isNolock(lock)) return
|
if (isNolock(lock)) return
|
||||||
|
|
||||||
try {
|
const _cleanupBatch = async () => {
|
||||||
|
let processed = 0
|
||||||
const transactions = await retrieve(app)
|
const transactions = await retrieve(app)
|
||||||
|
let i = 1
|
||||||
if (transactions.length > 0) {
|
if (transactions.length > 0) {
|
||||||
await executeTransactions(app)(transactions)
|
await executeTransactions(app)(transactions)
|
||||||
|
|
||||||
|
@ -34,10 +36,21 @@ export const cleanup = async app => {
|
||||||
])
|
])
|
||||||
|
|
||||||
await Promise.all(deleteFiles)
|
await Promise.all(deleteFiles)
|
||||||
|
|
||||||
|
processed = transactions.length
|
||||||
|
}
|
||||||
|
return processed
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let count = -1
|
||||||
|
while (count !== 0) {
|
||||||
|
count = await _cleanupBatch()
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
await releaseLock(app, lock)
|
await releaseLock(app, lock)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTransactionLock = async app =>
|
const getTransactionLock = async app =>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
filter,
|
filter,
|
||||||
map,
|
map,
|
||||||
|
reduce,
|
||||||
isUndefined,
|
isUndefined,
|
||||||
includes,
|
includes,
|
||||||
flatten,
|
flatten,
|
||||||
|
@ -10,6 +11,7 @@ import {
|
||||||
keys,
|
keys,
|
||||||
differenceBy,
|
differenceBy,
|
||||||
difference,
|
difference,
|
||||||
|
some,
|
||||||
} from "lodash/fp"
|
} from "lodash/fp"
|
||||||
import { union } from "lodash"
|
import { union } from "lodash"
|
||||||
import {
|
import {
|
||||||
|
@ -38,14 +40,22 @@ import {
|
||||||
fieldReversesReferenceToIndex,
|
fieldReversesReferenceToIndex,
|
||||||
isReferenceIndex,
|
isReferenceIndex,
|
||||||
getExactNodeForKey,
|
getExactNodeForKey,
|
||||||
|
getParentKey
|
||||||
} from "../templateApi/hierarchy"
|
} from "../templateApi/hierarchy"
|
||||||
import { getRecordInfo } from "../recordApi/recordInfo"
|
import { getRecordInfo } from "../recordApi/recordInfo"
|
||||||
import { getIndexDir } from "../indexApi/getIndexDir"
|
import { getIndexDir } from "../indexApi/getIndexDir"
|
||||||
|
import { initialiseIndex } from "../indexing/initialiseIndex"
|
||||||
|
|
||||||
export const executeTransactions = app => async transactions => {
|
export const executeTransactions = app => async transactions => {
|
||||||
const recordsByShard = mappedRecordsByIndexShard(app.hierarchy, transactions)
|
const recordsByShard = mappedRecordsByIndexShard(app.hierarchy, transactions)
|
||||||
|
|
||||||
for (const shard of keys(recordsByShard)) {
|
for (const shard of keys(recordsByShard)) {
|
||||||
|
if (recordsByShard[shard].isRebuild)
|
||||||
|
await initialiseIndex(
|
||||||
|
app.datastore,
|
||||||
|
getParentKey(recordsByShard[shard].indexDir),
|
||||||
|
recordsByShard[shard].indexNode
|
||||||
|
)
|
||||||
await applyToShard(
|
await applyToShard(
|
||||||
app.hierarchy,
|
app.hierarchy,
|
||||||
app.datastore,
|
app.datastore,
|
||||||
|
@ -66,9 +76,9 @@ const mappedRecordsByIndexShard = (hierarchy, transactions) => {
|
||||||
|
|
||||||
const indexBuild = getBuildIndexTransactionsByShard(hierarchy, transactions)
|
const indexBuild = getBuildIndexTransactionsByShard(hierarchy, transactions)
|
||||||
|
|
||||||
const toRemove = [...deletes, ...updates.toRemove]
|
const toRemove = [...deletes, ...updates.toRemove, ...indexBuild.toRemove]
|
||||||
|
|
||||||
const toWrite = [...created, ...updates.toWrite, ...indexBuild]
|
const toWrite = [...created, ...updates.toWrite, ...indexBuild.toWrite]
|
||||||
|
|
||||||
const transByShard = {}
|
const transByShard = {}
|
||||||
|
|
||||||
|
@ -77,6 +87,8 @@ const mappedRecordsByIndexShard = (hierarchy, transactions) => {
|
||||||
transByShard[t.indexShardKey] = {
|
transByShard[t.indexShardKey] = {
|
||||||
writes: [],
|
writes: [],
|
||||||
removes: [],
|
removes: [],
|
||||||
|
isRebuild: some(i => i.indexShardKey === t.indexShardKey)(indexBuild.toWrite)
|
||||||
|
|| some(i => i.indexShardKey === t.indexShardKey)(indexBuild.toRemove),
|
||||||
indexDir: t.indexDir,
|
indexDir: t.indexDir,
|
||||||
indexNodeKey: t.indexNode.nodeKey(),
|
indexNodeKey: t.indexNode.nodeKey(),
|
||||||
indexNode: t.indexNode,
|
indexNode: t.indexNode,
|
||||||
|
@ -207,7 +219,7 @@ const getUpdateTransactionsByShard = (hierarchy, transactions) => {
|
||||||
|
|
||||||
const getBuildIndexTransactionsByShard = (hierarchy, transactions) => {
|
const getBuildIndexTransactionsByShard = (hierarchy, transactions) => {
|
||||||
const buildTransactions = $(transactions, [filter(isBuildIndex)])
|
const buildTransactions = $(transactions, [filter(isBuildIndex)])
|
||||||
if (!isNonEmptyArray(buildTransactions)) return []
|
if (!isNonEmptyArray(buildTransactions)) return { toWrite:[], toRemove:[] }
|
||||||
const indexNode = transactions.indexNode
|
const indexNode = transactions.indexNode
|
||||||
|
|
||||||
const getIndexDirs = t => {
|
const getIndexDirs = t => {
|
||||||
|
@ -248,7 +260,7 @@ const getBuildIndexTransactionsByShard = (hierarchy, transactions) => {
|
||||||
return $(buildTransactions, [
|
return $(buildTransactions, [
|
||||||
map(t => {
|
map(t => {
|
||||||
const mappedRecord = evaluate(t.record)(indexNode)
|
const mappedRecord = evaluate(t.record)(indexNode)
|
||||||
if (!mappedRecord.passedFilter) return null
|
mappedRecord.result = mappedRecord.result || t.record
|
||||||
const indexDirs = getIndexDirs(t)
|
const indexDirs = getIndexDirs(t)
|
||||||
return $(indexDirs, [
|
return $(indexDirs, [
|
||||||
map(indexDir => ({
|
map(indexDir => ({
|
||||||
|
@ -262,9 +274,16 @@ const getBuildIndexTransactionsByShard = (hierarchy, transactions) => {
|
||||||
),
|
),
|
||||||
})),
|
})),
|
||||||
])
|
])
|
||||||
|
|
||||||
}),
|
}),
|
||||||
flatten,
|
flatten,
|
||||||
filter(isSomething),
|
reduce((obj, res) => {
|
||||||
|
if (res.mappedRecord.passedFilter)
|
||||||
|
obj.toWrite.push(res)
|
||||||
|
else
|
||||||
|
obj.toRemove.push(res)
|
||||||
|
return obj
|
||||||
|
}, { toWrite: [], toRemove: [] })
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,32 +22,41 @@ export const retrieve = async app => {
|
||||||
TRANSACTIONS_FOLDER
|
TRANSACTIONS_FOLDER
|
||||||
)
|
)
|
||||||
|
|
||||||
let transactions = []
|
|
||||||
|
|
||||||
if (some(isBuildIndexFolder)(transactionFiles)) {
|
if (some(isBuildIndexFolder)(transactionFiles)) {
|
||||||
const buildIndexFolder = find(isBuildIndexFolder)(transactionFiles)
|
const buildIndexFolders = filter(isBuildIndexFolder)(transactionFiles)
|
||||||
|
let currentFolderIndex = 0
|
||||||
|
while (currentFolderIndex < buildIndexFolders.length) {
|
||||||
|
const buildIndexFolder = buildIndexFolders[currentFolderIndex]
|
||||||
|
const transactions = await retrieveBuildIndexTransactions(
|
||||||
|
app,
|
||||||
|
joinKey(TRANSACTIONS_FOLDER, buildIndexFolder)
|
||||||
|
)
|
||||||
|
if(transactions.length === 0) {
|
||||||
|
await app.datastore.deleteFolder(
|
||||||
|
joinKey(TRANSACTIONS_FOLDER, buildIndexFolder))
|
||||||
|
} else {
|
||||||
|
return transactions
|
||||||
|
}
|
||||||
|
currentFolderIndex += 1
|
||||||
|
}
|
||||||
|
|
||||||
transactions = await retrieveBuildIndexTransactions(
|
return []
|
||||||
app,
|
|
||||||
joinKey(TRANSACTIONS_FOLDER, buildIndexFolder)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transactions.length > 0) return transactions
|
|
||||||
|
|
||||||
return await retrieveStandardTransactions(app, transactionFiles)
|
return await retrieveStandardTransactions(app, transactionFiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
const retrieveBuildIndexTransactions = async (app, buildIndexFolder) => {
|
const retrieveBuildIndexTransactions = async (app, buildIndexFolder) => {
|
||||||
const childFolders = await app.datastore.getFolderContents(buildIndexFolder)
|
const childFolders = await app.datastore.getFolderContents(buildIndexFolder)
|
||||||
if (childFolders.length === 0) {
|
const childFolderCount = childFolders.length
|
||||||
// cleanup
|
if (childFolderCount === 0) {
|
||||||
await app.datastore.deleteFolder(buildIndexFolder)
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTransactionFiles = async (childFolderIndex = 0) => {
|
const getTransactionFiles = async (childFolderIndex = 0) => {
|
||||||
if (childFolderIndex >= childFolders.length) return []
|
if (childFolderIndex >= childFolders.length) {
|
||||||
|
return { childFolderKey: "", files: [] }
|
||||||
|
}
|
||||||
|
|
||||||
const childFolderKey = joinKey(
|
const childFolderKey = joinKey(
|
||||||
buildIndexFolder,
|
buildIndexFolder,
|
||||||
|
@ -55,17 +64,19 @@ const retrieveBuildIndexTransactions = async (app, buildIndexFolder) => {
|
||||||
)
|
)
|
||||||
const files = await app.datastore.getFolderContents(childFolderKey)
|
const files = await app.datastore.getFolderContents(childFolderKey)
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length > 0) {
|
||||||
await app.datastore.deleteFolder(childFolderKey)
|
return { childFolderKey, files }
|
||||||
return await getTransactionFiles(childFolderIndex + 1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { childFolderKey, files }
|
await app.datastore.deleteFolder(childFolderKey)
|
||||||
|
return await getTransactionFiles(childFolderIndex + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const transactionFiles = await getTransactionFiles()
|
const transactionFiles = await getTransactionFiles()
|
||||||
|
|
||||||
if (transactionFiles.files.length === 0) return []
|
if (transactionFiles.files.length === 0) {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
const transactions = $(transactionFiles.files, [map(parseTransactionId)])
|
const transactions = $(transactionFiles.files, [map(parseTransactionId)])
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { cleanup } from "./cleanup"
|
||||||
|
|
||||||
|
export const setCleanupFunc = (app, cleanupTransactions) => {
|
||||||
|
if (cleanupTransactions) {
|
||||||
|
app.cleanupTransactions = cleanupTransactions
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!app.cleanupTransactions || app.cleanupTransactions.isDefault) {
|
||||||
|
const newCleanup = async () => cleanup(app)
|
||||||
|
newCleanup.isDefault = true
|
||||||
|
app.cleanupTransactions = newCleanup
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,20 @@
|
||||||
import { joinKey, keySep, getHashCode } from "../common"
|
import { joinKey, keySep, getHashCode } from "../common"
|
||||||
import { getLastPartInKey } from "../templateApi/hierarchy"
|
import { getLastPartInKey } from "../templateApi/hierarchy"
|
||||||
|
import { includes } from "lodash/fp"
|
||||||
|
|
||||||
export const TRANSACTIONS_FOLDER = `${keySep}.transactions`
|
export const TRANSACTIONS_FOLDER = `${keySep}.transactions`
|
||||||
export const LOCK_FILENAME = "lock"
|
export const LOCK_FILENAME = "lock"
|
||||||
export const LOCK_FILE_KEY = joinKey(TRANSACTIONS_FOLDER, LOCK_FILENAME)
|
export const LOCK_FILE_KEY = joinKey(TRANSACTIONS_FOLDER, LOCK_FILENAME)
|
||||||
export const idSep = "$"
|
export const idSep = "$"
|
||||||
|
|
||||||
const isOfType = typ => trans => trans.transactionType === typ
|
const isOfType = (...typ) => trans => includes(trans.transactionType)(typ)
|
||||||
|
|
||||||
export const CREATE_RECORD_TRANSACTION = "create"
|
export const CREATE_RECORD_TRANSACTION = "create"
|
||||||
export const UPDATE_RECORD_TRANSACTION = "update"
|
export const UPDATE_RECORD_TRANSACTION = "update"
|
||||||
export const DELETE_RECORD_TRANSACTION = "delete"
|
export const DELETE_RECORD_TRANSACTION = "delete"
|
||||||
export const BUILD_INDEX_TRANSACTION = "build"
|
export const BUILD_INDEX_TRANSACTION = "build"
|
||||||
|
|
||||||
|
export const isUpdate_Or_Rebuild = isOfType(UPDATE_RECORD_TRANSACTION, BUILD_INDEX_TRANSACTION)
|
||||||
export const isUpdate = isOfType(UPDATE_RECORD_TRANSACTION)
|
export const isUpdate = isOfType(UPDATE_RECORD_TRANSACTION)
|
||||||
export const isDelete = isOfType(DELETE_RECORD_TRANSACTION)
|
export const isDelete = isOfType(DELETE_RECORD_TRANSACTION)
|
||||||
export const isCreate = isOfType(CREATE_RECORD_TRANSACTION)
|
export const isCreate = isOfType(CREATE_RECORD_TRANSACTION)
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { filter, find } from "lodash/fp"
|
||||||
import { createBehaviourSources } from "../src/actionsApi/buildBehaviourSource"
|
import { createBehaviourSources } from "../src/actionsApi/buildBehaviourSource"
|
||||||
import { createAction, createTrigger } from "../src/templateApi/createActions"
|
import { createAction, createTrigger } from "../src/templateApi/createActions"
|
||||||
import { initialiseActions } from "../src/actionsApi/initialise"
|
import { initialiseActions } from "../src/actionsApi/initialise"
|
||||||
import { cleanup } from "../src/transactions/cleanup"
|
import { setCleanupFunc } from "../src/transactions/setCleanupFunc"
|
||||||
import { permission } from "../src/authApi/permissions"
|
import { permission } from "../src/authApi/permissions"
|
||||||
import { generateFullPermissions } from "../src/authApi/generateFullPermissions"
|
import { generateFullPermissions } from "../src/authApi/generateFullPermissions"
|
||||||
import { initialiseData } from "../src/appInitialise/initialiseData"
|
import { initialiseData } from "../src/appInitialise/initialiseData"
|
||||||
|
@ -39,9 +39,9 @@ export const testTemplatesPath = testAreaName =>
|
||||||
path.join(testFileArea(testAreaName), templateDefinitions)
|
path.join(testFileArea(testAreaName), templateDefinitions)
|
||||||
|
|
||||||
export const getMemoryStore = () => setupDatastore(memory({}))
|
export const getMemoryStore = () => setupDatastore(memory({}))
|
||||||
export const getMemoryTemplateApi = () => {
|
export const getMemoryTemplateApi = (store) => {
|
||||||
const app = {
|
const app = {
|
||||||
datastore: getMemoryStore(),
|
datastore: store || getMemoryStore(),
|
||||||
publish: () => {},
|
publish: () => {},
|
||||||
getEpochTime: async () => new Date().getTime(),
|
getEpochTime: async () => new Date().getTime(),
|
||||||
user: { name: "", permissions: [permission.writeTemplates.get()] },
|
user: { name: "", permissions: [permission.writeTemplates.get()] },
|
||||||
|
@ -78,8 +78,9 @@ export const appFromTempalteApi = async (
|
||||||
const fullPermissions = generateFullPermissions(app)
|
const fullPermissions = generateFullPermissions(app)
|
||||||
app.user.permissions = fullPermissions
|
app.user.permissions = fullPermissions
|
||||||
|
|
||||||
if (disableCleanupTransactions) app.cleanupTransactions = async () => {}
|
if (disableCleanupTransactions) setCleanupFunc(app, async () => {})
|
||||||
else app.cleanupTransactions = async () => await cleanup(app)
|
else setCleanupFunc(app)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,8 +101,9 @@ export const getRecordApiFromTemplateApi = async (
|
||||||
disableCleanupTransactions = false
|
disableCleanupTransactions = false
|
||||||
) => {
|
) => {
|
||||||
const app = await appFromTempalteApi(templateApi, disableCleanupTransactions)
|
const app = await appFromTempalteApi(templateApi, disableCleanupTransactions)
|
||||||
const recordapi = getRecordApi()
|
const recordapi = getRecordApi(app)
|
||||||
recordapi._storeHandle = app.datastore
|
recordapi._storeHandle = app.datastore
|
||||||
|
return recordapi
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCollectionApiFromTemplateApi = async (
|
export const getCollectionApiFromTemplateApi = async (
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import {
|
||||||
|
setupApphierarchy,
|
||||||
|
basicAppHierarchyCreator_WithFields,
|
||||||
|
stubEventHandler,
|
||||||
|
} from "./specHelpers"
|
||||||
|
import { canDeleteIndex } from "../src/templateApi/canDeleteIndex"
|
||||||
|
import { canDeleteRecord } from "../src/templateApi/canDeleteRecord"
|
||||||
|
|
||||||
|
describe("canDeleteIndex", () => {
|
||||||
|
it("should return no errors if deltion is valid", async () => {
|
||||||
|
const { appHierarchy } = await setupApphierarchy(
|
||||||
|
basicAppHierarchyCreator_WithFields
|
||||||
|
)
|
||||||
|
|
||||||
|
const partnerIndex = appHierarchy.root.indexes.find(i => i.name === "partner_index")
|
||||||
|
|
||||||
|
const result = canDeleteIndex(partnerIndex)
|
||||||
|
|
||||||
|
expect(result.canDelete).toBe(true)
|
||||||
|
expect(result.errors).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return errors if index is a lookup for a reference field", async () => {
|
||||||
|
const { appHierarchy } = await setupApphierarchy(
|
||||||
|
basicAppHierarchyCreator_WithFields
|
||||||
|
)
|
||||||
|
|
||||||
|
const customerIndex = appHierarchy.root.indexes.find(i => i.name === "customer_index")
|
||||||
|
|
||||||
|
const result = canDeleteIndex(customerIndex)
|
||||||
|
|
||||||
|
expect(result.canDelete).toBe(false)
|
||||||
|
expect(result.errors.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should return errors if index is a manyToOne index for a reference field", async () => {
|
||||||
|
const { appHierarchy } = await setupApphierarchy(
|
||||||
|
basicAppHierarchyCreator_WithFields
|
||||||
|
)
|
||||||
|
|
||||||
|
const referredToCustomersIndex = appHierarchy.customerRecord.indexes.find(i => i.name === "referredToCustomers")
|
||||||
|
|
||||||
|
const result = canDeleteIndex(referredToCustomersIndex)
|
||||||
|
|
||||||
|
expect(result.canDelete).toBe(false)
|
||||||
|
expect(result.errors.length).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
describe("canDeleteRecord", () => {
|
||||||
|
it("should return no errors when deletion is valid", () => {
|
||||||
|
const { appHierarchy } = await setupApphierarchy(
|
||||||
|
basicAppHierarchyCreator_WithFields
|
||||||
|
)
|
||||||
|
|
||||||
|
appHierarchy.root.
|
||||||
|
const result = canDeleteIndex(appHierarchy.customerRecord)
|
||||||
|
|
||||||
|
expect(result.canDelete).toBe(true)
|
||||||
|
expect(result.errors).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,6 +1,5 @@
|
||||||
import { getMemoryTemplateApi } from "./specHelpers"
|
import { setup } from "./upgradeDataSetup"
|
||||||
import { diffHierarchy, HierarchyChangeTypes } from "../src/templateApi/diffHierarchy"
|
import { diffHierarchy, HierarchyChangeTypes } from "../src/templateApi/diffHierarchy"
|
||||||
import { getFlattenedHierarchy } from "../src/templateApi/hierarchy"
|
|
||||||
|
|
||||||
describe("diffHierarchy", () => {
|
describe("diffHierarchy", () => {
|
||||||
|
|
||||||
|
@ -13,7 +12,7 @@ describe("diffHierarchy", () => {
|
||||||
|
|
||||||
it("should detect root record created", async () => {
|
it("should detect root record created", async () => {
|
||||||
const oldHierarchy = (await setup()).root;
|
const oldHierarchy = (await setup()).root;
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false)
|
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false)
|
||||||
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -25,7 +24,7 @@ describe("diffHierarchy", () => {
|
||||||
|
|
||||||
it("should only detect root record, when newly created root record has children ", async () => {
|
it("should only detect root record, when newly created root record has children ", async () => {
|
||||||
const oldHierarchy = (await setup()).root;
|
const oldHierarchy = (await setup()).root;
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false)
|
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false)
|
||||||
newSetup.templateApi.getNewRecordTemplate(opportunity, "invoice", true)
|
newSetup.templateApi.getNewRecordTemplate(opportunity, "invoice", true)
|
||||||
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
||||||
|
@ -38,7 +37,7 @@ describe("diffHierarchy", () => {
|
||||||
|
|
||||||
it("should detect child record created", async () => {
|
it("should detect child record created", async () => {
|
||||||
const oldHierarchy = (await setup()).root;
|
const oldHierarchy = (await setup()).root;
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.contact, "opportunity", false)
|
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.contact, "opportunity", false)
|
||||||
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -49,8 +48,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect root record deleted", async () => {
|
it("should detect root record deleted", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
newSetup.root.children = newSetup.root.children.filter(n => n.name !== "contact")
|
newSetup.root.children = newSetup.root.children.filter(n => n.name !== "contact")
|
||||||
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -61,8 +60,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect child record deleted", async () => {
|
it("should detect child record deleted", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
newSetup.contact.children = newSetup.contact.children.filter(n => n.name !== "deal")
|
newSetup.contact.children = newSetup.contact.children.filter(n => n.name !== "deal")
|
||||||
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -73,8 +72,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect root record renamed", async () => {
|
it("should detect root record renamed", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
newSetup.contact.collectionKey = "CONTACTS"
|
newSetup.contact.collectionKey = "CONTACTS"
|
||||||
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -85,8 +84,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect child record renamed", async () => {
|
it("should detect child record renamed", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
newSetup.deal.collectionKey = "CONTACTS"
|
newSetup.deal.collectionKey = "CONTACTS"
|
||||||
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -97,8 +96,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect root record field removed", async () => {
|
it("should detect root record field removed", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
newSetup.contact.fields = newSetup.contact.fields.filter(f => f.name !== "name")
|
newSetup.contact.fields = newSetup.contact.fields.filter(f => f.name !== "name")
|
||||||
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -109,8 +108,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect child record field removed", async () => {
|
it("should detect child record field removed", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
newSetup.deal.fields = newSetup.deal.fields.filter(f => f.name !== "name")
|
newSetup.deal.fields = newSetup.deal.fields.filter(f => f.name !== "name")
|
||||||
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -121,8 +120,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect record field added", async () => {
|
it("should detect record field added", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
const notesField = newSetup.templateApi.getNewField("string")
|
const notesField = newSetup.templateApi.getNewField("string")
|
||||||
notesField.name = "notes"
|
notesField.name = "notes"
|
||||||
newSetup.templateApi.addField(newSetup.contact, notesField)
|
newSetup.templateApi.addField(newSetup.contact, notesField)
|
||||||
|
@ -136,8 +135,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect 1 record field added and 1 removed (total no. fields unchanged)", async () => {
|
it("should detect 1 record field added and 1 removed (total no. fields unchanged)", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
const notesField = newSetup.templateApi.getNewField("string")
|
const notesField = newSetup.templateApi.getNewField("string")
|
||||||
notesField.name = "notes"
|
notesField.name = "notes"
|
||||||
newSetup.templateApi.addField(newSetup.contact, notesField)
|
newSetup.templateApi.addField(newSetup.contact, notesField)
|
||||||
|
@ -151,8 +150,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect root record estimated record count changed", async () => {
|
it("should detect root record estimated record count changed", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
newSetup.contact.estimatedRecordCount = 987
|
newSetup.contact.estimatedRecordCount = 987
|
||||||
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -163,8 +162,8 @@ describe("diffHierarchy", () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect root record estimated record count changed", async () => {
|
it("should detect root record estimated record count changed", async () => {
|
||||||
const oldSetup = (await setup());
|
const oldSetup = await setup()
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
newSetup.deal.estimatedRecordCount = 987
|
newSetup.deal.estimatedRecordCount = 987
|
||||||
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
|
@ -174,44 +173,97 @@ describe("diffHierarchy", () => {
|
||||||
}])
|
}])
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should detect root record created", async () => {
|
it("should detect root index created", async () => {
|
||||||
const oldHierarchy = (await setup()).root;
|
const oldHierarchy = (await setup()).root
|
||||||
const newSetup = (await setup());
|
const newSetup = await setup()
|
||||||
const opportunity = newSetup.templateApi.getNewRecordTemplate(newSetup.root, "opportunity", false)
|
const all_deals = newSetup.templateApi.getNewIndexTemplate(newSetup.root)
|
||||||
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
||||||
expect(diff).toEqual([{
|
expect(diff).toEqual([{
|
||||||
newNode: opportunity,
|
newNode: all_deals,
|
||||||
oldNode: null,
|
oldNode: null,
|
||||||
type: HierarchyChangeTypes.recordCreated
|
type: HierarchyChangeTypes.indexCreated
|
||||||
}])
|
}])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("should detect child index created", async () => {
|
||||||
|
const oldHierarchy = (await setup()).root
|
||||||
|
const newSetup = await setup()
|
||||||
|
const all_deals = newSetup.templateApi.getNewIndexTemplate(newSetup.contact)
|
||||||
|
const diff = diffHierarchy(oldHierarchy, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: all_deals,
|
||||||
|
oldNode: null,
|
||||||
|
type: HierarchyChangeTypes.indexCreated
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect root index deleted", async () => {
|
||||||
|
const oldSetup = await setup()
|
||||||
|
const newSetup = await setup()
|
||||||
|
newSetup.root.indexes = newSetup.root.indexes.filter(i => i.name !== "contact_index")
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: null,
|
||||||
|
oldNode: oldSetup.root.indexes.find(i => i.name === "contact_index"),
|
||||||
|
type: HierarchyChangeTypes.indexDeleted
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should detect child index deleted", async () => {
|
||||||
|
const oldSetup = await setup()
|
||||||
|
const newSetup = await setup()
|
||||||
|
newSetup.contact.indexes = newSetup.contact.indexes.filter(i => i.name !== "deal_index")
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: null,
|
||||||
|
oldNode: oldSetup.contact.indexes.find(i => i.name === "deal_index"),
|
||||||
|
type: HierarchyChangeTypes.indexDeleted
|
||||||
|
}])
|
||||||
|
})
|
||||||
|
|
||||||
|
const testIndexChanged = (parent, makechange) => async () => {
|
||||||
|
const oldSetup = await setup()
|
||||||
|
const newSetup = await setup()
|
||||||
|
makechange(newSetup)
|
||||||
|
const diff = diffHierarchy(oldSetup.root, newSetup.root)
|
||||||
|
expect(diff).toEqual([{
|
||||||
|
newNode: newSetup[parent].indexes[0],
|
||||||
|
oldNode: oldSetup[parent].indexes[0],
|
||||||
|
type: HierarchyChangeTypes.indexChanged
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should detect root index map changed", testIndexChanged("root", newSetup => {
|
||||||
|
newSetup.root.indexes[0].map = "new"
|
||||||
|
}))
|
||||||
|
|
||||||
|
it("should detect root index filter changed", testIndexChanged("root", newSetup => {
|
||||||
|
newSetup.root.indexes[0].filter = "new"
|
||||||
|
}))
|
||||||
|
|
||||||
|
it("should detect root index shardName changed", testIndexChanged("root", newSetup => {
|
||||||
|
newSetup.root.indexes[0].getShardName = "new"
|
||||||
|
}))
|
||||||
|
|
||||||
|
it("should detect root index allowedRecordIds changed", testIndexChanged("root", newSetup => {
|
||||||
|
newSetup.root.indexes[0].allowedRecordNodeIds.push(3)
|
||||||
|
}))
|
||||||
|
|
||||||
|
it("should detect child index allowedRecordIds changed", testIndexChanged("contact", newSetup => {
|
||||||
|
newSetup.contact.indexes[0].allowedRecordNodeIds.push(3)
|
||||||
|
}))
|
||||||
|
|
||||||
|
it("should detect child index map changed", testIndexChanged("contact", newSetup => {
|
||||||
|
newSetup.contact.indexes[0].map = "new"
|
||||||
|
}))
|
||||||
|
|
||||||
|
it("should detect child index filter changed", testIndexChanged("contact", newSetup => {
|
||||||
|
newSetup.contact.indexes[0].filter = "new"
|
||||||
|
}))
|
||||||
|
|
||||||
|
it("should detect child index shardName changed", testIndexChanged("contact", newSetup => {
|
||||||
|
newSetup.contact.indexes[0].getShardName = "new"
|
||||||
|
}))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const setup = async () => {
|
|
||||||
const { templateApi } = await getMemoryTemplateApi()
|
|
||||||
const root = templateApi.getNewRootLevel()
|
|
||||||
const contact = templateApi.getNewRecordTemplate(root, "contact", true)
|
|
||||||
|
|
||||||
const nameField = templateApi.getNewField("string")
|
|
||||||
nameField.name = "name"
|
|
||||||
const statusField = templateApi.getNewField("string")
|
|
||||||
statusField.name = "status"
|
|
||||||
|
|
||||||
templateApi.addField(contact, nameField)
|
|
||||||
templateApi.addField(contact, statusField)
|
|
||||||
|
|
||||||
const lead = templateApi.getNewRecordTemplate(root, "lead", true)
|
|
||||||
const deal = templateApi.getNewRecordTemplate(contact, "deal", true)
|
|
||||||
|
|
||||||
templateApi.addField(deal, {...nameField})
|
|
||||||
templateApi.addField(deal, {...statusField})
|
|
||||||
|
|
||||||
getFlattenedHierarchy(root)
|
|
||||||
return {
|
|
||||||
root, contact, lead, deal, templateApi,
|
|
||||||
all_contacts: root.indexes[0],
|
|
||||||
all_leads: root.indexes[1],
|
|
||||||
deals_for_contacts: contact.indexes[0]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
import {
|
||||||
|
getRecordApiFromTemplateApi,
|
||||||
|
getIndexApiFromTemplateApi,
|
||||||
|
} from "./specHelpers"
|
||||||
|
import { upgradeData } from "../src/templateApi/upgradeData"
|
||||||
|
import { setup } from "./upgradeDataSetup"
|
||||||
|
import { $, splitKey } from "../src/common"
|
||||||
|
import { keys, filter } from "lodash/fp"
|
||||||
|
import { _listItems } from "../src/indexApi/listItems"
|
||||||
|
import { _save } from "../src/recordApi/save"
|
||||||
|
|
||||||
|
describe("upgradeData", () => {
|
||||||
|
|
||||||
|
it("should delete all records and child records, when root record node deleted", async () => {
|
||||||
|
const { oldSetup, newSetup, recordApi } = await configure()
|
||||||
|
newSetup.root.children = newSetup.root.children.filter(n => n.name !== "contact")
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
const remainingKeys = $(recordApi._storeHandle.data, [
|
||||||
|
keys,
|
||||||
|
filter(k => splitKey(k)[0] === "contacts"),
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(remainingKeys.length).toBe(0)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should not delete other root record types, when root record node deleted", async () => {
|
||||||
|
const { oldSetup, newSetup, recordApi } = await configure()
|
||||||
|
newSetup.root.children = newSetup.root.children.filter(n => n.name !== "contact")
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
const remainingKeys = $(recordApi._storeHandle.data, [
|
||||||
|
keys,
|
||||||
|
filter(k => splitKey(k)[0] === "leads"),
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(remainingKeys.length > 0).toBe(true)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should delete all child records, when child record node deleted", async () => {
|
||||||
|
const { oldSetup, newSetup, recordApi } = await configure()
|
||||||
|
newSetup.contact.children = newSetup.contact.children.filter(n => n.name !== "deal")
|
||||||
|
|
||||||
|
const startingKeys = $(recordApi._storeHandle.data, [
|
||||||
|
keys,
|
||||||
|
filter(k => k.includes("/deals/")),
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(startingKeys.length > 0).toBe(true)
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
const remainingKeys = $(recordApi._storeHandle.data, [
|
||||||
|
keys,
|
||||||
|
filter(k => k.includes("/deals/")),
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(remainingKeys.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should build a new root index", async () => {
|
||||||
|
const { oldSetup, newSetup } = await configure()
|
||||||
|
const newIndex = newSetup.templateApi.getNewIndexTemplate(newSetup.root)
|
||||||
|
newIndex.name = "more_contacts"
|
||||||
|
newIndex.allowedRecordNodeIds = [newSetup.contact.nodeId]
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
const itemsInNewIndex = await _listItems(newSetup.app, "/more_contacts")
|
||||||
|
|
||||||
|
expect(itemsInNewIndex.length).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a root index", async () => {
|
||||||
|
const { oldSetup, newSetup } = await configure()
|
||||||
|
const contact_index = indexByName(newSetup.root, "contact_index")
|
||||||
|
contact_index.filter = "record.name === 'bobby'"
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
const itemsInNewIndex = await _listItems(newSetup.app, "/contact_index")
|
||||||
|
|
||||||
|
expect(itemsInNewIndex.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should delete a root index", async () => {
|
||||||
|
const { oldSetup, newSetup } = await configure()
|
||||||
|
|
||||||
|
// no exception
|
||||||
|
await _listItems(newSetup.app, "/contact_index")
|
||||||
|
|
||||||
|
newSetup.root.indexes = newSetup.root.indexes.filter(i => i.name !== "contact_index")
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
let er
|
||||||
|
try {
|
||||||
|
await _listItems(newSetup.app, "/contact_index")
|
||||||
|
} catch (e) {
|
||||||
|
er = e
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(er).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should build a new child index", async () => {
|
||||||
|
const { oldSetup, newSetup, records } = await configure()
|
||||||
|
const newIndex = newSetup.templateApi.getNewIndexTemplate(newSetup.contact)
|
||||||
|
newIndex.name = "more_deals"
|
||||||
|
newIndex.allowedRecordNodeIds = [newSetup.deal.nodeId]
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
const itemsInNewIndex = await _listItems(newSetup.app, `${records.contact1.key}/more_deals`)
|
||||||
|
|
||||||
|
expect(itemsInNewIndex.length).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should update a child index", async () => {
|
||||||
|
const { oldSetup, newSetup, records } = await configure()
|
||||||
|
const deal_index = indexByName(newSetup.contact, "deal_index")
|
||||||
|
deal_index.filter = "record.status === 'new'"
|
||||||
|
|
||||||
|
let itemsInIndex = await _listItems(newSetup.app, `${records.contact1.key}/deal_index`)
|
||||||
|
expect(itemsInIndex.length).toBe(2)
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
itemsInIndex = await _listItems(newSetup.app, `${records.contact1.key}/deal_index`)
|
||||||
|
expect(itemsInIndex.length).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should delete a child index", async () => {
|
||||||
|
const { oldSetup, newSetup, records } = await configure()
|
||||||
|
|
||||||
|
// no exception
|
||||||
|
await _listItems(newSetup.app, `${records.contact1.key}/deal_index`)
|
||||||
|
|
||||||
|
newSetup.contact.indexes = newSetup.contact.indexes.filter(i => i.name !== "deal_index")
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
let er
|
||||||
|
try {
|
||||||
|
await _listItems(newSetup.app, `${records.contact1.key}/deal_index`)
|
||||||
|
} catch (e) {
|
||||||
|
er = e
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(er).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should build a new reference index", async () => {
|
||||||
|
const { oldSetup, newSetup, records, recordApi } = await configure()
|
||||||
|
const newIndex = newSetup.templateApi.getNewIndexTemplate(newSetup.lead)
|
||||||
|
newIndex.name = "contact_leads"
|
||||||
|
newIndex.allowedRecordNodeIds = [newSetup.lead.nodeId]
|
||||||
|
newIndex.indexType = "reference"
|
||||||
|
|
||||||
|
const leadField = newSetup.templateApi.getNewField("string")
|
||||||
|
leadField.name = "lead"
|
||||||
|
leadField.type = "reference"
|
||||||
|
leadField.typeOptions = {
|
||||||
|
reverseIndexNodeKeys: [ newIndex.nodeKey() ],
|
||||||
|
indexNodeKey: "/lead_index",
|
||||||
|
displayValue: "name"
|
||||||
|
}
|
||||||
|
|
||||||
|
newSetup.templateApi.addField(newSetup.contact, leadField)
|
||||||
|
|
||||||
|
await upgradeData(oldSetup.app)(newSetup.root)
|
||||||
|
|
||||||
|
const indexKey = `${records.lead1.key}/contact_leads`
|
||||||
|
|
||||||
|
let itemsInNewIndex = await _listItems(newSetup.app, indexKey)
|
||||||
|
|
||||||
|
expect(itemsInNewIndex.length).toBe(0)
|
||||||
|
|
||||||
|
records.contact1.lead = records.lead1
|
||||||
|
records.contact1.isNew = false
|
||||||
|
|
||||||
|
await _save(newSetup.app, records.contact1)
|
||||||
|
|
||||||
|
itemsInNewIndex = await _listItems(newSetup.app, indexKey)
|
||||||
|
|
||||||
|
expect(itemsInNewIndex.length).toBe(1)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
const configure = async () => {
|
||||||
|
const oldSetup = await setup()
|
||||||
|
|
||||||
|
const recordApi = await getRecordApiFromTemplateApi(oldSetup.templateApi)
|
||||||
|
const indexApi = await getIndexApiFromTemplateApi(oldSetup.templateApi)
|
||||||
|
|
||||||
|
const newSetup = await setup(oldSetup.store)
|
||||||
|
|
||||||
|
const records = await createSomeRecords(recordApi)
|
||||||
|
|
||||||
|
return { oldSetup, newSetup, recordApi, records, indexApi }
|
||||||
|
}
|
||||||
|
|
||||||
|
const createSomeRecords = async recordApi => {
|
||||||
|
const contact1 = recordApi.getNew("/contacts", "contact")
|
||||||
|
contact1.name = "bobby"
|
||||||
|
const contact2 = recordApi.getNew("/contacts", "contact")
|
||||||
|
contact2.name = "poppy"
|
||||||
|
|
||||||
|
await recordApi.save(contact1)
|
||||||
|
await recordApi.save(contact2)
|
||||||
|
|
||||||
|
const deal1 = recordApi.getNew(`${contact1.key}/deals`, "deal")
|
||||||
|
deal1.name = "big mad deal"
|
||||||
|
deal1.status = "new"
|
||||||
|
const deal2 = recordApi.getNew(`${contact1.key}/deals`, "deal")
|
||||||
|
deal2.name = "smaller deal"
|
||||||
|
deal2.status = "old"
|
||||||
|
const deal3 = recordApi.getNew(`${contact2.key}/deals`, "deal")
|
||||||
|
deal3.name = "ok deal"
|
||||||
|
deal3.status = "new"
|
||||||
|
|
||||||
|
await recordApi.save(deal1)
|
||||||
|
await recordApi.save(deal2)
|
||||||
|
await recordApi.save(deal3)
|
||||||
|
|
||||||
|
const lead1 = recordApi.getNew("/leads", "lead")
|
||||||
|
lead1.name = "big new lead"
|
||||||
|
|
||||||
|
await recordApi.save(lead1)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return {
|
||||||
|
contact1, contact2, deal1, deal2, deal3, lead1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexByName = (parent, name) => parent.indexes.find(i => i.name === name)
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { getMemoryTemplateApi, appFromTempalteApi } from "./specHelpers"
|
||||||
|
import { getFlattenedHierarchy } from "../src/templateApi/hierarchy"
|
||||||
|
import { initialiseData } from "../src/appInitialise/initialiseData"
|
||||||
|
|
||||||
|
export const setup = async (store) => {
|
||||||
|
const { templateApi } = await getMemoryTemplateApi(store)
|
||||||
|
const root = templateApi.getNewRootLevel()
|
||||||
|
const contact = templateApi.getNewRecordTemplate(root, "contact", true)
|
||||||
|
contact.collectionName = "contacts"
|
||||||
|
|
||||||
|
const nameField = templateApi.getNewField("string")
|
||||||
|
nameField.name = "name"
|
||||||
|
const statusField = templateApi.getNewField("string")
|
||||||
|
statusField.name = "status"
|
||||||
|
|
||||||
|
templateApi.addField(contact, nameField)
|
||||||
|
templateApi.addField(contact, statusField)
|
||||||
|
|
||||||
|
const lead = templateApi.getNewRecordTemplate(root, "lead", true)
|
||||||
|
lead.collectionName = "leads"
|
||||||
|
const deal = templateApi.getNewRecordTemplate(contact, "deal", true)
|
||||||
|
deal.collectionName = "deals"
|
||||||
|
|
||||||
|
templateApi.addField(deal, {...nameField})
|
||||||
|
templateApi.addField(deal, {...statusField})
|
||||||
|
|
||||||
|
templateApi.addField(lead, {...nameField})
|
||||||
|
|
||||||
|
getFlattenedHierarchy(root)
|
||||||
|
|
||||||
|
if (!store)
|
||||||
|
await initialiseData(templateApi._storeHandle, {
|
||||||
|
hierarchy: root,
|
||||||
|
actions: [],
|
||||||
|
triggers: [],
|
||||||
|
})
|
||||||
|
const app = await appFromTempalteApi(templateApi)
|
||||||
|
app.hierarchy = root
|
||||||
|
|
||||||
|
return {
|
||||||
|
root, contact, lead, app,
|
||||||
|
deal, templateApi, store: templateApi._storeHandle,
|
||||||
|
all_contacts: root.indexes[0],
|
||||||
|
all_leads: root.indexes[1],
|
||||||
|
deals_for_contacts: contact.indexes[0],
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,6 +18,7 @@ const lookupField = require("./lookupField")
|
||||||
const getRecord = require("./getRecord")
|
const getRecord = require("./getRecord")
|
||||||
const deleteRecord = require("./deleteRecord")
|
const deleteRecord = require("./deleteRecord")
|
||||||
const saveAppHierarchy = require("./saveAppHierarchy")
|
const saveAppHierarchy = require("./saveAppHierarchy")
|
||||||
|
const upgradeData = require("./saveAppHierarchy")
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
authenticate,
|
authenticate,
|
||||||
|
@ -40,4 +41,5 @@ module.exports = {
|
||||||
getRecord,
|
getRecord,
|
||||||
deleteRecord,
|
deleteRecord,
|
||||||
saveAppHierarchy,
|
saveAppHierarchy,
|
||||||
|
upgradeData,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
const StatusCodes = require("../../utilities/statusCodes")
|
||||||
|
|
||||||
|
module.exports = async ctx => {
|
||||||
|
await ctx.instance.templateApi.upgradeData(ctx.request.body.newHierarchy)
|
||||||
|
ctx.response.status = StatusCodes.OK
|
||||||
|
}
|
|
@ -238,6 +238,10 @@ module.exports = (config, app) => {
|
||||||
ctx.response.status = StatusCodes.UNAUTHORIZED
|
ctx.response.status = StatusCodes.UNAUTHORIZED
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.post(
|
||||||
|
"/_builder/instance/:appname/:instanceid/api/upgradeData",
|
||||||
|
routeHandlers.upgradeData
|
||||||
|
)
|
||||||
.post("/:appname/api/changeMyPassword", routeHandlers.changeMyPassword)
|
.post("/:appname/api/changeMyPassword", routeHandlers.changeMyPassword)
|
||||||
.post(
|
.post(
|
||||||
"/_builder/instance/:appname/:instanceid/api/changeMyPassword",
|
"/_builder/instance/:appname/:instanceid/api/changeMyPassword",
|
||||||
|
|
|
@ -78,13 +78,12 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
color: #333;
|
color: #000333;
|
||||||
background-color: #f4f4f4;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.default:active {
|
.default:active {
|
||||||
background-color: #ddd;
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.default:focus {
|
.default:focus {
|
||||||
|
|
|
@ -155,16 +155,15 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
color: #333;
|
color: #000333;
|
||||||
background-color: #f4f4f4;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.default-button:active {
|
.default-button:active {
|
||||||
background-color: #ddd;
|
background-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.default-button:focus {
|
.default-button:focus {
|
||||||
border-color: #666;
|
border-color: #f9f9f9;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
Loading…
Reference in New Issue