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

[Feature] Add search request history and dialog to manager dashboard #11653

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
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
6 changes: 4 additions & 2 deletions api/app/Models/PoolCandidateSearchRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
/**
* Class PoolCandidateSearchRequest
*
* @property int $id
* @property string $id
* @property string $full_name
* @property string $email
* @property string $department_id
Expand All @@ -37,6 +37,7 @@
* @property string $reason
* @property string $community_id
* @property int $initial_result_count
* @property string $user_id
*/
class PoolCandidateSearchRequest extends Model
{
Expand Down Expand Up @@ -67,7 +68,8 @@ protected static function boot()

parent::boot();
static::creating(function ($searchRequest) use ($user) {
$searchRequest->user_id = $user?->id;
// unless the user_id was already specified use the currently authenticated user
$searchRequest->user_id = $searchRequest->user_id ?? $user?->id;
petertgiles marked this conversation as resolved.
Show resolved Hide resolved
});
}

Expand Down
6 changes: 6 additions & 0 deletions api/app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@ public function skills()
return $this->hasManyDeepFromRelations($this->userSkills(), (new UserSkill)->skill());
}

// User 1-0..* PoolCandidateSearchRequest
public function poolCandidateSearchRequests(): HasMany
{
return $this->hasMany(PoolCandidateSearchRequest::class);
}

