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

Speed up Add Device item types autocomplete #693

Merged
merged 6 commits into from
Nov 27, 2023
Merged
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
23 changes: 12 additions & 11 deletions app/Device.php
Original file line number Diff line number Diff line change
Expand Up @@ -429,14 +429,14 @@ public static function getItemTypes()
// This is a beast of a query, but the basic idea is to return a list of the categories most commonly
// used by the item types.
//
// ANY_VALUE is used to suppress errors when SQL mode is not set to ONLY_FULL_GROUP_BY.
// MAX is used to suppress errors when SQL mode is not set to ONLY_FULL_GROUP_BY.
$types = DB::select(DB::raw("
SELECT item_type,
SELECT TRIM(item_type) AS item_type,
MAX(powered) AS powered,
MAX(idcategories) AS idcategories,
MAX(categoryname)
MAX(categoryname) AS categoryname
FROM (SELECT DISTINCT s.*
FROM (SELECT item_type,
FROM (SELECT TRIM(item_type) AS item_type,
MAX(powered) AS powered,
MAX(idcategories) AS idcategories,
categories.name AS categoryname,
Expand All @@ -446,10 +446,10 @@ public static function getItemTypes()
ON devices.category = categories.idcategories
WHERE item_type IS NOT NULL
GROUP BY categoryname,
item_type) s
JOIN (SELECT item_type,
UPPER(item_type)) s
JOIN (SELECT TRIM(item_type) AS item_type,
MAX(count) AS maxcount
FROM (SELECT item_type AS item_type,
FROM (SELECT TRIM(item_type) AS item_type,
MAX(powered) AS powered,
MAX(idcategories) AS idcategories,
categories.name AS categoryname,
Expand All @@ -460,11 +460,12 @@ public static function getItemTypes()
categories.idcategories
WHERE item_type IS NOT NULL
GROUP BY categoryname,
item_type) s
GROUP BY s.item_type) AS m
ON s.item_type = m.item_type
UPPER(item_type)) s
GROUP BY UPPER(s.item_type)) AS m
ON UPPER(s.item_type) = UPPER(m.item_type)
AND s.count = m.maxcount) t
GROUP BY t.item_type
GROUP BY UPPER(t.item_type)
HAVING LENGTH(item_type) > 0
"));

return $types;
Expand Down
54 changes: 54 additions & 0 deletions app/Http/Controllers/API/ItemController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Http\Controllers\API;

use App\Device;
use App\Http\Controllers\Controller;
use App\Http\Resources\ItemCollection;
use Auth;
use Carbon\Carbon;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\QueryException;
use Illuminate\Http\Request;
use Notification;
use Illuminate\Validation\ValidationException;

class ItemController extends Controller
{
/**
* @OA\Get(
* path="/api/v2/items",
* operationId="listItemsv2",
* tags={"Items"},
* summary="Get list of items",
* @OA\Response(
* response=200,
* description="Successful operation",
* @OA\JsonContent(
* @OA\Property(
* property="data",
* title="data",
* description="An array of items",
* type="array",
* @OA\Items(
* ref="#/components/schemas/Item"
* )
* )
* )
* ),
* )
*/
public static function listItemsv2(Request $request) {
// Item types don't change often, so we can cache them.
if (\Cache::has('item_types')) {
$items = \Cache::get('item_types');
} else {
$items = Device::getItemTypes();
\Cache::put('item_types', $items, 7200);
}

return [
'data' => ItemCollection::make($items)
];
}
}
1 change: 0 additions & 1 deletion app/Http/Controllers/DeviceController.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ public function index($search = null)
'clusters' => $clusters,
'barriers' => \App\Helpers\Fixometer::allBarriers(),
'brands' => $brands,
'item_types' => Device::getItemTypes(),
]);
}

Expand Down
1 change: 0 additions & 1 deletion app/Http/Controllers/PartyController.php
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,6 @@ public function view($id)
'clusters' => $clusters,
'device_images' => $device_images,
'calendar_links' => $this->generateAddToCalendarLinks($event),
'item_types' => Device::getItemTypes(),
]);
}

Expand Down
60 changes: 60 additions & 0 deletions app/Http/Resources/Item.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\JsonResource;

/**
* @OA\Schema(
* title="Item",
* schema="Item",
* description="An item which can be specified in a device",
* @OA\Property(
* property="type",
* title="type",
* description="The name of the item",
* format="string",
* example="Laptop"
* ),
* @OA\Property(
* property="powered",
* title="powered",
* description="Whether the item is powered (true) or unpowered (false)",
* format="boolean",
* example="true"
* ),
* @OA\Property(
* property="idcategories",
* title="idcategories",
* description="The id of the category for this item",
* format="int64",
* example="16"
* ),
* @OA\Property(
* property="categoryname",
* title="categoryname",
* description="The name of the category for this item",
* format="string",
* example="Laptop medium"
* )
* )
*/

