Skip to content

Adding additional storage / backend adapter draft #2084

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 223 additions & 0 deletions src/common/storage/backends/Storage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import TaxonomyReference from "./TaxonomyReference"
import { assert, Result } from "./Utils"
import { filterMap } from "./Utils"
import { writable } from "svelte/store"

class Storage {
constructor() {
const chunks = window.location.href.split("/") // TODO:
chunks.pop()
chunks.pop()
this.baseUrl = chunks.join("/") + "/artifacts/"
}

async listArtifacts() {
const result = await this._fetchJson(this.baseUrl, null, ListError)
const items = await result.unwrap()
return filterMap(items, item => ArtifactSet.tryFrom(item))
}

async getDownloadUrl(parentId, ...ids) {
// TODO: add item IDs
const qs = `ids=${encodeURIComponent(JSON.stringify(ids))}`
return this.baseUrl + parentId + `/download?${qs}`
}

_uploadFile({ method, url, headers }, file) {
const { subscribe, set } = writable(0)
const request = new XMLHttpRequest()
request.upload.addEventListener(
"progress",
ev => {
set(ev.loaded / ev.total)
},
false
)
const promise = new Promise(function(resolve, reject) {
request.addEventListener(
"load",
() => {
set(1)
resolve(true)
},
false
)
request.addEventListener(
"error",
() => {
const error = new AppendDataError(
request.statusText || "Upload failed"
)
reject(error)
},
false
)
request.addEventListener("abort", () => resolve(false), false)
})
request.open(method, url)
Object.entries(headers || {}).forEach(([name, value]) =>
request.setRequestHeader(name, value)
)
request.send(file)
return Object.assign(promise, {
file,
subscribe,
abort() {
request.abort()
}
})
}

async appendArtifact(artifactSet, metadata, files) {
console.log({ action: "append", metadata, files })
const url = this.baseUrl + artifactSet.id + "/append"
const filenames = files.map(file => file.name)

const opts = {
method: "post",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
metadata,
filenames
})
}

const appendResult = await (
await this._fetchJson(url, opts, AppendDataError)
).unwrap()

return appendResult.files.map(({ name, params }) => {
const targetFile = files.find(a => a.name == name)
assert(
!!targetFile,
new AppendDataError("Could not find upload info for " + name)
)
return this._uploadFile(params, targetFile)
})
}

async updateArtifact(metadata, newContent) {
console.log("Updating artifact:", metadata, newContent)
}

async createArtifact(metadata, files) {
console.log("Creating artifact:", metadata, files)
metadata.taxonomyTags = metadata.taxonomyTags || []
const opts = {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
metadata
})
}
return (await this._fetchJson(this.baseUrl, opts, CreateError)).unwrap()
}

async _fetch(url, opts = null, ErrorClass = RequestError) {
const response = await fetch(url, opts)
let error = null
if (response.status === 422) {
const data = await response.json()
const context = new ModelContext(
data.context.projectId,
data.context.branch,
data.context.nodeId
)
error = new ModelError(data.message, context)
} else if (response.status > 399) {
error = new ErrorClass(await response.text())
}
return new Result(response, error)
}

async _fetchJson(url, opts = null, ErrorClass = RequestError) {
return (await this._fetch(url, opts, ErrorClass)).map(response =>
response.json()
)
}
}

export class RequestError extends Error {
constructor(msg) {
super(msg)
}
}

class ModelContext {
constructor(projectId, branch, nodeId) {
this.projectId = projectId
this.nodeId = nodeId
this.branch = branch
}

toQueryParams() {
const params = new URLSearchParams({
project: this.projectId,
branch: this.branch,
node: this.nodeId
})
return params.toString()
}
}

export class ModelError extends Error {
constructor(msg, context) {
super(msg)
this.context = context
}
}

class StorageError extends RequestError {
constructor(actionDisplayName, msg) {
super(`Unable to ${actionDisplayName}: ${msg}`)
}
}

class ListError extends StorageError {
constructor(msg) {
super("list artifacts", msg) // FIXME: rename "artifact"?
}
}

class DownloadError extends StorageError {
constructor(msg) {
super("download", msg)
}
}

class CreateError extends StorageError {
constructor(msg) {
super("create", msg)
}
}

