Przejdź do treści

Global Quick Search (GQS)

Głównym celem mechanizmu globalnej wyszukiwarki (GQS) jest umożliwienie użytkownikowi wyszukania dowolnych danych np. dokumentów w systemie, bez konieczności otwierania okien konkretnych modułów, w których można te dokumenty przeglądać. W globalnym polu wyszukiwania (dostępnym w prawym górnym rogu klienta WEB oraz w lewym górnym klienta Desktop) użytkownik wpisuje jakąś frazę i po chwili widzi okno z wynikami. Przy czym u góry listy wynikowej otrzymuje te wyniki, które są najlepiej dopasowane do wyszukiwanego ciągu znaków.

Od wersji 4.7 NEOSa powstał mechanizm globalnej wyszukiwarki w systemie (zarówno w kliencie Desktop jak i w kliencie WEB) umożliwający osadzanie własnego okna podłączonego do globalnego pola wyszukiwania. Od wersji 5.4 mechanizm ten został rozbudowany w taki sposób że:

  • programista aplikacji Teneum może indeksować w silniku GQS dowolne rekordy i umożliwiać ich wyszukiwanie
  • technologia dostarcza okno i silnik do szybkiego globalnego wyszukiwania zindeksowanych danych
  • programista może określić jakie okno ma wyświetlać szczegóły znalezionego rekordu

Indeksacja danych do GQS

Aby programowo dokonać indeksacji danych w silniku GQS należy podpiąć się pod miejsce w którym mamy kontrolę nad dodawaniem, modyfikacją i usuwaniem danych, które mają podlegać indeksacji. Warto w tym celu skorzystac z mechanizmu EDA i stworzyć sobie własne zdarzenie typu XXXChanged, które będzie zgłaszane na szynę EDA przy każdej zmianie w danym typie dokumentu lub kartoteki. Jednym z handlerów reagujących na takie zdarzenie może być handler, który będzie odpowiedzialny za indeksację tych danych w silniku GQS.

Aby dokonać indeksacji należy na liście referencji umieścić projekt WORKFLOW, ponieważ ten projekt zawiera obiekt GQS, który stanowi programowe API do mechanizmu GQS. Proces indeksacji dzielimy na dwie czynności:

  • dodanie lub aktualizacja indeksu w silniku GQS
  • usunięcie indeksu w silniku GQS

Poniżej zawarto przykład metody, która indeksuje aktywności WORKFLOW w silniku GQS. Ta metoda obsługuje zarówno tryb dodania/aktualizacji indeksu, jak również jego usunięcia. Decyduje o tym drugi parametr metody. Prawdziwa metoda w obiekcie WORKFLOW jest nieco bardziej rozbudowana, gdyż obsługuje jeszcze dodatkowe specyficzne elementy. Ten przykład jest pewnym uproszczeniem, stanowiącym przykład do naśladowania.