class Item extends JsonResource
{
/**
* Transform the resource into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return [
'type' => $this->item_type,
'powered' => $this->powered ? true : false,
'idcategories' => intval($this->idcategories),
'categoryname' => $this->categoryname,
];
}
}
31 changes: 31 additions & 0 deletions app/Http/Resources/ItemCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace App\Http\Resources;

use Illuminate\Http\Resources\Json\ResourceCollection;

/**
* @OA\Schema(
* title="ItemCollection",
* schema="ItemCollection",
* description="A collection of items.",
* type="array",
* @OA\Items(
* ref="#/components/schemas/Item"
* )
* )
*/

class ItemCollection extends ResourceCollection
{
/**
* Transform the resource collection into an array.
*
* @param \Illuminate\Http\Request $request
* @return array
*/
public function toArray($request)
{
return parent::toArray($request);
}
}
17 changes: 9 additions & 8 deletions resources/js/components/DeviceType.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ export default {
required: false,
default: false
},
itemTypes: {
type: Array,
required: false,
default: null
},
suppressTypeWarning: {
type: Boolean,
required: false,
Expand All @@ -55,18 +50,21 @@ export default {
}
},
computed: {
itemTypes() {
return this.$store.getters['items/list'];
},
suggestions() {
return this.itemTypes.map(i => i.item_type)
return this.itemTypes.map(i => i.type)
},
notASuggestion() {
if (!this.currentType) {
if (!this.currentType || !this.itemTypes.length) {
return false
}

let ret = true

this.itemTypes.forEach(t => {
if (t.item_type === this.currentType) {
if (t.type === this.currentType) {
ret = false
}
})
Expand Down Expand Up @@ -95,6 +93,9 @@ export default {
} catch (e){
console.error('Input focus failed', e)
}

// Fetch the item types. We do this here because it's slow, and we don't want to block the page load.
this.$store.dispatch('items/fetch')
},
watch: {
type(newVal) {
Expand Down
22 changes: 12 additions & 10 deletions resources/js/components/EventDevice.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<b-card no-body class="p-3 flex-grow-1 border-0">
<h3 class="mt-2 mb-4">{{ __('devices.title_items') }}</h3>
<DeviceType class="mb-2" :type.sync="currentDevice.item_type"
:icon-variant="add ? 'black' : 'brand'" :item-types="itemTypes" :disabled="disabled"
:icon-variant="add ? 'black' : 'brand'" :disabled="disabled"
:suppress-type-warning="suppressTypeWarning" :powered="powered"
:unknown.sync="unknownItemType"
/>
Expand Down Expand Up @@ -176,11 +176,6 @@ export default {
required: false,
default: null
},
itemTypes: {
type: Array,
required: false,
default: null
},
},
data () {
return {
Expand Down Expand Up @@ -217,6 +212,9 @@ export default {
}
},
computed: {
itemTypes() {
return this.$store.getters['items/list'];
},
idtouse() {
return this.currentDevice ? this.currentDevice.iddevices : null
},
Expand All @@ -226,14 +224,18 @@ export default {
currentCategory () {
return this.currentDevice ? this.currentDevice.category : null
},
currentType() {
return this.currentDevice ? this.currentDevice.item_type : null
},
suggestedCategory() {
let ret = null
if (this.currentDevice && this.currentDevice.item_type) {

if (this.currentType) {
// Some item types are the same as category names.
this.clusters.forEach((cluster) => {
cluster.categories.forEach((c) => {
const name = this.$lang.get('strings.' + c.name)
if (Boolean(c.powered) === Boolean(this.powered) && !name.toLowerCase().localeCompare(this.currentDevice.item_type.toLowerCase())) {
if (Boolean(c.powered) === Boolean(this.powered) && !name.toLowerCase().localeCompare(this.currentType.toLowerCase())) {
ret = {
idcategories: c.idcategories,
categoryname: c.name,
Expand All @@ -246,16 +248,16 @@ export default {
if (!ret) {
// Now check the item types. Stop at the first match, which is the most popular.
this.itemTypes.every(t => {
if (!ret && Boolean(t.powered) === Boolean(this.powered) && this.currentDevice.item_type.toLowerCase() == t.item_type.toLowerCase()) {
if (!ret && Boolean(t.powered) === Boolean(this.powered) && this.currentType.toLowerCase() == t.type.toLowerCase()) {
ret = {
idcategories: t.idcategories,
categoryname: t.categoryname,
powered: t.powered
}

console.log('Matched', t, this.itemTypes)
return false
}

return true
})
}
Expand Down
7 changes: 1 addition & 6 deletions resources/js/components/EventDeviceList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
</b-tr>
</b-thead>
<b-tbody class="borders">
<EventDeviceSummary v-for="device in devices" :key="'device-' + device.iddevices" :device="device" :canedit="canedit" :powered="powered" :idevents="idevents" :brands="brands" :barrier-list="barrierList" :itemTypes="itemTypes" :clusters="clusters" />
<EventDeviceSummary v-for="device in devices" :key="'device-' + device.iddevices" :device="device" :canedit="canedit" :powered="powered" :idevents="idevents" :brands="brands" :barrier-list="barrierList" :clusters="clusters" />
</b-tbody>
</b-table-simple>
</div>
Expand Down Expand Up @@ -76,11 +76,6 @@ export default {
required: false,
default: null
},
itemTypes: {
type: Array,
required: false,
default: null
},
},
}
</script>
Expand Down
7 changes: 1 addition & 6 deletions resources/js/components/EventDeviceSummary.vue
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@
</b-tr>
<b-tr v-else :key="'editing-' + device.iddevices">
<b-td colspan="8" class="p-0">
<EventDevice :device="device" :powered="powered" :add="false" :edit="true" :clusters="clusters" :idevents="idevents" :brands="brands" :barrier-list="barrierList" :itemTypes="itemTypes" @close="close" />
<EventDevice :device="device" :powered="powered" :add="false" :edit="true" :clusters="clusters" :idevents="idevents" :brands="brands" :barrier-list="barrierList" @close="close" />
</b-td>
</b-tr>
</transition>
Expand Down Expand Up @@ -119,11 +119,6 @@ export default {
required: false,
default: null
},
itemTypes: {
type: Array,
required: false,
default: null
},
},
data () {
return {
Expand Down
Loading