Class: GoodData::LCM2::SynchronizeUserFilters

Inherits:
BaseAction show all
Defined in:
lib/gooddata/lcm/actions/synchronize_user_filters.rb

Constant Summary collapse

DESCRIPTION =
'Synchronizes User Permissions Between Projects'
PARAMS =
define_params(self) do
  description 'Client Used For Connecting To GD'
  param :gdc_gd_client, instance_of(Type::GdClientType), required: true

  description 'Input Source'
  param :input_source, instance_of(Type::HashType), required: true

  description 'Synchronization Mode (e.g. sync_one_project_based_on_pid)'
  param :sync_mode, instance_of(Type::StringType), required: false, default: 'sync_project'

  description 'Column That Contains Target Project IDs'
  param :multiple_projects_column, instance_of(Type::StringType), required: false

  description 'Filters Config'
  param :filters_config, instance_of(Type::HashType), required: true

  description 'Input Source Contains CSV Headers?'
  param :csv_headers, instance_of(Type::StringType), required: false

  description 'Restrict If Missing Values In Input Source'
  param :restrict_if_missing_all_values, instance_of(Type::StringType), required: false

  description 'Ignore Missing Values In Input Source'
  param :ignore_missing_values, instance_of(Type::StringType), required: false

  description 'Do Not Touch Filters That Are Not Mentioned'
  param :do_not_touch_filters_that_are_not_mentioned, instance_of(Type::StringType), required: false

  description 'Restricts synchronization to specified segments'
  param :segments_filter, array_of(instance_of(Type::StringType)), required: false

  description 'Organization Name'
  param :organization, instance_of(Type::StringType), required: false

  description 'Domain'
  param :domain, instance_of(Type::StringType), required: false

  description 'DataProduct to manage'
  param :data_product, instance_of(Type::GDDataProductType), required: true

  description 'Segments to manage'
  param :segments, array_of(instance_of(Type::SegmentType)), required: false

  description 'Logger'
  param :gdc_logger, instance_of(Type::GdLogger), required: true

  description 'GDC Project'
  param :gdc_project, instance_of(Type::GdProjectType), required: false

  description 'GDC Project Id'
  param :gdc_project_id, instance_of(Type::StringType), required: false

  description 'User brick users'
  param :users_brick_users, instance_of(Type::ObjectType), required: false, default: []

  description 'Makes the brick run without altering user filters'
  param :dry_run, instance_of(Type::StringType), required: false, default: false
end
MODES =
%w(
  sync_project
  sync_multiple_projects_based_on_pid
  sync_one_project_based_on_pid
  sync_one_project_based_on_custom_id
  sync_multiple_projects_based_on_custom_id
  sync_domain_client_workspaces
)

Constants inherited from BaseAction

BaseAction::FAILED_CLIENTS, BaseAction::FAILED_PROJECTS, BaseAction::FAILED_SEGMENTS, BaseAction::SYNC_FAILED_LIST

Constants included from Dsl::Dsl

Dsl::Dsl::DEFAULT_OPTS, Dsl::Dsl::TYPES

Class Method Summary collapse

Methods inherited from BaseAction

add_failed_client, add_failed_project, add_failed_segment, add_new_clients_to_project_client_mapping, check_params, collect_synced_status, continue_on_error, print_result, process_failed_project, process_failed_projects, sync_failed_client, sync_failed_project, sync_failed_segment, without_check

Methods included from Dsl::Dsl

#define_params, #define_type, #process

Class Method Details

.call(params) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/gooddata/lcm/actions/synchronize_user_filters.rb', line 89