public static void IndexActivityInGQS(int wfactivity, bool delete = false)
{
  if (!delete)
  {
    //jesteśmy w trybie dodania lub aktualizacji indeksu
    //pobierz dane dokumentu, który chcemy indeksować
    ActivityInfo ai = LOGIC.ACTIVITY.GetInfo(wfactivity, "system");
    if (ai != null)
    {
      //załóż komendę indeksacji
      GQSIndexCommand command = new GQSIndexCommand()
      {
        ORef = wfactivity, //tutaj REF dokumentu
        OTable = "WFACTIVITY", //tutaj nazwa tabeli charakterystyczna dla tego dokumentu
        Title = string.IsNullOrEmpty(ai.Name) ? $"{ai.TypeName} {ai.Ref}" : ai.Name, // tutaj formatujemy tytuł wyświetlany w wynikach wyszukiwania GQS
        Subtitle = ai.SecondLine, // tutaj formatujemy podtytuł z dodatkowymi szczegółami
        EventDate = ai.IssueDate != null ? ai.IssueDate : ai.RegTimestamp.Date, //jeśli dokument posiada datę, to tutaj ją podajemy. W przeciwnym razie ustawiamy null
        Icon = ai.Icon, //jeśli dokument posiada własną ikonę, to tutaj podajemy jej symbol
        RuleInfo = new GQSRuleInfo() //tutaj podajemy dane reguły indeksacji. Zostanie założona tylko wtedy, gdy w tabeli GQSRULES, jeszcze jej nie ma
        {
          Icon = "MI_ZADANIA", //ikona domyślna dla każdego indeksu tej reguły o ile nie została ustalona indywidualnie dla każdego indeksu
          Name = "Aktywność workflow", //nazwa reguły, umożliwia filtrowanie dziedziny wyniku wyszukiwania (np chcemy ograniczyć wyszukiwanie tyko do zamówień)
          NeosObject = "WORKFLOW.WFACTIVITYEDIT" //obiekt biznesowy Neosa, który będzie użyty do wyświetlenia zindeksowanego dokumentu (musi mieć metodę OnShowEntity(int ref))
        },
        KeyInfoList = new List<GQSKeyInfo>() //lista słów do wyszukania danego indeksu wraz z ich ważnością
      };

      //dodaj wszystkie słowa wyszukiwania dla danego indeksu. W tym przypadku słowa zwraca nam procedura SQL
      string sql = string.Format("select * from WFCALCULATEKEYSFORACTIVITY(0,{0},'WFACTIVITY')", wfactivity);
      foreach (var row in CORE.QuerySQL(WORKFLOW.ProjectInfo.DefaultDatabaseAlias, sql))
      {
        command.KeyInfoList.Add(new GQSKeyInfo()
        {
          KeySentence = row["KEY_SENTENCE"].AsString, //każdy klucz może być postaci: "słowo1 słowo2 słowo3 ..." Każde słowo i tak zostanie potraktowane jako osobny klucz
          Rank = row["RANK"].AsInteger //staramy się użyć liczby 100 dla najważniejszych kluczy i proporcjonalnie mniej dla tych mniej ważnych
        });
      }

      //indeksuj wykorzystując API GQS
      WORKFLOW.LOGIC.GQS.IndexAsync(command);
    }
    else throw new Exception("Nie udało się pobrać danych dokumentu");
  }
  else
  {
    //załóż komendę usunięcia indeksacji i ją wyślij
    GQSDeleteIndexCommand command = new GQSDeleteIndexCommand()
    {
      ORef = wfactivity, //tutaj REF dokumentu
      OTable = "WFACTIVITY" //tutaj nazwa tabeli charakterystyczna dla tego dokumentu
    };
    WORKFLOW.LOGIC.GQS.DeleteIndexAsync(command);
  }
}

Definiowanie reguł GQS

Jeśli sam proces indeksacji (opisany wyżej) dba o programowe zakładanie reguł indeksacji, to nie trzeba tego robić ręcznie. Mechanizm wyszukiwania zaczyna działać od razu, gdy nastąpi pierwsza indeksacja danych. Możemy jednak administrować regulami GQS. W tym celu wchodzimy do: Administrator -> Reguły indeksacji GQS.

Zostanie pokazane okno z listą już założonych reguł. Możemy tam ręcznie dodawać, poprawiać i usuwać reguły. Jeśli reguła została założona automatycznie poprzez kod indeksujący, to takze możemy ją ręcznie zmodyfikować, gdyż raz założona reguła nie jest już programowo aktualizowana poprzez wywołanie indeksacji.

Definiująć regułę GQS należy wypełnić następujące pola:

  • Nazwa reguły - nazwa opisująca indeksowany zbiór danych. Należy używać rzeczowników w liczbie pojedynczej. Podczas wyszukiwania można zawęzić dziedzinę, poprzez zaznaczenie w checklistboxie reguły w wynikach których chcemy szukać.
  • Ikona - jest to symbol ikony, która będzie pokazywała się w oknie wyników wyszukiwania, pomagając rozróżnić jakiego typu daną wyszukiwaliśmy.
  • Tabela indeskowana - nazwa głównej tabeli w bazie danych, z którą kojarzymy indeksowane dane.
  • Obiekt biznesowy odpowiedzialny za prezentację - obiekt który powinien implementować metodę statyczną public static void OnShowEntity(int oref). Metoda ta powinna wyświetlić okno prezentujące dane elementu, który znaleźliśmy w wyniku wyszukiwania. Jest ona uruchamiana automatycznie w reakcji na dwuklik w oknie z wynikami wyszukiwania.