// This method will add the specified skills to UserSkills if they don't exist yet.
public function addSkills($skill_ids)
{
Expand Down
4 changes: 4 additions & 0 deletions api/app/Policies/PoolCandidateSearchRequestPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public function view(User $user, PoolCandidateSearchRequest $poolCandidateSearch
return true;
}

if ($user->isAbleTo('view-own-searchRequest') && $poolCandidateSearchRequest->user_id == $user->id) {
return true;
}

$poolCandidateSearchRequest->loadMissing('community.team');

if (isset($poolCandidateSearchRequest->community->team)) {
Expand Down
5 changes: 5 additions & 0 deletions api/config/rolepermission.php
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,10 @@
'en' => 'Delete SearchRequests submitted to this Team',
'fr' => 'Supprimer n\'import quelle demande de recherche',
],
'view-own-searchRequest' => [
'en' => 'View own SearchRequests',
'fr' => 'Voir ses propres demandes de recherche',
],

'view-any-team' => [
'en' => 'View Any Team',
Expand Down Expand Up @@ -871,6 +875,7 @@
],
'searchRequest' => [
'any' => ['create'],
'own' => ['view'],
],
'team' => [
'any' => ['view'],
Expand Down
2 changes: 2 additions & 0 deletions api/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ type User {

# Bookmarks
poolBookmarks: [Pool] @belongsToMany
# My talent requests
poolCandidateSearchRequests: [PoolCandidateSearchRequest!]
petertgiles marked this conversation as resolved.
Show resolved Hide resolved
}

interface Notification {
Expand Down
5 changes: 0 additions & 5 deletions api/phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1912,11 +1912,6 @@ parameters:
count: 1
path: app/Notify/Client.php

-
message: "#^Parameter \\#1 \\$trackingNumber of class App\\\\Notifications\\\\TalentRequestSubmissionConfirmation constructor expects string, int given\\.$#"
count: 1
path: app/Observers/PoolCandidateSearchRequestObserver.php

-
message: "#^Access to an undefined property Illuminate\\\\Database\\\\Eloquent\\\\Collection\\<int, Illuminate\\\\Database\\\\Eloquent\\\\Model\\>\\|Illuminate\\\\Database\\\\Eloquent\\\\Model\\:\\:\\$community\\.$#"
count: 2
Expand Down
1 change: 1 addition & 0 deletions api/storage/app/lighthouse-schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,7 @@ type User {
enabledEmailNotifications: [NotificationFamily]
enabledInAppNotifications: [NotificationFamily]
poolBookmarks: [Pool]
poolCandidateSearchRequests: [PoolCandidateSearchRequest!]
}

interface Notification {
Expand Down
80 changes: 78 additions & 2 deletions api/tests/Feature/PoolCandidateSearchRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,8 @@ public function testPoolCandidateSearchRequestPolicy()

$community = Community::factory()->create();
$otherCommunity = Community::factory()->create();
$searchRequest1 = PoolCandidateSearchRequest::factory()->create(['community_id' => $community->id]);
$searchRequest2 = PoolCandidateSearchRequest::factory()->create(['community_id' => $otherCommunity->id]);
$searchRequest1 = PoolCandidateSearchRequest::factory()->create(['community_id' => $community->id, 'user_id' => null]);
$searchRequest2 = PoolCandidateSearchRequest::factory()->create(['community_id' => $otherCommunity->id, 'user_id' => null]);

$communityRecruiter = User::factory()
->asCommunityRecruiter([$community->id])
Expand Down Expand Up @@ -344,4 +344,80 @@ public function testUserIdForLoggedInUsers()

$this->assertTrue(PoolCandidateSearchRequest::where('user_id', $this->adminUser->id)->exists());
}

public function testUserCanSeeTheirOwnRequests()
{
$this->seed(DepartmentSeeder::class);
$this->seed(CommunitySeeder::class);

$user1 = User::factory()->create();
$request1 = PoolCandidateSearchRequest::factory()->create(['user_id' => $user1->id]);
$user2 = User::factory()->create();
$request2 = PoolCandidateSearchRequest::factory()->create(['user_id' => $user2->id]);

$this->actingAs($user1, 'api')->graphQL(<<<'GRAPHQL'
query MyRequests {
me {
poolCandidateSearchRequests {
id
}
}
}
GRAPHQL
)->assertExactJson([
'data' => [
'me' => [
'poolCandidateSearchRequests' => [
// Can only see request 1. Request 2 belongs to another user.
['id' => $request1->id],
],
],
],
]);
}

public function testUserCanSeeTheirOwnRequest()
{
$this->seed(DepartmentSeeder::class);
$this->seed(CommunitySeeder::class);

$user1 = User::factory()->create();
$request1 = PoolCandidateSearchRequest::factory()->create(['user_id' => $user1->id]);

$this->actingAs($user1, 'api')->graphQL(<<<'GRAPHQL'
query MyRequest($requestId: ID!) {
poolCandidateSearchRequest(id: $requestId) {
id
}
}
GRAPHQL,
['requestId' => $request1->id]
)->assertExactJson([
'data' => [
'poolCandidateSearchRequest' => [
'id' => $request1->id,
],
],
]);
}

public function testUserCanNotSeeOtherRequest()
{
$this->seed(DepartmentSeeder::class);
$this->seed(CommunitySeeder::class);

$user1 = User::factory()->create();
$user2 = User::factory()->create();
$request2 = PoolCandidateSearchRequest::factory()->create(['user_id' => $user2->id]);

$this->actingAs($user1, 'api')->graphQL(<<<'GRAPHQL'
query MyRequest($requestId: ID!) {
poolCandidateSearchRequest(id: $requestId) {
id
}
}
GRAPHQL,
['requestId' => $request2->id]
)->assertGraphQLErrorMessage('This action is unauthorized.');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@

import { getShortPoolTitleHtml } from "~/utils/poolUtils";
import { wrapAbbr } from "~/utils/nameUtils";
import { positionDurationToEmploymentDuration } from "~/utils/searchRequestUtils";
import {
equitySelectionsToDescriptions,
hasDiplomaToEducationLevel,
positionDurationToEmploymentDuration,
} from "~/utils/searchRequestUtils";
import processMessages from "~/messages/processMessages";
import messages from "~/messages/adminMessages";
import adminMessages from "~/messages/adminMessages";
import talentRequestMessages from "~/messages/talentRequestMessages";

import FilterBlock from "./FilterBlock";

Expand Down Expand Up @@ -79,62 +84,15 @@
});

// eslint-disable-next-line deprecation/deprecation
const educationLevel: string | undefined = applicantFilter?.hasDiploma
? intl.formatMessage({
defaultMessage: "Required diploma from post-secondary institution",
id: "/mFrpj",
description:
"Education level message when candidate has a diploma found on the request page.",
})
: intl.formatMessage({
defaultMessage:
"Can accept a combination of work experience and education",
id: "9DCx2n",
description:
"Education level message when candidate does not have a diploma found on the request page.",
});
const educationLevel: string | undefined = hasDiplomaToEducationLevel(
applicantFilter?.hasDiploma,

Check warning on line 88 in apps/web/src/components/SearchRequestFilters/SearchRequestFilters.tsx

View workflow job for this annotation

GitHub Actions / Lint

'hasDiploma' is deprecated. hasDiploma to be replaced
intl,
);

const employmentEquity: string[] | undefined = [
...(applicantFilter?.equity?.isWoman
? [
intl.formatMessage({
defaultMessage: "Woman",
id: "/fglL0",
description:
"Message for woman option in the employment equity section of the request page.",
}),
]
: []),
...(applicantFilter?.equity?.isVisibleMinority
? [
intl.formatMessage({
defaultMessage: "Visible Minority",
id: "4RK/oW",
description:
"Message for visible minority option in the employment equity section of the request page.",
}),
]
: []),
...(applicantFilter?.equity?.isIndigenous
? [
intl.formatMessage({
defaultMessage: "Indigenous",
id: "YoIRbn",
description: "Title for Indigenous",
}),
]
: []),
...(applicantFilter?.equity?.hasDisability
? [
intl.formatMessage({
defaultMessage: "Disability",
id: "GHlK/f",
description:
"Message for disability option in the employment equity section of the request page.",
}),
]
: []),
];
const employmentEquity = equitySelectionsToDescriptions(
applicantFilter?.equity,
intl,
);

const operationalRequirementIds = unpackMaybes(
applicantFilter?.operationalRequirements?.flatMap((req) => req?.value),
Expand Down Expand Up @@ -172,7 +130,7 @@
<div data-h2-flex-item="base(1of1) p-tablet(1of2)">
<div>
<FilterBlock
title={intl.formatMessage(messages.community)}
title={intl.formatMessage(adminMessages.community)}
content={communityName}
/>
<FilterBlock
Expand Down Expand Up @@ -252,11 +210,9 @@
/>
{employmentDuration && (
<FilterBlock
title={intl.formatMessage({
defaultMessage: "Employment duration",
description: "Title for Employment duration section",
id: "Muh/+P",
})}
title={intl.formatMessage(
talentRequestMessages.employmentDuration,
)}
content={employmentDuration}
/>
)}
Expand Down
Loading
Loading