ICIJ/datashare-client

View on GitHub
src/core/ProjectsMixin.js

Summary

Maintainability
A
1 hr
Test Coverage
import { castArray, find, findIndex, get, iteratee, noop, sortBy } from 'lodash'

/**
  Mixin class extending the core to add helpers for projects.
  @mixin ProjectsMixin
  @typicalname datashare
*/
const ProjectsMixin = (superclass) =>
  class extends superclass {
    /**
     * Call a function when a project is selected
     * @param {String} name - Name of the project
     * @param {Function} withFn - Function to call when the project is selected
     * @param {Function} withoutFn - Function to call when the project is unselected
     * @param {String} mutationType - Mutation type that will be watched for changes.
     * @param {String} storePath - Path to the project in the store
     * @memberof ProjectsMixin.prototype
     */
    toggleForProject(
      {
        project = null,
        withFn = noop,
        withoutFn = noop,
        mutationType = 'search/indices',
        storePath = 'search.indices'
      } = {},
      ...args
    ) {
      const toggle = (projects) => {
        if (castArray(projects).includes(project)) {
          return withFn(...args)
        }
        return withoutFn(...args)
      }
      // Toggle once
      toggle(get(this.store.state, storePath))
      // Watch store mutations
      return this.store.subscribe(({ type, payload }) => {
        if (type === mutationType) {
          // The payload contains the name of the selected project
          toggle(payload)
        }
      })
    }
    /**
     * Create a default project on Datashare using the API
     * @memberof ProjectsMixin.prototype
     * @returns {Promise:Object} The HTTP response object
     */
    async createDefaultProject() {
      const name = this.defaultProject
      const label = 'Default'
      const description = 'Your main project on Datashare'
      const sourcePath = this.config.get('dataDir')
      const allowFromMask = '*.*.*.*'
      const project = await this.api.createProject({ name, label, description, sourcePath, allowFromMask })
      return this.setProject(project)
    }
    /**
     * Return true if the default project exists
     * @returns {Promise:Boolean}
     */
    async defaultProjectExists() {
      try {
        return !!(await this.api.getProject(this.defaultProject))
      } catch (_) {
        return false
      }
    }
    /**
     * Retrieve a project by its name
     * @param {String} name Name of the project to retrieve
     * @returns {Object} The project matching with this name
     */
    findProject(name) {
      return find(this.projects, { name })
    }
    /**
     * Delete a project by it name identifier.
     * @param {String} name Name of the project to retrieve
     * @returns {Promise:Integer} Index of the project deleted or -1 if project does not exist
     */
    async deleteProject(name) {
      // Create a new projects list so we avoid mutating object
      const projects = [...this.projects]
      const index = findIndex(projects, { name })

      if (index > -1) {
        projects.splice(index, 1)
        this.config.set('projects', projects)
      }

      // Deleting the default project will recreate it instantly
      if (name === this.defaultProject) {
        await this.createDefaultProject()
      }

      // Ensure there is no conflict with existing search
      this.deleteProjectFromSearch(name)

      return index
    }
    /**
     * Delete a project from the search store
     * @param {String} name Name of the project to delete fropm the store
     */
    deleteProjectFromSearch(name) {
      // Delete the project only if it's already
      // used by the search store. We use a safe accessor
      // to ensure this method won't throw an error when
      // the search store is not initialized yet.
      if (get(this, 'store.state.search.indices', []).includes(name)) {
        this.store.commit('search/index', this.defaultProject)
        this.store.commit('search/reset')
      }
    }
    /**
     * Update a project in the list or add it if it doesn't exist yet.
     * @param {Object} project
     * @returns {Object} The project
     */
    setProject(project) {
      // Create a new projects list so we avoid mutating object
      const projects = [...this.projects]
      const index = findIndex(projects, { name: project?.name })
      if (index > -1) {
        projects[index] = project
      } else {
        projects.push(project)
      }
      this.config.set('projects', projects)
      return project
    }
    /**
     * List all projects this user has access to.
     * @returns {Array:String}
     */
    get projects() {
      return sortBy(this.config.get('projects', []), iteratee('name'))
    }
    /**
     * List all projects name ids this user has access to.
     * @returns {Array:String}
     */
    get projectIds() {
      return this.projects.map(iteratee('name'))
    }
    /**
     * Get the name of the default project
     * @returns {String}
     */
    get defaultProject() {
      return this.config.get('defaultProject', this.projects[0]?.name)
    }
  }

export default ProjectsMixin