<commit_before>class RepositoryDatatableService

  attr_reader :repository_rows, :assigned_rows, :mappings

  def initialize(repository, params, user, my_module = nil)
    @repository = repository
    @user = user
    @my_module = my_module
    @params = params
    create_columns_mappings
    process_query
  end

  private

  def create_columns_mappings
    # Make mappings of custom columns, so we have same id for every
    # column
    index = 6
    @mappings = {}
    @repository.repository_columns.order(:id).each do |column|
      @mappings[column.id] = index.to_s
      index += 1
    end
  end

  def process_query
    search_value = build_conditions(@params)[:search_value]
    order_obj = build_conditions(@params)[:order_by_column]

    repository_rows = fetch_rows(search_value)
    assigned_rows = repository_rows.joins(:my_module_repository_rows)
    if @my_module
      assigned_rows = assigned_rows
                      .where(my_module_repository_rows: {
                               my_module_id: @my_module
                             })
      repository_rows = assigned_rows if @params[:assigned] == 'assigned'
    end

    @assigned_rows = assigned_rows
    @repository_rows = sort_rows(order_obj, repository_rows)
  end

  def fetch_rows(search_value)
    repository_rows = @repository.repository_rows
                                 .left_outer_joins(:created_by)

    if search_value.present?
      includes_json = { repository_cells: Extends::REPOSITORY_SEARCH_INCLUDES }
      searchable_attributes = ['repository_rows.name',
                               'users.full_name',
                               'repository_rows.id'] +
                              Extends::REPOSITORY_EXTRA_SEARCH_ATTR

      # Using distinct raises error when combined with sort on a custom column
      repository_row_ids = repository_rows
                           .left_outer_joins(includes_json)
                           .where_attributes_like(searchable_attributes,
                                                  search_value)
                           .pluck(:id)
                           .uniq
      repository_rows = RepositoryRow.where(id: repository_row_ids)
    end

    repository_rows
  end

  def build_conditions(params)
    search_value = params[:search][:value]
    order = params[:order].values.first
    order_by_column = { column: order[:column].to_i,
                        dir: order[:dir] }
    { search_value: search_value, order_by_column: order_by_column }
  end

  def sortable_columns
    array = [
      'assigned',
      'repository_rows.id',
      'repository_rows.name',
      'repository_rows.created_at',
      'users.full_name'
    ]
    @repository.repository_columns.count.times do
      array << 'repository_cell.value'
    end
    array
  end

  def sort_rows(column_obj, records)
    dir = %w(DESC ASC).find do |direction|
      direction == column_obj[:dir].upcase
    end || 'ASC'
    column_index = column_obj[:column]
    service = RepositoryTableStateService.new(@user, @repository)
    col_order = service.load_state.state['ColReorder']
    column_id = col_order[column_index].to_i

    if sortable_columns[column_id - 1] == 'assigned'
      return records if @my_module && @params[:assigned] == 'assigned'
      if @my_module
        # Depending on the sort, order nulls first or
        # nulls last on repository_cells association
        return records.joins(
          "LEFT OUTER JOIN my_module_repository_rows ON
          (repository_rows.id =
            my_module_repository_rows.repository_row_id
          AND (my_module_repository_rows.my_module_id =
            #{@my_module.id}
          OR my_module_repository_rows.id IS NULL))"
        ).order(
          "my_module_repository_rows.id NULLS
           #{sort_null_direction(dir)}"
        )
      else
        return sort_assigned_records(records, dir)
      end
    elsif sortable_columns[column_id - 1] == 'repository_cell.value'
      id = @mappings.key(column_id.to_s)
      type = RepositoryColumn.find_by_id(id)
      return records unless type
      return select_type(type.data_type, records, id, dir)
    elsif sortable_columns[column_id - 1] == 'users.full_name'
      return records.joins(:created_by).order("users.full_name #{dir}")
    else
      return records.order(
        "#{sortable_columns[column_id - 1]} #{dir}"
      )
    end
  end

  def sort_assigned_records(records, direction)
    assigned = records.joins(:my_module_repository_rows)
                      .distinct
                      .pluck(:id)
    unassigned = records.where.not(id: assigned).pluck(:id)<commit_msg>Fix user order bug (#1687)<commit_after>class RepositoryDatatableService

  attr_reader :repository_rows, :assigned_rows, :mappings

  def initialize(repository, params, user, my_module = nil)
    @repository = repository
    @user = user
    @my_module = my_module
    @params = params
    create_columns_mappings
    process_query
  end

  private

  def create_columns_mappings
    # Make mappings of custom columns, so we have same id for every
    # column
    index = 6
    @mappings = {}
    @repository.repository_columns.order(:id).each do |column|
      @mappings[column.id] = index.to_s
      index += 1
    end
  end

  def process_query
    search_value = build_conditions(@params)[:search_value]
    order_obj = build_conditions(@params)[:order_by_column]

    repository_rows = fetch_rows(search_value)
    assigned_rows = repository_rows.joins(:my_module_repository_rows)
    if @my_module
      assigned_rows = assigned_rows
                      .where(my_module_repository_rows: {
                               my_module_id: @my_module
                             })
      repository_rows = assigned_rows if @params[:assigned] == 'assigned'
    end

    @assigned_rows = assigned_rows
    @repository_rows = sort_rows(order_obj, repository_rows)
  end

  def fetch_rows(search_value)
    repository_rows = @repository.repository_rows
                                 .left_outer_joins(:created_by)

    if search_value.present?
      includes_json = { repository_cells: Extends::REPOSITORY_SEARCH_INCLUDES }
      searchable_attributes = ['repository_rows.name',
                               'users.full_name',
                               'repository_rows.id'] +
                              Extends::REPOSITORY_EXTRA_SEARCH_ATTR

      # Using distinct raises error when combined with sort on a custom column
      repository_row_ids = repository_rows
                           .left_outer_joins(includes_json)
                           .where_attributes_like(searchable_attributes,
                                                  search_value)
                           .pluck(:id)
                           .uniq
      repository_rows = RepositoryRow.where(id: repository_row_ids)
    end

    repository_rows
  end

  def build_conditions(params)
    search_value = params[:search][:value]
    order = params[:order].values.first
    order_by_column = { column: order[:column].to_i,
                        dir: order[:dir] }
    { search_value: search_value, order_by_column: order_by_column }
  end

  def sortable_columns
    array = [
      'assigned',
      'repository_rows.id',
      'repository_rows.name',
      'repository_rows.created_at',
      'users.full_name'
    ]
    @repository.repository_columns.count.times do
      array << 'repository_cell.value'
    end
    array
  end

  def sort_rows(column_obj, records)
    dir = %w(DESC ASC).find do |direction|
      direction == column_obj[:dir].upcase
    end || 'ASC'
    column_index = column_obj[:column]
    service = RepositoryTableStateService.new(@user, @repository)
    col_order = service.load_state.state['ColReorder']
    column_id = col_order[column_index].to_i

    if sortable_columns[column_id - 1] == 'assigned'
      return records if @my_module && @params[:assigned] == 'assigned'
      if @my_module
        # Depending on the sort, order nulls first or
        # nulls last on repository_cells association
        return records.joins(
          "LEFT OUTER JOIN my_module_repository_rows ON
          (repository_rows.id =
            my_module_repository_rows.repository_row_id
          AND (my_module_repository_rows.my_module_id =
            #{@my_module.id}
          OR my_module_repository_rows.id IS NULL))"
        ).order(
          "my_module_repository_rows.id NULLS
           #{sort_null_direction(dir)}"
        )
      else
        return sort_assigned_records(records, dir)
      end
    elsif sortable_columns[column_id - 1] == 'repository_cell.value'
      id = @mappings.key(column_id.to_s)
      type = RepositoryColumn.find_by_id(id)
      return records unless type
      return select_type(type.data_type, records, id, dir)
    elsif sortable_columns[column_id - 1] == 'users.full_name'
      # We don't need join user table, because it already joined in fetch_row method
      return records.order("users.full_name #{dir}")
    else
      return records.order(
        "#{sortable_columns[column_id - 1]} #{dir}"
      )
    end
  end

  def sort_assigned_records(records, direction)
    assigned = records.joins(:my_module_repository_rows)
                      .distinct
                      .pluck(:id)
    unassigned = records.where.not(id: assigned).pluck(:id)
{'fraction_non_alphanumeric': 0.08382337218004918, 'fraction_numerical': 0.0017106810648989629, 'mean_word_length': 2.396514161220044, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2548433', 'n_tokens_mistral': 2755, 'n_tokens_neox': 2704, 'n_words': 544}