Skip to content
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

Saved Custom Report Templates #15714

Merged
merged 178 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from 174 commits
Commits
Show all changes
178 commits
Select commit Hold shift + click to select a range
f9fc2a4
alphatyping for layout
akemidx Aug 22, 2023
c9fcc90
create saved reports migration
akemidx Aug 23, 2023
78d589f
beginning of migrations
akemidx Aug 24, 2023
734af87
template structuring
akemidx Aug 28, 2023
f1cc2c8
submitting form after capturing/formatting fixes
akemidx Aug 29, 2023
45dbc02
save commit
akemidx Sep 5, 2023
e5792fd
api/savereports controller
akemidx Oct 25, 2023
7a5faa9
Adjust margin on custom report page
marcusmoore Oct 30, 2023
06186c9
WIP: Simply post the form to a different controller
marcusmoore Oct 30, 2023
b7011d8
WIP: add methods to restore settings from saved report
marcusmoore Nov 3, 2023
4f03114
Scaffold a couple test cases
marcusmoore Nov 30, 2023
bca7f20
Implement restoring select values
marcusmoore Dec 1, 2023
505d601
Implement restoring date ranges
marcusmoore Dec 1, 2023
5041c07
WIP: implement restoring checkbox inputs
marcusmoore Dec 1, 2023
327d275
Merge branch 'saved-custom-reports' into saving_custom_report_template
marcusmoore Dec 1, 2023
b7f6c7d
html work, code comments for tomorrow's tasks
akemidx Dec 6, 2023
4a0bb31
Scaffold two more test cases
marcusmoore Dec 7, 2023
bd86c54
Add a few typehints
marcusmoore Dec 11, 2023
e636d7b
multiple saved reports, beginning of dynamic dropdown listing/queried…
akemidx Dec 11, 2023
c3b53b2
Allow saving custom reports
marcusmoore Dec 11, 2023
c9157dc
Update docblock
marcusmoore Dec 11, 2023
c3845f4
Add tests around loading saved reports
marcusmoore Dec 11, 2023
52028dd
Add authorization to saving saved reports route
marcusmoore Dec 11, 2023
b9cda88
Alphabetize saved reports list
marcusmoore Dec 12, 2023
89c47c1
Add validation for saving reports
marcusmoore Dec 12, 2023
c68a2a3
Add test case for saving custom reports
marcusmoore Dec 12, 2023
d92893b
Remove old javascript
marcusmoore Dec 12, 2023
e791ebb
Display report name in input
marcusmoore Dec 12, 2023
e9e6817
frontend stuff
akemidx Dec 12, 2023
ca35b66
frontys
akemidx Dec 13, 2023
ad202be
update method (WIP ver)
akemidx Dec 13, 2023
75bd056
Display saved template name in header
marcusmoore Dec 13, 2023
27bb938
WIP: add dedicated edit report page
marcusmoore Dec 14, 2023
9fcb1a2
Rename SavedReport to ReportTemplate
marcusmoore Dec 18, 2023
ebf760a
translations, UI fixes
akemidx Dec 19, 2023
b2d0cbb
Display validation error for report name
marcusmoore Dec 20, 2023
c35179b
Use existing class in place of inline styling
marcusmoore Dec 20, 2023
26cc449
Use dedicated show route for report templates
marcusmoore Dec 20, 2023
cf5c780
Only show template name input on default screen
marcusmoore Dec 20, 2023
0527201
Allow templates to be updated
marcusmoore Dec 20, 2023
9d062f9
Make control statements more explicit
marcusmoore Dec 21, 2023
0504c09
Implement ability to delete templates
marcusmoore Dec 21, 2023
9a5c8c4
Formatting and clean ups
marcusmoore Dec 21, 2023
578495b
Update tests
marcusmoore Dec 21, 2023
fda7717
Simplify url
marcusmoore Dec 21, 2023
495d74f
Formatting
marcusmoore Dec 21, 2023
71761a4
A few more small clean ups
marcusmoore Dec 21, 2023
6fcbb10
Implement test for filtering out invalid models from selectValues
marcusmoore Dec 21, 2023
5c0c60a
Formatting
marcusmoore Dec 21, 2023
2eeaef0
Improve readability
marcusmoore Dec 21, 2023
1dd9273
Improve readability
marcusmoore Dec 21, 2023
62f8353
Implement model filtering for selectValue method
marcusmoore Dec 21, 2023
3e5f804
Display "Save" on the update template page button
marcusmoore Dec 21, 2023
8a496cc
Formatting
marcusmoore Dec 21, 2023
8785392
Formatting
marcusmoore Dec 21, 2023
4c62e8a
Add guard against attempting to access property on unsaved template
marcusmoore Dec 21, 2023
dc27e67
Remove edit and delete buttons from edit template view
marcusmoore Dec 21, 2023
7f0e3e2
Scaffold additional test
marcusmoore Dec 21, 2023
92e3a1e
Update variable names
marcusmoore Dec 21, 2023
48e5ee2
Add filtering to remaining selects
marcusmoore Dec 21, 2023
4fc8e8d
Add todo
marcusmoore Dec 21, 2023
7f153b3
Always return an array from selectValues method
marcusmoore Dec 22, 2023
8c434c7
Implement and scaffold tests
marcusmoore Dec 22, 2023
618bbc4
Mark test as incomplete
marcusmoore Dec 22, 2023
1ac0be5
Remove test case
marcusmoore Dec 22, 2023
a23a3b9
Partially implement test
marcusmoore Dec 22, 2023
9e0897b
Remove old comments
marcusmoore Dec 22, 2023
fcef604
Implement radioValue properly
marcusmoore Dec 22, 2023
137193a
Remove duplicate test
marcusmoore Jan 3, 2024
d8d92a6
Scaffold test case
marcusmoore Jan 3, 2024
740d46a
Repopulate report after validation error
marcusmoore Jan 3, 2024
a756d2b
Implement test
marcusmoore Jan 3, 2024
25cba65
Remove marker test case
marcusmoore Jan 3, 2024
f2d34b2
Remove inline javascript
marcusmoore Jan 3, 2024
2710312
messages/translations
akemidx Jan 9, 2024
a5099b5
translations/messages on report template controller
akemidx Jan 10, 2024
5f8e914
clarifying name box
akemidx Jan 10, 2024
6f6341b
about saved reports box
akemidx Jan 10, 2024
b34886e
Move box header into box
marcusmoore Jan 11, 2024
0202a97
Add missing tag
marcusmoore Jan 11, 2024
82df7a6
Add form label and remove info box from show and edit pages
marcusmoore Jan 11, 2024
e5fb888
Implement test
marcusmoore Jan 11, 2024
8d8bf73
Scaffold additional tests
marcusmoore Jan 11, 2024
9c1bea0
Add failing test
marcusmoore Jan 11, 2024
d72970b
Add global scope to limit template to current user
marcusmoore Jan 11, 2024
20bd832
standardizing naming to use Template
akemidx Jan 12, 2024
2768f19
code cleanup
akemidx Jan 16, 2024
0883321
Only limit template creator scope when authenticated
marcusmoore Jan 17, 2024
5a396cc
Add assertion
marcusmoore Jan 17, 2024
4d8d069
Update placeholder
marcusmoore Jan 17, 2024
82f4cc7
Improve variable name
marcusmoore Jan 17, 2024
0ac1dd3
Organize tests
marcusmoore Jan 17, 2024
691e81d
Implement some tests
marcusmoore Jan 18, 2024
bbfee27
Implement test
marcusmoore Jan 18, 2024
1a1f417
Change variable name to keep foreach scoped
marcusmoore Jan 18, 2024
b24d806
Add clarifying comments
marcusmoore Jan 18, 2024
786c41a
Add some type hints
marcusmoore Jan 18, 2024
f64aa4d
Improve variable name
marcusmoore Jan 18, 2024
861ef63
Use is_iterable for checks instead of is_array
marcusmoore Jan 18, 2024
1630392
Uncheck a couple boxes by default to match existing behavior
marcusmoore Jan 18, 2024
b74115b
Add docblocks
marcusmoore Jan 18, 2024
2195402
Add trailing commas
marcusmoore Jan 22, 2024
530ea47
Merge branch 'develop' into saving_custom_report_template
marcusmoore Jan 23, 2024
0881301
Fix language strings
marcusmoore Jan 23, 2024
3952fc1
Re-render order number properly
marcusmoore Jan 23, 2024
2d56312
Indent
marcusmoore Jan 23, 2024
d65c0c8
Remove comma
marcusmoore Jan 23, 2024
8bba11e
Merge branch 'develop' into saving_custom_report_template
marcusmoore Sep 17, 2024
b51c505
Fix department, manufacturer, and category multi-selects
marcusmoore Sep 17, 2024
8e0b718
Scope Select All checkbox to fields on left side of page
marcusmoore Sep 17, 2024
89e2d03
Fix the deleted assets radio
marcusmoore Sep 17, 2024
6b70443
Formatting
marcusmoore Sep 17, 2024
6e16f58
Remove reference to old trait
marcusmoore Sep 18, 2024
5306e1c
Merge branch 'develop' into saving_custom_report_template
marcusmoore Oct 15, 2024
a20e03f
Merge branch 'develop' into saving_custom_report_template
marcusmoore Oct 22, 2024
dd97e4e
Update permission tests
marcusmoore Oct 22, 2024
d953519
Merge branch 'develop' into saving_custom_report_template
marcusmoore Oct 22, 2024
02c22c9
Add test case
marcusmoore Oct 22, 2024
5ebf013
Add comment
marcusmoore Oct 22, 2024
37d65da
Add todo
marcusmoore Oct 23, 2024
c313a78
Remove outdated @since annotations
marcusmoore Oct 23, 2024
b6aae1f
Group routes
marcusmoore Oct 23, 2024
271de1e
Improve tests
marcusmoore Oct 23, 2024
3d28a90
Improve test
marcusmoore Oct 23, 2024
4217d74
Improve tests
marcusmoore Oct 23, 2024
94e168f
Fix label
marcusmoore Oct 23, 2024
30dc5fa
Allow retrieving eol start and end dates
marcusmoore Oct 23, 2024
c1aa338
Add error message for eol start and end fields
marcusmoore Oct 23, 2024
853e14f
Fix width
marcusmoore Oct 23, 2024
8a06f4a
Improve label
marcusmoore Oct 23, 2024
2fcc7e1
Make template name required
marcusmoore Oct 23, 2024
3616c92
Reflash template name
marcusmoore Oct 23, 2024
e390a95
Improve back button behavior
marcusmoore Oct 23, 2024
84f6638
Add authorization check
marcusmoore Oct 23, 2024
7238238
Use created_by instead of user_id
marcusmoore Oct 23, 2024
54dec8d
Add translations
marcusmoore Oct 28, 2024
b45749a
Update translation strings
marcusmoore Oct 28, 2024
ee00699
Remove unused strings
marcusmoore Oct 28, 2024
c881727
Add redirect for missing template
marcusmoore Oct 28, 2024
d4cf392
Organize tests
marcusmoore Oct 28, 2024
2042733
Switch to array syntax
marcusmoore Oct 28, 2024
7777147
Add custom-reporting group
marcusmoore Oct 28, 2024
7c08fbe
Update test name
marcusmoore Oct 28, 2024
0d58ac6
Make options required in model
marcusmoore Oct 28, 2024
b97c54a
Improve handling of old data for text inputs
marcusmoore Oct 29, 2024
45e98e0
Merge branch 'develop' into saving_custom_report_template
marcusmoore Oct 29, 2024
0eadab4
Add soft deletes
marcusmoore Oct 29, 2024
54b4db8
Revert "Add soft deletes"
marcusmoore Oct 29, 2024
702edf7
Remove x from select dropdown
marcusmoore Oct 29, 2024
3c75fc2
Move test
marcusmoore Oct 29, 2024
86762c5
Remove marker test case
marcusmoore Oct 29, 2024
35f8a71
Test organization
marcusmoore Oct 29, 2024
930ef3f
Work on tests for activity logging
marcusmoore Oct 30, 2024
59e6874
Scaffold test cases
marcusmoore Oct 30, 2024
0cc3031
Implement deleted log
marcusmoore Oct 30, 2024
d727b03
Implement activity log test
marcusmoore Oct 30, 2024
e871481
Add group
marcusmoore Oct 30, 2024
20dab4d
Simplify tests
marcusmoore Oct 30, 2024
4aedbb5
Implement activity logging transformer tests
marcusmoore Oct 31, 2024
ae24b73
Fix action_type from "created" to" create"
marcusmoore Oct 31, 2024
5f83cb6
Use route model binding
marcusmoore Oct 31, 2024
41f2534
Populate select with selected template
marcusmoore Nov 4, 2024
4daa8e7
Indenting
marcusmoore Nov 4, 2024
37d7923
Update page title dynamically
marcusmoore Nov 6, 2024
8873137
Scaffold updating template name
marcusmoore Nov 6, 2024
c5710b8
Add test validation test for update method and remove name uniqueness…
marcusmoore Nov 7, 2024
7862b74
Inline fields when updating
marcusmoore Nov 7, 2024
4aa5961
Update page titles
marcusmoore Nov 7, 2024
f8d0ddb
Improve template name input
marcusmoore Nov 7, 2024
363ec84
Re-introduce soft deletes
marcusmoore Nov 8, 2024
0e3efdf
Add string to name validation
marcusmoore Nov 8, 2024
b8265d5
Improve comment
marcusmoore Nov 8, 2024
dc0b8c7
Inline route helpers in tests
marcusmoore Nov 8, 2024
4bb1915
Move test
marcusmoore Nov 8, 2024
7373e20
Improve comment
marcusmoore Nov 12, 2024
5574c5f
Improve comments
marcusmoore Nov 12, 2024
53de97d
Merge branch 'develop' into saving_custom_report_template
marcusmoore Nov 12, 2024
92f33c0
Merge branch 'develop' into saving_custom_report_template
marcusmoore Nov 13, 2024
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
80 changes: 80 additions & 0 deletions app/Http/Controllers/ReportTemplatesController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

