revisions complete

This commit is contained in:
2023-10-15 19:14:07 +03:00
parent 1faac31372
commit 8d3e8373c0
16 changed files with 288 additions and 218 deletions
+1 -1
View File
@@ -221,7 +221,7 @@
{:else if activeContentTab === "_graph"} {:else if activeContentTab === "_graph"}
<Graph {graph} {record}/> <Graph {graph} {record}/>
{:else if activeContentTab === "_info"} {:else if activeContentTab === "_info"}
<Info bind:record {users} {schema}/> <Info {record} {graph} {users} {schema}/>
{/if} {/if}
</div> </div>
</div> </div>
+69 -25
View File
@@ -2,23 +2,26 @@
import {friendlyDate} from "../../helpers"; import {friendlyDate} from "../../helpers";
import Avatar from "../account/Avatar.svelte"; import Avatar from "../account/Avatar.svelte";
import {usernameById} from "../account/users"; import {usernameById} from "../account/users";
import {isEqual} from "lodash"; import {isEqual, sortBy} from "lodash";
import Status from "./Status.svelte";
import Icon from "../common/Icon.svelte"; import Icon from "../common/Icon.svelte";
import RevisionCell from "./revisions/RevisionCell.svelte"; import RevisionCell from "./revisions/RevisionCell.svelte";
import {getContext} from "svelte";
import RevisionEdgeRow from "./revisions/RevisionEdgeRow.svelte";
const channel = getContext("channel");
export let record; export let record;
export let graph;
export let users; export let users;
export let schema; export let schema;
let revisionSection;
let rollbackError = ""; let rollbackError = "";
$: revisions = []; $: revisions = [];
$: fieldsWithDiff = []; $: fieldsWithDiff = [];
$: selectedRevision = null; $: selectedRevision = null;
$: recordEdges = {}; $: edgeFieldsDiff = {};
$: selectedRevisionEdges = {};
axios axios
.get(`/records/${record.id}/revisions`) .get(`${channel.lucentUrl}/records/${record.id}/revisions`)
.then((response) => { .then((response) => {
revisions = response.data; revisions = response.data;
}) })
@@ -26,19 +29,30 @@
console.log(error); console.log(error);
}); });
function getEdgesByField(edges) { function getEdgesByField(fieldsWithDiff, revision) {
return schema.fields
.filter((f) => ["file", "reference"].includes(f.ui))
.reduce((c, f) => {
let fieldEdges = edges
.filter((e) => e.field === f.name)
.map((e) =>
record._children.find((child) => child.id === e.to)
);
c[f.name] = fieldEdges; edgeFieldsDiff = graph.edges.filter((e) => e.depth === 1).reduce((c, e) => {
if (!c[e.field]) {
c[e.field] = {
record: [],
revision: [],
}
}
c[e.field]["record"].push(e)
return c; return c;
}, {}); }, {});
edgeFieldsDiff = revision._edges.reduce((c, e) => {
if (!c[e.field]) {
c[e.field] = {
record: [],
revision: [],
}
}
c[e.field]["revision"].push(e)
return c;
}, edgeFieldsDiff);
} }
function compare(e, revision) { function compare(e, revision) {
@@ -48,6 +62,8 @@
fieldsWithDiff = schema.fields.filter((f) => { fieldsWithDiff = schema.fields.filter((f) => {
return !isEqual(selectedRevision.data[f.name], record.data[f.name]); return !isEqual(selectedRevision.data[f.name], record.data[f.name]);
}); });
getEdgesByField(fieldsWithDiff, revision)
revisionSection.scrollIntoView();
} }
function rollback(e) { function rollback(e) {
@@ -55,7 +71,7 @@
rollbackError = ""; rollbackError = "";
axios axios
.post( .post(
`/records/${record.id}/rollback/${selectedRevision._sys.version}` `${channel.lucentUrl}/records/${record.id}/rollback/${selectedRevision._sys.version}`
) )
.then((response) => { .then((response) => {
window.location.reload(); window.location.reload();
@@ -101,16 +117,14 @@
>Rules for this schema >Rules for this schema
</span> </span>
<small> <small>
Revisions are retained for {schema.revisionRetentionDays} days Each record maintains the last {schema.revisions}
<br/>
Each record maintains the last {schema.revisionRetentionNumber}
versions versions
</small> </small>
</div> </div>
</div> </div>
</div> </div>
<div class="lx-card mt-4"> <div class="lx-card mt-4">
{#if schema.revisionRetentionDays > 0} {#if schema.revisions > 0}
<div class="header-small mb-3">Revisions</div> <div class="header-small mb-3">Revisions</div>
{#each revisions as revision} {#each revisions as revision}
{#if revision._sys.version != record._sys.version} {#if revision._sys.version != record._sys.version}
@@ -119,9 +133,7 @@
class:active={revision._sys.version === class:active={revision._sys.version ===
selectedRevision?._sys.version} selectedRevision?._sys.version}
> >
<div class="col-2">
<Status status={revision.status}/>
</div>
<div class="col-2">version {revision._sys.version}</div> <div class="col-2">version {revision._sys.version}</div>
<div class="col-5"> <div class="col-5">
<Avatar <Avatar
@@ -150,6 +162,7 @@
</div> </div>
{/if} {/if}
</div> </div>
<div bind:this={revisionSection}>
{#if selectedRevision} {#if selectedRevision}
<div class="mt-4"> <div class="mt-4">
{#if fieldsWithDiff.length > 0} {#if fieldsWithDiff.length > 0}
@@ -181,7 +194,6 @@
> >
<div class="col-5"> <div class="col-5">
<RevisionCell <RevisionCell
edges={recordEdges}
{field} {field}
side={record.data[field.name]} side={record.data[field.name]}
colorClass="text-danger" colorClass="text-danger"
@@ -201,7 +213,7 @@
</div> </div>
<div class="col-5"> <div class="col-5">
<RevisionCell <RevisionCell
edges={selectedRevisionEdges} edges={selectedRevision._edges}
{field} {field}
side={selectedRevision.data[field.name]} side={selectedRevision.data[field.name]}
colorClass="text-success" colorClass="text-success"
@@ -216,8 +228,40 @@
<span>Nothing will change</span> <span>Nothing will change</span>
</div> </div>
{/if} {/if}
<div class="mt-3">
<p class="text-center fw-bold mb-3 mt-5">
Record References
</p>
{#each Object.entries(edgeFieldsDiff) as [field, edges]}
<div
class="lx-card row p-4 mb-4 w-100"
style="overflow:hidden"
>
<div class="col-4">
{field}:
</div> </div>
<div class="col-8">
<p class="mb-2 text-danger">Record</p>
{#each edges.record as edge}
<RevisionEdgeRow {edge} />
{:else}
<p>No references</p>
{/each}
<p class="mt-4 mb-2 text-success">Revision</p>
{#each edges.revision as edge}
<RevisionEdgeRow {edge} />
{:else}
<p>No references</p>
{/each}
</div>
</div>
{/each}
</div>
</div>
{/if} {/if}
</div>
<style> <style>
.label { .label {
@@ -8,7 +8,7 @@
export let colorClass; export let colorClass;
</script> </script>
{#if ["reference", "file"].includes(field.ui)} {#if ["reference", "file"].includes(field.info.name)}
<div class="{colorClass} field-content"> <div class="{colorClass} field-content">
<div class="d-flex align-items-center text-center flex-wrap"> <div class="d-flex align-items-center text-center flex-wrap">
{#each edges[field.name] as edgeRecord} {#each edges[field.name] as edgeRecord}
@@ -31,11 +31,11 @@
{/each} {/each}
</div> </div>
</div> </div>
{:else if field.ui === "json"} {:else if ["json", "block"].includes(field.info.name)}
<div class="{colorClass} field-content" style="white-space: break-spaces;"> <div class="{colorClass} field-content" style="white-space: break-spaces;">
{JSON.stringify(side, null, 2) ?? ""} {JSON.stringify(side, null, 2) ?? ""}
</div> </div>
{:else if field.ui === "rich"} {:else if field.info.name === "rich"}
<div class="{colorClass} field-content">{@html side ?? ""}</div> <div class="{colorClass} field-content">{@html side ?? ""}</div>
{:else} {:else}
<div class="{colorClass} field-content">{JSON.stringify(side) ?? ""}</div> <div class="{colorClass} field-content">{JSON.stringify(side) ?? ""}</div>
@@ -0,0 +1,15 @@
<script>
import Preview from "../../files/Preview.svelte";
import PreviewCardSmall from "../PreviewCardSmall.svelte";
import {getContext} from "svelte";
const channel = getContext("channel")
export let edge;
</script>
<div>
<span class="me-3">Rank: {edge.rank}</span>
<span>id: </span> <a href="{channel.lucentUrl}/records/{edge.target}" target="_blank">{edge.target}</a>
</div>
+15 -16
View File
@@ -341,20 +341,19 @@ class RecordController extends Controller
} }
return ok(); return ok();
} }
//
// public function rollback(Request $request) public function rollback(Request $request)
// { {
// try { try {
// $this->recordService->rollback( $this->recordService->rollback(
// userId: AuthService::currentUserId($request), recordId: $request->route("rid"),
// recordId: $request->route("rid"), version: (int)$request->route("version")
// version: (int)$request->route("version") );
// ); } catch (ValidatorException $th) {
// } catch (ValidatorException $th) { return fail($th->getFirstValidatorError());
// return fail($th->getFirstValidatorError()); } catch (LucentException|Throwable $th) {
// } catch (LucentException|Throwable $th) { return fail($th);
// return fail($th); }
// } return ok();
// return ok(); }
// }
} }
+6 -22
View File
@@ -4,38 +4,22 @@ namespace Lucent\Http\Controller;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Lucent\Account\Auth;
use Lucent\Channel\ChannelContext;
use Lucent\Record\RecordRepo; use Lucent\Record\RecordRepo;
use Lucent\Revision\RevisionRepo; use Lucent\Revision\RevisionRepo;
use Lucent\Schema\SchemaRepo; use Lucent\Revision\RevisionService;
use function Lucent\Response\fail; use function Lucent\Response\fail;
use function Lucent\Response\ok; use function Lucent\Response\ok;
class RevisionController extends Controller class RevisionController extends Controller
{ {
public function __construct(
public RevisionService $revisionService
){}
public function index(Request $request) public function index(Request $request)
{ {
$revisions = $this->revisionService->getByRecordId($request->route("rid"));
$revisions = RevisionRepo::getByRecordId($request->route("rid")); return ok($revisions->toArray());
return ok($revisions);
} }
public function rollback(Request $request)
{
$schemas = SchemaRepo::all();
$revision = RevisionRepo::getByRecordIdAndVersion($request->route("rid"), (int)$request->route("version"));
try {
RecordRepo::replaceMany($schemas, [$revision->toDB()], Auth::currentUserId());
} catch (\Lucent\Schema\Validator\ValidatorException $th) {
return fail($th->getFirstValidatorError());
} catch (\Lucent\LucentException $th) {
return fail($th);
} catch (\Throwable $th) {
return fail($th);
}
return ok();
}
} }
+1 -1
View File
@@ -8,7 +8,7 @@ use Lucent\Http\Controller\FileController;
use Lucent\Http\Controller\HomeController; use Lucent\Http\Controller\HomeController;
use Lucent\Http\Controller\MemberController; use Lucent\Http\Controller\MemberController;
use Lucent\Http\Controller\RecordController; use Lucent\Http\Controller\RecordController;
use Lucent\Revision\RevisionController; use Lucent\Http\Controller\RevisionController;
Route::group([ Route::group([
+1 -2
View File
@@ -43,8 +43,7 @@ class RecordRepo
): void ): void
{ {
DB::table("records") DB::table("records")->whereIn("id", $ids)->delete();
->whereIn("id", $ids)->delete();
DB::table("edges")->whereIn("source", $ids)->delete(); DB::table("edges")->whereIn("source", $ids)->delete();
DB::table("edges")->whereIn("target", $ids)->delete(); DB::table("edges")->whereIn("target", $ids)->delete();
DB::table("revisions")->whereIn("recordId", $ids)->delete(); DB::table("revisions")->whereIn("recordId", $ids)->delete();
+5 -3
View File
@@ -92,7 +92,7 @@ readonly class RecordService
RecordRepo::create($record); RecordRepo::create($record);
EdgeRepo::update($record->id, $uniqueEdgesCollection); EdgeRepo::update($record->id, $uniqueEdgesCollection);
$this->revisionService->create($record); $this->revisionService->create($record,$uniqueEdgesCollection);
return $record->id; return $record->id;
} }
@@ -119,7 +119,7 @@ readonly class RecordService
} }
$formattedData = $this->inputFormatter->fill($record->schema, new RecordData($data)); $formattedData = $this->inputFormatter->fill($record->schema, new RecordData($data));
$uniqueEdgesCollection = null; $uniqueEdgesCollection = new EdgeCollection();
if ($updateEdges) { if ($updateEdges) {
$uniqueEdges = collect($edges) $uniqueEdges = collect($edges)
->map(function ($edge, $index) { ->map(function ($edge, $index) {
@@ -161,7 +161,7 @@ readonly class RecordService
EdgeRepo::update($newRecord->id, $uniqueEdgesCollection); EdgeRepo::update($newRecord->id, $uniqueEdgesCollection);
} }
$this->revisionService->create($newRecord); $this->revisionService->create($newRecord,$uniqueEdgesCollection);
} }
@@ -240,6 +240,8 @@ readonly class RecordService
$this->update( $this->update(
id: $revision->recordId, id: $revision->recordId,
data: $revision->data->toArray(), data: $revision->data->toArray(),
edges: toArray($revision->_edges),
updateEdges: true
); );
} }
+8 -35
View File
@@ -3,6 +3,9 @@
namespace Lucent\Revision; namespace Lucent\Revision;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Lucent\Edge\Edge;
use Lucent\Edge\EdgeCollection;
use Lucent\Primitive\Collection;
use Lucent\Record\File; use Lucent\Record\File;
use Lucent\Record\Record; use Lucent\Record\Record;
use Lucent\Record\RecordData; use Lucent\Record\RecordData;
@@ -18,46 +21,15 @@ readonly class Revision
public string $schema, public string $schema,
public System $_sys, public System $_sys,
public RecordData $data, public RecordData $data,
public EdgeCollection $_edges,
public ?File $_file = null, public ?File $_file = null,
) )
{ {
} }
public static function fromDB(stdClass $data): Revision
{
$file = json_decode($data->_file, true); public static function fromRecord(Record $record, EdgeCollection $edges): Revision
if (!empty($file)) {
$file = new File(...$file);
} else {
$file = null;
}
return new Revision(
id: $data->id,
recordId: $data->recordId,
schema: $data->schema,
_sys: System::fromArray(json_decode($data->_sys, true)),
data: new RecordData(json_decode($data->data, true)),
_file: $file,
);
}
public function toDB(): array
{
return [
"id" => $this->id,
"recordId" => $this->recordId,
"schema" => $this->schema,
"_sys" => json_encode($this->_sys),
"_file" => json_encode($this->_file),
"data" => json_encode($this->data),
];
}
public static function fromRecord(Record $record): Revision
{ {
return new Revision( return new Revision(
id: (string)Str::uuid(), id: (string)Str::uuid(),
@@ -65,7 +37,8 @@ readonly class Revision
schema: $record->schema, schema: $record->schema,
_sys: $record->_sys, _sys: $record->_sys,
data: $record->data, data: $record->data,
_file: $record->_file, _edges: $edges,
_file: $record->_file
); );
} }
-26
View File
@@ -1,26 +0,0 @@
<?php
namespace Lucent\Revision;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use function Lucent\Response\ok;
class RevisionController extends Controller
{
public function __construct(
private readonly RevisionRepo $revisionRepo,
)
{
}
public function index(Request $request): Response
{
$revisions = $this->revisionRepo->getByRecordId($request->route("rid"));
return ok($revisions->toArray());
}
}
+60 -3
View File
@@ -3,8 +3,14 @@
namespace Lucent\Revision; namespace Lucent\Revision;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Lucent\Edge\Edge;
use Lucent\Edge\EdgeCollection;
use Lucent\Primitive\Collection; use Lucent\Primitive\Collection;
use Lucent\Record\File;
use Lucent\Record\RecordData;
use Lucent\Record\System;
use PhpOption\Option; use PhpOption\Option;
use stdClass;
class RevisionRepo class RevisionRepo
{ {
@@ -13,7 +19,7 @@ class RevisionRepo
public function create(Revision $revision): string public function create(Revision $revision): string
{ {
$revisionDB = $revision->toDB(); $revisionDB = $this->toDB($revision);
DB::table($this->table)->insert($revisionDB); DB::table($this->table)->insert($revisionDB);
return $revision->id; return $revision->id;
} }
@@ -27,13 +33,28 @@ class RevisionRepo
$revisions = DB::table($this->table) $revisions = DB::table($this->table)
->where("recordId", $rid) ->where("recordId", $rid)
->get() ->get()
->map([Revision::class, 'fromDB']) ->map([$this, 'fromDB'])
->sortByDesc("_sys.version") ->sortByDesc("_sys.version")
->toArray(); ->toArray();
return new Collection($revisions); return new Collection($revisions);
} }
public function cleanupRecord(string $rid, int $numKeep): void
{
$revisionIds = DB::table($this->table)
->where("recordId", $rid)
->orderBy("_sys->version", "desc")
->limit(100)
->skip($numKeep)
->get()
->pluck("id");
DB::table($this->table)
->whereIn("id", $revisionIds)
->delete();
}
/** /**
* @return Option<Revision> * @return Option<Revision>
@@ -49,6 +70,42 @@ class RevisionRepo
return none(); return none();
} }
return some(Revision::fromDB($res)); return some($this->fromDB($res));
}
public function toDB(Revision $revision): array
{
return [
"id" => $revision->id,
"recordId" => $revision->recordId,
"schema" => $revision->schema,
"_sys" => json_encode($revision->_sys),
"_file" => json_encode($revision->_file),
"data" => json_encode($revision->data),
"_edges" => $revision->_edges->toJson(),
];
}
public function fromDB(stdClass $data): Revision
{
$file = json_decode($data->_file, true);
if (!empty($file)) {
$file = new File(...$file);
} else {
$file = null;
}
$edges = array_map(fn($e) => Edge::fromArray($e), json_decode($data->_edges ?? "[]", true));
return new Revision(
id: $data->id,
recordId: $data->recordId,
schema: $data->schema,
_sys: System::fromArray(json_decode($data->_sys, true)),
data: new RecordData(json_decode($data->data, true)),
_edges: new EdgeCollection(...$edges),
_file: $file
);
} }
} }
+23 -3
View File
@@ -2,6 +2,10 @@
namespace Lucent\Revision; namespace Lucent\Revision;
use Lucent\Channel\ChannelService;
use Lucent\Edge\Edge;
use Lucent\Edge\EdgeCollection;
use Lucent\Primitive\Collection;
use Lucent\Record\Record; use Lucent\Record\Record;
use PhpOption\Option; use PhpOption\Option;
@@ -9,11 +13,21 @@ readonly class RevisionService
{ {
public function __construct( public function __construct(
private ChannelService $channelService,
private RevisionRepo $revisionRepo, private RevisionRepo $revisionRepo,
) )
{ {
} }
/**
* @return Collection<Revision>
*/
public function getByRecordId(string $recordId): Collection
{
return $this->revisionRepo->getByRecordId($recordId);
}
/** /**
* @return Option<Revision> * @return Option<Revision>
*/ */
@@ -23,10 +37,16 @@ readonly class RevisionService
return $this->revisionRepo->getByRecordIdAndVersion($recordId, $version); return $this->revisionRepo->getByRecordIdAndVersion($recordId, $version);
} }
public function create(Record $record): string
public function create(Record $record, EdgeCollection $edges): void
{ {
$revision = Revision::fromRecord($record); $schema = $this->channelService->getSchema($record->schema)->get();
return $this->revisionRepo->create($revision); if($schema->revisions <= 0){
return;
}
$revision = Revision::fromRecord($record, $edges);
$this->revisionRepo->create($revision);
$this->revisionRepo->cleanupRecord($record->id,$schema->revisions);
} }
+1 -1
View File
@@ -11,7 +11,6 @@ class CollectionSchema implements Schema
/** /**
* @param Collection<FieldInterface> $fields * @param Collection<FieldInterface> $fields
* @param array<string> $visible * @param array<string> $visible
* @param array<string> $fields
*/ */
function __construct( function __construct(
public string $name, public string $name,
@@ -22,6 +21,7 @@ class CollectionSchema implements Schema
public bool $isEntry = false, public bool $isEntry = false,
public string $color = "", public string $color = "",
public string $titleTemplate = "", public string $titleTemplate = "",
public int $revisions = 0,
) )
{ {
} }
+1
View File
@@ -22,6 +22,7 @@ class FilesSchema implements Schema
public bool $isEntry = false, public bool $isEntry = false,
public string $color = "", public string $color = "",
public string $titleTemplate = "", public string $titleTemplate = "",
public int $revisions = 0,
) )
{ {
} }
+2
View File
@@ -25,6 +25,7 @@ class SchemaService
isEntry: $schemaArr["isEntry"] ?? false, isEntry: $schemaArr["isEntry"] ?? false,
color: $schemaArr["color"] ?? "", color: $schemaArr["color"] ?? "",
titleTemplate: $schemaArr["titleTemplate"] ?? "", titleTemplate: $schemaArr["titleTemplate"] ?? "",
revisions: $schemaArr["revisions"] ?? 0,
), ),
"files" => new FilesSchema( "files" => new FilesSchema(
name: $schemaArr["name"], name: $schemaArr["name"],
@@ -35,6 +36,7 @@ class SchemaService
isEntry: $schemaArr["isEntry"] ?? false, isEntry: $schemaArr["isEntry"] ?? false,
color: $schemaArr["color"] ?? "", color: $schemaArr["color"] ?? "",
titleTemplate: $schemaArr["titleTemplate"] ?? "", titleTemplate: $schemaArr["titleTemplate"] ?? "",
revisions: $schemaArr["revisions"] ?? 0,
), ),
"block" => new BlockSchema( "block" => new BlockSchema(
name: $schemaArr["name"], name: $schemaArr["name"],