class AppendDataError extends StorageError {
constructor(msg) {
super("append", msg)
}
}

class ArtifactSet {
static tryFrom(item) {
if (!item.displayName) {
console.log("Found malformed data. Filtering out. Data:", item)
} else {
const hash = [
item.id,
...item.children.map(child => child.id).sort()
].join("/")
item.hash = hash
item.children = item.children.map(child => {
if (child.taxonomy) {
child.taxonomy = TaxonomyReference.from(child.taxonomy)
}
return child
})
return item
}
}
}

export default Storage
133 changes: 133 additions & 0 deletions src/common/storage/backends/TaxonomyReference.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
"use strict";
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SemanticVersion = exports.ParseError = exports.Branch = exports.Tag = exports.Commit = void 0;
var TaxonomyReference = /** @class */ (function () {
function TaxonomyReference(id, version) {
this.id = id;
this.version = version;
}
TaxonomyReference.prototype.supports = function (otherVersion) {
return this.id === otherVersion.id &&
this.version.supports(otherVersion.version);
};
TaxonomyReference.from = function (taxonomyVersion) {
var version;
if (taxonomyVersion.tag) {
version = new Tag(taxonomyVersion.commit, taxonomyVersion.tag);
}
else if (taxonomyVersion.branch) {
version = new Branch(taxonomyVersion.commit, taxonomyVersion.branch);
}
else if (taxonomyVersion.commit) {
version = new Commit(taxonomyVersion.commit);
}
else {
var taxVersion = JSON.stringify(taxonomyVersion);
throw new Error("Could not find tag, branch, or commit in ".concat(taxVersion));
}
return new TaxonomyReference(taxonomyVersion.id, version);
};
return TaxonomyReference;
}());
exports.default = TaxonomyReference;
var Commit = /** @class */ (function () {
function Commit(hash) {
this.hash = hash;
}
Commit.prototype.supports = function (otherVersion) {
return otherVersion.hash === this.hash;
};
return Commit;
}());
exports.Commit = Commit;
var Tag = /** @class */ (function (_super) {
__extends(Tag, _super);
function Tag(hash, versionString) {
var _this = _super.call(this, hash) || this;
_this.version = SemanticVersion.parse(versionString);
return _this;
}
Tag.prototype.supports = function (otherTag) {
if (otherTag instanceof Tag) {
return this.version.major === otherTag.version.major &&
this.version.gte(otherTag.version);
}
else {
return _super.prototype.supports.call(this, otherTag);
}
};
return Tag;
}(Commit));
exports.Tag = Tag;
var Branch = /** @class */ (function (_super) {
__extends(Branch, _super);
function Branch(hash, name) {
var _this = _super.call(this, hash) || this;
_this.name = name;
return _this;
}
Branch.prototype.supports = function (otherVersion) {
if (otherVersion instanceof Branch) {
return otherVersion.name === this.name;
}
else {
return _super.prototype.supports.call(this, otherVersion);
}
};
return Branch;
}(Commit));
exports.Branch = Branch;
var ParseError = /** @class */ (function (_super) {
__extends(ParseError, _super);
function ParseError(input) {
return _super.call(this, "Unable to parse: ".concat(input)) || this;
}
return ParseError;
}(Error));
exports.ParseError = ParseError;
var SemanticVersion = /** @class */ (function () {
function SemanticVersion(major, minor, patch) {
if (minor === void 0) { minor = 0; }
if (patch === void 0) { patch = 0; }
this.major = major;
this.minor = minor;
this.patch = patch;
}
SemanticVersion.prototype.gte = function (other) {
if (this.major < other.major)
return false;
if (this.minor < other.minor)
return false;
if (this.patch < other.patch)
return false;
return true;
};
SemanticVersion.parse = function (versionString) {
versionString = versionString.replace(/^v?/, "");
var _a = versionString.split(".")
.map(function (str) {
if (!/\d+/.test(str)) {
throw new ParseError(versionString);
}
return parseInt(str);
}), major = _a[0], _b = _a[1], minor = _b === void 0 ? 0 : _b, _c = _a[2], patch = _c === void 0 ? 0 : _c;
return new SemanticVersion(major, minor, patch);
};
return SemanticVersion;
}());
exports.SemanticVersion = SemanticVersion;
Loading