Najbardziej przydatna administratora jest możliwość zarzadzania uprawnieniami na poszczególne reguły indeksacji dla konkretnych grup operatorów. Domyślnie zakładane reguły są publiczne, a więc indeksy są dostępne dla wszystkich. Jednak możemy zdjąc ten znacznik i wskazać ręcznie konkretne grupy operatorów, które będą miały dostęp do wyników wyszukiwania w obrębie indeksów należących do konkretnych reguł GQS.

W oknie jest także dostępny przycisk Indeksuj ponownie wszystko. Aby działał poprawnie dla danej reguły, programista aplikacji wykorzystujący silnik GQS powinien w obiekcie, w którym realizuje indeksację obsłużyć zdarzenie 'ReindexAllEvent'. Przykładowa metoda realizująca ponowną indeksację całej dziedziny może wyglądać następująco:

public HandlerResult ReindexAllEventHandler(ConsumeContext<WORKFLOW.ReindexAllEvent> context)
{
  //sprawdzamy, czy to zdarzenie skierowano do naszej reguły. Jeśli nie, to nie obsługujemy tego zdarzenia
  if(context.Message.OTable!="" && context.Message.OTable!="WFACTIVITY")
    return HandlerResult.Unhandled;

  //przeglądamy całą dziedzinę dokumentów i wykonujemy uprzednio napisaną metodę indeksacji pojedynczego dokumentu  
  foreach(var activity in CORE.QuerySQL(WORKFLOW.ProjectInfo.DefaultDatabaseAlias,"select REF from WFACTIVITY"))
  {
      IndexActivityInGQS(activity["REF"].AsInteger,false);
  }
  return HandlerResult.Handled;
}

Wyszukiwanie w standardowym oknie mechanizmu GQS

Jest narzędziem do szybkiego wyszukiwania obiektów w systemie Teneum. Ma ikonę lupy, w aplikacji desktopowej znajduje się w lewym górnym rogu a w aplikacji webowej w prawym górnym.

Aby skorzystać z GQS’a wystarczy nacisnąć ikonę lupy, wpisać interesujący nas obiekt i chwilę poczekać. Wpisując dowolny ciąg słów można szybko i trafnie odnaleźć interesujące nas dane. Indeksacji zależnie od konfiguracji może podlegać każde pole użyte w oknie obiektu np nazwa, symbol, imię, nazwisko itp. Po chwili powinniśmy zobaczyć wynik naszego wyszukiwania.

Oprócz tytułu obiektu widzimy również podstawowe informację o obiekcie. Aby otworzyć obiekt wystarczy kliknąć w jego tytuł, zostaniemy przeniesieni do jego okna.

Wykorzystanie silnika GQS w wyszukiwaniu danych w innych oknach aplikacji

