<commit_before>#
# Copyright (C) 2012 - 2015 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

require 'account_reports/report_helper'

module AccountReports
  class SisExporter
    include ReportHelper

    SIS_CSV_REPORTS = ["users", "accounts", "terms", "courses", "sections",
                       "enrollments", "groups", "group_membership",
                       "group_categories", "xlist", "user_observers", "admins"].freeze

    def initialize(account_report, params = {})
      @account_report = account_report
      @reports = SIS_CSV_REPORTS & @account_report.parameters.select { |_k, v| value_to_boolean(v) }.keys
      @sis_format = params[:sis_format]
      @created_by_sis = @account_report.parameters['created_by_sis']
      extra_text_term(@account_report)
      include_deleted_objects
    end

    def csv
      files = ""
      @reports.each do |report_name|
        files << "#{report_name} "
      end
      add_extra_text(I18n.t(
        'account_reports.sis_exporter.reports',
        'Reports: %{files}',
        :files => files
      ))

      if @reports.length == 0
        send_report()
      elsif @reports.length == 1
        csv = self.send(@reports.first)
        send_report(csv)
      else
        csvs = {}

        @reports.each do |report_name|
          csvs[report_name] = self.send(report_name)
        end
        send_report(csvs)
        csvs
      end
    end

    def users
      headers = user_headers
      users = user_query
      users = user_query_options(users)

      generate_and_run_report headers do |csv|
        users.find_in_batches do |batch|
          emails = emails_by_user_id(batch.map(&:user_id))

          batch.each do |u|
            csv << user_row(u, emails)
          end
        end
      end
    end

    def user_headers
      headers = []
      if @sis_format
        # headers are not translated on sis_export to maintain import compatibility
        headers = ['user_id', 'integration_id', 'authentication_provider_id',
                   'login_id', 'password', 'first_name', 'last_name', 'full_name',
                   'sortable_name', 'short_name', 'email', 'status']
      else # provisioning_report
        headers << I18n.t('#account_reports.report_header_canvas_user_id', 'canvas_user_id')
        headers << I18n.t('#account_reports.report_header_user__id', 'user_id')
        headers << I18n.t('#account_reports.report_header_integration_id', 'integration_id')
        headers << I18n.t('#account_reports.report_header_authentication_provider_id', 'authentication_provider_id')
        headers << I18n.t('#account_reports.report_header_login_id', 'login_id')
        headers << I18n.t('#account_reports.report_header_first_name', 'first_name')
        headers << I18n.t('#account_reports.report_header_last_name', 'last_name')
        headers << I18n.t('#account_reports.report_header_full_name', 'full_name')
        headers << I18n.t('#account_reports.report_header_sortable_name', 'sortable_name')
        headers << I18n.t('#account_reports.report_header_user_short_name', 'short_name')
        headers << I18n.t('#account_reports.report_header_email', 'email')
        headers << I18n.t('#account_reports.report_header_status', 'status')
        headers << I18n.t('created_by_sis')
      end
      headers
    end

    def user_query
      users = root_account.pseudonyms.except(:preload).joins(:user).select(
        "pseudonyms.id, pseudonyms.sis_user_id, pseudonyms.user_id, pseudonyms.sis_batch_id,
         pseudonyms.integration_id,pseudonyms.authentication_provider_id,pseudonyms.unique_id,
         pseudonyms.workflow_state, users.sortable_name,users.updated_at AS user_updated_at,
         users.name, users.short_name").
        where("NOT EXISTS (SELECT user_id
                           FROM #{Enrollment.quoted_table_name} e
                           WHERE e.type = 'StudentViewEnrollment'
                           AND e.user_id = pseudonyms.user_id)")
    end

    def user_query_options(users)
      users = users.where.not(sis_user_id: nil) if @sis_format
      users = users.where.not(pseudonyms: {sis_batch_id: nil}) if @created_by_sis

      if @include_deleted
        users.where!("pseudonyms.workflow_state<>'deleted' OR pseudonyms.sis_user_id IS NOT NULL")
      else
        users.where!("pseudonyms.workflow_state<>'deleted'")
      end

      users = add_user_sub_account_scope(users)
    end

    def user_row(u, emails)
      row = []
      row << u.user_id unless @sis_format
      row << u.sis_user_id
      row << u.integration_id
      row << u.authentication_provider_id
      row << u.unique_id
      row << nil if @sis_format
      name_parts = User.name_parts(u.sortable_name, likely_already_surname_first: true)
      row << name_parts[0] || '' # first name
      row << name_parts[1] || '' # last name
      row << u.name
      row << u.sortable_name
      row << u.short_name
      row << emails[u.user_id].try(:path)
      row << u.workflow_state
      row << u.sis_batch_id? unless @sis_format
      row
    end

    def accounts
      if @sis_format
        # headers are not translated on sis_export to maintain import compatibility
        headers = ['account_id', 'parent_account_id', 'name', 'status']
      else
        headers = []
        headers << I18n.t('#account_reports.report_header_canvas_account_id', 'canvas_account_id')
        headers << I18n.t('#account_reports.report_header_account_id', 'account_id')
        headers << I18n.t('#account_reports.report_header_canvas_parent_id', 'canvas_parent_id')
        headers << I18n.t('#account_reports.report_header_parent_account_id', 'parent_account_id')
        headers << I18n.t('#account_reports.report_header_name', 'name')
        headers << I18n.t('#account_reports.report_header_status', 'status')
        headers << I18n.t('created_by_sis')
      end
      accounts = root_account.all_accounts.
        select("accounts.*, pa.id AS parent_id,
                pa.sis_source_id AS parent_sis_source_id").
        joins("INNER JOIN #{Account.quoted_table_name} AS pa ON accounts.parent_account_id=pa.id")

      accounts = accounts.where.not(accounts: {sis_source_id: nil}) if @sis_format
      accounts = accounts.where.not(accounts: {sis_batch_id: nil}) if @created_by_sis

      if @include_deleted
        accounts.where!("accounts.workflow_state<>'deleted' OR accounts.sis_source_id IS NOT NULL")
      else
        accounts.where!("accounts.workflow_state<>'deleted'")
      end

      if account != root_account
        # this does not give the full tree pf sub accounts, just the direct children.
        accounts.where!(:accounts => {:parent_account_id => account})
      end

      generate_and_run_report headers do |csv|
        accounts.find_each do |a|
          row = []
          row << a.id unless @sis_format
          row << a.sis_source_id
          row << a.parent_id unless @sis_format
          row << a.parent_sis_source_id
          row << a.name
          row << a.workflow_state
          row << a.sis_batch_id? unless @sis_format
          csv << row
        end
      end
    end

    def terms
      if @sis_format
        # headers are not translated on sis_export to maintain import compatibility
        headers = ['term_id', 'name', 'status', 'start_date', 'end_date']
      else
        headers = []
        headers << I18n.t('#account_reports.report_header_canvas_term_id', 'canvas_term_id')
        headers << I18n.t('#account_reports.report_header_term__id', 'term_id')
        headers << I18n.t('#account_reports.report_header_name', 'name')
        headers << I18n.t('#account_reports.report_header_status', 'status')
        headers << I18n.t('#account_reports.report_header_start__date', 'start_date')
        headers << I18n.t('#account_reports.report_header_end__date', 'end_date')
        headers << I18n.t('created_by_sis')<commit_msg>refactor account sis_exporter so it can be precisely overriden<commit_after>#
# Copyright (C) 2012 - 2015 Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

require 'account_reports/report_helper'

module AccountReports
  class SisExporter
    include ReportHelper

    SIS_CSV_REPORTS = ["users", "accounts", "terms", "courses", "sections",
                       "enrollments", "groups", "group_membership",
                       "group_categories", "xlist", "user_observers", "admins"].freeze

    def initialize(account_report, params = {})
      @account_report = account_report
      @reports = SIS_CSV_REPORTS & @account_report.parameters.select { |_k, v| value_to_boolean(v) }.keys
      @sis_format = params[:sis_format]
      @created_by_sis = @account_report.parameters['created_by_sis']
      extra_text_term(@account_report)
      include_deleted_objects
    end

    def csv
      files = ""
      @reports.each do |report_name|
        files << "#{report_name} "
      end
      add_extra_text(I18n.t(
        'account_reports.sis_exporter.reports',
        'Reports: %{files}',
        :files => files
      ))

      if @reports.length == 0
        send_report()
      elsif @reports.length == 1
        csv = self.send(@reports.first)
        send_report(csv)
      else
        csvs = {}

        @reports.each do |report_name|
          csvs[report_name] = self.send(report_name)
        end
        send_report(csvs)
        csvs
      end
    end

    def users
      headers = user_headers
      users = user_query
      users = user_query_options(users)

      generate_and_run_report headers do |csv|
        users.find_in_batches do |batch|
          emails = emails_by_user_id(batch.map(&:user_id))

          batch.each do |u|
            csv << user_row(u, emails)
          end
        end
      end
    end

    def user_headers
      headers = []
      if @sis_format
        # headers are not translated on sis_export to maintain import compatibility
        headers = ['user_id', 'integration_id', 'authentication_provider_id',
                   'login_id', 'password', 'first_name', 'last_name', 'full_name',
                   'sortable_name', 'short_name', 'email', 'status']
      else # provisioning_report
        headers << I18n.t('#account_reports.report_header_canvas_user_id', 'canvas_user_id')
        headers << I18n.t('#account_reports.report_header_user__id', 'user_id')
        headers << I18n.t('#account_reports.report_header_integration_id', 'integration_id')
        headers << I18n.t('#account_reports.report_header_authentication_provider_id', 'authentication_provider_id')
        headers << I18n.t('#account_reports.report_header_login_id', 'login_id')
        headers << I18n.t('#account_reports.report_header_first_name', 'first_name')
        headers << I18n.t('#account_reports.report_header_last_name', 'last_name')
        headers << I18n.t('#account_reports.report_header_full_name', 'full_name')
        headers << I18n.t('#account_reports.report_header_sortable_name', 'sortable_name')
        headers << I18n.t('#account_reports.report_header_user_short_name', 'short_name')
        headers << I18n.t('#account_reports.report_header_email', 'email')
        headers << I18n.t('#account_reports.report_header_status', 'status')
        headers << I18n.t('created_by_sis')
      end
      headers
    end

    def user_query
      users = root_account.pseudonyms.except(:preload).joins(:user).select(
        "pseudonyms.id, pseudonyms.sis_user_id, pseudonyms.user_id, pseudonyms.sis_batch_id,
         pseudonyms.integration_id,pseudonyms.authentication_provider_id,pseudonyms.unique_id,
         pseudonyms.workflow_state, users.sortable_name,users.updated_at AS user_updated_at,
         users.name, users.short_name").
        where("NOT EXISTS (SELECT user_id
                           FROM #{Enrollment.quoted_table_name} e
                           WHERE e.type = 'StudentViewEnrollment'
                           AND e.user_id = pseudonyms.user_id)")
    end

    def user_query_options(users)
      users = users.where.not(sis_user_id: nil) if @sis_format
      users = users.where.not(pseudonyms: {sis_batch_id: nil}) if @created_by_sis

      if @include_deleted
        users.where!("pseudonyms.workflow_state<>'deleted' OR pseudonyms.sis_user_id IS NOT NULL")
      else
        users.where!("pseudonyms.workflow_state<>'deleted'")
      end

      users = add_user_sub_account_scope(users)
    end

    def user_row(u, emails)
      row = []
      row << u.user_id unless @sis_format
      row << u.sis_user_id
      row << u.integration_id
      row << u.authentication_provider_id
      row << u.unique_id
      row << nil if @sis_format
      name_parts = User.name_parts(u.sortable_name, likely_already_surname_first: true)
      row << name_parts[0] || '' # first name
      row << name_parts[1] || '' # last name
      row << u.name
      row << u.sortable_name
      row << u.short_name
      row << emails[u.user_id].try(:path)
      row << u.workflow_state
      row << u.sis_batch_id? unless @sis_format
      row
    end

    def accounts
      headers = account_headers
      accounts = account_query
      accounts = account_query_options(accounts)

      generate_and_run_report headers do |csv|
        accounts.find_each do |a|
          csv << account_row(a)
        end
      end
    end

    def account_headers
      headers = []
      if @sis_format
        # headers are not translated on sis_export to maintain import compatibility
        headers = ['account_id', 'parent_account_id', 'name', 'status']
      else
        headers << I18n.t('#account_reports.report_header_canvas_account_id', 'canvas_account_id')
        headers << I18n.t('#account_reports.report_header_account_id', 'account_id')
        headers << I18n.t('#account_reports.report_header_canvas_parent_id', 'canvas_parent_id')
        headers << I18n.t('#account_reports.report_header_parent_account_id', 'parent_account_id')
        headers << I18n.t('#account_reports.report_header_name', 'name')
        headers << I18n.t('#account_reports.report_header_status', 'status')
        headers << I18n.t('created_by_sis')
      end
      headers
    end

    def account_query
      accounts = root_account.all_accounts.
        select("accounts.*, pa.id AS parent_id,
                pa.sis_source_id AS parent_sis_source_id").
        joins("INNER JOIN #{Account.quoted_table_name} AS pa ON accounts.parent_account_id=pa.id")
    end

    def account_query_options(accounts)
      accounts = accounts.where.not(accounts: {sis_source_id: nil}) if @sis_format
      accounts = accounts.where.not(accounts: {sis_batch_id: nil}) if @created_by_sis

      if @include_deleted
        accounts.where!("accounts.workflow_state<>'deleted' OR accounts.sis_source_id IS NOT NULL")
      else
        accounts.where!("accounts.workflow_state<>'deleted'")
      end

      if account != root_account
        # this does not give the full tree pf sub accounts, just the direct children.
        accounts.where!(:accounts => {:parent_account_id => account})
      end
      accounts
    end

    def account_row(a)
      row = []
      row << a.id unless @sis_format
      row << a.sis_source_id
      row << a.parent_id unless @sis_format
      row << a.parent_sis_source_id
      row << a.name
      row << a.workflow_state
      row << a.sis_batch_id? unless @sis_format
      row
    end

    def terms
      if @sis_format
        # headers are not translated on sis_export to maintain import compatibility
        headers = ['term_id', 'name', 'status', 'start_date', 'end_date']
      else
        headers = []
        headers << I18n.t('#account_reports.report_header_canvas_term_id', 'canvas_term_id')
        headers << I18n.t('#account_reports.report_header_term__id', 'term_id')
        headers << I18n.t('#account_reports.report_header_name', 'name')
        headers << I18n.t('#account_reports.report_header_status', 'status')
        headers << I18n.t('#account_reports.report_header_start__date', 'start_date')
        headers << I18n.t('#account_reports.report_header_end__date', 'end_date')
        headers << I18n.t('created_by_sis')
{'fraction_non_alphanumeric': 0.10605097381956886, 'fraction_numerical': 0.007975495578801364, 'mean_word_length': 2.9273717657739446, 'pattern_counts': {'":': 0, '<': 217, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3632751', 'n_tokens_mistral': 5503, 'n_tokens_neox': 5291, 'n_words': 1150}