Module: Labimotion::SegmentHelpers

Extended by:
Grape::API::Helpers
Defined in:
lib/labimotion/helpers/segment_helpers.rb

Overview

ElementHelpers

Instance Method Summary collapse

Instance Method Details

#create_ai_segment_klass(current_user, params) ⇒ Object

Create a new (inactive) segment klass whose properties template is generated by an LLM from a user-provided label plus optional description, reference links and uploaded files. The parent ElementKlass must be set on admin reviews/edits the generated template in the designer and activates it.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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
# File 'lib/labimotion/helpers/segment_helpers.rb', line 62

def create_ai_segment_klass(current_user, params)
  return { status: 'error', message: 'A parent element class is required.' } if @klass.nil?

  if Array(params[:files]).size > Labimotion::AiTemplate::MAX_FILES
    return { status: 'error', message: "Too many files (max #{Labimotion::AiTemplate::MAX_FILES})." }
  end

  overrides = ai_user_overrides(current_user)
  ai = Labimotion::AiTemplate.generate(
    kind: 'segment',
    subject: params[:label],
    desc: params[:desc],
    cols: params[:cols],
    references: params[:references],
    files: params[:files],
    **overrides
  )

  uuid = SecureRandom.uuid
  # property-base schema requires pkg, uuid, klass, layers, version, identifier.
  properties_template = {
    'uuid' => uuid,
    'klass' => 'SegmentKlass',
    'pkg' => Labimotion::Utils.pkg(nil),
    'version' => '1.0.0',
    'identifier' => uuid,
    'layers' => ai['layers'],
    'select_options' => ai['select_options'],
    'metadata' => ai['metadata']
  }
  attributes = {
    'label' => params[:label],
    'desc' => params[:desc].presence || ai['label'].presence,
    'element_klass' => @klass,
    'place' => 100,
    'is_active' => false,
    'uuid' => uuid,
    'released_at' => DateTime.now,
    'properties_template' => properties_template,
    'properties_release' => properties_template,
    'created_by' => current_user.id
  }
  # Klass-level segment metadata (render kind: own tab vs the host element's
  # Properties tab), mirroring the non-AI create_segment_klass which persists
  # params[:metadata] via declared. Absent -> leave the column default.
  attributes['metadata'] = params[:metadata] if params[:metadata].present?

  # Same as the non-AI create beside it: the owner row shares the create's
  # transaction, because a klass committed without one is owner-less and
  # falls OPEN to the legacy designer-wide gate — every designer of the
  # family could then edit, release and delete it, while its creator holds
  # no special standing at all. seed_owner! rescues only RecordNotUnique and
  # RecordInvalid, so any other failure has to take the klass down with it;
  # this helper turns every StandardError into { status: 'error' }, and
  # without the transaction the caller would be told the create failed while
  # an unowned template silently persisted.
  klass = Labimotion::SegmentKlass.transaction do
    Labimotion::SegmentKlass.create!(attributes).tap do |klz|
      Labimotion::KlassShare.seed_owner!(klz, current_user.id)
    end
  end
  klass.reload
  klass.create_klasses_revision(current_user)
  # `record` is the row itself. The background job that calls this needs the
  # template, not a sentence about it, to link the notification back to it.
  { status: 'success', record: klass,
    message: "The AI segment template [#{params[:label]}] has been created as inactive. Review and activate it in the designer." }
rescue StandardError => e
  Labimotion.log_exception(e, current_user)
  { status: 'error', message: e.message }
end

#create_repo_klass(params, current_user, origin) ⇒ Object



198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/labimotion/helpers/segment_helpers.rb', line 198

def create_repo_klass(params, current_user, origin)
  response = Labimotion::TemplateHub.fetch_identifier('SegmentKlass', params[:identifier], origin)
  attributes = response.slice('label', 'desc', 'uuid', 'identifier', 'released_at', 'properties_release', 'version', 'metadata')
  attributes['properties_release']['identifier'] = attributes['identifier']
  attributes['properties_template'] = attributes['properties_release']
  attributes['place'] = ((Labimotion::SegmentKlass.all.length * 10) || 0) + 10
  attributes['is_active'] = false
  attributes['updated_by'] = current_user.id
  attributes['sync_by'] = current_user.id
  attributes['sync_time'] = DateTime.now
  attr_klass = response['element_klass']    ## response.dig('element_klass', {})        # response['element_klass']
  validate_klass(attributes, attr_klass)

rescue StandardError => e
  Labimotion.log_exception(e, current_user)
  raise e
end

#create_segment_klass(current_user, params) ⇒ Object



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/labimotion/helpers/segment_helpers.rb', line 21

