Explorar el Código

Simplify native app by changing the message protocol

jherve hace 1 año
padre
commit
27ee49afb1

+ 4 - 4
make_prod.patch

@@ -12,15 +12,15 @@ index f3c3aca..e365fca 100755
  EXTENSION_BIN=${EXTENSION_BACKEND_PATH}/run.sh
  
 diff --git a/src/Background.purs b/src/Background.purs
-index 885ac8e..a742a65 100644
+index b73fd21..86d7396 100644
 --- a/src/Background.purs
 +++ b/src/Background.purs
-@@ -24,7 +24,7 @@ import LinkedIn.Output.Types (Output(..))
+@@ -28,7 +28,7 @@ import Web.URL as URL
  main :: Effect Unit
  main = do
    log "[bg] starting up"
 -  port <- connectToNativeApplication "job_search_background"
 +  port <- connectToNativeApplication "job_search_background.prod"
-   onNativeMessageAddListener port nativeMessageHandler
+   -- TODO: This is redundant to send "port" to both addListener and the handler
+   onNativeMessageAddListener port $ nativeMessageHandler port
    onNativeDisconnectAddListener port \p -> log $ "disconnected from native port " <> p.name <> " (" <> p.error <> ")"
- 

+ 2 - 14
native/src/job_search/job_storage.py

@@ -41,7 +41,7 @@ class Flexibility(Enum):
 
 def convert_to_parse_result(url):
     if isinstance(url, str):
-        return urlparse(url)._replace(query=None)
+        return urlparse(url)
     elif isinstance(url, ParseResult):
         return url
 
@@ -51,7 +51,7 @@ def convert_to_bool(s: str) -> bool:
 
 @dataclass
 class JobOffer:
-    id: str = field(init=False)
+    id: str
     url: str = field(repr=False)
     title: str
     company: str
@@ -89,10 +89,6 @@ class JobOffer:
             self._alternate_url = convert_to_parse_result(self.alternate_url)
             self.alternate_url = self._alternate_url.geturl()
 