namespace App\Http\Controllers;

use App\Models\CustomField;
use App\Models\ReportTemplate;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;

class ReportTemplatesController extends Controller
{
public function store(Request $request): RedirectResponse
{
$this->authorize('reports.view');

// Ignore "options" rules since data does not come in under that key...
$validated = $request->validate(Arr::except((new ReportTemplate)->getRules(), 'options'));

$report = $request->user()->reportTemplates()->create([
'name' => $validated['name'],
'options' => $request->except(['_token', 'name']),
]);

session()->flash('success', trans('admin/reports/message.create.success'));

return redirect()->route('report-templates.show', $report->id);
}

public function show(ReportTemplate $reportTemplate)
{
$this->authorize('reports.view');

$customfields = CustomField::get();
$report_templates = ReportTemplate::orderBy('name')->get();

return view('reports/custom', [
'customfields' => $customfields,
'report_templates' => $report_templates,
'template' => $reportTemplate,
]);
}

public function edit(ReportTemplate $reportTemplate)
{
$this->authorize('reports.view');

return view('reports/custom', [
'customfields' => CustomField::get(),
'template' => $reportTemplate,
]);
}

public function update(Request $request, ReportTemplate $reportTemplate): RedirectResponse
{
$this->authorize('reports.view');

// Ignore "options" rules since data does not come in under that key...
$validated = $request->validate(Arr::except((new ReportTemplate)->getRules(), 'options'));

$reportTemplate->update([
'name' => $validated['name'],
'options' => $request->except(['_token', 'name']),
]);

session()->flash('success', trans('admin/reports/message.update.success'));

return redirect()->route('report-templates.show', $reportTemplate->id);
}

public function destroy(ReportTemplate $reportTemplate): RedirectResponse
{
$this->authorize('reports.view');

$reportTemplate->delete();

return redirect()->route('reports/custom')
->with('success', trans('admin/reports/message.delete.success'));
}
}
20 changes: 18 additions & 2 deletions app/Http/Controllers/ReportsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use App\Models\CustomField;
use App\Models\Depreciation;
use App\Models\License;
use App\Models\ReportTemplate;
use App\Models\Setting;
use App\Notifications\CheckoutAssetNotification;
use Carbon\Carbon;
Expand Down Expand Up @@ -392,12 +393,27 @@ public function exportLicenseReport() : Response
* @see ReportsController::postCustomReport() method that generates the CSV
* @since [v1.0]
*/
public function getCustomReport() : View
public function getCustomReport(Request $request) : View
{
$this->authorize('reports.view');
$customfields = CustomField::get();
$report_templates = ReportTemplate::orderBy('name')->get();

return view('reports/custom')->with('customfields', $customfields);
// The view needs a template to render correctly, even if it is empty...
$template = new ReportTemplate;

// Set the report's input values in the cases we were redirected back
// with validation errors so the report is populated as expected.
if ($request->old()) {
$template->name = $request->old('name');
$template->options = $request->old();
}

return view('reports/custom', [
'customfields' => $customfields,
'report_templates' => $report_templates,
'template' => $template,
]);
}

