forked from tsai/budibase
Browse Source
# Conflicts: # packages/builder/src/components/design/NavigationPanel/NewScreenModal.sveltegh-pages
133 changed files with 2877 additions and 851 deletions
@ -1,5 +1,6 @@ |
|||
const env = require("../src/environment") |
|||
|
|||
env._set("SELF_HOSTED", "1") |
|||
env._set("NODE_ENV", "jest") |
|||
env._set("JWT_SECRET", "test-jwtsecret") |
|||
env._set("LOG_LEVEL", "silent") |
|||
|
|||
@ -0,0 +1,22 @@ |
|||
const API = require("./api") |
|||
const env = require("../environment") |
|||
|
|||
const api = new API(env.ACCOUNT_PORTAL_URL) |
|||
|
|||
// TODO: Authorization
|
|||
|
|||
exports.getAccount = async email => { |
|||
const payload = { |
|||
email, |
|||
} |
|||
const response = await api.post(`/api/accounts/search`, { |
|||
body: payload, |
|||
}) |
|||
const json = await response.json() |
|||
|
|||
if (response.status !== 200) { |
|||
throw Error(`Error getting account by email ${email}`, json) |
|||
} |
|||
|
|||
return json[0] |
|||
} |
|||
@ -0,0 +1,44 @@ |
|||
const fetch = require("node-fetch") |
|||
class API { |
|||
constructor(host) { |
|||
this.host = host |
|||
} |
|||
|
|||
apiCall = |
|||
method => |
|||
async (url = "", options = {}) => { |
|||
if (!options.headers) { |
|||
options.headers = {} |
|||
} |
|||
|
|||
if (!options.headers["Content-Type"]) { |
|||
options.headers = { |
|||
"Content-Type": "application/json", |
|||
Accept: "application/json", |
|||
...options.headers, |
|||
} |
|||
} |
|||
|
|||
let json = options.headers["Content-Type"] === "application/json" |
|||
|
|||
const requestOptions = { |
|||
method: method, |
|||
body: json ? JSON.stringify(options.body) : options.body, |
|||
headers: options.headers, |
|||
// TODO: See if this is necessary
|
|||
credentials: "include", |
|||
} |
|||
|
|||
const resp = await fetch(`${this.host}${url}`, requestOptions) |
|||
|
|||
return resp |
|||
} |
|||
|
|||
post = this.apiCall("POST") |
|||
get = this.apiCall("GET") |
|||
patch = this.apiCall("PATCH") |
|||
del = this.apiCall("DELETE") |
|||
put = this.apiCall("PUT") |
|||
} |
|||
|
|||
module.exports = API |
|||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@ -1,139 +0,0 @@ |
|||
import * as Sentry from "@sentry/browser" |
|||
import posthog from "posthog-js" |
|||
import api from "builderStore/api" |
|||
|
|||
let analyticsEnabled |
|||
const posthogConfigured = process.env.POSTHOG_TOKEN && process.env.POSTHOG_URL |
|||
const sentryConfigured = process.env.SENTRY_DSN |
|||
|
|||
const FEEDBACK_SUBMITTED_KEY = "budibase:feedback_submitted" |
|||
const APP_FIRST_STARTED_KEY = "budibase:first_run" |
|||
const feedbackHours = 12 |
|||
|
|||
async function activate() { |
|||
if (analyticsEnabled === undefined) { |
|||
// only the server knows the true NODE_ENV
|
|||
// this was an issue as NODE_ENV = 'cypress' on the server,
|
|||
// but 'production' on the client
|
|||
const response = await api.get("/api/analytics") |
|||
analyticsEnabled = (await response.json()).enabled === true |
|||
} |
|||
if (!analyticsEnabled) return |
|||
if (sentryConfigured) Sentry.init({ dsn: process.env.SENTRY_DSN }) |
|||
if (posthogConfigured) { |
|||
posthog.init(process.env.POSTHOG_TOKEN, { |
|||
autocapture: false, |
|||
capture_pageview: false, |
|||
api_host: process.env.POSTHOG_URL, |
|||
}) |
|||
posthog.set_config({ persistence: "cookie" }) |
|||
} |
|||
} |
|||
|
|||
function identify(id) { |
|||
if (!analyticsEnabled || !id) return |
|||
if (posthogConfigured) posthog.identify(id) |
|||
if (sentryConfigured) |
|||
Sentry.configureScope(scope => { |
|||
scope.setUser({ id: id }) |
|||
}) |
|||
} |
|||
|
|||
async function identifyByApiKey(apiKey) { |
|||
if (!analyticsEnabled) return true |
|||
try { |
|||
const response = await fetch( |
|||
`https://03gaine137.execute-api.eu-west-1.amazonaws.com/prod/account/id?api_key=${apiKey.trim()}` |
|||
) |
|||
if (response.status === 200) { |
|||
const id = await response.json() |
|||
|
|||
await api.put("/api/keys/userId", { value: id }) |
|||
identify(id) |
|||
return true |
|||
} |
|||
|
|||
return false |
|||
} catch (error) { |
|||
console.log(error) |
|||
} |
|||
} |
|||
|
|||
function captureException(err) { |
|||
if (!analyticsEnabled) return |
|||
Sentry.captureException(err) |
|||
captureEvent("Error", { error: err.message ? err.message : err }) |
|||
} |
|||
|
|||
function captureEvent(eventName, props = {}) { |
|||
if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return |
|||
props.sourceApp = "builder" |
|||
posthog.capture(eventName, props) |
|||
} |
|||
|
|||
if (!localStorage.getItem(APP_FIRST_STARTED_KEY)) { |
|||
localStorage.setItem(APP_FIRST_STARTED_KEY, Date.now()) |
|||
} |
|||
|
|||
const isFeedbackTimeElapsed = sinceDateStr => { |
|||
const sinceDate = parseFloat(sinceDateStr) |
|||
const feedbackMilliseconds = feedbackHours * 60 * 60 * 1000 |
|||
return Date.now() > sinceDate + feedbackMilliseconds |
|||
} |
|||
|
|||
function submitFeedback(values) { |
|||
if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return |
|||
localStorage.setItem(FEEDBACK_SUBMITTED_KEY, Date.now()) |
|||
|
|||
const prefixedValues = Object.entries(values).reduce((obj, [key, value]) => { |
|||
obj[`feedback_${key}`] = value |
|||
return obj |
|||
}, {}) |
|||
|
|||
posthog.capture("Feedback Submitted", prefixedValues) |
|||
} |
|||
|
|||
function requestFeedbackOnDeploy() { |
|||
if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false |
|||
const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY) |
|||
if (!lastSubmittedStr) return true |
|||
return isFeedbackTimeElapsed(lastSubmittedStr) |
|||
} |
|||
|
|||
function highlightFeedbackIcon() { |
|||
if (!analyticsEnabled || !process.env.POSTHOG_TOKEN) return false |
|||
const lastSubmittedStr = localStorage.getItem(FEEDBACK_SUBMITTED_KEY) |
|||
if (lastSubmittedStr) return isFeedbackTimeElapsed(lastSubmittedStr) |
|||
const firstRunStr = localStorage.getItem(APP_FIRST_STARTED_KEY) |
|||
if (!firstRunStr) return false |
|||
return isFeedbackTimeElapsed(firstRunStr) |
|||
} |
|||
|
|||
// Opt In/Out
|
|||
const ifAnalyticsEnabled = func => () => { |
|||
if (analyticsEnabled && process.env.POSTHOG_TOKEN) { |
|||
return func() |
|||
} |
|||
} |
|||
const disabled = () => posthog.has_opted_out_capturing() |
|||
const optIn = () => posthog.opt_in_capturing() |
|||
const optOut = () => posthog.opt_out_capturing() |
|||
|
|||
export default { |
|||
activate, |
|||
identify, |
|||
identifyByApiKey, |
|||
captureException, |
|||
captureEvent, |
|||
requestFeedbackOnDeploy, |
|||
submitFeedback, |
|||
highlightFeedbackIcon, |
|||
disabled: () => { |
|||
if (analyticsEnabled == null) { |
|||
return true |
|||
} |
|||
return ifAnalyticsEnabled(disabled) |
|||
}, |
|||
optIn: ifAnalyticsEnabled(optIn), |
|||
optOut: ifAnalyticsEnabled(optOut), |
|||
} |
|||
@ -0,0 +1,94 @@ |
|||
export default class IntercomClient { |
|||
constructor(token) { |
|||
this.token = token |
|||
} |
|||
|
|||
/** |
|||
* Instantiate intercom using their provided script. |
|||
*/ |
|||
init() { |
|||
if (!this.token) return |
|||
|
|||
const token = this.token |
|||
|
|||
var w = window |
|||
var ic = w.Intercom |
|||
if (typeof ic === "function") { |
|||
ic("reattach_activator") |
|||
ic("update", w.intercomSettings) |
|||
} else { |
|||
var d = document |
|||
var i = function () { |
|||
i.c(arguments) |
|||
} |
|||
i.q = [] |
|||
i.c = function (args) { |
|||
i.q.push(args) |
|||
} |
|||
w.Intercom = i |
|||
var l = function () { |
|||
var s = d.createElement("script") |
|||
s.type = "text/javascript" |
|||
s.async = true |
|||
s.src = "https://widget.intercom.io/widget/" + token |
|||
var x = d.getElementsByTagName("script")[0] |
|||
x.parentNode.insertBefore(s, x) |
|||
} |
|||
if (document.readyState === "complete") { |
|||
l() |
|||
} else if (w.attachEvent) { |
|||
w.attachEvent("onload", l) |
|||
} else { |
|||
w.addEventListener("load", l, false) |
|||
} |
|||
|
|||
this.initialised = true |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Show the intercom chat bubble. |
|||
* @param {Object} user - user to identify |
|||
* @returns Intercom global object |
|||
*/ |
|||
show(user = {}) { |
|||
if (!this.initialised) return |
|||
|
|||
return window.Intercom("boot", { |
|||
app_id: this.token, |
|||
...user, |
|||
}) |
|||
} |
|||
|
|||
/** |
|||
* Update intercom user details and messages. |
|||
* @returns Intercom global object |
|||
*/ |
|||
update() { |
|||
if (!this.initialised) return |
|||
|
|||
return window.Intercom("update") |
|||
} |
|||
|
|||
/** |
|||
* Capture analytics events and send them to intercom. |
|||
* @param {String} event - event identifier |
|||
* @param {Object} props - properties for the event |
|||
* @returns Intercom global object |
|||
*/ |
|||
captureEvent(event, props = {}) { |
|||
if (!this.initialised) return |
|||
|
|||
return window.Intercom("trackEvent", event, props) |
|||
} |
|||
|
|||
/** |
|||
* Disassociate the user from the current session. |
|||
* @returns Intercom global object |
|||
*/ |
|||
logout() { |
|||
if (!this.initialised) return |
|||
|
|||
return window.Intercom("shutdown") |
|||
} |
|||
} |
|||
@ -0,0 +1,80 @@ |
|||
import posthog from "posthog-js" |
|||
import { Events } from "./constants" |
|||
|
|||
export default class PosthogClient { |
|||
constructor(token, url) { |
|||
this.token = token |
|||
this.url = url |
|||
} |
|||
|
|||
init() { |
|||
if (!this.token || !this.url) return |
|||
|
|||
posthog.init(this.token, { |
|||
autocapture: false, |
|||
capture_pageview: false, |
|||
api_host: this.url, |
|||
}) |
|||
posthog.set_config({ persistence: "cookie" }) |
|||
|
|||
this.initialised = true |
|||
} |
|||
|
|||
/** |
|||
* Set the posthog context to the current user |
|||
* @param {String} id - unique user id |
|||
*/ |
|||
identify(id) { |
|||
if (!this.initialised) return |
|||
|
|||
posthog.identify(id) |
|||
} |
|||
|
|||
/** |
|||
* Update user metadata associated with current user in posthog |
|||
* @param {Object} meta - user fields |
|||
*/ |
|||
updateUser(meta) { |
|||
if (!this.initialised) return |
|||
|
|||
posthog.people.set(meta) |
|||
} |
|||
|
|||
/** |
|||
* Capture analytics events and send them to posthog. |
|||
* @param {String} event - event identifier |
|||
* @param {Object} props - properties for the event |
|||
*/ |
|||
captureEvent(eventName, props) { |
|||
if (!this.initialised) return |
|||
|
|||
props.sourceApp = "builder" |
|||
posthog.capture(eventName, props) |
|||
} |
|||
|
|||
/** |
|||
* Submit NPS feedback to posthog. |
|||
* @param {Object} values - NPS Values |
|||
*/ |
|||
npsFeedback(values) { |
|||
if (!this.initialised) return |
|||
|
|||
localStorage.setItem(Events.NPS.SUBMITTED, Date.now()) |
|||
|
|||
const prefixedFeedback = {} |
|||
for (let key in values) { |
|||
prefixedFeedback[`feedback_${key}`] = values[key] |
|||
} |
|||
|
|||
posthog.capture(Events.NPS.SUBMITTED, prefixedFeedback) |
|||
} |
|||
|
|||
/** |
|||
* Reset posthog user back to initial state on logout. |
|||
*/ |
|||
logout() { |
|||
if (!this.initialised) return |
|||
|
|||
posthog.reset() |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
import * as Sentry from "@sentry/browser" |
|||
|
|||
export default class SentryClient { |
|||
constructor(dsn) { |
|||
this.dsn = dsn |
|||
} |
|||
|
|||
init() { |
|||
if (this.dsn) { |
|||
Sentry.init({ dsn: this.dsn }) |
|||
|
|||
this.initalised = true |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* Capture an exception and send it to sentry. |
|||
* @param {Error} err - JS error object |
|||
*/ |
|||
captureException(err) { |
|||
if (!this.initalised) return |
|||
|
|||
Sentry.captureException(err) |
|||
} |
|||
|
|||
/** |
|||
* Identify user in sentry. |
|||
* @param {String} id - Unique user id |
|||
*/ |
|||
identify(id) { |
|||
if (!this.initalised) return |
|||
|
|||
Sentry.configureScope(scope => { |
|||
scope.setUser({ id }) |
|||
}) |
|||
} |
|||
} |
|||
@ -0,0 +1,49 @@ |
|||
export const Events = { |
|||
BUILDER: { |
|||
STARTED: "Builder Started", |
|||
}, |
|||
COMPONENT: { |
|||
CREATED: "Added Component", |
|||
}, |
|||
DATASOURCE: { |
|||
CREATED: "Datasource Created", |
|||
UPDATED: "Datasource Updated", |
|||
}, |
|||
TABLE: { |
|||
CREATED: "Table Created", |
|||
}, |
|||
VIEW: { |
|||
CREATED: "View Created", |
|||
ADDED_FILTER: "Added View Filter", |
|||
ADDED_CALCULATE: "Added View Calculate", |
|||
}, |
|||
SCREEN: { |
|||
CREATED: "Screen Created", |
|||
}, |
|||
AUTOMATION: { |
|||
CREATED: "Automation Created", |
|||
SAVED: "Automation Saved", |
|||
BLOCK_ADDED: "Added Automation Block", |
|||
}, |
|||
NPS: { |
|||
SUBMITTED: "budibase:feedback_submitted", |
|||
}, |
|||
APP: { |
|||
CREATED: "budibase:app_created", |
|||
PUBLISHED: "budibase:app_published", |
|||
UNPUBLISHED: "budibase:app_unpublished", |
|||
}, |
|||
ANALYTICS: { |
|||
OPT_IN: "budibase:analytics_opt_in", |
|||
OPT_OUT: "budibase:analytics_opt_out", |
|||
}, |
|||
USER: { |
|||
INVITE: "budibase:portal_user_invite", |
|||
}, |
|||
SMTP: { |
|||
SAVED: "budibase:smtp_saved", |
|||
}, |
|||
SSO: { |
|||
SAVED: "budibase:sso_saved", |
|||
}, |
|||
} |
|||
@ -0,0 +1,79 @@ |
|||
import api from "builderStore/api" |
|||
import PosthogClient from "./PosthogClient" |
|||
import IntercomClient from "./IntercomClient" |
|||
import SentryClient from "./SentryClient" |
|||
import { Events } from "./constants" |
|||
import { auth } from "stores/portal" |
|||
import { get } from "svelte/store" |
|||
|
|||
const posthog = new PosthogClient( |
|||
process.env.POSTHOG_TOKEN, |
|||
process.env.POSTHOG_URL |
|||
) |
|||
const sentry = new SentryClient(process.env.SENTRY_DSN) |
|||
const intercom = new IntercomClient(process.env.INTERCOM_TOKEN) |
|||
|
|||
class AnalyticsHub { |
|||
constructor() { |
|||
this.clients = [posthog, sentry, intercom] |
|||
} |
|||
|
|||
async activate() { |
|||
// Setting the analytics env var off in the backend overrides org/tenant settings
|
|||
const analyticsStatus = await api.get("/api/analytics") |
|||
const json = await analyticsStatus.json() |
|||
|
|||
// Multitenancy disabled on the backend
|
|||
if (!json.enabled) return |
|||
|
|||
const tenantId = get(auth).tenantId |
|||
|
|||
if (tenantId) { |
|||
const res = await api.get( |
|||
`/api/global/configs/public?tenantId=${tenantId}` |
|||
) |
|||
const orgJson = await res.json() |
|||
|
|||
// analytics opted out for the tenant
|
|||
if (orgJson.config?.analytics === false) return |
|||
} |
|||
|
|||
this.clients.forEach(client => client.init()) |
|||
this.enabled = true |
|||
} |
|||
|
|||
identify(id, metadata) { |
|||
posthog.identify(id) |
|||
if (metadata) { |
|||
posthog.updateUser(metadata) |
|||
} |
|||
sentry.identify(id) |
|||
} |
|||
|
|||
captureException(err) { |
|||
sentry.captureException(err) |
|||
} |
|||
|
|||
captureEvent(eventName, props = {}) { |
|||
posthog.captureEvent(eventName, props) |
|||
intercom.captureEvent(eventName, props) |
|||
} |
|||
|
|||
showChat(user) { |
|||
intercom.show(user) |
|||
} |
|||
|
|||
submitFeedback(values) { |
|||
posthog.npsFeedback(values) |
|||
} |
|||
|
|||
async logout() { |
|||
posthog.logout() |
|||
intercom.logout() |
|||
} |
|||
} |
|||
|
|||
const analytics = new AnalyticsHub() |
|||
|
|||
export { Events } |
|||
export default analytics |
|||
@ -0,0 +1,76 @@ |
|||
<script> |
|||
import { automationStore } from "builderStore" |
|||
import { notifications } from "@budibase/bbui" |
|||
import { Icon, Input, ModalContent, Modal } from "@budibase/bbui" |
|||
import analytics, { Events } from "analytics" |
|||
|
|||
let name |
|||
let error = "" |
|||
let modal |
|||
|
|||
export let automation |
|||
export let onCancel = undefined |
|||
|
|||
export const show = () => { |
|||
name = automation?.name |
|||
modal.show() |
|||
} |
|||
export const hide = () => { |
|||
modal.hide() |
|||
} |
|||
|
|||
async function saveAutomation() { |
|||
const updatedAutomation = { |
|||
...automation, |
|||
name, |
|||
} |
|||
await automationStore.actions.save(updatedAutomation) |
|||
notifications.success(`Automation ${name} updated successfully.`) |
|||
analytics.captureEvent(Events.AUTOMATION.SAVED, { name }) |
|||
hide() |
|||
} |
|||
|
|||
function checkValid(evt) { |
|||
name = evt.target.value |
|||
if (!name) { |
|||
error = "Name is required" |
|||
return |
|||
} |
|||
error = "" |
|||
} |
|||
</script> |
|||
|
|||
<Modal bind:this={modal} on:hide={onCancel}> |
|||
<ModalContent |
|||
title="Edit Automation" |
|||
confirmText="Save" |
|||
size="L" |
|||
onConfirm={saveAutomation} |
|||
disabled={error} |
|||
> |
|||
<Input bind:value={name} label="Name" on:input={checkValid} {error} /> |
|||
<a |
|||
slot="footer" |
|||
target="_blank" |
|||
href="https://docs.budibase.com/automate/introduction-to-automate" |
|||
> |
|||
<Icon name="InfoOutline" /> |
|||
<span>Learn about automations</span> |
|||
</a> |
|||
</ModalContent> |
|||
</Modal> |
|||
|
|||
<style> |
|||
a { |
|||
color: var(--ink); |
|||
font-size: 14px; |
|||
vertical-align: middle; |
|||
display: flex; |
|||
align-items: center; |
|||
text-decoration: none; |
|||
} |
|||
a span { |
|||
text-decoration: underline; |
|||
margin-left: var(--spectrum-alias-item-padding-s); |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
@ -0,0 +1,28 @@ |
|||
version: "3.8" |
|||
services: |
|||
db: |
|||
container_name: postgres-vehicle |
|||
image: postgres |
|||
restart: always |
|||
environment: |
|||
POSTGRES_USER: root |
|||
POSTGRES_PASSWORD: root |
|||
POSTGRES_DB: main |
|||
ports: |
|||
- "5432:5432" |
|||
volumes: |
|||
#- pg_data:/var/lib/postgresql/data/ |
|||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql |
|||
|
|||
pgadmin: |
|||
container_name: pgadmin |
|||
image: dpage/pgadmin4 |
|||
restart: always |
|||
environment: |
|||
PGADMIN_DEFAULT_EMAIL: root@root.com |
|||
PGADMIN_DEFAULT_PASSWORD: root |
|||
ports: |
|||
- "5050:80" |
|||
|
|||
#volumes: |
|||
# pg_data: |
|||
@ -0,0 +1,52 @@ |
|||
SELECT 'CREATE DATABASE main' |
|||
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'main')\gexec |
|||
CREATE TABLE Vehicles ( |
|||
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ), |
|||
Registration text COLLATE pg_catalog."default", |
|||
Make text COLLATE pg_catalog."default", |
|||
Model text COLLATE pg_catalog."default", |
|||
Colour text COLLATE pg_catalog."default", |
|||
Year smallint, |
|||
CONSTRAINT Vehicles_pkey PRIMARY KEY (id) |
|||
); |
|||
|
|||
CREATE TABLE ServiceLog ( |
|||
id bigint NOT NULL GENERATED ALWAYS AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ), |
|||
Description text COLLATE pg_catalog."default", |
|||
VehicleId bigint, |
|||
ServiceDate timestamp without time zone, |
|||
Category text COLLATE pg_catalog."default", |
|||
Mileage bigint, |
|||
CONSTRAINT ServiceLog_pkey PRIMARY KEY (id), |
|||
CONSTRAINT vehicle_foreign_key FOREIGN KEY (VehicleId) |
|||
REFERENCES Vehicles (id) MATCH SIMPLE |
|||
ON UPDATE NO ACTION |
|||
ON DELETE NO ACTION |
|||
); |
|||
|
|||
INSERT INTO Vehicles (Registration, Make, Model, Colour, Year) |
|||
VALUES ('FAZ 9837','Volkswagen','Polo','White',2002); |
|||
INSERT INTO Vehicles (Registration, Make, Model, Colour, Year) |
|||
VALUES ('JHI 8827','BMW','M3','Black',2013); |
|||
INSERT INTO Vehicles (Registration, Make, Model, Colour, Year) |
|||
VALUES ('D903PI','Volvo','XC40','Grey',2014); |
|||
INSERT INTO Vehicles (Registration, Make, Model, Colour, Year) |
|||
VALUES ('YFI002','Volkswagen','Golf','Dark Blue',2018); |
|||
INSERT INTO Vehicles (Registration, Make, Model, Colour, Year) |
|||
VALUES ('HGT5677','Skoda','Octavia','Graphite',2009); |
|||
INSERT INTO Vehicles (Registration, Make, Model, Colour, Year) |
|||
VALUES ('PPF9276','Skoda','Octavia','Graphite',2021); |
|||
INSERT INTO Vehicles (Registration, Make, Model, Colour, Year) |
|||
VALUES ('J893FT','Toyota','Corolla','Red',2015); |
|||
INSERT INTO Vehicles (Registration, Make, Model, Colour, Year) |
|||
VALUES ('MJK776','Honda','HR-V','Silver',2015); |
|||
|
|||
|
|||
INSERT INTO ServiceLog (Description, VehicleId, ServiceDate, Category, Mileage) |
|||
VALUES ('Change front brakes', 1, '2021-05-04', 'Brakes', 20667); |
|||
INSERT INTO ServiceLog (Description, VehicleId, ServiceDate, Category, Mileage) |
|||
VALUES ('Tyres - full set', 1, '2021-05-04', 'Brakes', 20667); |
|||
INSERT INTO ServiceLog (Description, VehicleId, ServiceDate, Category, Mileage) |
|||
VALUES ('Engine tune up', 2, '2021-07-14', 'Brakes', 50889); |
|||
INSERT INTO ServiceLog (Description, VehicleId, ServiceDate, Category, Mileage) |
|||
VALUES ('Replace transmission', 3, '2021-09-26', 'Transmission', 98002); |
|||
@ -0,0 +1,3 @@ |
|||
#!/bin/bash |
|||
docker-compose down |
|||
docker volume prune -f |
|||
@ -0,0 +1,109 @@ |
|||
const { |
|||
ViewNames, |
|||
generateMemoryViewID, |
|||
getMemoryViewParams, |
|||
} = require("../../../db/utils") |
|||
const env = require("../../../environment") |
|||
|
|||
exports.getView = async (db, viewName) => { |
|||
if (env.SELF_HOSTED) { |
|||
const designDoc = await db.get("_design/database") |
|||
return designDoc.views[viewName] |
|||
} else { |
|||
const viewDoc = await db.get(generateMemoryViewID(viewName)) |
|||
return viewDoc.view |
|||
} |
|||
} |
|||
|
|||
exports.getViews = async db => { |
|||
const response = [] |
|||
if (env.SELF_HOSTED) { |
|||
const designDoc = await db.get("_design/database") |
|||
for (let name of Object.keys(designDoc.views)) { |
|||
// Only return custom views, not built ins
|
|||
if (Object.values(ViewNames).indexOf(name) !== -1) { |
|||
continue |
|||
} |
|||
response.push({ |
|||
name, |
|||
...designDoc.views[name], |
|||
}) |
|||
} |
|||
} else { |
|||
const views = ( |
|||
await db.allDocs( |
|||
getMemoryViewParams({ |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
).rows.map(row => row.doc) |
|||
for (let viewDoc of views) { |
|||
response.push({ |
|||
name: viewDoc.name, |
|||
...viewDoc.view, |
|||
}) |
|||
} |
|||
} |
|||
return response |
|||
} |
|||
|
|||
exports.saveView = async (db, originalName, viewName, viewTemplate) => { |
|||
if (env.SELF_HOSTED) { |
|||
const designDoc = await db.get("_design/database") |
|||
designDoc.views = { |
|||
...designDoc.views, |
|||
[viewName]: viewTemplate, |
|||
} |
|||
// view has been renamed
|
|||
if (originalName) { |
|||
delete designDoc.views[originalName] |
|||
} |
|||
await db.put(designDoc) |
|||
} else { |
|||
const id = generateMemoryViewID(viewName) |
|||
const originalId = originalName ? generateMemoryViewID(originalName) : null |
|||
const viewDoc = { |
|||
_id: id, |
|||
view: viewTemplate, |
|||
name: viewName, |
|||
tableId: viewTemplate.meta.tableId, |
|||
} |
|||
try { |
|||
const old = await db.get(id) |
|||
if (originalId) { |
|||
const originalDoc = await db.get(originalId) |
|||
await db.remove(originalDoc._id, originalDoc._rev) |
|||
} |
|||
if (old && old._rev) { |
|||
viewDoc._rev = old._rev |
|||
} |
|||
} catch (err) { |
|||
// didn't exist, just skip
|
|||
} |
|||
await db.put(viewDoc) |
|||
} |
|||
} |
|||
|
|||
exports.deleteView = async (db, viewName) => { |
|||
if (env.SELF_HOSTED) { |
|||
const designDoc = await db.get("_design/database") |
|||
const view = designDoc.views[viewName] |
|||
delete designDoc.views[viewName] |
|||
await db.put(designDoc) |
|||
return view |
|||
} else { |
|||
const id = generateMemoryViewID(viewName) |
|||
const viewDoc = await db.get(id) |
|||
await db.remove(viewDoc._id, viewDoc._rev) |
|||
return viewDoc.view |
|||
} |
|||
} |
|||
|
|||
exports.migrateToInMemoryView = async (db, viewName) => { |
|||
// delete the view initially
|
|||
const designDoc = await db.get("_design/database") |
|||
const view = designDoc.views[viewName] |
|||
delete designDoc.views[viewName] |
|||
await db.put(designDoc) |
|||
await exports.saveView(db, null, viewName, view) |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue