From 8f5ccd7338b39f9397b77972dd65ba8e1da75c9a Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Sun, 1 Aug 2021 23:09:45 -0400 Subject: [PATCH] First cut of profile search migrated --- src/JobsJobsJobs/Api/Data.fs | 76 ++++++---- src/JobsJobsJobs/App/src/api/index.ts | 30 +++- src/JobsJobsJobs/App/src/api/types.ts | 28 ++++ .../App/src/components/CollapsePanel.vue | 55 +++++++ .../App/src/components/ErrorList.vue | 23 +++ .../App/src/components/LoadData.vue | 12 +- .../App/src/components/profile/SearchForm.vue | 82 ++++++++++ src/JobsJobsJobs/App/src/store/index.ts | 2 +- .../App/src/views/citizen/Authorized.vue | 5 +- .../App/src/views/profile/ProfileSearch.vue | 143 +++++++++++++++++- src/JobsJobsJobs/Domain/SharedTypes.fs | 15 +- 11 files changed, 423 insertions(+), 48 deletions(-) create mode 100644 src/JobsJobsJobs/App/src/components/CollapsePanel.vue create mode 100644 src/JobsJobsJobs/App/src/components/ErrorList.vue create mode 100644 src/JobsJobsJobs/App/src/components/profile/SearchForm.vue diff --git a/src/JobsJobsJobs/Api/Data.fs b/src/JobsJobsJobs/Api/Data.fs index 67dedfe..33140ac 100644 --- a/src/JobsJobsJobs/Api/Data.fs +++ b/src/JobsJobsJobs/Api/Data.fs @@ -237,35 +237,46 @@ module Profile = }) /// Search profiles (logged-on users) - let search (srch : ProfileSearch) conn = task { - let results = - seq { - match srch.continentId with - | Some conId -> - yield (fun (q : ReqlExpr) -> - q.Filter(r.HashMap(nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr) - | None -> () - match srch.remoteWork with - | "" -> () - | _ -> yield (fun q -> q.Filter(r.HashMap(nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr) - match srch.skill with - | Some skl -> - yield (fun q -> q.Filter(ReqlFunction1(fun it -> - upcast it.G("skills.description").Downcase().Match(skl.ToLowerInvariant ()))) :> ReqlExpr) - | None -> () - match srch.bioExperience with - | Some text -> - let txt = text.ToLowerInvariant () - yield (fun q -> q.Filter(ReqlFunction1(fun it -> - upcast it.G("biography" ).Downcase().Match(txt) - .Or(it.G("experience").Downcase().Match(txt)))) :> ReqlExpr) - | None -> () - } - |> Seq.toList - |> List.fold (fun q f -> f q) (r.Table(Table.Profile) :> ReqlExpr) - // TODO: pluck fields, include display name - return! results.RunResultAsync conn - } + let search (srch : ProfileSearch) conn = + withReconn(conn).ExecuteAsync(fun () -> + (seq { + match srch.continentId with + | Some conId -> + yield (fun (q : ReqlExpr) -> + q.Filter(r.HashMap(nameof srch.continentId, ContinentId.ofString conId)) :> ReqlExpr) + | None -> () + match srch.remoteWork with + | "" -> () + | _ -> yield (fun q -> q.Filter(r.HashMap(nameof srch.remoteWork, srch.remoteWork = "yes")) :> ReqlExpr) + match srch.skill with + | Some skl -> + yield (fun q -> q.Filter(ReqlFunction1(fun it -> + upcast it.G("skills.description").Downcase().Match(skl.ToLowerInvariant ()))) :> ReqlExpr) + | None -> () + match srch.bioExperience with + | Some text -> + let txt = text.ToLowerInvariant () + yield (fun q -> q.Filter(ReqlFunction1(fun it -> + upcast it.G("biography" ).Downcase().Match(txt) + .Or(it.G("experience").Downcase().Match(txt)))) :> ReqlExpr) + | None -> () + } + |> Seq.toList + |> List.fold + (fun q f -> f q) + (r.Table(Table.Profile) + .EqJoin("id", r.Table(Table.Citizen)) + .Without(r.HashMap("right", "id")) + .Zip() :> ReqlExpr)) + .Merge(ReqlFunction1(fun it -> + upcast r + .HashMap("displayName", + r.Branch(it.G("realName" ).Default_("").Ne(""), it.G("realName"), + it.G("displayName").Default_("").Ne(""), it.G("displayName"), + it.G("naUser"))) + .With("citizenId", it.G("id")))) + .Pluck("citizenId", "displayName", "seekingEmployment", "remoteWork", "fullTime", "lastUpdatedOn") + .RunResultAsync conn) // Search profiles (public) let publicSearch (srch : PublicSearch) conn = task { @@ -438,6 +449,11 @@ module Success = .EqJoin("citizenId", r.Table(Table.Citizen)) .Without(r.HashMap("right", "id")) .Zip() - .Merge(Javascript "function (s) { return { citizenName: s.realName || s.displayName || s.naUser } }") + .Merge(ReqlFunction1(fun it -> + upcast r + .HashMap("displayName", + r.Branch(it.G("realName" ).Default_("").Ne(""), it.G("realName"), + it.G("displayName").Default_("").Ne(""), it.G("displayName"), + it.G("naUser"))))) .Pluck("id", "citizenId", "citizenName", "recordedOn", "fromHere", "hasStory") .RunResultAsync conn) diff --git a/src/JobsJobsJobs/App/src/api/index.ts b/src/JobsJobsJobs/App/src/api/index.ts index 15cf401..91000ba 100644 --- a/src/JobsJobsJobs/App/src/api/index.ts +++ b/src/JobsJobsJobs/App/src/api/index.ts @@ -1,5 +1,16 @@ import { MarkedOptions } from 'marked' -import { Citizen, Continent, Count, LogOnSuccess, Profile, ProfileForView, StoryEntry, Success } from './types' +import { + Citizen, + Continent, + Count, + LogOnSuccess, + Profile, + ProfileForView, + ProfileSearch, + ProfileSearchResult, + StoryEntry, + Success +} from './types' /** * Create a URL that will access the API @@ -126,6 +137,23 @@ export default { retreiveForView: async (id : string, user : LogOnSuccess) : Promise => apiResult(await fetch(apiUrl(`profile/view/${id}`), reqInit('GET', user)), 'retrieving profile'), + /** + * Search for profiles using the given parameters + * + * @param query The profile search parameters + * @param user The currently logged-on user + * @returns The matching profiles (if found), undefined (if API returns 404), or an error string + */ + search: async (query : ProfileSearch, user : LogOnSuccess) : Promise => { + const params = new URLSearchParams() + if (query.continentId) params.append('continentId', query.continentId) + if (query.skill) params.append('skill', query.skill) + if (query.bioExperience) params.append('bioExperience', query.bioExperience) + params.append('remoteWork', query.remoteWork) + return apiResult(await fetch(apiUrl(`profile/search?${params.toString()}`), + reqInit('GET', user)), 'searching profiles') + }, + /** * Count profiles in the system * diff --git a/src/JobsJobsJobs/App/src/api/types.ts b/src/JobsJobsJobs/App/src/api/types.ts index b82a57f..5dd05d6 100644 --- a/src/JobsJobsJobs/App/src/api/types.ts +++ b/src/JobsJobsJobs/App/src/api/types.ts @@ -81,6 +81,34 @@ export interface ProfileForView { continent : Continent } +/** The various ways profiles can be searched */ +export interface ProfileSearch { + /** Retrieve citizens from this continent */ + continentId : string | undefined + /** Text for a search within a citizen's skills */ + skill : string | undefined + /** Text for a search with a citizen's professional biography and experience fields */ + bioExperience : string | undefined + /** Whether to retrieve citizens who do or do not want remote work */ + remoteWork : string +} + +/** A user matching the profile search */ +export interface ProfileSearchResult { + /** The ID of the citizen */ + citizenId : string + /** The citizen's display name */ + displayName : string + /** Whether this citizen is currently seeking employment */ + seekingEmployment : boolean + /** Whether this citizen is looking for remote work */ + remoteWork : boolean + /** Whether this citizen is looking for full-time work */ + fullTime : boolean + /** When this profile was last updated (date) */ + lastUpdatedOn : string +} + /** A count */ export interface Count { /** The count being returned */ diff --git a/src/JobsJobsJobs/App/src/components/CollapsePanel.vue b/src/JobsJobsJobs/App/src/components/CollapsePanel.vue new file mode 100644 index 0000000..052b1c0 --- /dev/null +++ b/src/JobsJobsJobs/App/src/components/CollapsePanel.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/src/JobsJobsJobs/App/src/components/ErrorList.vue b/src/JobsJobsJobs/App/src/components/ErrorList.vue new file mode 100644 index 0000000..7bc5c67 --- /dev/null +++ b/src/JobsJobsJobs/App/src/components/ErrorList.vue @@ -0,0 +1,23 @@ + + + diff --git a/src/JobsJobsJobs/App/src/components/LoadData.vue b/src/JobsJobsJobs/App/src/components/LoadData.vue index 0cf17c0..781d2cd 100644 --- a/src/JobsJobsJobs/App/src/components/LoadData.vue +++ b/src/JobsJobsJobs/App/src/components/LoadData.vue @@ -1,17 +1,17 @@ diff --git a/src/JobsJobsJobs/App/src/store/index.ts b/src/JobsJobsJobs/App/src/store/index.ts index cd472d0..4d3d320 100644 --- a/src/JobsJobsJobs/App/src/store/index.ts +++ b/src/JobsJobsJobs/App/src/store/index.ts @@ -24,7 +24,7 @@ export default createStore({ state: () : State => { return { user: undefined, - logOnState: 'Logging you on with No Agenda Social...', + logOnState: 'Welcome back! Verifying your No Agenda Social account…', continents: [] } }, diff --git a/src/JobsJobsJobs/App/src/views/citizen/Authorized.vue b/src/JobsJobsJobs/App/src/views/citizen/Authorized.vue index f6fc781..86b888b 100644 --- a/src/JobsJobsJobs/App/src/views/citizen/Authorized.vue +++ b/src/JobsJobsJobs/App/src/views/citizen/Authorized.vue @@ -1,7 +1,7 @@ @@ -32,7 +32,8 @@ export default defineComponent({ } } } else { - store.commit('setLogOnState', 'Did not receive a token from No Agenda Social (perhaps you clicked "Cancel"?)') + store.commit('setLogOnState', + 'Did not receive a token from No Agenda Social (perhaps you clicked “Cancel”?)') } } diff --git a/src/JobsJobsJobs/App/src/views/profile/ProfileSearch.vue b/src/JobsJobsJobs/App/src/views/profile/ProfileSearch.vue index 28bcbc5..6c7c071 100644 --- a/src/JobsJobsJobs/App/src/views/profile/ProfileSearch.vue +++ b/src/JobsJobsJobs/App/src/views/profile/ProfileSearch.vue @@ -1,3 +1,144 @@ + + diff --git a/src/JobsJobsJobs/Domain/SharedTypes.fs b/src/JobsJobsJobs/Domain/SharedTypes.fs index a5db2cd..56b6aab 100644 --- a/src/JobsJobsJobs/Domain/SharedTypes.fs +++ b/src/JobsJobsJobs/Domain/SharedTypes.fs @@ -82,6 +82,7 @@ module ProfileForm = /// The various ways profiles can be searched +[] type ProfileSearch = { /// Retrieve citizens from this continent continentId : string option @@ -107,18 +108,18 @@ module ProfileSearch = /// A user matching the profile search type ProfileSearchResult = { - // The ID of the citizen + /// The ID of the citizen citizenId : CitizenId - // The citizen's display name + /// The citizen's display name displayName : string - // Whether this citizen is currently seeking employment + /// Whether this citizen is currently seeking employment seekingEmployment : bool - // Whether this citizen is looking for remote work + /// Whether this citizen is looking for remote work remoteWork : bool - // Whether this citizen is looking for full-time work + /// Whether this citizen is looking for full-time work fullTime : bool - // When this profile was last updated - lastUpdated : Instant + /// When this profile was last updated + lastUpdatedOn : Instant }