-        if self.origin == JobOfferOrigin.LINKED_IN:
-            path = Path(self._url.path)
-            self.id = f"linked_in_{path.name}"
-
     def to_storage(self):
         return {
             k: v
@@ -102,8 +98,6 @@ class JobOffer:
 
     @staticmethod
     def from_storage(dict: dict):
-        id = dict.pop("id")
-
         for field, converter in [
             ("origin", JobOfferOrigin),
             ("application_process", ApplicationProcess),
@@ -183,12 +177,6 @@ class JobStorage:
     def read_all(self) -> dict[str, JobOffer]:
         return {r["id"]: JobOffer.from_storage(r) for r in self.select_all("job_offer")}
 
-    def add_job(self, offer: JobOffer):
-        self.insert(offer)
-
-    def insert(self, offer: JobOffer):
-        self.insert_record("job_offer", offer.to_storage())
-
     def insert_record(self, type_, fields):
         cmd_args = [
             arg

+ 34 - 35
native/src/job_search/messages.py

@@ -27,17 +27,23 @@ class BackgroundScriptMessage(Message):
 
         try:
             tag = message.pop("tag")
-            [values] = message.pop("values")
+            match message.pop("values"):
+                case []:
+                    values = {}
+                case [v]:
+                    values = v
         except KeyError:
             raise ValueError("messages from background script should contain a tag and values")
 
         values = {to_snake_case(k): v for k, v in values.items()}
 
         match tag:
-            case "NativeMessageVisitedJobPage":
-                return VisitedLinkedInJobPageMessage(**values)
             case "NativeMessageInitialConfiguration":
                 return InitialConfigurationMessage(**values)
+            case "NativeMessageAddJob":
+                return AddJobMessage(**values)
+            case "NativeMessageListJobsRequest":
+                return ListJobsRequestMessage(**values)
             case _:
                 raise ValueError(f"Got message with unknown tag {tag}")
 
@@ -48,40 +54,28 @@ class NativeMessage(Message):
 
 
 @dataclass
-class VisitedLinkedInJobPageMessage(BackgroundScriptMessage):
+class AddJobMessage(BackgroundScriptMessage):
+    id: str
+    origin: str
+    title: str
     url: str
-    job_title: str
-    page_title: str
     company: str
-    location: str
-    has_simplified_process: bool
-    company_url: str
-    flexibility: Optional[str] = None
+    location: Optional[str] = None
     company_domain: Optional[str] = None
+    company_url: Optional[str] = None
+    flexibility: Optional[Flexibility] = None
+    application_process: Optional[ApplicationProcess] = None
+    application_considered: Optional[bool] = None
+    application_date: Optional[str] = None
+    application_rejection_date: Optional[str] = None
 
-    def extract_job_offer(self):
-        application_process = (
-            ApplicationProcess.LINKED_IN_SIMPLIFIED
-            if self.has_simplified_process
-            else ApplicationProcess.REGULAR
-        )
-
-        if isinstance(self.flexibility, str):
-            flexibility = Flexibility(self.flexibility)
-        elif self.flexibility is None:
-            flexibility = None
-
-        return JobOffer(
-            url=self.url,
-            title=self.job_title,
-            company=self.company,
-            origin=JobOfferOrigin.LINKED_IN,
-            application_process=application_process,
-            location=self.location,
-            company_domain=self.company_domain,
-            company_url=self.company_url,
-            flexibility=flexibility,
-        )
+    def serialize(self):
+        return {"tag": "NativeMessageAddJob", "values": [asdict(self)]}
+
+
+@dataclass
+class ListJobsRequestMessage(BackgroundScriptMessage):
+    pass
 
 
 @dataclass
@@ -97,14 +91,19 @@ class JobOfferListMessage(NativeMessage):
         return {"tag": "NativeMessageJobOfferList", "values": [self.job_offers]}
 
 
+@dataclass
+class StorageReadyMessage(NativeMessage):
+    def serialize(self):
+        return {"tag": "NativeMessageStorageReady", "values": []}
+
+
 @dataclass
 class JobAddedMessage(NativeMessage):
-    job: JobOffer
+    job_id: str
 
     def serialize(self):
         return {"tag": "NativeMessageJobAdded", "values": [asdict(self)]}
 
-
 @dataclass
 class JobAlreadyExistsMessage(NativeMessage):
     job_id: str

+ 13 - 11
native/src/job_search/writer.py

@@ -1,12 +1,15 @@
 import sys
 import traceback
 from pathlib import Path
+from dataclasses import asdict
 
 from job_search.read_write import ReadWriter
 from job_search.job_storage import JobStorage
 from job_search.messages import (
-    VisitedLinkedInJobPageMessage,
+    AddJobMessage,
     InitialConfigurationMessage,
+    StorageReadyMessage,
+    ListJobsRequestMessage,
     JobOfferListMessage,
     LogMessage,
     Message,
@@ -22,17 +25,20 @@ class Application:
 
     def handle_message(self, message: Message):
         match message:
-            case VisitedLinkedInJobPageMessage():
-                offer = message.extract_job_offer()
-
+            case AddJobMessage():
                 try:
-                    self.job_storage.add_job(offer)
-                    self.read_writer.send_message(JobAddedMessage(offer))
+                    self.job_storage.insert_record("job_offer", asdict(message))
+                    self.read_writer.send_message(JobAddedMessage(message.id))
                 except FileExistsError as e:
-                    self.read_writer.send_message(JobAlreadyExistsMessage(offer.id))
+                    self.read_writer.send_message(JobAlreadyExistsMessage(message.id))
+
+            case ListJobsRequestMessage():
+                offers = list(self.job_storage.read_all().values())
+                self.read_writer.send_message(JobOfferListMessage(offers))
 
             case InitialConfigurationMessage(jobs_path):
                 self.job_storage = JobStorage(base_dir=Path(jobs_path))
+                self.read_writer.send_message(StorageReadyMessage())
 
     def loop(self):
         while True:
@@ -40,10 +46,6 @@ class Application:
                 received_message = self.read_writer.get_message()
                 self.handle_message(received_message)
 
-                if self.job_storage:
-                    offers = list(self.job_storage.read_all().values())
-                    self.read_writer.send_message(JobOfferListMessage(offers))
-
             except Exception as e:
                 exc_info = sys.exc_info()
                 tb = "".join(traceback.format_exception(*exc_info))

+ 0 - 47
native/tests/test_input.py

@@ -1,47 +0,0 @@
-import pytest
-from datetime import date, datetime
-from job_search.messages import VisitedLinkedInJobPageMessage
-from job_search.job_storage import (
-    JobOffer,
-    JobOfferOrigin,
-    ApplicationProcess,
-    Flexibility,
-)
-
-
-@pytest.fixture(
-    params=[
-        (
-            VisitedLinkedInJobPageMessage(
-                url="https://www.linkedin.com/jobs/view/3755217595",
-                job_title="Job title",
-                page_title="Page title",
-                company="Company",
-                location="location",
-                company_domain="domain",
-                company_url="https://www.linkedin.com/company/the-company/life",
-                has_simplified_process=True,
-                flexibility=Flexibility.FULL_REMOTE.value,
-            ),
-            JobOffer(
-                url="https://www.linkedin.com/jobs/view/3755217595",
-                title="Job title",
-                company="Company",
-                origin=JobOfferOrigin.LINKED_IN,
-                application_process=ApplicationProcess.LINKED_IN_SIMPLIFIED,
-                location="location",
-                company_domain="domain",
-                company_url="https://www.linkedin.com/company/the-company/life",
-                flexibility=Flexibility.FULL_REMOTE,
-            ),
-        ),
-    ]
-)
-def message_job_offer(request):
-    return request.param
-
-
-class TestJobOfferExtraction:
-    def test_extract_from_visited_linkedin(self, message_job_offer):
-        (message, expected_job_offer) = message_job_offer
-        assert message.extract_job_offer() == expected_job_offer

+ 3 - 31
native/tests/test_read_write.py

@@ -7,7 +7,6 @@ from dataclasses import asdict
 from job_search.read_write import StdReadWriter, ReadWriter
 from job_search.messages import (
     InitialConfigurationMessage,
-    VisitedLinkedInJobPageMessage,
     JobOfferListMessage,
     JobAddedMessage,
     JobAlreadyExistsMessage,
@@ -84,34 +83,7 @@ class TestReadWriter:
             (
                 {"tag": "NativeMessageInitialConfiguration", "values": [{"jobsPath": "jobs_path"}]},
                 InitialConfigurationMessage(jobs_path="jobs_path"),
-            ),
-            (
-                {
-                    "tag": "NativeMessageVisitedJobPage",
-                    "values": [{
-                        "url": "url",
-                        "jobTitle": "job_title",
-                        "pageTitle": "page_title",
-                        "company": "company",
-                        "companyUrl": "company_url",
-                        "companyDomain": "company_domain",
-                        "location": "location",
-                        "hasSimplifiedProcess": True,
-                        "flexibility": "hybrid"
-                    }]
-                },
-                VisitedLinkedInJobPageMessage(
-                    url="url",
-                    job_title="job_title",
-                    page_title="page_title",
-                    company="company",
-                    company_url="company_url",
-                    company_domain="company_domain",
-                    location="location",
-                    has_simplified_process=True,
-                    flexibility="hybrid",
-                ),
-            ),
+            )
         ]
     )
     def input_message(self, request):
@@ -133,8 +105,8 @@ class TestReadWriter:
                 {"tag": "NativeMessageJobOfferList", "values": [[{"id": "job_offer_1"}, {"id": "job_offer_2"}]]},
             ),
             (
-                JobAddedMessage(job="job"),
-                {"tag": "NativeMessageJobAdded", "values": [{"job": "job"}]},
+                JobAddedMessage(job_id="job_id"),
+                {"tag": "NativeMessageJobAdded", "values": [{"job_id": "job_id"}]},
             ),
             (
                 JobAlreadyExistsMessage(job_id="job_id"),

+ 6 - 3
native/tests/test_storage.py

@@ -19,6 +19,7 @@ def job_storage(request, tmp_path):
 @pytest.fixture(
     params=[
         JobOffer(
+            id="linked_in_3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             title="Job title",
             company="Company",
@@ -30,6 +31,7 @@ def job_storage(request, tmp_path):
             publication_date=date.today(),
         ),
         JobOffer(
+            id="linked_in_3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             title="Job title",
             company="Company",
@@ -52,6 +54,7 @@ def job_storage(request, tmp_path):
             application_rejection_date=date.today(),
         ),
         JobOffer(
+            id="linked_in_3755217595",
             url="https://www.linkedin.com/jobs/view/3755217595",
             title="Job title",
             company="Company",
@@ -78,7 +81,7 @@ class TestJobStorage:
         assert job_storage.read_all() == {}
 
     def test_job_addition(self, job_storage, linked_in_job_offer):
-        job_storage.add_job(linked_in_job_offer)
+        job_storage.insert_record("job_offer", linked_in_job_offer.to_storage())
 
         all_items = job_storage.read_all().items()
         assert len(all_items) == 1
@@ -92,9 +95,9 @@ class TestJobStorage:
         assert stored_job == linked_in_job_offer
 
     def test_job_duplicate_addition(self, job_storage, linked_in_job_offer):
-        job_storage.add_job(linked_in_job_offer)
+        job_storage.insert_record("job_offer", linked_in_job_offer.to_storage())
 
         with pytest.raises(FileExistsError) as excinfo:
-            job_storage.add_job(linked_in_job_offer)
+            job_storage.insert_record("job_offer", linked_in_job_offer.to_storage())
 
         assert linked_in_job_offer.id in str(excinfo.value)

+ 27 - 18
src/Background.purs

@@ -10,23 +10,27 @@ import Browser.WebExt.Tabs (Tab)
 import Data.Argonaut.Decode (printJsonDecodeError)
 import Data.Either (Either(..))
 import Data.Foldable (for_)
+import Data.Int64 as I64
 import Data.Maybe (Maybe(..))
 import Effect (Effect)
 import Effect.Aff (launchAff_)
 import Effect.Class (liftEffect)
 import Effect.Class.Console (debug, error, log, logShow)
-import ExampleWebExt.NativeMessage (NativeMessage(..), connectToNativeApplication, onNativeDisconnectAddListener, onNativeMessageAddListener, sendMessageToNative)
+import ExampleWebExt.NativeMessage (ApplicationProcess(..), NativeMessage(..), connectToNativeApplication, onNativeDisconnectAddListener, onNativeMessageAddListener, sendMessageToNative)
 import ExampleWebExt.RuntimeMessage (RuntimeMessage(..), onRuntimeMessageAddListener, sendMessageToContent)
 import ExampleWebExt.Storage (clearAllJobs, getJobsPath, storeJob)
 import LinkedIn.Jobs.JobOffer (JobOffer(..))
 import LinkedIn.Output.Types (Output(..))
+import LinkedIn.PageUrl (PageUrl(..))
+import LinkedIn.UI.Basic.Types (JobOfferId(..))
 import Web.URL as URL
 
 main :: Effect Unit
 main = do
   log "[bg] starting up"
   port <- connectToNativeApplication "job_search_background"
-  onNativeMessageAddListener port nativeMessageHandler
+  -- TODO: This is redundant to send "port" to both addListener and the handler
+  onNativeMessageAddListener port $ nativeMessageHandler port
   onNativeDisconnectAddListener port \p -> log $ "disconnected from native port " <> p.name <> " (" <> p.error <> ")"
 
   sendConfigurationToNative port
@@ -43,7 +47,7 @@ browserActionOnClickedHandler tab = do
 contentScriptMessageHandler ∷ Port -> RuntimeMessage -> MessageSender → Effect Unit
 contentScriptMessageHandler
   port
-  (RuntimeMessagePageContent _ (OutJobOffer offer))
+  (RuntimeMessagePageContent (UrlJobOffer (JobOfferId jobId)) (OutJobOffer offer))
   (MessageSender {tab: Just {url, title}}) =
     case maybeMsg offer of
       Just msg -> sendMessageToNative port msg
@@ -51,19 +55,22 @@ contentScriptMessageHandler
 
   where
     maybeMsg (JobOffer jo) = ado
-      location <- jo.location
       url <- cleanUpUrl url
-    in NativeMessageVisitedJobPage {
-      url: url,
-      jobTitle: jo.title,
-      pageTitle: title,
-      company: jo.companyName,
-      companyDomain: jo.companyDomain,
-      companyUrl: jo.companyLink,
-      location,
-      hasSimplifiedProcess: jo.hasSimplifiedApplicationProcess,
-      flexibility: jo.flexibility
-    }
+    in NativeMessageAddJob {
+        id: "linked_in_" <> I64.toString jobId,
+        origin: "linked_in",
+        title: jo.title,
+        url,
+        company: jo.companyName,
+        location: jo.location,
+        company_domain: jo.companyDomain,
+        company_url: Just jo.companyLink,
+        flexibility: jo.flexibility,
+        application_process: Just $ if jo.hasSimplifiedApplicationProcess then ApplicationProcessLinkedInSimplified else ApplicationProcessRegular,
+        application_date: Nothing,
+        application_rejection_date: Nothing,
+        application_considered: Nothing
+      }
 
 contentScriptMessageHandler _ m (MessageSender {tab, id}) = do
   let
@@ -79,13 +86,15 @@ cleanUpUrl u = do
   url <- URL.fromAbsolute u
   pure $ URL.toString $ URL.setSearch "" url
 
-nativeMessageHandler ∷ NativeMessage → Effect Unit
-nativeMessageHandler (NativeMessageJobOfferList job_offers) = do
+nativeMessageHandler ∷ Port -> NativeMessage → Effect Unit
+nativeMessageHandler _ (NativeMessageJobOfferList job_offers) = do
   clearAllJobs
   for_ job_offers \jo -> do
     storeJob jo
 
-nativeMessageHandler m = logShow m
+nativeMessageHandler port NativeMessageStorageReady = sendMessageToNative port $ NativeMessageListJobsRequest
+nativeMessageHandler port (NativeMessageJobAdded _) = sendMessageToNative port $ NativeMessageListJobsRequest
+nativeMessageHandler _ m = logShow m
 
 sendConfigurationToNative ∷ Port → Effect Unit
 sendConfigurationToNative port = launchAff_ do

+ 6 - 13
src/NativeMessage.purs

@@ -24,19 +24,11 @@ data NativeMessage =
   NativeMessageBackground String
   | NativeMessageLog {level :: String, content :: String}
   | NativeMessageInitialConfiguration {jobsPath :: String}
-  | NativeMessageVisitedJobPage {
-    url :: String,
-    jobTitle :: String,
-    pageTitle :: String,
-    company :: String,
-    companyDomain :: Maybe String,
-    companyUrl :: String,
-    location :: String,
-    hasSimplifiedProcess :: Boolean,
-    flexibility :: Maybe JobFlexibility
-  }
+  | NativeMessageStorageReady
+  | NativeMessageAddJob NativePythonJobOffer
+  | NativeMessageListJobsRequest
   | NativeMessageJobAlreadyExists {job_id :: String}
-  | NativeMessageJobAdded {job :: NativePythonJobOffer}
+  | NativeMessageJobAdded {job_id :: String}
   | NativeMessageJobOfferList (Array NativePythonJobOffer)
 
 data ApplicationProcess
@@ -64,6 +56,7 @@ instance DecodeJson ApplicationProcess where
 
 type NativePythonJobOffer = {
   id :: String,
+  origin :: String,
   title :: String,
   url :: String,
   company :: String,
@@ -78,7 +71,7 @@ type NativePythonJobOffer = {
 }
 
 derive instance Generic NativeMessage _
-instance Show NativeMessage where show = genericShow
+instance Show NativeMessage where show a = genericShow a
 instance EncodeJson NativeMessage where encodeJson a = genericEncodeJson a
 
 instance DecodeJson NativeMessage where