def call(params)
  client = params.gdc_gd_client
  domain_name = params.organization || params.domain
  fail "Either organisation or domain has to be specified in params" unless domain_name

  domain = client.domain(domain_name) if domain_name
  project = client.projects(params.gdc_project) || client.projects(params.gdc_project_id)
  fail "Either project or project_id has to be specified in params" unless project

  data_product = params.data_product

  config = params.filters_config
  fail 'User filters brick requires configuration how the filter should be setup. For this use the param "filters_config"' if config.blank?

  symbolized_config = GoodData::Helpers.deep_dup(config)
  symbolized_config = GoodData::Helpers.symbolize_keys(symbolized_config)
  symbolized_config[:labels] = symbolized_config[:labels].map { |l| GoodData::Helpers.symbolize_keys(l) }
  multiple_projects_column = params.multiple_projects_column

  mode = params.sync_mode
  unless MODES.include?(mode)
    fail "The parameter \"sync_mode\" has to have one of the values #{MODES.map(&:to_s).join(', ')} or has to be empty."
  end

  user_filters = load_data(params, symbolized_config)

  run_params = {
    restrict_if_missing_all_values: params.restrict_if_missing_all_values == 'true',
    ignore_missing_values: params.ignore_missing_values == 'true',
    do_not_touch_filters_that_are_not_mentioned: params.do_not_touch_filters_that_are_not_mentioned == 'true',
    domain: domain,
    dry_run: params[:dry_run].to_b,
    users_brick_input: params.users_brick_users
  }
  all_clients = domain.clients(:all, data_product).to_a
  GoodData.gd_logger.info("Synchronizing in mode=#{mode}, number_of_clients=#{all_clients.size}, data_rows=#{user_filters.size} ,")

  GoodData.logger.info("Synchronizing in mode \"#{mode}\"")
  results = []
  case mode
  when 'sync_project', 'sync_one_project_based_on_pid', 'sync_one_project_based_on_custom_id'
    if mode == 'sync_one_project_based_on_pid'
      filter = project.pid
    elsif mode == 'sync_one_project_based_on_custom_id'
      filter = UserBricksHelper.resolve_client_id(domain, project, params.data_product)
    end
    user_filters = user_filters.select { |f| f[:pid] == filter } if filter

    GoodData.gd_logger.info("Synchronizing in mode=#{mode}, project_id=#{project.pid}, data_rows=#{user_filters.size} ,")
    current_results = sync_user_filters(project, user_filters, run_params, symbolized_config)

    results.concat(current_results[:results]) unless current_results.nil? || current_results[:results].empty?
  when 'sync_multiple_projects_based_on_pid', 'sync_multiple_projects_based_on_custom_id'
    users_by_project = run_params[:users_brick_input].group_by { |u| u[:pid] }
    user_filters.group_by { |u| u[:pid] }.flat_map.pmap do |id, new_filters|
      users = users_by_project[id]
      fail "The #{multiple_projects_column} cannot be empty" if id.blank?

      if mode == 'sync_multiple_projects_based_on_custom_id'
        c = all_clients.detect { |specific_client| specific_client.id == id }
        current_project = c.project
      elsif mode == 'sync_multiple_projects_based_on_pid'
        current_project = client.projects(id)
      end

      GoodData.gd_logger.info("Synchronizing in mode=#{mode}, project_id=#{id}, data_rows=#{new_filters.size} ,")
      current_results = sync_user_filters(current_project, new_filters, run_params.merge(users_brick_input: users), symbolized_config)

      results.concat(current_results[:results]) unless current_results.nil? || current_results[:results].empty?
    end
  when 'sync_domain_client_workspaces'
    domain_clients = all_clients
    if params.segments
      segment_uris = params.segments.map(&:uri)
      domain_clients = domain_clients.select { |c| segment_uris.include?(c.segment_uri) }
    end

    working_client_ids = []
    semaphore = Mutex.new

    users_by_project = run_params[:users_brick_input].group_by { |u| u[:pid] }
    user_filters.group_by { |u| u[multiple_projects_column] }.flat_map.pmap do |client_id, new_filters|
      users = users_by_project[client_id]
      fail "Client id cannot be empty" if client_id.blank?

      c = all_clients.detect { |specific_client| specific_client.id == client_id }
      if c.nil?
        params.gdc_logger.warn "Client #{client_id} is not found"
        next
      end
      if params.segments && !segment_uris.include?(c.segment_uri)
        params.gdc_logger.warn "Client #{client_id} is outside segments_filter #{params.segments}"
        next
      end
      current_project = c.project
      fail "Client #{client_id} does not have project." unless current_project

      semaphore.synchronize do
        working_client_ids << client_id.to_s
      end

      GoodData.gd_logger.info("Synchronizing in mode=#{mode}, client_id=#{client_id}, data_rows=#{new_filters.size} ,")
      partial_results = sync_user_filters(current_project, new_filters, run_params.merge(users_brick_input: users), symbolized_config)
      results.concat(partial_results[:results]) unless partial_results.nil? || partial_results[:results].empty?
    end

    GoodData.gd_logger.info("Synchronizing in mode=#{mode}, working_client_ids=#{working_client_ids.join(', ')} ,") if working_client_ids.size < 50

    unless run_params[:do_not_touch_filters_that_are_not_mentioned]
      to_be_deleted_clients = UserBricksHelper.non_working_clients(domain_clients, working_client_ids)
      to_be_deleted_clients.peach do |c|
        begin
          current_project = c.project
          users = users_by_project[c.client_id]
          params.gdc_logger.info "Delete all filters in project #{current_project.pid} of client #{c.client_id}"

          GoodData.gd_logger.info("Delete all filters in project_id=#{current_project.pid}, client_id=#{c.client_id} ,")
          current_results = sync_user_filters(current_project, [], run_params.merge(users_brick_input: users), symbolized_config)

          results.concat(current_results[:results]) unless current_results.nil? || current_results[:results].empty?
        rescue StandardError => e
          params.gdc_logger.error "Failed to clear filters of  #{c.client_id} due to: #{e.inspect}"
        end
      end
    end
  end

  {
    results: results
  }