/**
Expand Down
238 changes: 238 additions & 0 deletions app/Models/ReportTemplate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes;
use Watson\Validating\ValidatingTrait;

class ReportTemplate extends Model
{
use HasFactory;
use SoftDeletes;
use ValidatingTrait;

protected $casts = [
'options' => 'array',
];

protected $fillable = [
'created_by',
'name',
'options',
];

protected $rules = [
'name' => [
'required',
'string',
],
'options' => [
'required',
'array',
],
];

protected static function booted()
{
// Scope to current user
static::addGlobalScope('current_user', function (Builder $builder) {
if (auth()->check()) {
$builder->where('created_by', auth()->id());
}
});

static::created(function (ReportTemplate $reportTemplate) {
$logAction = new Actionlog([
'item_type' => ReportTemplate::class,
'item_id' => $reportTemplate->id,
'created_by' => auth()->id(),
]);

$logAction->logaction('create');
});

static::updated(function (ReportTemplate $reportTemplate) {
$changed = [];

foreach ($reportTemplate->getDirty() as $key => $value) {
$changed[$key] = [
'old' => $reportTemplate->getOriginal($key),
'new' => $reportTemplate->getAttribute($key),
];
}

$logAction = new Actionlog();
$logAction->item_type = ReportTemplate::class;
$logAction->item_id = $reportTemplate->id;
$logAction->created_by = auth()->id();
$logAction->log_meta = json_encode($changed);
$logAction->logaction('update');
});

static::deleted(function (ReportTemplate $reportTemplate) {
$logAction = new Actionlog([
'item_type' => ReportTemplate::class,
'item_id' => $reportTemplate->id,
'created_by' => auth()->id(),
]);

$logAction->logaction('delete');
});
Comment on lines +48 to +84
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Understandable if we want to move these to an observer.

}

/**
* Establishes the report template -> creator relationship.
*
*/
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'created_by');
}