Zwykle wszystkie okna zawierające tabelę z danymi obsługują mechanizm wyszukiwania danych w dziedzinie wyświetlanej przez tabelę. Działa on w sposób standardowy w oparciu o kolumnę, po której następuje sortowanie danych. Można to zachowanie zmienić i przekierować proces wyszukiwania do obiektu biznesowego. Robimy to podpinając jakiś parametr obiektu biznesowego (np o symbolu _search) do definicji okna (Definicja okna -> komponent TABLE -> zakładka "Bindowanie" -> "Pole wyszukiwania (po stronie serwera)". Wtedy nie działa standardowy mechanizm wyszukiwania, a zawartość pola wyszukiwania jest wpisywana automatycznie do podłączonego parametru. W takim przypadku, to kod obiektu biznesowego decyduje, co zrobić dalej z zawartością tego parametru. W szczególności można programowo wykorzystać mechanizm GQS, aby taki indywidualny tryb wyszukiwania w konkretnym oknie był oparty o silnik GQS.

Istnieją dwa sposoby, aby programowo wykorzystać silnik GQS:

  • wykorzystanie API C# GQS-a
var gqsRecords = WORKFLOW.LOGIC.GQS.Find(gqsExpression, "WFACTIVITY", 2000)
  • wykorzystanie procedury SQL do wyszukiwania
    select substring(list(OREF) from 1 for 2000)
    from GQSFIND(:gqsExpression, 'WFACTIVITY') into :REFLIST;

W obu sposobac obowiązkowy jest tylko pierwszy parametr - wyszukiwany ciąg znaków. Jeśli nie przekażemy kolejnych parametrów, to wyszukiwanie będzie w całej dziedzinie zindeksowanej mechanizmem GQS. Kolejne parametry ograniczają dziedzinę w ramach której silnik GQS robi wyszukiwanie.

Dzięki powyższym sposobom, możemy w różny sposób implementować zawężenie dziedziny wyświetlanych danych w zależności od tego, jak zdefiniowano źródło danych obiektu biznesowego:

  • wykorzystanie GQS w obiektach opartych o tabelę - w tym przypadku jedynym sposobem zawężenia dziedziny jest metoda na filtrację danych podpięta do okna. Metoda ta może wywołać albo API C# albo procedurę SQL przekazując jej szukany ciąg znaków i zwrócic wyrażenie SQL filtrujące dziedzinę postaci: REF in (...). Wadą tego rozwiązania jest górne ograniczenie na długość wyrażenia filtrującego, a więc należy określić maksymalną ilość rekordów zwracanych przez silnik GQS.

  • wykorzystanie w obiektach opartych o select z procedury SQL - w tym przypadku warto przekazać wyszukiwany ciąg jako parametr procedury i wewnąrz niej wywołaać procedurę GQSFIND i jej wynikiem filtrować zwracane dane. Filtracja może odbywać się w różny sposób - zarówno przez warunek typu REF in (...) jak i przez join do wyniku zwracanego przez GQSFIND. Przykład znajduje się w standardzie w procedurze WFGETACTIVITYLIST.

  • wykorzystanie w obiektach opartych o programowe źródło danych (metoda Fill) - w takim przypadku najlepiej użyć API C# i wynik z GQS-a przechować w lokalnej liście. Listę tą można zmergować z wynikową listą rekordów dziedziny np poprzez odpowiednie wyrażenia LINQ.

Uwaga!

Bywa tak, że okno zawęża dane przez jakiś filtr biznesowy (np dokumenty z zadanego okresu). Natomiast do silnika GQS nie przekazujemy takiego filtru biznesowego, więc naturalnie GQS przeszukuje pełną dziedzinę biznesową w poszukiwaniu jedynie podanych kluczy. Może być tak, że przy słowie wyszukiwanym, które słabo zawęża dziedzinę i pasuje do niego niemal każdy dokument silnik GQS zwróci tak dużo pasujących wyników, że wszystkie zwracane wyniki będą poza aktualną dziedziną biznesową. Jeśli dodatkowo silnik GQS zwraca nie więcej niż np 2000 rekordów, to może się okazać, że bieżąca dziedzina biznesowa zawiera inne rekordy niż te zwrócone przez GQS. W takim wypadku uzytkownik może zobaczyć pustą dziedzinę danych mimo iż teoretycznie, coś w tej dziedzinie powinno się znaleźć.

Istnieje sposób, aby poradzić sobie z tym problemem. Jeśli istnieje pewien często używany filtr biznesowy (np okres dokumentu), to możemy taki okres potraktować już w momencie indeksacji jako dodatkowy klucz do wyszukiwania. Wtedy każdy dokument podczas indeksacji może otrzymywać ten klucz, postaci: OKRES:202201, OKRES:202202, itp. Jeśli okno przeglądania dokumentów jest biznesowo ograniczone do okresu 202201, a użytkownik wpisze do wyszukiwarki frazę kowalski, to kod okna może do API GQS-a przekazać ciąg znaków: kowalski OKRES:202201. Dzięki temu sam silnik GQS ograniczy sprawdzaną dziedzinę tylko do tych indeksów, które posiadają klucz OKRES:202201 a dodatkowo posiadają klucz kowalski. Zwrócona dziedzina będzie krótsza i bardziej dopasowana do filtru biznesowego.

Przykład dodania własnego okna zastępującego standardowe okno wyszukiwania w GQS (tylko WEB)

W aplikacji napisanej w technologii Neos nie trzeba korzystać ze standardowego silnika GSQ. Programista może stworzyć własne okno, które podpina się pod pole globalnej wyszukiwarki i samodzielnie zaimplementować własny mechanizm wyszukiwania z własnym oknem (tylko w kliencie WEB).

Poniżej znajduje się przykład jak możemy wykorzystać opisywany tutaj mechanizm. Oczywiście na wygląd formy (GRIDA) możemy dowolnie wpływać (Template grida)

Dodanie obiektu i formy BROWSE

  • W pierwszej kolejności należy utworzyć obiekt (z zapytania) w projekcie, który przygotowujemy dla klienta. Zaleca się aby obiekt był z procedury BD, która może wyglądać następująco:
create or alter procedure X_CUSTOM_SEARCH (
    PHRASE STRING = null)
returns (
    DESCRIPT STRING,
    OTABLE STRING,
    OID integer)
AS
begin
  if(phrase is not null and phrase<>'' and char_length(phrase)>2) then
  begin
    for select first 10 'zamówienie '||nz.id||' z '||cast(nz.datawe as date), 'nagzam', nz.ref from nagzam nz
      where upper(nz.id) starting with upper(:phrase)
      order by nz.datawe desc
    into descript, otable, oid
    do
    begin
     suspend;
    end

    for  select first 10 'faktura '||nf.symbol||' z '||cast(nf.data as date), 'nagfak', nf.ref from nagfak nf
      where upper(nf.symbol) starting with upper(:phrase)
      order by nf.data desc
    into descript, otable, oid
    do
    begin
     suspend;
    end
  end
end

Procedura ta przyjmuje wpisany parametr (PHRASE) przez usera do pola wyszukiwania. Następnie wyszukuje i zwraca odpowiednie informacje potrzebne do wyświetlenia oraz otworzenia konkretnego okna użytkownikowi.

Ważne!

Powyższa procedura jest tylko i wyłącznie przykładowa!!! Aby zadbać o UX użytkownika procedura ta musi działać błyskawicznie.

  • W obiekcie dodajemy parametr (_query), który będzie przechowywał wartość wpisaną przez usera
  • Następnie dodajemy okno BROWSE tylko i wyłącznie z jednym GRIDEM (element traficzny TABLE).
  • Na GRIDZIE w zakładce bindowanie ustawiamy pole wyszukiwania danych po stronie serwera na nasz parametr _query
  • Dodajemy metodę na treść zapytania SQL jak niżej:
public string SetDBQuery()
{
  if(_query.Empty)
    return "from X_CUSTOM_SEARCH q";
  else
    return "from X_CUSTOM_SEARCH("+_query.SQLString()+") q";
}
  • Dodajemy akcję, która zostanie wykonana po kliknięciu w link (następny punkt template) na wyszukanym elemencie
public void Select()
{
  if(OTABLE.ToLower()=="nagfak") {
      API.ShowRecord("NAGFAK", "EDIT", new Contexts().Add("REF", OID));
      CloseForm();
  } else if(OTABLE.ToLower()=="nagzam") { 
      API.ShowRecord("NAGZAM", "EDIT4CUSTOMER", new Contexts().Add("REF", OID));
      CloseForm();
  } else {
      API.ShowBalloonHint("Nie obsługiwany typ dokumentu", "Błąd", IconType.INFORMATION);
  }
}
  • Dodajemy przykładowy template do grida aby forma wyglądała jak lista wyszukanych elementów
public string SetGridRowTemplate(HtmlTemplate html)
{
  return @"<div style='margin:10px'>
    <span>"+html.Link(Actions.Select,html.Val(DESCRIPT))+@"</span>
  </div>";
}

Konfiguracja profilu WEB

Aby użyć wyżej przygotowanego obiektu należy w pliku *.smd wskazać ten obiekt jako główna wyszukiwarka danych w systemie. Wskazanie takie dokonujemy w konfiguracji profilu, aby można było per aplikację WEB skonfigurować różne obiekty wyszukujące. Przykład:

[Profile:test]
Name=Profil testowy
LoginProcedure=SYS_GET_USERINFO
DefaultProject=SENTE
DefaultDatabase=esystem
GlobalSearchObjectName=NAZWA_DODANEGO_OBIEKTU
GlobalSearchFormName=BROWSE

Uruchamiamy NEOSa i testujemy wyszukiwarkę pod adresem np. http://localhost:9001/index.html?profile=test Forma powinna działać tak samo jak na w/w przykładzie.

Podsumowanie

Powyższy przykład należy traktować tylko i wyłącznie jako przykład użycia. Należy pamiętać, aby utworzony template był zgodny z podstawowymi zasadami UX oraz chyba najważniejszy aspekt to procedura wyszukująca musi działać szybko. Warto zapoznać się z API GQS (w projekcie WORKFLOW).