end

.load_data(params, symbolized_config) ⇒ Object



226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/gooddata/lcm/actions/synchronize_user_filters.rb', line 226

def load_data(params, symbolized_config)
  filters = []
  headers_in_options = params.csv_headers == 'false' || true
  csv_with_headers = if GoodData::UserFilterBuilder.row_based?(symbolized_config)
                       false
                     else
                       headers_in_options
                     end

  multiple_projects_column = params.multiple_projects_column
  data_source = GoodData::Helpers::DataSource.new(params.input_source)

  tmp = without_check(PARAMS, params) do
    File.open(data_source.realize(params), 'r:UTF-8')
  end

  begin
    GoodData.logger.info('Start reading data')
    row_count = 0
    CSV.foreach(tmp, :headers => csv_with_headers, :return_headers => false, :header_converters => :downcase, :encoding => 'utf-8') do |row|
      filters << row.to_hash.merge(pid: row[multiple_projects_column.downcase])
      row_count += 1
      GoodData.logger.info("Read #{row_count} rows") if (row_count % 50_000).zero?
    end
    GoodData.logger.info("Done reading data, total #{row_count} rows")
  rescue Exception => e # rubocop:disable RescueException
    fail "There was an error during loading data. Message: #{e.message}. Error: #{e}"
  end

  if filters.empty? && %w(sync_multiple_projects_based_on_pid sync_multiple_projects_based_on_custom_id).include?(params.sync_mode)
    fail 'The filter set can not be empty when using sync_multiple_projects_* mode as the filters contain \
          the project ids in which the permissions should be changed'
  end

  filters
end

.sync_user_filters(project, filters, params, filters_config) ⇒ Object



221
222
223
224
# File 'lib/gooddata/lcm/actions/synchronize_user_filters.rb', line 221

def sync_user_filters(project, filters, params, filters_config)
  # Do not change this line -> can cause paralelisation errors in jRuby viz TMA-963
  project.add_data_permissions(GoodData::UserFilterBuilder.get_filters(filters, filters_config), params)
end