/**
* Get the value of a checkbox field for the given field name.
*
* @param string $fieldName
* @param string $fallbackValue The value to return if the report template is not saved yet.
*
*/
public function checkmarkValue(string $fieldName, string $fallbackValue = '1'): string
{
// Assuming we're using the null object pattern, and an empty model
// was passed to the view when showing the default report page,
// return the fallback value so that checkboxes are checked by default.
if (is_null($this->id)) {
return $fallbackValue;
}

// Return the field's value if it exists and return 0
// if not so that checkboxes are unchecked by default.
return $this->options[$fieldName] ?? '0';
}

/**
* Get the value of a radio field for the given field name.
*
* @param string $fieldName
* @param string $value The value to check against.
* @param bool $isDefault Whether the radio input being checked is the default.
*
*/
public function radioValue(string $fieldName, string $value, bool $isDefault = false): bool
{
$fieldExists = array_has($this->options, $fieldName);

// If the field doesn't exist but the radio input
// being checked is the default then return true.
if (!$fieldExists && $isDefault) {
return true;
}

// If the field exists and matches what we're checking then return true.
if ($fieldExists && $this->options[$fieldName] === $value) {
return true;
}

// Otherwise return false.
return false;
}

/**
* Get the value of a select field for the given field name.
*
* @param string $fieldName
* @param string|null $model The Eloquent model to check against.
*
* @return mixed|null
*
*/
public function selectValue(string $fieldName, string $model = null)
{
// If the field does not exist then return null.
if (!isset($this->options[$fieldName])) {
return null;
}

$value = $this->options[$fieldName];

// If the value was stored as an array, most likely
// due to a previously being a multi-select,
// then return the first value.
if (is_array($value)) {
$value = $value[0];
}

// If a model is provided then we should ensure we only return
// the value if the model still exists.
// Note: It is possible $value is an id that no longer exists and this will return null.
if ($model) {
$foundModel = $model::find($value);

return $foundModel ? $foundModel->id : null;
}

return $value;
}