def create_segment_klass(current_user, params)
  place = params[:place]
  begin
    place = place.to_i if place.present? && place.to_i == place.to_f
  rescue StandardError
    place = 100
  end

  uuid = SecureRandom.uuid
  template = { uuid: uuid, layers: {}, select_options: {} }
  attributes = declared(params, include_missing: false)
  attributes[:properties_template]['uuid'] = uuid if attributes[:properties_template].present?
  template = (attributes[:properties_template].presence || template)
  template['pkg'] = Labimotion::Utils.pkg(template['pkg'])
  template['klass'] = 'SegmentKlass'
  attributes.merge!(properties_template: template, element_klass: @klass, created_by: current_user.id,
                    place: place)
  attributes[:is_active] = false
  attributes[:uuid] = uuid
  attributes[:released_at] = DateTime.now
  attributes[:properties_release] = attributes[:properties_template]
  # The owner row shares the create's transaction: a klass committed without it is
  # owner-less, which falls open to the legacy designer-wide gate.
  klass = Labimotion::SegmentKlass.transaction do
    Labimotion::SegmentKlass.create!(attributes).tap do |klz|
      Labimotion::KlassShare.seed_owner!(klz, current_user.id)
    end
  end
  klass.reload
  klass.create_klasses_revision(current_user)
  klass
rescue StandardError => e
  Labimotion.log_exception(e, current_user)
  raise e
end

#klass_list(el_klass, is_active = false, displayed_in_list = false) ⇒ Object



14
15
16
17
18
19
# File 'lib/labimotion/helpers/segment_helpers.rb', line 14

def klass_list(el_klass, is_active = false, displayed_in_list = false)
  scope = displayed_in_list ? Labimotion::SegmentKlass.for_list_display : Labimotion::SegmentKlass.all
  scope = scope.where(is_active: is_active) if is_active.present? && is_active == true
  scope = scope.joins(:element_klass).where(klass_element: params[:element], is_active: true).preload(:element_klass) if el_klass.present?
  scope.order('place') || []
end

#update_segment_klass(current_user, params) ⇒ Object



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/labimotion/helpers/segment_helpers.rb', line 134

def update_segment_klass(current_user, params)
  segment = fetch_klass('SegmentKlass', params[:id])
  authorize_klass!(segment, :write)
  place = params[:place]
  begin
    place = place.to_i if place.present? && place.to_i == place.to_f
  rescue StandardError
    place = 100
  end
  attributes = declared(params, include_missing: false)
  attributes.delete(:id)
  attributes[:place] = place
  segment&.update!(attributes)
  segment
rescue StandardError => e
  Labimotion.log_exception(e, current_user)
  raise e
end

#validate_klass(attributes, attr_klass) ⇒ Object



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
# File 'lib/labimotion/helpers/segment_helpers.rb', line 153

def validate_klass(attributes, attr_klass)
  element_klass = Labimotion::ElementKlass.find_by(identifier: attr_klass['identifier']) if attr_klass.dig('identifier').present?
  element_klass = Labimotion::ElementKlass.find_by(name: attr_klass['name'], is_generic: false) if element_klass.nil?
  return { status: 'error', message: "The element [#{attr_klass['name']}] does not exist in this instance" } if element_klass.nil?

  # el_attributes = response['element_klass'].slice('name', 'label', 'desc', 'uuid', 'identifier', 'icon_name', 'klass_prefix', 'is_generic', 'released_at')
  # el_attributes['properties_template'] = response['element_klass']['properties_release']
  # Labimotion::ElementKlass.create!(el_attributes)
  attributes['element_klass_id'] = element_klass.id
  segment_klass = Labimotion::SegmentKlass.find_by(identifier: attributes['identifier']) if attributes['identifier'].present?
  if segment_klass.present?
    if segment_klass['uuid'] == attributes['uuid'] && segment_klass['version'] == attributes['version']
      return { status: 'success', message: "This segment: #{attributes['label']} has the latest version!" }
    else
      # Upgrading bumps the version and cuts a revision — :release, owner-only, and the
      # gate lives here because the create/update branch is unknown until the identifier
      # lookup. error! throws rather than raises, so it escapes this method's rescue.
      authorize_klass!(segment_klass, :release)
      # Same reassignment defect as the element path: `upload_klass` merges
      # `created_by: current_user.id` unconditionally (segment_api.rb:147), so upgrading an
      # existing segment template would hand its authorship to the importer.
      segment_klass.update!(attributes.except('created_by', :created_by))
      segment_klass.create_klasses_revision(current_user)
      return { status: 'success', message: "This segment: [#{attributes['label']}] has been upgraded to the version: #{attributes['version']}!" }
    end
  else
    exist_klass = Labimotion::SegmentKlass.find_by(label: attributes['label'], element_klass_id: element_klass.id)
    if exist_klass.present?
      return { status: 'error', message: "The segment [#{attributes['label']}] is already in use." }
    else
      attributes['created_by'] = current_user.id
      segment_klass = Labimotion::SegmentKlass.transaction do
        Labimotion::SegmentKlass.create!(attributes).tap do |klz|
          Labimotion::KlassShare.seed_owner!(klz, current_user.id)
        end
      end
      segment_klass.create_klasses_revision(current_user)
      return { status: 'success', message: "The segment: #{attributes['label']} has been created using version: #{attributes['version']}!" }
    end
  end
rescue StandardError => e
  Labimotion.log_exception(e, current_user)
  return { status: 'error', message: e.message }
end