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

Needed Interface for OSjs Search Engine #812

Open
mahsashadi opened this issue Oct 24, 2022 · 21 comments
Open

Needed Interface for OSjs Search Engine #812

mahsashadi opened this issue Oct 24, 2022 · 21 comments

Comments

@mahsashadi
Copy link

Hi.
How can we extend osjs-search-engine?
Imagine we have a search engine like Elasticsearch, what kind of interface osjs-search-engine needs to sync with our search engine?

@andersevenrud
Copy link
Member

I had a look, and the Search is exported, but it's not possible to override it:

https://github.com/os-js/osjs-client/blob/a777f29e2a3b23eef6c354c8ba106777225fe1e1/src/desktop.js#L128-L133

This should probably be exposed via the DesktopServiceProvider as done with everything else.

@andersevenrud
Copy link
Member

Feel free to open a PR on this (or anyone stumbling across this).

Should be a fairly easy modification :)

@andersevenrud
Copy link
Member

Just thought of a thing. This could probably benefit from an Adapter pattern so that you don't have to override the actual Search abstraction, but rather have a layer on top that can gather results from different sources.

// Changed core service
import VFSSearchAdapter from './adapters/search/vfs'

class Search {
  constructor(core, options) {
    // The options would come from the Service provider
    const providedAdapters = options.adapters || []
    const useAdapters = [VFSSearchAdapter, ...providedAdapters]
    this.adapters = useAdapters.map(A => new A(core))
  }

  async init() {
    await Promise.all(this.adapters.map(a => a.init()))
  }

  async search(pattern) {
    const results = await Promise.all(this.adapters.map(a => a.search(pattern)))
    // Maybe sorted ?! Maybe provide grouping in UI ?!
    // Start simple with a flat result ?!
    return results.flat(1)
  }
}
// Changed core service initialization
this.search = core.config('search.enabled')
  ? new Search(core, options.search || {})
  : null;
// A custom adapter in the distro
// Uses the standard interface as the rest of OS.js
export default class MySearchAdapter {
  constructor(core) {
    this.core = core

    // If settings are needed it can be provided via a custom config entry
    const config = core.config('search.mySearchAdapterSetttings', {})

    this.settings = deepmerge({
     someDefault: 'value',
    }, config)
  }

  destroy() {}

  async init() {
    // For custom stuff during boot
  }

  async search(pattern) {
    return []
  }
}
// Desktop service provider can now do this
import MySearchAdapter from './SearchAdapter'

osjs.register(DesktopServiceProvider, {
  args: {
    search: {
      adapters: [MySearchAdapter],
    }
  }
});

@mahsashadi
Copy link
Author

mahsashadi commented Nov 1, 2022

Since we have previously developed and contributed our VFS Adapter, isn't it enough to only implement search function of ours, which returns an array of files metadata?

It seems here it loops over mountpoints and execute all search functions.

@mahsashadi
Copy link
Author

mahsashadi commented Nov 1, 2022

Before going ahead, I create a PR for search function annotation.
os-js/osjs-server#70

@andersevenrud
Copy link
Member

Since we have previously developed and contributed our VFS Adapter, isn't it enough to only implement search function of ours, which returns an array of files metadata?

Yes.

But adding this could allow for custom searches, not just VFS. It could return http links for example as paths and open tabs, etc :)

@mahdi00021
Copy link

mahdi00021 commented Dec 5, 2022

@andersevenrud

// Changed core service
import VFSSearchAdapter from './adapters/search/vfs'

class Search {
  constructor(core, options) {
    // The options would come from the Service provider
    const providedAdapters = options.adapters || []
    const useAdapters = [VFSSearchAdapter, ...providedAdapters]
    this.adapters = useAdapters.map(A => new A(core))
  }

  async init() {
    await Promise.all(this.adapters.map(a => a.init()))
  }

  async search(pattern) {
    const results = await Promise.all(this.adapters.map(a => a.search(pattern)))
    // Maybe sorted ?! Maybe provide grouping in UI ?!
    // Start simple with a flat result ?!
    return results.flat(1)
  }
}

// Changed core service initialization
this.search = core.config('search.enabled')
  ? new Search(core, options.search || {})
  : null;

Hi , Now in which file and where should we put this piece of code?
And what changes should we make?

@andersevenrud
Copy link
Member

andersevenrud commented Dec 5, 2022 via email

@mahdi00021
Copy link

@andersevenrud

Should a part of the search be deleted or how can I replace it?

@andersevenrud
Copy link
Member

andersevenrud commented Dec 5, 2022 via email

@mahdi00021
Copy link

@andersevenrud
hi
this file search.js :
Should I change like this?

I am newcomer to this project

export default class Search {
  /**
   * Create Search instance
   * @param {Core} core Core reference
   */
  constructor(core, options) {
     const providedAdapters = options.adapters || []
    const useAdapters = [VFSSearchAdapter, ...providedAdapters]
    this.adapters = useAdapters.map(A => new A(core))

    /**
     * Core instance reference
     * @type {Core}
     * @readonly
     */
    this.core = core;

    /**
     * Wired actions
     * @type {Object}
     */
    this.ui = null;

    /**
     * Last focused window
     * @type {Window}
     */
    this.focusLastWindow = null;

    /**
     * Search root DOM element
     * @type {Element}
     * @readonly
     */
    this.$element = document.createElement('div');


  }