/**
* Get the values of a multi-select field for the given field name.
*
* @param string $fieldName
* @param string|null $model The Eloquent model to check against.
*
* @return iterable
*
*/
public function selectValues(string $fieldName, string $model = null): iterable
{
// If the field does not exist then return an empty array.
if (!isset($this->options[$fieldName])) {
return [];
}

// If a model is provided then we should ensure we only return
// the ids of models that exist and are not deleted.
if ($model) {
return $model::findMany($this->options[$fieldName])->pluck('id');
}

// Wrap the value in an array if needed. This is to ensure
// values previously stored as a single value,
// most likely from a single select, are returned as an array.
if (!is_array($this->options[$fieldName])) {
return [$this->options[$fieldName]];
}

return $this->options[$fieldName];
}

/**
* Get the value of a text field for the given field name.
*
* @param string $fieldName
* @param string|null $fallbackValue
*
* @return string
*/
public function textValue(string $fieldName, string|null $fallbackValue = ''): string
{
// Assuming we're using the null object pattern,
// return the default value if the object is not saved yet.
if (is_null($this->id)) {
return (string) $fallbackValue;
}

// Return the field's value if it exists
// and return the default value if not.
return $this->options[$fieldName] ?? '';
}

public function getDisplayNameAttribute()
{
return $this->name;
}
}
10 changes: 10 additions & 0 deletions app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Illuminate\Contracts\Translation\HasLocalePreference;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\Access\Authorizable;
use Illuminate\Notifications\Notifiable;
Expand Down Expand Up @@ -360,6 +361,15 @@ public function licenses()
return $this->belongsToMany(\App\Models\License::class, 'license_seats', 'assigned_to', 'license_id')->withPivot('id', 'created_at', 'updated_at');
}

/**
* Establishes the user -> reportTemplates relationship
*
*/
public function reportTemplates(): HasMany
{
return $this->hasMany(ReportTemplate::class, 'created_by');
}

/**
* Establishes a count of all items assigned
*
Expand Down
Loading
Loading