Working proposal apply code, fixed schema (need reset - artisan migrate:fresh)
This commit is contained in:
@@ -28,7 +28,7 @@ class TableController extends Controller
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
/** @var Table $tableModel */
|
/** @var Table $tableModel */
|
||||||
$tableModel = $user->tables()->withCount(['favourites', 'forks', 'revisions', 'comments', 'proposals'])
|
$tableModel = $user->tables()->withCount(['favourites', 'forks', 'revisions', 'comments'])
|
||||||
->where('name', $table)->first();
|
->where('name', $table)->first();
|
||||||
if ($tableModel === null) abort(404, "No such table.");
|
if ($tableModel === null) abort(404, "No such table.");
|
||||||
|
|
||||||
@@ -50,6 +50,7 @@ class TableController extends Controller
|
|||||||
return view('table.view', [
|
return view('table.view', [
|
||||||
'table' => $tableModel,
|
'table' => $tableModel,
|
||||||
'revision' => $revision,
|
'revision' => $revision,
|
||||||
|
'proposals_count' => $tableModel->proposals()->unmerged($tableModel)->count(),
|
||||||
'columns' => $columns,
|
'columns' => $columns,
|
||||||
'rows' => $rows,
|
'rows' => $rows,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Proposal;
|
||||||
use App\Models\Table;
|
use App\Models\Table;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Tables\Changeset;
|
use App\Tables\Changeset;
|
||||||
@@ -351,6 +352,17 @@ class TableEditController extends Controller
|
|||||||
return back();
|
return back();
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
$proposal = Proposal::fromChangeset($changeset);
|
||||||
|
$proposal->saveOrFail();
|
||||||
|
|
||||||
|
if (\user()->ownsTable($tableModel)) {
|
||||||
|
$proposal->createRevision();
|
||||||
|
} else {
|
||||||
|
// TODO send a notification to the table owner
|
||||||
|
}
|
||||||
|
|
||||||
|
session()->forget($tableModel->draftSessionKey);
|
||||||
|
|
||||||
|
return redirect($tableModel->viewRoute);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+109
-7
@@ -4,6 +4,9 @@ namespace App\Models;
|
|||||||
|
|
||||||
use App\Models\Concerns\Reportable;
|
use App\Models\Concerns\Reportable;
|
||||||
use App\Tables\Changeset;
|
use App\Tables\Changeset;
|
||||||
|
use App\Tables\Column;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
|
use Illuminate\Support\Facades\DB;
|
||||||
use MightyPork\Exceptions\NotApplicableException;
|
use MightyPork\Exceptions\NotApplicableException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,7 +19,7 @@ use MightyPork\Exceptions\NotApplicableException;
|
|||||||
* @property int $revision_id
|
* @property int $revision_id
|
||||||
* @property int $author_id
|
* @property int $author_id
|
||||||
* @property string $note
|
* @property string $note
|
||||||
* @property Proposal $changes - JSONB
|
* @property Changeset $changeset - JSONB
|
||||||
* @property-read User $author
|
* @property-read User $author
|
||||||
* @property-read Table $table
|
* @property-read Table $table
|
||||||
* @property-read Revision $revision
|
* @property-read Revision $revision
|
||||||
@@ -46,17 +49,30 @@ class Proposal extends BaseModel
|
|||||||
return $this->belongsTo(Table::class);
|
return $this->belongsTo(Table::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getChangesAttribute($value)
|
public function scopeUnmerged(Builder $query, Table $table)
|
||||||
{
|
{
|
||||||
$changeset = Changeset::fromObject(fromJSON($value));
|
return $query->whereRaw('
|
||||||
|
"id" NOT IN
|
||||||
|
(SELECT "proposal_id" FROM "revisions"
|
||||||
|
LEFT JOIN "table_revision_pivot"
|
||||||
|
ON "revisions"."id" = "table_revision_pivot"."revision_id"
|
||||||
|
WHERE "table_revision_pivot"."table_id" = ?)',
|
||||||
|
[
|
||||||
|
$table->getKey()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getChangesetAttribute()
|
||||||
|
{
|
||||||
|
$changeset = Changeset::fromObject(fromJSON($this->attributes['changes'], true));
|
||||||
$changeset->revision = $this->revision;
|
$changeset->revision = $this->revision;
|
||||||
$changeset->table = $this->table;
|
$changeset->table = $this->getAttribute('table');
|
||||||
$changeset->note = $this->note;
|
$changeset->note = $this->note;
|
||||||
|
|
||||||
return $changeset;
|
return $changeset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setChangesAttribute($value)
|
public function setChangesetAttribute($value)
|
||||||
{
|
{
|
||||||
if ($value instanceof Changeset) {
|
if ($value instanceof Changeset) {
|
||||||
$this->attributes['changes'] = toJSON($value->toObject());
|
$this->attributes['changes'] = toJSON($value->toObject());
|
||||||
@@ -98,10 +114,96 @@ class Proposal extends BaseModel
|
|||||||
// relations
|
// relations
|
||||||
'table_id' => $changeset->table->getKey(),
|
'table_id' => $changeset->table->getKey(),
|
||||||
'revision_id' => $changeset->revision->getKey(),
|
'revision_id' => $changeset->revision->getKey(),
|
||||||
'author_id' => \Auth::user()->getKey(),
|
'author_id' => \user()->getKey(),
|
||||||
// the proposal info
|
// the proposal info
|
||||||
'note' => $changeset->note,
|
'note' => $changeset->note,
|
||||||
'changes' => $changeset->toObject(),
|
'changeset' => $changeset, // this is without a note
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Accept the proposed changes: create a new revision based on the parent revision
|
||||||
|
* and changes described in this Proposal, and link it to the table.
|
||||||
|
*/
|
||||||
|
public function createRevision()
|
||||||
|
{
|
||||||
|
DB::transaction(function () {
|
||||||
|
$changeset = $this->changeset;
|
||||||
|
|
||||||
|
$columns = $changeset->fetchAndTransformColumns();
|
||||||
|
|
||||||
|
$newRevision = new Revision([
|
||||||
|
'ancestor_id' => $changeset->revision->getKey(),
|
||||||
|
'proposal_id' => $this->getKey(),
|
||||||
|
'note' => $changeset->note,
|
||||||
|
'row_count' => 0, // Will be set later when we are sure about the row count
|
||||||
|
'columns' => array_map(function(Column $c) {
|
||||||
|
return $c->toArray(false);
|
||||||
|
}, $columns),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$newRevision->save(); // this gives it an ID, needed to associate rows
|
||||||
|
|
||||||
|
// --- Copy over rows that are left unchanged ---
|
||||||
|
// ...this directly works with the pivot
|
||||||
|
$removedRowIds = (array)($changeset->removedRows ?? []);
|
||||||
|
$changedRowIds = array_keys((array)($changeset->rowUpdates ?? []));
|
||||||
|
$excludedGRIDs = array_merge($removedRowIds, $changedRowIds);
|
||||||
|
|
||||||
|
$excluded_ids = [];
|
||||||
|
|
||||||
|
if ($excludedGRIDs) {
|
||||||
|
$questionmarks = str_repeat('?,', count($excludedGRIDs) - 1) . '?';
|
||||||
|
$excluded_ids = $changeset->revision->rows()->whereRaw("data->'_id' IN ($questionmarks)", $excludedGRIDs)
|
||||||
|
->get(['id'])->pluck('id')->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = '
|
||||||
|
INSERT INTO revision_row_pivot
|
||||||
|
SELECT ? as revision_id, row_id FROM revision_row_pivot
|
||||||
|
WHERE revision_id = ?';
|
||||||
|
$subs = [
|
||||||
|
$newRevision->getKey(),
|
||||||
|
$changeset->revision->getKey()
|
||||||
|
];
|
||||||
|
if ($excluded_ids) {
|
||||||
|
$questionmarks = str_repeat('?,', count($excluded_ids) - 1) . '?';
|
||||||
|
$query .= ' AND row_id NOT IN (' . $questionmarks . ')';
|
||||||
|
$subs = array_merge($subs, $excluded_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
DB::statement($query, $subs);
|
||||||
|
|
||||||
|
// --- Insert modified rows ---
|
||||||
|
if ($changeset->rowUpdates) {
|
||||||
|
$ids = array_keys($changeset->rowUpdates);
|
||||||
|
$questionmarks = str_repeat('?,', count($ids) - 1) . '?';
|
||||||
|
|
||||||
|
$toChange = $changeset->revision->rows()->whereRaw("data->'_id' IN ($questionmarks)", $ids)->get();
|
||||||
|
|
||||||
|
$updateData = [];
|
||||||
|
foreach ($toChange as $row) {
|
||||||
|
$updateData[] = new Row([
|
||||||
|
'data' => $changeset->transformRow($row->data, false),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$newRevision->rows()->saveMany($updateData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Insert new rows ---
|
||||||
|
if ($changeset->newRows) {
|
||||||
|
$newRowData = [];
|
||||||
|
foreach ($changeset->newRows as $newRow) {
|
||||||
|
$newRowData[] = new Row(['data' => $newRow]);
|
||||||
|
}
|
||||||
|
$newRevision->rows()->saveMany($newRowData);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newRevision->update(['row_count' => $newRevision->rows()->count()]);
|
||||||
|
|
||||||
|
// --- Attach this revision to the table ---
|
||||||
|
$changeset->table->revisions()->save($newRevision);
|
||||||
|
$changeset->table->update(['revision_id' => $newRevision->getKey()]);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ use Riesjart\Relaquent\Model\Concerns\HasRelaquentRelationships;
|
|||||||
* @property \Carbon\Carbon $created_at
|
* @property \Carbon\Carbon $created_at
|
||||||
* @property \Carbon\Carbon $updated_at
|
* @property \Carbon\Carbon $updated_at
|
||||||
* @property int $ancestor_id
|
* @property int $ancestor_id
|
||||||
|
* @property int $proposal_id
|
||||||
* @property string $note
|
* @property string $note
|
||||||
* @property object $columns
|
* @property object $columns
|
||||||
* @property int $row_count - cached number of rows in the revision
|
* @property int $row_count - cached number of rows in the revision
|
||||||
* @property-read Revision|null $parentRevision
|
* @property-read Revision|null $parentRevision
|
||||||
* @property-read Row[]|Collection $rows
|
* @property-read Row[]|Collection $rows
|
||||||
* @property-read Proposal|null $sourceProposal - proposal that was used to create this revision
|
* @property-read Proposal|null $proposal - proposal that was used to create this revision
|
||||||
* @property-read Proposal[]|Collection $dependentProposals
|
* @property-read Proposal[]|Collection $dependentProposals
|
||||||
*/
|
*/
|
||||||
class Revision extends BaseModel
|
class Revision extends BaseModel
|
||||||
@@ -52,15 +53,15 @@ class Revision extends BaseModel
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Proposal that lead to this revision */
|
/** Proposal that lead to this revision */
|
||||||
public function sourceProposal()
|
public function proposal()
|
||||||
{
|
{
|
||||||
return $this->hasOneThrough(Proposal::class, 'revision_proposal_pivot');
|
return $this->belongsTo(Proposal::class, 'proposal_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Proposals that depend on this revision */
|
/** Proposals that depend on this revision */
|
||||||
public function dependentProposals()
|
public function dependentProposals()
|
||||||
{
|
{
|
||||||
return $this->hasMany(Proposal::class);
|
return $this->hasMany(Proposal::class, 'revision_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Revision this orignates from */
|
/** Revision this orignates from */
|
||||||
|
|||||||
@@ -136,16 +136,17 @@ class Changeset
|
|||||||
* Note that the fromProposal() method should be used when the
|
* Note that the fromProposal() method should be used when the
|
||||||
* proposal is available, as it populates additional fields.
|
* proposal is available, as it populates additional fields.
|
||||||
*
|
*
|
||||||
* @param \stdClass $changes
|
* @param \stdClass $object
|
||||||
* @return Changeset
|
* @return Changeset
|
||||||
*/
|
*/
|
||||||
public static function fromObject($changes)
|
public static function fromObject($object)
|
||||||
{
|
{
|
||||||
|
$object = (array)$object;
|
||||||
$changeset = new Changeset();
|
$changeset = new Changeset();
|
||||||
|
|
||||||
foreach ($changeset->walkProps() as $prop) {
|
foreach ($changeset->walkProps() as $prop) {
|
||||||
if (isset($changes->$prop)) {
|
if (isset($object[$prop])) {
|
||||||
$changeset->$prop = $changes->$prop;
|
$changeset->$prop = $object[$prop];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,7 +213,7 @@ class Changeset
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if marked for removal, hide changes
|
// if marked for removal, hide changes
|
||||||
if (!$row->_remove) {
|
if (!isset($row->_remove) || !$row->_remove) {
|
||||||
// Changed values
|
// Changed values
|
||||||
if (isset($this->rowUpdates[$row->_id])) {
|
if (isset($this->rowUpdates[$row->_id])) {
|
||||||
$newVals = $this->rowUpdates[$row->_id];
|
$newVals = $this->rowUpdates[$row->_id];
|
||||||
@@ -233,6 +234,7 @@ class Changeset
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// move junk left over from the select
|
||||||
unset($row->_row_pivot);
|
unset($row->_row_pivot);
|
||||||
|
|
||||||
if ($decorate) {
|
if ($decorate) {
|
||||||
@@ -857,6 +859,8 @@ class Changeset
|
|||||||
*/
|
*/
|
||||||
public function renumberRows()
|
public function renumberRows()
|
||||||
{
|
{
|
||||||
|
if (count($this->newRows) == 0) return;
|
||||||
|
|
||||||
$rows = [];
|
$rows = [];
|
||||||
$numerator = new RowNumerator(count($this->newRows));
|
$numerator = new RowNumerator(count($this->newRows));
|
||||||
foreach ($this->newRows as $row) {
|
foreach ($this->newRows as $row) {
|
||||||
|
|||||||
+10
-1
@@ -154,8 +154,9 @@ class Column implements JsonSerializable, Arrayable
|
|||||||
/**
|
/**
|
||||||
* @return array with keys {name, title, type}
|
* @return array with keys {name, title, type}
|
||||||
*/
|
*/
|
||||||
public function toArray()
|
public function toArray($decorate=true)
|
||||||
{
|
{
|
||||||
|
if ($decorate) {
|
||||||
return [
|
return [
|
||||||
'id' => $this->id,
|
'id' => $this->id,
|
||||||
'name' => $this->name,
|
'name' => $this->name,
|
||||||
@@ -166,6 +167,14 @@ class Column implements JsonSerializable, Arrayable
|
|||||||
'_changed' => $this->modified_attribs,
|
'_changed' => $this->modified_attribs,
|
||||||
'_orig' => $this->orig_attribs,
|
'_orig' => $this->orig_attribs,
|
||||||
];
|
];
|
||||||
|
} else {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'name' => $this->name,
|
||||||
|
'title' => $this->title,
|
||||||
|
'type' => $this->type,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ const VALI_LINE = 'string|max:255';
|
|||||||
const FEATURE_FORKS = false;
|
const FEATURE_FORKS = false;
|
||||||
const FEATURE_FAVES = false;
|
const FEATURE_FAVES = false;
|
||||||
const FEATURE_TABLE_COMMENTS = false;
|
const FEATURE_TABLE_COMMENTS = false;
|
||||||
const FEATURE_PROPOSALS = true;
|
const FEATURE_PROPOSALS = false;
|
||||||
|
|
||||||
// global helpers
|
// global helpers
|
||||||
function authed() {
|
function authed() {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class CreateRevisionsTable extends Migration
|
|||||||
$table->increments('id');
|
$table->increments('id');
|
||||||
$table->timestamps();
|
$table->timestamps();
|
||||||
$table->unsignedInteger('ancestor_id')->index()->nullable(); // parent revision
|
$table->unsignedInteger('ancestor_id')->index()->nullable(); // parent revision
|
||||||
|
$table->unsignedInteger('proposal_id')->index()->nullable(); // parent revision
|
||||||
|
|
||||||
$table->unsignedInteger('row_count'); // cached nbr of rows
|
$table->unsignedInteger('row_count'); // cached nbr of rows
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,12 @@ class CreateProposalsTable extends Migration
|
|||||||
$table->foreign('author_id')->references('id')->on('users')
|
$table->foreign('author_id')->references('id')->on('users')
|
||||||
->onDelete('cascade');
|
->onDelete('cascade');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// add FK to revisions to point to the source proposal
|
||||||
|
Schema::table('revisions', function (Blueprint $table) {
|
||||||
|
$table->foreign('proposal_id')->references('id')->on('proposals')
|
||||||
|
->onDelete('set null');
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Schema;
|
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
|
||||||
|
|
||||||
class CreateRevisionProposalPivotTable extends Migration
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Run the migrations.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function up()
|
|
||||||
{
|
|
||||||
Schema::create('revision_proposal_pivot', function (Blueprint $table) {
|
|
||||||
$table->unsignedInteger('proposal_id')->index();
|
|
||||||
$table->unsignedInteger('revision_id')->index();
|
|
||||||
|
|
||||||
$table->foreign('proposal_id')->references('id')->on('proposals')
|
|
||||||
->onDelete('cascade');
|
|
||||||
|
|
||||||
$table->foreign('revision_id')->references('id')->on('revisions')
|
|
||||||
->onDelete('cascade');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reverse the migrations.
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
|
||||||
public function down()
|
|
||||||
{
|
|
||||||
Schema::dropIfExists('revision_proposal_pivot');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,7 @@ Complex animated column editor for the table edit page
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<input type="hidden" :name="name" :value="JSON.stringify(columns)" v-if="!newTable">
|
<input type="hidden" :name="name" :value="JSON.stringify(columns)" v-if="newTable">
|
||||||
|
|
||||||
<div :class="newTable ? ['col-md-12', 'mt-3'] : []">
|
<div :class="newTable ? ['col-md-12', 'mt-3'] : []">
|
||||||
<table :class="[
|
<table :class="[
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
<div class="btn-group" role="group" aria-label="Fork">
|
<div class="btn-group" role="group" aria-label="Fork">
|
||||||
@if(FEATURE_PROPOSALS)
|
@if(FEATURE_PROPOSALS)
|
||||||
<a href="" class="btn btn-outline-primary py-1 btn-sm" @tooltip(Change Proposals)>
|
<a href="" class="btn btn-outline-primary py-1 btn-sm" @tooltip(Change Proposals)>
|
||||||
@icon(fa-inbox fa-pr, sr:Change Proposals){{ $table->proposals_count ?: '–' }}
|
@icon(fa-inbox fa-pr, sr:Change Proposals){{ $proposals_count ?: '–' }}
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
@auth
|
@auth
|
||||||
|
|||||||
Reference in New Issue
Block a user