Added an menu to show some simple statistics.

This commit is contained in:
Jan Böhmer 2020-02-10 23:26:45 +01:00
parent 15957203af
commit f79975832a
7 changed files with 454 additions and 4 deletions

View file

@ -0,0 +1,44 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Controller;
use App\Services\StatisticsHelper;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class StatisticsController extends AbstractController
{
/**
* @Route("/statistics", name="statistics_view")
* @return Response
*/
public function showStatistics(StatisticsHelper $helper): Response
{
$this->denyAccessUnlessGranted('@tools.statistics');
return $this->render('/Statistics/statistics.html.twig', [
'helper' => $helper,
]);
}
}

View file

@ -33,7 +33,7 @@ use LogicException;
/** /**
* Class Attachment. * Class Attachment.
* *
* @ORM\Entity * @ORM\Entity(repositoryClass="App\Repository\AttachmentRepository")
* @ORM\Table(name="`attachments`") * @ORM\Table(name="`attachments`")
* @ORM\InheritanceType("SINGLE_TABLE") * @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="class_name", type="string") * @ORM\DiscriminatorColumn(name="class_name", type="string")

View file

@ -0,0 +1,80 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Repository;
use Doctrine\ORM\EntityRepository;
class AttachmentRepository extends EntityRepository
{
/**
* Gets the count of all private/secure attachments.
* @return int
*/
public function getPrivateAttachmentsCount(): int
{
$qb = $this->createQueryBuilder('attachment');
$qb->select('COUNT(attachment)')
->where('attachment.path LIKE :like');
$qb->setParameter('like', '\\%SECURE\\%%');
$query = $qb->getQuery();
return (int) $query->getSingleScalarResult();
}
/**
* Gets the count of all external attachments (attachments only containing an URL)
* @return int
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getExternalAttachments(): int
{
$qb = $this->createQueryBuilder('attachment');
$qb->select('COUNT(attachment)')
->where('attachment.path LIKE :http')
->orWhere('attachment.path LIKE :https');
$qb->setParameter('http', 'http://%');
$qb->setParameter('https', 'https://%');
$query = $qb->getQuery();
return (int) $query->getSingleScalarResult();
}
/**
* Gets the count of all attachments where an user uploaded an file.
* @return int
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getUserUploadedAttachments(): int
{
$qb = $this->createQueryBuilder('attachment');
$qb->select('COUNT(attachment)')
->where('attachment.path LIKE :base')
->orWhere('attachment.path LIKE :media')
->orWhere('attachment.path LIKE :secure');
$qb->setParameter('secure', '\\%SECURE\\%%');
$qb->setParameter('base', '\\%BASE\\%%');
$qb->setParameter('media', '\\%MEDIA\\%%');
$query = $qb->getQuery();
return (int) $query->getSingleScalarResult();
}
}

View file

@ -24,9 +24,45 @@ declare(strict_types=1);
namespace App\Repository; namespace App\Repository;
use App\Entity\Parts\PartLot;
use Doctrine\ORM\EntityRepository; use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
class PartRepository extends EntityRepository class PartRepository extends EntityRepository
{ {
//TODO /**
* Gets the summed up instock of all parts (only parts without an measurent unit)
* @return string
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getPartsInstockSum(): string
{
$qb = new QueryBuilder($this->getEntityManager());
$qb->select('SUM(part_lot.amount)')
->from(PartLot::class, 'part_lot')
->leftJoin('part_lot.part', 'part')
->where('part.partUnit IS NULL');
$query = $qb->getQuery();
return $query->getSingleScalarResult();
}
/**
* Gets the number of parts that has price informations.
* @return int
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getPartsCountWithPrice(): int
{
$qb = $this->createQueryBuilder('part');
$qb->select('COUNT(part)')
->innerJoin('part.orderdetails', 'orderdetail')
->innerJoin('orderdetail.pricedetails', 'pricedetail')
->where('pricedetail.price > 0.0');
$query = $qb->getQuery();
return (int) $query->getSingleScalarResult();
}
} }

View file

@ -0,0 +1,149 @@
<?php
/**
* This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
*
* Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
namespace App\Services;
use App\Entity\Attachments\Attachment;
use App\Entity\Attachments\AttachmentType;
use App\Entity\Devices\Device;
use App\Entity\Parts\Category;
use App\Entity\Parts\Footprint;
use App\Entity\Parts\Manufacturer;
use App\Entity\Parts\MeasurementUnit;
use App\Entity\Parts\Part;
use App\Entity\Parts\Storelocation;
use App\Entity\Parts\Supplier;
use App\Entity\PriceInformations\Currency;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
class StatisticsHelper
{
protected $em;
protected $part_repo;
protected $attachment_repo;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
$this->part_repo = $this->em->getRepository(Part::class);
$this->attachment_repo = $this->em->getRepository(Attachment::class);
}
/**
* Returns the count of distinct parts
*/
public function getDistinctPartsCount(): int
{
return $this->part_repo->count([]);
}
/**
* Returns the summed instocked over all parts (only parts without a measurement unit)
* @return string
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getPartsInstockSum(): string
{
return $this->part_repo->getPartsInstockSum();
}
/**
* Returns the number of all parts which have price informations
* @return int
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getPartsCountWithPrice(): int
{
return $this->part_repo->getPartsCountWithPrice();
}
/**
* Returns the number of datastructures for the given type.
* @param string $type
* @return int
*/
public function getDataStructuresCount(string $type): int
{
$arr = [
'attachment_type' => AttachmentType::class,
'category' => Category::class,
'device' => Device::class,
'footprint' => Footprint::class,
'manufacturer' => Manufacturer::class,
'measurement_unit' => MeasurementUnit::class,
'storelocation' => Storelocation::class,
'supplier' => Supplier::class,
'currency' => Currency::class,
];
if (!isset($arr[$type])) {
throw new \InvalidArgumentException('No count for the given type available!');
}
/** @var EntityRepository $repo */
$repo = $this->em->getRepository($arr[$type]);
return $repo->count([]);
}
/**
* Gets the count of all attachments.
* @return int
*/
public function getAttachmentsCount(): int
{
return $this->attachment_repo->count([]);
}
/**
* Gets the count of all private/secure attachments
* @return int
*/
public function getPrivateAttachmentsCount(): int
{
return $this->attachment_repo->getPrivateAttachmentsCount();
}
/**
* Gets the count of all external (only containing an URL) attachments
* @return int
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getExternalAttachmentsCount(): int
{
return $this->attachment_repo->getExternalAttachments();
}
/**
* Gets the count of all attachments where the user uploaded an file.
* @return int
* @throws \Doctrine\ORM\NoResultException
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getUserUploadedAttachmentsCount(): int
{
return $this->attachment_repo->getUserUploadedAttachments();
}
}

View file

@ -187,6 +187,13 @@ class ToolsTreeBuilder
); );
} }
if ($this->security->isGranted('@tools.statistics')) {
$show_nodes[] = new TreeViewNode(
$this->translator->trans('tree.tools.show.statistics'),
$this->urlGenerator->generate('statistics_view')
);
}
return $show_nodes; return $show_nodes;
} }

View file

@ -0,0 +1,134 @@
{% extends "main_card.html.twig" %}
{# @var StatisticsHelper helper #}
{% block title %}{% trans %}statistics.title{% endtrans %}{% endblock %}
{% block card_title %}<i class="fas fa-chart-bar fa-fw"></i>
{% trans %}statistics.title{% endtrans %}{% endblock %}
{% block card_body %}
<ul class="nav nav-tabs ml-3 mr-3 mt-2" id="statistics_tabs" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="parts-tab" data-toggle="tab" href="#parts" role="tab" aria-controls="home" aria-selected="true">
{% trans %}statistics.parts{% endtrans %}
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="data_structures-tab" data-toggle="tab" href="#data_structures" role="tab" aria-controls="profile" aria-selected="false">
{% trans %}statistics.data_structures{% endtrans %}
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="attachments-tab" data-toggle="tab" href="#attachments" role="tab" aria-controls="contact" aria-selected="false">
{% trans %}statistics.attachments{% endtrans %}
</a>
</li>
</ul>
<div class="tab-content" id="statistics_content">
<div class="tab-pane fade show active" id="parts" role="tabpanel" aria-labelledby="parts-tab">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>{% trans %}statistics.property{% endtrans %}</th>
<th>{% trans %}statistics.value{% endtrans %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{% trans %}statistics.distinct_parts_count{% endtrans %}</td>
<td>{{ helper.distinctPartsCount }}</td>
</tr>
<tr>
<td>{% trans %}statistics.parts_instock_sum{% endtrans %}</td>
<td>{{ helper.PartsInstockSum }}</td>
</tr>
<tr>
<td>{% trans %}statistics.parts_with_price{% endtrans %}</td>
<td>{{ helper.partsCountWithPrice }}</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane fade" id="data_structures" role="tabpanel" aria-labelledby="data_structures-tab">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>{% trans %}statistics.property{% endtrans %}</th>
<th>{% trans %}statistics.value{% endtrans %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{% trans %}statistics.categories_count{% endtrans %}</td>
<td>{{ helper.dataStructuresCount("category") }}</td>
</tr>
<tr>
<td>{% trans %}statistics.footprints_count{% endtrans %}</td>
<td>{{ helper.dataStructuresCount("footprint") }}</td>
</tr>
<tr>
<td>{% trans %}statistics.manufacturers_count{% endtrans %}</td>
<td>{{ helper.dataStructuresCount("manufacturer") }}</td>
</tr>
<tr>
<td>{% trans %}statistics.storelocations_count{% endtrans %}</td>
<td>{{ helper.dataStructuresCount("storelocation") }}</td>
</tr>
<tr>
<td>{% trans %}statistics.suppliers_count{% endtrans %}</td>
<td>{{ helper.dataStructuresCount("supplier") }}</td>
</tr>
<tr>
<td>{% trans %}statistics.currencies_count{% endtrans %}</td>
<td>{{ helper.dataStructuresCount("currency") }}</td>
</tr>
<tr>
<td>{% trans %}statistics.measurement_units_count{% endtrans %}</td>
<td>{{ helper.dataStructuresCount("measurement_unit") }}</td>
</tr>
<tr>
<td>{% trans %}statistics.devices_count{% endtrans %}</td>
<td>{{ helper.dataStructuresCount("device") }}</td>
</tr>
</tbody>
</table>
</div>
<div class="tab-pane fade show" id="attachments" role="tabpanel" aria-labelledby="attachments-tab">
<table class="table table-striped table-hover">
<thead>
<tr>
<th>{% trans %}statistics.property{% endtrans %}</th>
<th>{% trans %}statistics.value{% endtrans %}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{% trans %}statistics.attachment_types_count{% endtrans %}</td>
<td>{{ helper.dataStructuresCount("attachment_type") }}</td>
</tr>
<tr>
<td>{% trans %}statistics.all_attachments_count{% endtrans %}</td>
<td>{{ helper.AttachmentsCount }}</td>
</tr>
<tr>
<td>{% trans %}statistics.user_uploaded_attachments_count{% endtrans %}</td>
<td>{{ helper.UserUploadedAttachmentsCount }}</td>
</tr>
<tr>
<td>{% trans %}statistics.private_attachments_count{% endtrans %}</td>
<td>{{ helper.PrivateAttachmentsCount }}</td>
</tr>
<tr>
<td>{% trans %}statistics.external_attachments_count{% endtrans %}</td>
<td>{{ helper.ExternalAttachmentsCount }}</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endblock %}