several enhancements (#1808)

* show all fields in timesheet api docs
* fix action dropdown links #1806
* apply same validation rules for passwords and for api-tokens #1753
* added "duplicate team" action #1136
This commit is contained in:
Kevin Papst
2020-07-10 03:57:15 +02:00
committed by GitHub
parent ea39d5c74d
commit 19e4ebf88c
22 changed files with 196 additions and 15 deletions

View File

@@ -94,7 +94,7 @@ final class ConfigurationController extends BaseApiController
}
/**
* Returns the instance specific timesheet configuration
* Returns the timesheet configuration
*
* @SWG\Response(
* response=200,
@@ -116,6 +116,7 @@ final class ConfigurationController extends BaseApiController
->setActiveEntriesHardLimit($this->timesheetConfiguration->getActiveEntriesHardLimit())
->setActiveEntriesSoftLimit($this->timesheetConfiguration->getActiveEntriesSoftLimit())
->setIsAllowFutureTimes($this->timesheetConfiguration->isAllowFutureTimes())
->setIsAllowOverlapping($this->timesheetConfiguration->isAllowOverlappingRecords())
;
$view = new View($model, 200);

View File

@@ -20,21 +20,35 @@ final class TimesheetConfig
*/
private $trackingMode = 'default';
/**
* Default begin datetime in PHP format
*
* @var string
*/
private $defaultBeginTime = 'now';
/**
* How many running timesheets a user is allowed to have at the same time
*
* @var int
*/
private $activeEntriesHardLimit = 1;
/**
* How many running timesheets a user is allowed before a warning is shown
*
* @var int
*/
private $activeEntriesSoftLimit = 1;
/**
* Whether entries for future times are allowed
*
* @var bool
*/
private $isAllowFutureTimes = true;
/**
* Whether overlapping entries are allowed
*
* @var bool
*/
private $isAllowOverlapping = true;
/**
* @return string
@@ -98,4 +112,16 @@ final class TimesheetConfig
return $this;
}
public function isAllowOverlapping(): bool
{
return $this->isAllowOverlapping;
}
public function setIsAllowOverlapping(bool $isAllowOverlapping): TimesheetConfig
{
$this->isAllowOverlapping = $isAllowOverlapping;
return $this;
}
}

View File

@@ -16,22 +16,32 @@ use App\Constants;
class Version
{
/**
* Kimai Version, eg. "1.9"
*
* @var string
*/
protected $version = Constants::VERSION;
/**
* Candidate: either "prod" or "dev"
*
* @var string
*/
protected $candidate = Constants::STATUS;
/**
* Full version including status, eg: "1.9-prod"
*
* @var string
*/
protected $semver = Constants::VERSION . '-' . Constants::STATUS;
/**
* The version name
*
* @var string
*/
protected $name = Constants::NAME;
/**
* A full copyright notice
*
* @var string
*/
protected $copyright = Constants::SOFTWARE . ' - ' . Constants::VERSION . ' ' . Constants::STATUS . ' (' . Constants::NAME . ') by Kevin Papst and contributors.';

View File

@@ -93,6 +93,7 @@ final class UserController extends BaseApiController
public function cgetAction(ParamFetcherInterface $paramFetcher): Response
{
$query = new UserQuery();
$query->setCurrentUser($this->getUser());
if (null !== ($visible = $paramFetcher->get('visible'))) {
$query->setVisibility($visible);

View File

@@ -376,7 +376,6 @@ class ProfileController extends AbstractController
UserApiTokenType::class,
$user,
[
'validation_groups' => ['apiTokenUpdate'],
'action' => $this->generateUrl('user_profile_api_token', ['username' => $user->getUsername()]),
'method' => 'POST'
]

View File

@@ -83,6 +83,42 @@ final class TeamController extends AbstractController
return $this->renderEditScreen(new Team(), $request);
}
/**
* @Route(path="/{id}/duplicate", name="team_duplicate", methods={"GET", "POST"})
* @Security("is_granted('edit', team) and is_granted('create_team')")
*/
public function duplicateTeam(Team $team, Request $request)
{
$newTeam = new Team();
$newTeam->setName($team->getName() . ' [COPY]');
$newTeam->setTeamLead($team->getTeamLead());
foreach ($team->getUsers() as $user) {
$newTeam->addUser($user);
}
foreach ($team->getCustomers() as $customer) {
$newTeam->addCustomer($customer);
}
foreach ($team->getProjects() as $project) {
$newTeam->addProject($project);
}
try {
// make sure that the teamlead is always part of the team, otherwise permission checks
// and filtering might not work as expected!
$team->addUser($team->getTeamLead());
$this->repository->saveTeam($newTeam);
$this->flashSuccess('action.update.success');
return $this->redirectToRoute('admin_team_edit', ['id' => $newTeam->getId()]);
} catch (\Exception $ex) {
$this->flashError('action.update.error', ['%reason%' => $ex->getMessage()]);
}
return $this->redirectToRoute('admin_team');
}
/**
* @Route(path="/{id}/edit", name="admin_team_edit", methods={"GET", "POST"})
* @Security("is_granted('edit', team)")

View File

@@ -70,6 +70,7 @@ final class UserController extends AbstractController
public function indexAction($page, Request $request): Response
{
$query = new UserQuery();
$query->setCurrentUser($this->getUser());
$query->setPage($page);
$form = $this->getToolbarForm($query);

View File

@@ -50,6 +50,11 @@ class TimesheetApiEditForm extends TimesheetEditForm
$resolver->setDefaults([
'csrf_protection' => false,
'allow_duration' => false,
// overwritten and changed to default "true",
// because the docs are cached without these fields otherwise
'include_user' => true,
'include_exported' => true,
'include_rate' => true,
]);
}
}

View File

@@ -41,6 +41,7 @@ class UserApiTokenType extends AbstractType
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'validation_groups' => ['ApiTokenUpdate'],
'data_class' => User::class,
'csrf_protection' => true,
'csrf_field_name' => '_token',

View File

@@ -30,4 +30,19 @@ trait VisibilityTrait
return $this;
}
public function isShowHidden(): bool
{
return $this->visibility === VisibilityInterface::SHOW_HIDDEN;
}
public function isShowVisible(): bool
{
return $this->visibility === VisibilityInterface::SHOW_VISIBLE;
}
public function isShowBoth(): bool
{
return $this->visibility === VisibilityInterface::SHOW_BOTH;
}
}