 init() {
    const {icon} = this.core.make('osjs/theme');
    const _ = this.core.make('osjs/locale').translate;

    this.$element.className = 'osjs-search';
    this.core.$root.appendChild(this.$element);

    this.core.make('osjs/tray').create({
      title: _('LBL_SEARCH_TOOLTOP', 'F3'),
      icon: icon('system-search.png')
    }, () => this.show());

    this.ui = createUI(this.core, this.$element);
    this.ui.on('hide', () => this.hide());
    this.ui.on('open', iter => this.core.open(iter));
    this.ui.on('search', query => {
      this.search(query)
        .then(results => this.ui.emit('success', results))
        .catch(error => this.ui.emit('error', error));
    });
  await Promise.all(this.adapters.map(a => a.init()))

  }

search(pattern) {
 const results = await Promise.all(this.adapters.map(a => a.search(pattern)))
    // Maybe sorted ?! Maybe provide grouping in UI ?!
    // Start simple with a flat result ?!
  
    const vfs = this.core.make('osjs/vfs');
    const promises = this.core.make('osjs/fs')
      .mountpoints()
      .map(mount => `${mount.name}:/`)
      .map(path => {
        return vfs.search({path}, pattern)
          .catch(error => {
            logger.warn('Error while searching', error);
            return [];
          });
      });

     return results.flat(1)
  }

....
....

and file desktop.js in constructor:
Should I change like this?

    /**
     * Search instance
     * @type {Search|null}
     * @readonly
     */
    this.search = core.config('search.enabled') ? new Search(core) : null;

@andersevenrud
Copy link
Member

andersevenrud commented Dec 8, 2022 via email

@mahdi00021
Copy link

I wanted to know that I did the change right?

@andersevenrud
Copy link
Member

andersevenrud commented Dec 8, 2022 via email

@mahdi00021
Copy link

@andersevenrud
i opened pr for your reviews

@mahdi00021
Copy link

@andersevenrud
Many ambiguities have arisen for me in relation to changing the codes, for example, which sections should be deleted, which sections should remain, or which sections should be defined.

@andersevenrud
Copy link
Member

Many ambiguities have arisen for me in relation to changing the codes, for example, which sections should be deleted, which sections should remain, or which sections should be defined.\

That was kind of the whole point of this. I wrote some ideas and suggestions on how it could be solved.

If I were to point out (or write here) exactly what sections should be deleted/changed, I could just have written it myself.

@andersevenrud
Copy link
Member

I wanted this to be an easy exercise into getting familiar with how everything works, and I thought that I had explained myself clearly.

But maybe there is some kind of language barrier here or some knowledge gap which makes it very hard for me to give the information needed.

@andersevenrud
Copy link
Member

andersevenrud commented Dec 11, 2022

Based on my comments, this is basically what's needed to make this work as I explained:

New adapter file src/adapters/search/vfs.js

export default class VFSSearchAdapter {
  constructor(core) {
    this.core = core
  }

  destroy() {}

  async init() {}

  async search(pattern) {
    const vfs = this.core.make('osjs/vfs');
    const promises = this.core.make('osjs/fs')
      .mountpoints()
      .map(mount => `${mount.name}:/`)
      .map(path => {
        return vfs.search({path}, pattern)
          .catch(error => {
            logger.warn('Error while searching', error);
            return [];
          });
      });

    return Promise.all(promises)
      .then(lists => [].concat(...lists));
  }
}

Changes in src/search.js

// New import to adapter that replaces search function
import VFSSearchAdapter from './adapters/search/vfs.js';

export default class Search {
  constructor(core) {
    // ...
    // New lines on bottom
    const providedAdapters = options.adapters || []
    const useAdapters = [VFSSearchAdapter, ...providedAdapters]
    this.adapters = useAdapters.map(A => new A(core))
  }

  async destroy() {
    // ...
    // New lines on bottom
    await this.adapters.map(a => a.destroy())
  }

  /**
   * Initializes Search Service
   */
  init() {
    // ...
    // New lines on bottom
    await Promise.all(this.adapters.map(a => a.init()))
  }

  async search(pattern) {
    // Replace with this
    const results = await Promise.all(this.adapters.map(a => a.search(pattern)))
    return results.flat(1)
  }
}

Changes in src/desktop.js

export default class Desktop extends EventEmitter {
  constructor(core, options = {}) {
    // Replace the old construtor with this
    this.search = core.config('search.enabled')
      ? new Search(core, options.search || {})
      : null;
  }
}

@mahdi00021
Copy link

mahdi00021 commented Dec 11, 2022

@andersevenrud
very thanks
i again reviews code

@mahdi00021
Copy link

@andersevenrud

Excuse me, one question
how MySearchAdapter be connected (I mean is override) to search through the DesktopServiceProvider ?
what is bridge between these?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants