(function() {
var MAX_FILE_BYTES = 256 * 1024;
var APPLY_REPLAY_STORAGE_KEY = 'crank_openapi_apply_replay_v1';
var restoredApplyRequest = loadApplyReplay();
var state = {
workspaceId: null,
onImported: null,
file: null,
jobId: null,
preview: null,
filterQuery: '',
filterMethod: '',
revision: 0,
previewController: null,
applyController: null,
applyInFlight: false,
applyRequest: restoredApplyRequest,
applyMetadata: restoredApplyRequest && restoredApplyRequest.operationMetadata || [],
applyResult: null,
applyOutcomeUnknown: !!restoredApplyRequest,
applyRefreshFailed: false,
restoreFocus: null,
};
function validApplyReplay(request) {
return request
&& typeof request.workspaceId === 'string' && request.workspaceId.length > 0 && request.workspaceId.length <= 128
&& typeof request.jobId === 'string' && request.jobId.length > 0 && request.jobId.length <= 128
&& request.payload && Array.isArray(request.payload.selected_operation_keys)
&& request.payload.selected_operation_keys.length <= 1024
&& request.payload.selected_operation_keys.every(function(key) {
return typeof key === 'string' && key.length > 0 && key.length <= 512;
})
&& (request.payload.server_url === null
|| (typeof request.payload.server_url === 'string' && request.payload.server_url.length <= 2048))
&& (request.payload.conflict_mode === 'skip' || request.payload.conflict_mode === 'rename')
&& Array.isArray(request.operationMetadata)
&& request.operationMetadata.length <= 1024
&& request.operationMetadata.every(function(operation) {
return operation
&& typeof operation.key === 'string' && operation.key.length > 0 && operation.key.length <= 512
&& typeof operation.method === 'string' && operation.method.length > 0 && operation.method.length <= 16
&& typeof operation.path === 'string' && operation.path.length > 0 && operation.path.length <= 2048;
});
}
function loadApplyReplay() {
try {
var request = JSON.parse(sessionStorage.getItem(APPLY_REPLAY_STORAGE_KEY) || 'null');
return validApplyReplay(request) ? request : null;
} catch (_error) {
return null;
}
}
function persistApplyReplay(request) {
if (!validApplyReplay(request)) return;
try {
sessionStorage.setItem(APPLY_REPLAY_STORAGE_KEY, JSON.stringify(request));
} catch (_error) {}
}
function clearApplyReplay() {
try {
sessionStorage.removeItem(APPLY_REPLAY_STORAGE_KEY);
} catch (_error) {}
}
function qs(id) {
return document.getElementById(id);
}
function tKey(key) {
return window.t ? window.t(key) : key;
}
function tfKey(key, vars) {
return window.tf ? window.tf(key, vars) : tKey(key);
}
function locale() {
return localStorage.getItem('crank_lang') === 'ru' ? 'ru' : 'en';
}
function isSameContext(revision, workspaceId, language) {
return revision === state.revision
&& workspaceId === state.workspaceId
&& language === locale();
}
function isCurrent(revision, workspaceId, language) {
return isSameContext(revision, workspaceId, language)
&& !qs('openapi-import-modal').hidden;
}
function setStatus(message, isError, focus) {
var node = qs('openapi-import-status');
if (!node) return;
node.textContent = message || '';
node.classList.toggle('error', !!isError);
if (focus && message) {
node.focus();
}
}
function boundedFileName(file) {
var name = String(file && file.name || '');
return name.length > 128 ? name.slice(0, 127) + '…' : name;
}
function formatSize(bytes) {
var language = locale() === 'ru' ? 'ru-RU' : 'en-US';
return new Intl.NumberFormat(language).format(bytes) + ' B';
}
function renderFileName() {
var node = qs('openapi-import-file-name');
if (!node) return;
node.textContent = state.file
? boundedFileName(state.file) + ' · ' + formatSize(state.file.size)
: tKey('openapi.file.none');
}
function selectedKeys() {
return Array.from(document.querySelectorAll('[data-openapi-operation]:checked'))
.map(function(input) {
return input.value;
});
}
function operationRows() {
return Array.from(document.querySelectorAll('.openapi-import-operation'));
}
function visibleOperationRows() {
return operationRows().filter(function(row) {
return !row.hidden;
});
}
function updateSelection() {
var node = qs('openapi-import-selection');
var selectedCount = selectedKeys().length;
var totalCount = document.querySelectorAll('[data-openapi-operation]').length;
var visibleCount = visibleOperationRows().length;
if (node) {
node.textContent = tfKey('openapi.selection', {
selected: selectedCount,
total: totalCount,
visible: visibleCount,
});
}
document.querySelectorAll('.openapi-import-group').forEach(function(groupNode) {
var rows = Array.from(groupNode.querySelectorAll('.openapi-import-operation'));
var visibleInputs = rows
.filter(function(row) { return !row.hidden; })
.map(function(row) { return row.querySelector('[data-openapi-operation]'); })
.filter(Boolean);
var selectedVisible = visibleInputs.filter(function(input) {
return input.checked;
}).length;
var selectedTotal = rows.filter(function(row) {
var input = row.querySelector('[data-openapi-operation]');
return input && input.checked;
}).length;
var checkbox = groupNode.querySelector('[data-openapi-group]');
var counter = groupNode.querySelector('[data-openapi-group-count]');
if (checkbox) {
checkbox.indeterminate = selectedVisible > 0 && selectedVisible < visibleInputs.length;
checkbox.checked = visibleInputs.length > 0 && selectedVisible === visibleInputs.length;
}
if (counter) {
counter.textContent = tfKey('openapi.group.selection', {
selected: selectedTotal,
total: rows.length,
visible: visibleInputs.length,
});
}
});
}
function applyFilters() {
var query = state.filterQuery.toLowerCase();
var method = state.filterMethod.toLowerCase();
document.querySelectorAll('.openapi-import-group').forEach(function(groupNode) {
var visibleInGroup = 0;
groupNode.querySelectorAll('.openapi-import-operation').forEach(function(row) {
var matchesMethod = !method || String(row.dataset.openapiMethod).toLowerCase() === method;
var matchesQuery = !query || String(row.dataset.openapiSearch).toLowerCase().indexOf(query) >= 0;
var visible = matchesMethod && matchesQuery;
row.hidden = !visible;
if (visible) visibleInGroup += 1;
});
groupNode.hidden = visibleInGroup === 0;
});
updateSelection();
}
function setVisibleSelection(checked) {
visibleOperationRows().forEach(function(row) {
var input = row.querySelector('[data-openapi-operation]');
if (input) input.checked = checked;
});
updateSelection();
}
function escapeHtml(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"');
}
function renderFinding(finding) {
var severity = String(finding && finding.severity || 'warning').toLowerCase();
var label = severity === 'info' ? 'i' : severity === 'error' ? '×' : '!';
return ''
+ '' + label + ' '
+ escapeHtml(findingMessage(finding))
+ '';
}
function findingMessage(finding) {
var code = String(finding && finding.code || '').replace(/^.*\./, '');
var known = {
missing_servers: 'openapi.finding.missing_servers',
multiple_servers: 'openapi.finding.multiple_servers',
unsupported_request_body: 'openapi.finding.unsupported_request_body',
operation_name_renamed: 'openapi.finding.operation_name_renamed',
operation_name_conflict: 'openapi.finding.operation_name_conflict',
weak_tool_description: 'openapi.finding.weak_tool_description',
};
return tKey(known[code] || 'openapi.finding.generic');
}
function appendMappingGroup(root, label, values) {
if (!values || !values.length) return;
var group = document.createElement('div');
group.className = 'openapi-import-mapping-group';
var title = document.createElement('span');
title.className = 'openapi-import-mapping-label';
title.textContent = label;
group.appendChild(title);
values.slice(0, 8).forEach(function(value) {
var chip = document.createElement('code');
chip.className = 'openapi-import-mapping-chip';
chip.textContent = value;
group.appendChild(chip);
});
if (values.length > 8) {
var more = document.createElement('span');
more.className = 'openapi-import-mapping-more';
more.textContent = '+' + (values.length - 8);
group.appendChild(more);
}
root.appendChild(group);
}
function renderOperationMappingPreview(operation) {
var details = document.createElement('div');
var request = { path: [], query: [], headers: [], body: [] };
var inputRules = operation
&& operation.draft
&& operation.draft.input_mapping
&& Array.isArray(operation.draft.input_mapping.rules)
? operation.draft.input_mapping.rules
: [];
var outputRules = operation
&& operation.draft
&& operation.draft.output_mapping
&& Array.isArray(operation.draft.output_mapping.rules)
? operation.draft.output_mapping.rules
: [];
details.className = 'openapi-import-mapping-preview';
inputRules.forEach(function(rule) {
var target = String(rule.target || '');
if (target.indexOf('$.request.path.') === 0) {
request.path.push(target.slice(15));
} else if (target.indexOf('$.request.query.') === 0) {
request.query.push(target.slice(16));
} else if (target.indexOf('$.request.headers.') === 0) {
request.headers.push(target.slice(18));
} else if (target.indexOf('$.request.body.') === 0) {
request.body.push(target.slice(15));
} else if (target === '$.request.body') {
request.body.push('body');
}
});
appendMappingGroup(details, tKey('openapi.mapping.path'), request.path);
appendMappingGroup(details, tKey('openapi.mapping.query'), request.query);
appendMappingGroup(details, tKey('openapi.mapping.header'), request.headers);
appendMappingGroup(details, tKey('openapi.mapping.body'), request.body);
appendMappingGroup(details, tKey('openapi.mapping.response'), outputRules.map(function(rule) {
var target = String(rule.target || '').replace('$.output.', '').replace('$.output', 'result');
var source = String(rule.source || '').replace('$.response.body.', '').replace('$.response.body', 'body');
return target + ' ← ' + source;
}));
return details;
}
function populateServers(preview) {
var serverSelect = qs('openapi-import-server');
var servers = preview.source && preview.source.servers && preview.source.servers.length
? preview.source.servers
: [''];
serverSelect.replaceChildren();
servers.forEach(function(server) {
var option = document.createElement('option');
option.value = server;
option.textContent = server || tKey('openapi.server.required');
serverSelect.appendChild(option);
});
}
function populateMethodFilter(preview) {
var methodFilter = qs('openapi-import-method-filter');
var all = document.createElement('option');
var methods = Array.from(new Set((preview.groups || []).flatMap(function(group) {
return (group.operations || []).map(function(operation) {
return operation.method;
});
}))).sort();
methodFilter.replaceChildren();
all.value = '';
all.textContent = tKey('openapi.method.all');
methodFilter.appendChild(all);
methods.forEach(function(method) {
var option = document.createElement('option');
option.value = method;
option.textContent = method;
methodFilter.appendChild(option);
});
}
function renderPreview(response) {
var preview = response.preview;
var count = (preview.groups || []).reduce(function(sum, group) {
return sum + (group.operations || []).length;
}, 0);
var source = qs('openapi-import-source');
var groupsNode = qs('openapi-import-groups');
state.jobId = response.job_id;
state.preview = preview;
state.filterQuery = '';
state.filterMethod = '';
qs('openapi-import-preview-panel').hidden = false;
qs('openapi-import-result').hidden = true;
qs('openapi-import-result').replaceChildren();
source.textContent = tfKey('openapi.source.summary', {
name: boundedFileName(state.file),
size: formatSize(state.file.size),
count: count,
});
qs('openapi-import-search').value = '';
populateServers(preview);
populateMethodFilter(preview);
groupsNode.replaceChildren();
if (preview.findings && preview.findings.length) {
var documentFindings = document.createElement('div');
documentFindings.className = 'openapi-import-document-findings';
documentFindings.innerHTML = preview.findings.map(renderFinding).join('');
groupsNode.appendChild(documentFindings);
}
(preview.groups || []).forEach(function(group) {
var groupNode = document.createElement('section');
var header = document.createElement('div');
groupNode.className = 'openapi-import-group';
header.className = 'openapi-import-group-header';
header.innerHTML = '' + escapeHtml(group.title) + ' · ' + group.operations.length + ''
+ ''
+ '';
groupNode.appendChild(header);
group.operations.forEach(function(operation) {
var row = document.createElement('label');
row.className = 'openapi-import-operation';
row.dataset.openapiMethod = operation.method;
row.dataset.openapiSearch = [
group.title,
operation.key,
operation.suggested_name,
operation.suggested_display_name,
operation.path,
operation.method,
].join(' ');
row.innerHTML = ''
+ '
' + escapeHtml(operation.suggested_display_name) + '
'
+ '
' + escapeHtml(operation.suggested_name) + ' · '
+ escapeHtml(operation.path) + ' · '
+ escapeHtml(tfKey('openapi.operation.fields', { input: operation.input_fields, output: operation.output_fields }))
+ '
' + escapeHtml(operation.method) + '';
row.appendChild(renderOperationMappingPreview(operation));
if (operation.findings && operation.findings.length) {
var findings = document.createElement('div');
findings.className = 'openapi-import-findings';
findings.innerHTML = operation.findings.map(renderFinding).join('');
row.appendChild(findings);
}
groupNode.appendChild(row);
});
groupNode.querySelector('[data-openapi-group]').addEventListener('change', function(event) {
groupNode.querySelectorAll('.openapi-import-operation').forEach(function(row) {
var input = row.querySelector('[data-openapi-operation]');
if (!row.hidden && input) input.checked = event.target.checked;
});
updateSelection();
});
groupsNode.appendChild(groupNode);
});
groupsNode.querySelectorAll('[data-openapi-operation]').forEach(function(input) {
input.addEventListener('change', updateSelection);
});
applyFilters();
}
function validateFile(file) {
if (!file) return 'openapi.error.file_required';
var name = String(file.name || '').toLowerCase();
var yaml = /\.(yaml|yml)$/.test(name);
var json = /\.json$/.test(name);
if (!yaml && !json) return 'openapi.error.file_type';
if (!file.size) return 'openapi.error.file_empty';
if (file.size > MAX_FILE_BYTES) return 'openapi.error.file_large';
var type = String(file.type || '').toLowerCase();
if (!type || type === 'application/octet-stream') return null;
var allowed = yaml
? ['application/yaml', 'application/x-yaml', 'text/yaml', 'text/x-yaml']
: ['application/json', 'application/openapi+json'];
return allowed.indexOf(type) >= 0 ? null : 'openapi.error.file_type';
}
function abort(controller) {
if (controller) controller.abort();
}
function clearPreview() {
state.jobId = null;
state.preview = null;
state.applyRequest = null;
state.applyMetadata = [];
state.applyResult = null;
state.applyOutcomeUnknown = false;
state.applyRefreshFailed = false;
state.filterQuery = '';
state.filterMethod = '';
qs('openapi-import-preview-panel').hidden = true;
qs('openapi-import-groups').replaceChildren();
qs('openapi-import-result').hidden = true;
qs('openapi-import-result').replaceChildren();
updateSelection();
}
function clearServerSelection() {
qs('openapi-import-server-custom').value = '';
qs('openapi-import-server').replaceChildren();
}
function invalidate(options) {
state.revision += 1;
abort(state.previewController);
abort(state.applyController);
state.previewController = null;
state.applyController = null;
state.applyInFlight = false;
qs('openapi-import-preview').disabled = false;
qs('openapi-import-create').disabled = false;
qs('openapi-import-file-select').disabled = false;
qs('openapi-import-reset').disabled = false;
qs('openapi-import-cancel').hidden = true;
clearPreview();
if (options && options.clearFile) {
state.file = null;
qs('openapi-import-file').value = '';
renderFileName();
}
}
function showRetry(show) {
qs('openapi-import-retry').hidden = !show;
}
function lockApplyInputs(locked) {
qs('openapi-import-create').disabled = locked;
qs('openapi-import-preview').disabled = locked;
qs('openapi-import-file-select').disabled = locked;
qs('openapi-import-reset').disabled = locked;
}
function markApplyOutcomeUnknown(options) {
if (!state.applyRequest) return false;
abort(state.applyController);
state.applyController = null;
state.applyInFlight = false;
state.applyOutcomeUnknown = true;
persistApplyReplay(state.applyRequest);
// Until the same job is replayed, starting another import could hide an
// already committed server outcome and lead to duplicate drafts.
lockApplyInputs(true);
qs('openapi-import-cancel').hidden = true;
if (!options || options.render !== false) {
showRetry(true);
setStatus(tKey('openapi.status.apply_outcome_unknown'), true, true);
}
return true;
}
function cancelCurrent() {
if (state.applyInFlight || state.applyController) {
markApplyOutcomeUnknown();
return;
}
if (!state.previewController) return;
invalidate();
showRetry(!!state.file);
setStatus(tKey('openapi.status.cancelled'));
}
function errorStatus(error, fallback) {
var known = {
'openapi_upload.missing_file': 'openapi.error.file_required',
'openapi_upload.invalid_media_type': 'openapi.error.file_type',
'openapi_upload.empty_file': 'openapi.error.file_empty',
'openapi_upload.file_too_large': 'openapi.error.file_large',
'openapi_upload.no_methods': 'openapi.error.no_methods',
'openapi_upload.malformed_multipart': 'openapi.error.malformed_multipart',
'openapi_upload.invalid_utf8': 'openapi.error.invalid_utf8',
'openapi_upload.invalid_document': 'openapi.error.invalid_document',
'openapi_upload.parser_unavailable': 'openapi.error.parser_unavailable',
'openapi_upload.storage_unavailable': 'openapi.error.storage_unavailable',
'openapi_upload.source_integrity': 'openapi.error.source_integrity',
'openapi_upload.source_unavailable': 'openapi.error.source_unavailable',
};
var message = tKey(known[error && error.code] || fallback);
var identifiers = [];
if (error && error.requestId) {
identifiers.push(tfKey('openapi.correlation.request_id', { id: error.requestId }));
}
if (error && error.traceId) {
identifiers.push(tfKey('openapi.correlation.trace_id', { id: error.traceId }));
}
return identifiers.length ? message + ' ' + identifiers.join(' · ') : message;
}
async function preview() {
var localError = validateFile(state.file);
if (localError) {
setStatus(tKey(localError), true, true);
showRetry(false);
return;
}
if (!state.workspaceId) {
setStatus(tKey('openapi.error.workspace'), true, true);
return;
}
invalidate();
var revision = state.revision;
var workspaceId = state.workspaceId;
var language = locale();
var controller = new AbortController();
state.previewController = controller;
qs('openapi-import-preview').disabled = true;
qs('openapi-import-cancel').hidden = false;
showRetry(false);
setStatus(tKey('openapi.status.previewing'));
try {
var response = await window.CrankApi.previewOpenApiImport(workspaceId, state.file, { signal: controller.signal });
if (!isCurrent(revision, workspaceId, language)) return;
renderPreview(response);
setStatus(tKey('openapi.status.preview_ready'));
} catch (error) {
if (!isCurrent(revision, workspaceId, language)) return;
if (error && error.name === 'AbortError') {
setStatus(tKey('openapi.status.cancelled'));
return;
}
clearPreview();
showRetry(true);
setStatus(errorStatus(error, 'openapi.error.preview'), true, true);
} finally {
if (isCurrent(revision, workspaceId, language)) {
state.previewController = null;
qs('openapi-import-preview').disabled = false;
qs('openapi-import-cancel').hidden = true;
}
}
}
function previewOperationForCreated(created) {
var groups = state.preview && state.preview.groups || [];
for (var groupIndex = 0; groupIndex < groups.length; groupIndex += 1) {
var operations = groups[groupIndex].operations || [];
for (var operationIndex = 0; operationIndex < operations.length; operationIndex += 1) {
if (created.operation_key && operations[operationIndex].key === created.operation_key) {
return operations[operationIndex];
}
}
}
// Older servers did not return operation_key. Keep result rendering useful
// during a rolling upgrade, but use the unambiguous key whenever it exists.
for (var fallbackGroupIndex = 0; fallbackGroupIndex < groups.length; fallbackGroupIndex += 1) {
var fallbackOperations = groups[fallbackGroupIndex].operations || [];
for (var fallbackOperationIndex = 0; fallbackOperationIndex < fallbackOperations.length; fallbackOperationIndex += 1) {
if (fallbackOperations[fallbackOperationIndex].suggested_name === created.name) {
return fallbackOperations[fallbackOperationIndex];
}
}
}
for (var metadataIndex = 0; metadataIndex < state.applyMetadata.length; metadataIndex += 1) {
if (created.operation_key && state.applyMetadata[metadataIndex].key === created.operation_key) {
return state.applyMetadata[metadataIndex];
}
}
return null;
}
function wizardHref(operationId) {
return ((window.CrankRoutes && window.CrankRoutes.wizard) || '/wizard/')
+ '?mode=edit&operationId=' + encodeURIComponent(operationId);
}
function appendFindingNodes(root, findings) {
if (!findings || !findings.length) {
var empty = document.createElement('span');
empty.className = 'openapi-import-result-ok';
empty.textContent = tKey('openapi.result.no_findings');
root.appendChild(empty);
return;
}
findings.slice(0, 4).forEach(function(finding) {
var wrapper = document.createElement('div');
wrapper.innerHTML = renderFinding(finding);
root.appendChild(wrapper.firstElementChild);
});
if (findings.length > 4) {
var more = document.createElement('span');
more.className = 'openapi-import-result-more';
more.textContent = tfKey('openapi.result.more', { count: findings.length - 4 });
root.appendChild(more);
}
}
function renderResult(response) {
var node = qs('openapi-import-result');
var title = document.createElement('strong');
node.replaceChildren();
title.textContent = tKey('openapi.result.title');
node.appendChild(title);
if (response.created && response.created.length) {
var primary = document.createElement('div');
var table = document.createElement('div');
primary.className = 'openapi-import-primary-result';
primary.innerHTML = ''
+ escapeHtml(tKey('openapi.result.open_first'))
+ '';
node.appendChild(primary);
table.className = 'openapi-import-result-table';
table.setAttribute('role', 'table');
table.innerHTML = ''
+ '
' + escapeHtml(tKey('openapi.result.draft')) + '
'
+ '
' + escapeHtml(tKey('openapi.result.findings')) + '
'
+ '
' + escapeHtml(tKey('openapi.result.action')) + '
'
+ '
';
response.created.forEach(function(operation) {
var previewOperation = previewOperationForCreated(operation);
var row = document.createElement('div');
var nameCell = document.createElement('div');
var name = document.createElement('div');
var meta = document.createElement('div');
var findingsCell = document.createElement('div');
var actionCell = document.createElement('div');
var action = document.createElement('a');
row.className = 'openapi-import-result-row';
row.setAttribute('role', 'row');
nameCell.setAttribute('role', 'cell');
name.className = 'openapi-import-result-name';
name.textContent = operation.name;
meta.className = 'openapi-import-result-meta';
meta.textContent = previewOperation
? previewOperation.method + ' ' + previewOperation.path + ' · v' + operation.version
: 'v' + operation.version;
nameCell.append(name, meta);
findingsCell.className = 'openapi-import-result-findings';
findingsCell.setAttribute('role', 'cell');
appendFindingNodes(findingsCell, previewOperation && previewOperation.findings);
actionCell.setAttribute('role', 'cell');
action.className = 'btn-secondary openapi-import-result-action';
action.href = wizardHref(operation.operation_id);
action.textContent = previewOperation && previewOperation.findings && previewOperation.findings.length
? tKey('openapi.result.fix')
: tKey('openapi.result.open');
actionCell.appendChild(action);
row.append(nameCell, findingsCell, actionCell);
table.appendChild(row);
});
node.appendChild(table);
}
if (response.skipped && response.skipped.length) {
var skippedNode = document.createElement('div');
skippedNode.className = 'openapi-import-result-skipped';
skippedNode.textContent = tKey('openapi.result.skipped') + ': ' + response.skipped.map(function(item) {
return (item.name || item.operation_key || tKey('openapi.result.unnamed')) + ' — '
+ findingMessage({ code: item.code || item.reason_code || item.reason });
}).join('; ');
node.appendChild(skippedNode);
}
if (response.findings && response.findings.length) {
var findings = document.createElement('div');
findings.className = 'openapi-import-result-findings';
findings.innerHTML = response.findings.map(renderFinding).join('');
node.appendChild(findings);
}
node.hidden = false;
}
function createdStatus(response) {
return tfKey('openapi.status.created', {
created: (response.created || []).length,
skipped: (response.skipped || []).length,
});
}
async function notifyImported(revision, workspaceId, language) {
if (typeof state.onImported !== 'function') return;
try {
await state.onImported();
} catch (_error) {
state.applyRefreshFailed = true;
if (isCurrent(revision, workspaceId, language) && state.applyResult) {
setStatus(createdStatus(state.applyResult) + ' ' + tKey('openapi.status.refresh_failed'), false, true);
}
}
}
async function submitApply(request) {
if (state.applyInFlight) return;
var revision = state.revision;
var workspaceId = request.workspaceId;
var language = locale();
var controller = new AbortController();
state.applyController = controller;
state.applyInFlight = true;
state.applyOutcomeUnknown = false;
state.applyRefreshFailed = false;
lockApplyInputs(true);
qs('openapi-import-cancel').hidden = false;
showRetry(false);
setStatus(tKey('openapi.status.creating'));
try {
var response = await window.CrankApi.createOpenApiImport(
workspaceId,
request.jobId,
request.payload,
{ signal: controller.signal }
);
// Abort is advisory: a superseded request can still deliver a response.
// Only the controller installed by this attempt may mutate UI authority.
if (state.applyController !== controller) return;
state.applyOutcomeUnknown = false;
clearApplyReplay();
state.applyController = null;
state.applyInFlight = false;
lockApplyInputs(false);
qs('openapi-import-cancel').hidden = true;
showRetry(false);
if (!isSameContext(revision, workspaceId, language)) {
// The original workspace job is now resolved. Do not render its result
// into another workspace/language context.
state.applyResult = null;
state.applyRequest = null;
return;
}
state.applyResult = response;
state.applyRequest = null;
if (isCurrent(revision, workspaceId, language)) {
setStatus(createdStatus(response));
renderResult(response);
}
await notifyImported(revision, workspaceId, language);
} catch (error) {
// A cancelled attempt may settle after an idempotent retry has already
// installed a new controller. It must not overwrite the newer state.
if (state.applyController !== controller) return;
if (error && error.name === 'AbortError') {
state.applyOutcomeUnknown = true;
persistApplyReplay(state.applyRequest);
showRetry(true);
if (!qs('openapi-import-modal').hidden) {
setStatus(tKey('openapi.status.apply_outcome_unknown'), true, true);
}
return;
}
if (Number.isInteger(error && error.status)
&& error.status >= 400 && error.status < 500 && error.status !== 408) {
state.applyOutcomeUnknown = false;
state.applyRequest = null;
clearApplyReplay();
showRetry(true);
if (!qs('openapi-import-modal').hidden) {
setStatus(errorStatus(error, 'openapi.error.create'), true, true);
}
return;
}
// A browser/network failure is not proof that the server rolled the
// transaction back. Replaying this exact job is idempotent server-side.
state.applyOutcomeUnknown = true;
persistApplyReplay(state.applyRequest);
showRetry(true);
if (!qs('openapi-import-modal').hidden) {
setStatus(errorStatus(error, 'openapi.error.create') + ' ' + tKey('openapi.status.apply_outcome_unknown'), true, true);
}
} finally {
if (state.applyController === controller) {
state.applyController = null;
state.applyInFlight = false;
lockApplyInputs(state.applyOutcomeUnknown);
qs('openapi-import-cancel').hidden = true;
}
}
}
async function createDrafts() {
if (state.applyInFlight) return;
var keys = selectedKeys();
if (!keys.length) {
setStatus(tKey('openapi.error.selection'), true, true);
return;
}
if (!state.jobId || !state.preview) {
setStatus(tKey('openapi.error.preview_required'), true, true);
return;
}
var serverUrl = (qs('openapi-import-server-custom').value || qs('openapi-import-server').value || '').trim();
if (!serverUrl) {
setStatus(tKey('openapi.error.server'), true, true);
return;
}
state.applyRequest = {
workspaceId: state.workspaceId,
jobId: state.jobId,
payload: {
selected_operation_keys: keys,
server_url: serverUrl,
conflict_mode: qs('openapi-import-conflict-mode').value || 'rename',
},
operationMetadata: (state.preview.groups || []).flatMap(function(group) {
return (group.operations || []).filter(function(operation) {
return keys.indexOf(operation.key) >= 0;
}).map(function(operation) {
return { key: operation.key, method: operation.method, path: operation.path };
});
}),
};
state.applyMetadata = state.applyRequest.operationMetadata;
persistApplyReplay(state.applyRequest);
await submitApply(state.applyRequest);
}
function retryCurrent() {
if (state.applyOutcomeUnknown && state.applyRequest) {
return submitApply(state.applyRequest);
}
return preview();
}
function reset() {
invalidate({ clearFile: true });
showRetry(false);
setStatus('');
qs('openapi-import-server-custom').value = '';
qs('openapi-import-conflict-mode').value = 'rename';
}
function open(options) {
if (state.workspaceId && state.workspaceId !== options.workspaceId
&& !(state.applyOutcomeUnknown && state.applyRequest)) {
invalidate({ clearFile: true });
}
state.workspaceId = options.workspaceId;
state.onImported = options.onImported;
state.restoreFocus = document.activeElement;
qs('openapi-import-modal').hidden = false;
renderFileName();
if (state.applyResult) {
renderResult(state.applyResult);
setStatus(createdStatus(state.applyResult) + (state.applyRefreshFailed ? ' ' + tKey('openapi.status.refresh_failed') : ''));
} else if (state.applyOutcomeUnknown && state.applyRequest) {
lockApplyInputs(true);
showRetry(true);
setStatus(tKey('openapi.status.apply_outcome_unknown'), true, true);
qs('openapi-import-retry').focus();
return;
}
qs('openapi-import-file-select').focus();
}
function close() {
if (state.applyInFlight || state.applyController) {
markApplyOutcomeUnknown({ render: false });
qs('openapi-import-modal').hidden = true;
if (state.restoreFocus && typeof state.restoreFocus.focus === 'function') {
state.restoreFocus.focus();
}
return;
}
if (state.applyOutcomeUnknown && state.applyRequest) {
persistApplyReplay(state.applyRequest);
qs('openapi-import-modal').hidden = true;
if (state.restoreFocus && typeof state.restoreFocus.focus === 'function') {
state.restoreFocus.focus();
}
return;
}
invalidate({ clearFile: true });
showRetry(false);
qs('openapi-import-modal').hidden = true;
if (state.restoreFocus && typeof state.restoreFocus.focus === 'function') {
state.restoreFocus.focus();
}
}
function selectFile(file) {
clearServerSelection();
invalidate();
state.file = file || null;
showRetry(false);
var localError = validateFile(state.file);
renderFileName();
if (localError) {
setStatus(tKey(localError), true, true);
} else if (state.file) {
setStatus(tKey('openapi.status.file_ready'));
}
}
document.addEventListener('DOMContentLoaded', function() {
if (!qs('openapi-import-modal')) return;
renderFileName();
qs('openapi-import-file-select').addEventListener('click', function(event) {
event.preventDefault();
qs('openapi-import-file').click();
});
qs('openapi-import-file').addEventListener('change', function(event) {
selectFile(event.target.files && event.target.files[0]);
});
qs('openapi-import-preview').addEventListener('click', preview);
qs('openapi-import-retry').addEventListener('click', retryCurrent);
qs('openapi-import-create').addEventListener('click', createDrafts);
qs('openapi-import-cancel').addEventListener('click', cancelCurrent);
qs('openapi-import-reset').addEventListener('click', reset);
qs('openapi-import-search').addEventListener('input', function(event) {
state.filterQuery = event.target.value.trim();
applyFilters();
});
qs('openapi-import-method-filter').addEventListener('change', function(event) {
state.filterMethod = event.target.value;
applyFilters();
});
qs('openapi-import-select-visible').addEventListener('click', function() {
setVisibleSelection(true);
});
qs('openapi-import-clear-visible').addEventListener('click', function() {
setVisibleSelection(false);
});
document.querySelectorAll('[data-openapi-close]').forEach(function(node) {
node.addEventListener('click', close);
});
document.addEventListener('keydown', function(event) {
var modal = qs('openapi-import-modal');
if (event.key === 'Escape' && !qs('openapi-import-modal').hidden) {
event.preventDefault();
close();
}
if (event.key !== 'Tab' || modal.hidden) return;
var focusable = Array.from(modal.querySelectorAll('button:not([disabled]):not([hidden]), input:not([disabled]), select:not([disabled]), a[href]'))
.filter(function(node) { return node.tabIndex >= 0 && !node.closest('[hidden]'); });
if (!focusable.length) return;
var first = focusable[0];
var last = focusable[focusable.length - 1];
if (event.shiftKey && (document.activeElement === first || !modal.contains(document.activeElement))) {
event.preventDefault();
last.focus();
} else if (!event.shiftKey && document.activeElement === last) {
event.preventDefault();
first.focus();
}
});
function invalidateForContext(options) {
if (!qs('openapi-import-modal').hidden) {
if (state.applyInFlight || state.applyController) {
markApplyOutcomeUnknown();
return;
}
if (state.applyOutcomeUnknown && state.applyRequest) {
showRetry(true);
setStatus(tKey('openapi.status.apply_outcome_unknown'), true, true);
return;
}
invalidate(options);
renderFileName();
showRetry(!options || !options.clearFile ? !!state.file : false);
setStatus(tKey('openapi.status.context_changed'));
}
}
window.addEventListener('crank:langchange', function() {
invalidateForContext();
});
window.addEventListener('crank:workspacechange', function(event) {
if (!qs('openapi-import-modal').hidden) {
state.workspaceId = event.detail && event.detail.id || null;
invalidateForContext({ clearFile: true });
}
});
window.addEventListener('storage', function(event) {
if (event.key === 'crank_lang') invalidateForContext();
});
window.addEventListener('pagehide', function() {
if (state.applyInFlight || state.applyController) {
markApplyOutcomeUnknown({ render: false });
state.file = null;
qs('openapi-import-file').value = '';
renderFileName();
} else if (state.applyOutcomeUnknown && state.applyRequest) {
persistApplyReplay(state.applyRequest);
} else {
invalidate({ clearFile: true });
}
clearServerSelection();
showRetry(false);
setStatus('');
});
window.addEventListener('pageshow', function(event) {
if (!event.persisted || qs('openapi-import-modal').hidden) return;
if (state.applyOutcomeUnknown && state.applyRequest) {
lockApplyInputs(true);
showRetry(true);
setStatus(tKey('openapi.status.apply_outcome_unknown'), true, true);
} else {
invalidateForContext({ clearFile: true });
}
});
});
window.CrankOpenApiImport = {
open: open,
close: close,
reset: reset,
};
}());