Source code for oci_policy_analysis.presentation.qt.pages.simulation_request

"""Configure an API request and condition inputs without evaluating access."""

import json

from PySide6.QtCore import Signal
from PySide6.QtWidgets import (
    QApplication,
    QCheckBox,
    QComboBox,
    QFormLayout,
    QHBoxLayout,
    QLineEdit,
    QPlainTextEdit,
    QPushButton,
    QScrollArea,
    QTabWidget,
    QVBoxLayout,
    QWidget,
)

from oci_policy_analysis.presentation.qt.tables import RecordTable
from oci_policy_analysis.presentation.qt.widgets import label


[docs] class SimulationRequestPage(QWidget): """Select a catalog operation and supply values for checked condition fields. Args: controller: Owner of the current snapshot, inclusion and request state. Attributes: input_widgets: Condition variable mapping to value and null controls. back_requested: Navigate to Statements without discarding request inputs. Notes: Metadata comes from ReferenceDataService; condition schemas are prepared by SimulationService in the owned worker. No policy is evaluated here. """ back_requested = Signal() run_requested = Signal() inputs_changed = Signal() def __init__(self, controller): """Build guided operation selection, context fields and a request preview.""" super().__init__() self.controller = controller self.context = None self.catalog = [] self.input_widgets = {} self._updating = False self._conditions = [] layout = QVBoxLayout(self) layout.setContentsMargins(20, 22, 20, 18) layout.setSpacing(12) layout.addWidget(label('1. Choose the API request to test', 'eyebrow')) layout.addWidget( label( 'Choose an API group and operation from the bundled reference catalog. Required permissions describe the primary operation; related checks are advisory.', wrap=True, ) ) selectors = QHBoxLayout() self.group = QComboBox() self.group.setAccessibleName('API catalog group') self.search = QLineEdit() self.search.setPlaceholderText('Find an API operation…') self.search.setClearButtonEnabled(True) selectors.addWidget(self.group, 1) selectors.addWidget(self.search, 2) layout.addLayout(selectors) self.operation = QComboBox() self.operation.setAccessibleName('API operation') self.operation.setMinimumContentsLength(30) layout.addWidget(self.operation) self.metadata = QPlainTextEdit() self.metadata.setReadOnly(True) self.metadata.setMaximumHeight(100) self.metadata.setPlaceholderText('Select an operation to see its primary permissions and reference notes.') layout.addWidget(self.metadata) layout.addWidget(label('2. Supply condition values for the checked statements', 'eyebrow')) layout.addWidget( label( 'Blank or Null / not supplied leaves a value absent. This can be intentional, such as for a !variable condition. Values stay when you return to Statements and keep the same fields.', 'muted', wrap=True, ) ) self.tabs = QTabWidget() self.inputs = QScrollArea() self.inputs.setWidgetResizable(True) self.tabs.addTab(self.inputs, 'Condition inputs') self.conditions = RecordTable( ( ('Statement ID', 'internal_id'), ('Parse status', 'parse_status'), ('Warnings', 'warnings'), ('Condition', 'clause'), ) ) self.tabs.addTab(self.conditions, 'Condition evidence') related = QWidget() related_layout = QVBoxLayout(related) related_layout.addWidget( label( 'Advisory dependencies only. They do not change the primary model outcome, and the snapshot may not establish whether a related resource is attached.', 'notice', wrap=True, ) ) self.related = RecordTable( ( ('Resource', 'resource'), ('Permissions', 'permissions'), ('Applies when', 'applies_when'), ('Hint', 'failure_hint'), ) ) related_layout.addWidget(self.related, 1) self.tabs.addTab(related, 'Related checks') self.preview = QPlainTextEdit() self.preview.setReadOnly(True) preview_panel = QWidget() preview_layout = QVBoxLayout(preview_panel) preview_layout.addWidget( label( 'Read-only JSON input for the local simulation: principal, operation, target, context values and statement selections. Nothing is sent to OCI.', 'muted', wrap=True, ) ) preview_layout.addWidget(self.preview, 1) self.tabs.addTab(preview_panel, 'Request JSON') layout.addWidget(self.tabs, 1) self.status = label('Prepare statements first.', 'muted', wrap=True) layout.addWidget(self.status) actions = QHBoxLayout() back = QPushButton('Back: Statements') back.clicked.connect(self.back_requested.emit) actions.addWidget(back) actions.addStretch() self.copy = QPushButton('Copy request JSON') self.copy.setEnabled(False) self.copy.clicked.connect(lambda: QApplication.clipboard().setText(self.preview.toPlainText())) actions.addWidget(self.copy) self.run = QPushButton('Run simulation') self.run.setObjectName('primary') self.run.setEnabled(False) self.run.clicked.connect(self.run_requested.emit) actions.addWidget(self.run) layout.addLayout(actions) layout.addWidget( label( 'Run evaluates the loaded model and records original inputs in this snapshot’s history.', 'muted', wrap=True, ) ) self.group.currentIndexChanged.connect(self._filter_operations) self.search.textChanged.connect(self._filter_operations) self.operation.currentIndexChanged.connect(self._operation_changed) self._replace_inputs([])
[docs] def set_context(self, context): """Replace the reference catalog and discard inputs from the prior snapshot. Args: context: Newly published AppContext, read only for reference lookups. """ self.context = context self.catalog = context.reference_data_service.list_operations_with_permissions() self._updating = True self.group.clear() self.group.addItem('All API groups', '') for group in sorted({row['api_name'] for row in self.catalog}): self.group.addItem(group, group) self.search.clear() self._updating = False self._filter_operations() self.clear_request()
[docs] def clear_request(self): """Clear operation, values and evidence after upstream invalidation.""" self.controller.api_operation = '' self.controller.operation_name = '' self.controller.where_values = {} self.operation.setCurrentIndex(0) self.metadata.clear() self.related.model.replace([]) self.conditions.model.replace([]) self._conditions = [] self._replace_inputs([]) self._update_preview()
[docs] def refresh_selection(self): """Project prepared schemas onto checked IDs, keeping surviving input values.""" checked = set(self.controller.checked_ids) self._conditions = [ row for row in self.controller.condition_schema['conditions'] if row['internal_id'] in checked ] fields = sorted({name for row in self._conditions for name in row['variables']} - {'request.operation'}) self.conditions.model.replace(self._conditions) self._replace_inputs(fields) self._values_changed()
def _filter_operations(self, *_args): if self._updating: return prior = self.controller.api_operation group = self.group.currentData() query = self.search.text().strip().casefold() self.operation.blockSignals(True) self.operation.clear() rows = [ row for row in self.catalog if (not group or row['api_name'] == group) and query in row['label'].casefold() ] self.operation.addItem('Choose an API operation…' if rows else 'No matching API operations', None) for row in rows: self.operation.addItem(row['label'], row) for index in range(1, self.operation.count()): if self.operation.itemData(index)['label'] == prior: self.operation.setCurrentIndex(index) break self.operation.blockSignals(False) self._operation_changed() def _operation_changed(self, *_args): row = self.operation.currentData() self.controller.api_operation = row['label'] if row else '' self.controller.operation_name = (row.get('request_operation') or row['operation_name']) if row else '' self.related.model.replace(row['related_checks'] if row else []) self.metadata.setPlainText( 'Primary permissions: ' + (', '.join(row['permissions']) or 'No primary permission mapping listed; coverage is incomplete.') + '\nrequest.operation (automatic): ' + (row.get('request_operation') or row['operation_name']) + ('\nReference notes: ' + row['notes'] if row['notes'] else '') if row else '' ) self._update_preview() self.inputs_changed.emit() def _replace_inputs(self, names): prior = {name: (value.text(), null.isChecked()) for name, (value, null) in self.input_widgets.items()} content = QWidget() form = QFormLayout(content) form.setVerticalSpacing(12) self.input_widgets = {} if not names: form.addRow( label('No manual condition values are needed for the current checked statements.', 'muted', wrap=True) ) for name in names: value = QLineEdit() value.setPlaceholderText( {'request.utc-timestamp': '2026-01-05T12:34:56Z', 'request.utc-timestamp.time-of-day': '13:27:00Z'}.get( name, 'Blank = not supplied' ) ) null = QCheckBox('Null / not supplied') if name in prior: value.setText(prior[name][0]) null.setChecked(prior[name][1]) value.setEnabled(not null.isChecked()) value.textChanged.connect(self._values_changed) null.toggled.connect(value.setDisabled) null.toggled.connect(self._values_changed) row = QHBoxLayout() row.addWidget(value, 1) row.addWidget(null) form.addRow(name, row) self.input_widgets[name] = (value, null) old = self.inputs.takeWidget() self.inputs.setWidget(content) if old: old.deleteLater() def _values_changed(self, *_args): self.controller.where_values = { name: None if null.isChecked() else value.text() for name, (value, null) in self.input_widgets.items() } self._update_preview() self.inputs_changed.emit() def _update_preview(self): self.preview.setPlainText(json.dumps(self.controller.request_payload(), indent=2, ensure_ascii=False)) unsupported = sum(row['parse_status'] != 'parsed' for row in self._conditions) unsupplied = sum(null.isChecked() or value.text() == '' for value, null in self.input_widgets.values()) self.status.setText( f'{len(self.controller.checked_ids)} checked statements · {len(self.input_widgets)} manual fields · {unsupplied} not supplied. ' + ('Choose an API operation. ' if not self.controller.api_operation else '') + ( f'{unsupported} unsupported conditions: review Condition evidence; input coverage is incomplete. ' if unsupported else '' ) + 'Review inputs, then run the model simulation.' ) self.copy.setEnabled(self.controller.ready and bool(self.controller.api_operation)) self.run.setEnabled(self.controller.ready and bool(self.controller.api_operation))