ui: trim community streaming surface
This commit is contained in:
@@ -34,7 +34,7 @@ Progress:
|
||||
- done: Community wizard protocol list hides premium protocol cards;
|
||||
- done: Community security note is shown inside wizard flow;
|
||||
- done: server-side Community enforcement rejects unsupported protocols and premium security levels;
|
||||
- pending: убрать или честно заблокировать оставшиеся Community-incompatible streaming affordances;
|
||||
- done: Community protocol capabilities and wizard surface no longer present streaming execution as available;
|
||||
- pending: привести Agents/Settings copy к окончательной open-core границе.
|
||||
|
||||
DoD:
|
||||
|
||||
@@ -1065,6 +1065,13 @@ mod tests {
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["rest", "graphql", "grpc"]
|
||||
);
|
||||
for item in response["items"].as_array().unwrap() {
|
||||
assert_eq!(item["supports_execution_modes"], json!(["unary"]));
|
||||
assert_eq!(
|
||||
item["supports_transport_behaviors"],
|
||||
json!(["request_response"])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
|
||||
@@ -1562,11 +1562,11 @@ impl AdminService {
|
||||
}
|
||||
|
||||
pub async fn list_protocol_capabilities(&self) -> Vec<ProtocolCapabilityView> {
|
||||
self.get_capabilities()
|
||||
.await
|
||||
let capabilities = self.get_capabilities().await;
|
||||
capabilities
|
||||
.supported_protocols
|
||||
.into_iter()
|
||||
.map(protocol_capability_view)
|
||||
.map(|protocol| protocol_capability_view(protocol, capabilities.edition))
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -5124,25 +5124,34 @@ fn runtime_error_code(error: &RuntimeError) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
fn protocol_capability_view(protocol: Protocol) -> ProtocolCapabilityView {
|
||||
let supports_execution_modes = [
|
||||
ExecutionMode::Unary,
|
||||
ExecutionMode::Window,
|
||||
ExecutionMode::Session,
|
||||
ExecutionMode::AsyncJob,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|mode| protocol.supports_execution_mode(*mode))
|
||||
.collect();
|
||||
let supports_transport_behaviors = [
|
||||
TransportBehavior::RequestResponse,
|
||||
TransportBehavior::ServerStream,
|
||||
TransportBehavior::StatefulSession,
|
||||
TransportBehavior::DeferredResult,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|behavior| protocol.supports_transport_behavior(*behavior))
|
||||
.collect();
|
||||
fn protocol_capability_view(protocol: Protocol, edition: ProductEdition) -> ProtocolCapabilityView {
|
||||
let community_build = matches!(edition, ProductEdition::Community);
|
||||
let supports_execution_modes = if community_build {
|
||||
vec![ExecutionMode::Unary]
|
||||
} else {
|
||||
[
|
||||
ExecutionMode::Unary,
|
||||
ExecutionMode::Window,
|
||||
ExecutionMode::Session,
|
||||
ExecutionMode::AsyncJob,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|mode| protocol.supports_execution_mode(*mode))
|
||||
.collect()
|
||||
};
|
||||
let supports_transport_behaviors = if community_build {
|
||||
vec![TransportBehavior::RequestResponse]
|
||||
} else {
|
||||
[
|
||||
TransportBehavior::RequestResponse,
|
||||
TransportBehavior::ServerStream,
|
||||
TransportBehavior::StatefulSession,
|
||||
TransportBehavior::DeferredResult,
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|behavior| protocol.supports_transport_behavior(*behavior))
|
||||
.collect()
|
||||
};
|
||||
let supports_upload_artifacts = match protocol {
|
||||
Protocol::Rest | Protocol::Graphql | Protocol::Websocket => Vec::new(),
|
||||
Protocol::Grpc => vec!["proto".to_owned(), "descriptor_set".to_owned()],
|
||||
|
||||
@@ -69,12 +69,9 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="protocol-card-name" data-i18n="wizard.step1.grpc_name">gRPC</div>
|
||||
<div class="protocol-card-tagline" data-i18n="wizard.step1.grpc_tagline">High-performance binary RPC over HTTP/2 with unary and bounded streaming flows for low-latency internal services.</div>
|
||||
<div class="protocol-card-tagline" data-i18n="wizard.step1.grpc_tagline">High-performance binary RPC over HTTP/2 for unary calls to low-latency internal services.</div>
|
||||
<div class="protocol-card-tags">
|
||||
<span class="protocol-tag" data-i18n="wizard.step1.unary">Unary</span>
|
||||
<span class="protocol-tag" data-i18n="wizard.streaming.transport.server_stream">Server stream</span>
|
||||
<span class="protocol-tag" data-i18n="wizard.streaming.mode.window">Window</span>
|
||||
<span class="protocol-tag" data-i18n="wizard.streaming.mode.session">Session</span>
|
||||
<span class="protocol-tag">Protobuf</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -98,6 +98,17 @@ tls:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-callout" id="wizard-streaming-capability-note" hidden style="margin-bottom: 20px;">
|
||||
<svg class="info-callout-icon" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="8" cy="8" r="7"/>
|
||||
<path d="M8 11V8M8 5v-.5"/>
|
||||
</svg>
|
||||
<div class="info-callout-body">
|
||||
<div class="info-callout-title" data-i18n="wizard.step5.streaming_title">Streaming execution in Community</div>
|
||||
<div class="info-callout-text"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="wizard-streaming-config-card" class="config-card" style="margin-bottom: 20px;">
|
||||
<div class="config-card-header">
|
||||
<div class="config-card-header-icon">
|
||||
|
||||
+6
-2
@@ -565,7 +565,7 @@ var TRANSLATIONS = {
|
||||
'wizard.step1.graphql_name': 'GraphQL',
|
||||
'wizard.step1.graphql_tagline': 'Query and mutate a GraphQL schema. Supports queries and mutations over one fixed operation.',
|
||||
'wizard.step1.grpc_name': 'gRPC',
|
||||
'wizard.step1.grpc_tagline': 'High-performance binary RPC over HTTP/2 with unary and bounded streaming flows for low-latency internal services.',
|
||||
'wizard.step1.grpc_tagline': 'High-performance binary RPC over HTTP/2 for unary calls to low-latency internal services.',
|
||||
'wizard.step1.websocket_name': 'WebSocket',
|
||||
'wizard.step1.websocket_tagline': 'Stateful event streams and push-oriented integrations normalized into bounded MCP streaming tools.',
|
||||
'wizard.step1.soap_name': 'SOAP',
|
||||
@@ -577,6 +577,8 @@ var TRANSLATIONS = {
|
||||
'wizard.step1.tip_body': 'Switching protocol after configuring subsequent steps will clear schema and mapping data. Save your operation as a draft before experimenting. See protocol migration guide for details.',
|
||||
'wizard.step1.community_protocol_title': 'Community protocol scope',
|
||||
'wizard.step1.community_protocol_note': 'This Community build hides {protocols}. Commercial editions unlock those protocol surfaces.',
|
||||
'wizard.step5.streaming_title': 'Streaming execution in Community',
|
||||
'wizard.step5.community_streaming_note': 'This Community build keeps operations in unary mode. Window, session and async job tool families require a commercial edition.',
|
||||
'wizard.step2.title': 'Select upstream & endpoint',
|
||||
'wizard.step2.subtitle': 'Choose an existing upstream or register a new one. Then define the specific endpoint path this operation will call.',
|
||||
'wizard.required': 'Required',
|
||||
@@ -1619,7 +1621,7 @@ var TRANSLATIONS = {
|
||||
'wizard.step1.graphql_name': 'GraphQL',
|
||||
'wizard.step1.graphql_tagline': 'Операции по GraphQL-схеме. Поддерживаются Query и Mutation для одной фиксированной операции.',
|
||||
'wizard.step1.grpc_name': 'gRPC',
|
||||
'wizard.step1.grpc_tagline': 'Высокопроизводительный бинарный RPC поверх HTTP/2 с unary и bounded streaming flow для низколатентных внутренних сервисов.',
|
||||
'wizard.step1.grpc_tagline': 'Высокопроизводительный бинарный RPC поверх HTTP/2 для unary-вызовов к низколатентным внутренним сервисам.',
|
||||
'wizard.step1.websocket_name': 'WebSocket',
|
||||
'wizard.step1.websocket_tagline': 'Состояние соединения, event streams и push-oriented интеграции, нормализованные в bounded MCP streaming tools.',
|
||||
'wizard.step1.soap_name': 'SOAP',
|
||||
@@ -1631,6 +1633,8 @@ var TRANSLATIONS = {
|
||||
'wizard.step1.tip_body': 'Смена протокола после настройки следующих шагов очистит схемы и mapping. Сохраните операцию как черновик перед экспериментами. См. гайд по миграции протоколов.',
|
||||
'wizard.step1.community_protocol_title': 'Граница протоколов Community',
|
||||
'wizard.step1.community_protocol_note': 'В этой Community-сборке скрыты {protocols}. Эти протокольные поверхности открываются в коммерческих редакциях.',
|
||||
'wizard.step5.streaming_title': 'Потоковое выполнение в Community',
|
||||
'wizard.step5.community_streaming_note': 'В этой Community-сборке операции остаются в режиме unary. Семейства инструментов window, session и async job требуют коммерческую редакцию.',
|
||||
'wizard.step2.title': 'Выберите upstream и endpoint',
|
||||
'wizard.step2.subtitle': 'Выберите существующий upstream или зарегистрируйте новый. Затем задайте конкретный путь endpoint-а для этой операции.',
|
||||
'wizard.required': 'Обязательно',
|
||||
|
||||
@@ -187,6 +187,11 @@ function updateWizardProtocolVisibility() {
|
||||
if (grpcTools) grpcTools.hidden = wizardProtocol !== 'grpc';
|
||||
updateStreamingModeOptions();
|
||||
updateStreamingConfigVisibility();
|
||||
if (typeof window.renderEditionCapabilityHints === 'function'
|
||||
&& window.CrankWizardState
|
||||
&& typeof window.CrankWizardState.currentEditionCapabilities === 'function') {
|
||||
window.renderEditionCapabilityHints(window.CrankWizardState.currentEditionCapabilities());
|
||||
}
|
||||
}
|
||||
|
||||
function showWizardLiveStatus(title, text, isError) {
|
||||
|
||||
@@ -47,24 +47,24 @@
|
||||
function defaultProtocolCapabilities() {
|
||||
return {
|
||||
rest: {
|
||||
supports_execution_modes: ['unary', 'window', 'session', 'async_job'],
|
||||
supports_transport_behaviors: ['request_response', 'server_stream', 'deferred_result'],
|
||||
supports_execution_modes: ['unary'],
|
||||
supports_transport_behaviors: ['request_response'],
|
||||
},
|
||||
graphql: {
|
||||
supports_execution_modes: ['unary'],
|
||||
supports_transport_behaviors: ['request_response'],
|
||||
},
|
||||
grpc: {
|
||||
supports_execution_modes: ['unary', 'window', 'session', 'async_job'],
|
||||
supports_transport_behaviors: ['request_response', 'server_stream'],
|
||||
supports_execution_modes: ['unary'],
|
||||
supports_transport_behaviors: ['request_response'],
|
||||
},
|
||||
websocket: {
|
||||
supports_execution_modes: ['window', 'session', 'async_job'],
|
||||
supports_transport_behaviors: ['server_stream', 'stateful_session', 'deferred_result'],
|
||||
supports_execution_modes: ['unary'],
|
||||
supports_transport_behaviors: ['request_response'],
|
||||
},
|
||||
soap: {
|
||||
supports_execution_modes: ['unary', 'async_job'],
|
||||
supports_transport_behaviors: ['request_response', 'server_stream', 'deferred_result'],
|
||||
supports_execution_modes: ['unary'],
|
||||
supports_transport_behaviors: ['request_response'],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -149,6 +149,9 @@ async function initWizardPage() {
|
||||
function renderEditionCapabilityHints(capabilities) {
|
||||
var securityNote = document.getElementById('wizard-security-level-note');
|
||||
var securityText = securityNote ? securityNote.querySelector('.info-callout-text') : null;
|
||||
var streamingNote = document.getElementById('wizard-streaming-capability-note');
|
||||
var streamingText = streamingNote ? streamingNote.querySelector('.info-callout-text') : null;
|
||||
var streamingCard = document.getElementById('wizard-streaming-config-card');
|
||||
if (!securityText) return;
|
||||
var levels = capabilities && Array.isArray(capabilities.supported_security_levels)
|
||||
? capabilities.supported_security_levels
|
||||
@@ -157,8 +160,29 @@ function renderEditionCapabilityHints(capabilities) {
|
||||
if (!securityNote.hidden) {
|
||||
securityText.textContent = tKey('wizard.step5.community_security_note');
|
||||
}
|
||||
|
||||
var protocolCapabilities = currentProtocolCapabilities();
|
||||
var modes = protocolCapabilities && Array.isArray(protocolCapabilities.supports_execution_modes)
|
||||
? protocolCapabilities.supports_execution_modes
|
||||
: ['unary'];
|
||||
var hasStreamingModes = modes.some(function(mode) { return mode !== 'unary'; });
|
||||
if (streamingCard) {
|
||||
streamingCard.hidden = !hasStreamingModes;
|
||||
}
|
||||
if (streamingNote && streamingText) {
|
||||
var edition = capabilities && capabilities.edition ? capabilities.edition : 'community';
|
||||
var shouldExplainCommunityLimit = edition === 'community'
|
||||
&& !hasStreamingModes
|
||||
&& ['rest', 'grpc'].indexOf(wizardProtocol) >= 0;
|
||||
streamingNote.hidden = !shouldExplainCommunityLimit;
|
||||
if (shouldExplainCommunityLimit) {
|
||||
streamingText.textContent = tKey('wizard.step5.community_streaming_note');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.renderEditionCapabilityHints = renderEditionCapabilityHints;
|
||||
|
||||
if (window.CrankDiagnostics && typeof window.CrankDiagnostics.bootstrap === 'function') {
|
||||
window.CrankDiagnostics.bootstrap('wizard', initWizardPage);
|
||||
} else {
|
||||
|
||||
@@ -20,4 +20,13 @@ test('community wizard hides premium protocols and explains edition scope', asyn
|
||||
await expect(page.locator('[data-testid="wizard-protocol-websocket"]')).toBeHidden();
|
||||
await expect(page.locator('[data-testid="wizard-protocol-soap"]')).toBeHidden();
|
||||
await expect(page.locator('#wizard-edition-protocol-note')).toContainText(localized('Commercial editions unlock', 'коммерческих редакциях'));
|
||||
|
||||
await page.locator('[data-testid="wizard-protocol-grpc"]').click();
|
||||
await page.locator('#btn-continue').click();
|
||||
await page.locator('#btn-continue').click();
|
||||
await page.locator('#btn-continue').click();
|
||||
await page.locator('#btn-continue').click();
|
||||
|
||||
await expect(page.locator('#wizard-streaming-config-card')).toBeHidden();
|
||||
await expect(page.locator('#wizard-streaming-capability-note')).toContainText(localized('commercial edition', 'коммерческую редакцию'));
|
||||
});
|
||||
|
||||
+1
-3
@@ -270,9 +270,7 @@
|
||||
- upstream auth selector;
|
||||
- quick-create secret / auth profile modal.
|
||||
- execution mode selector;
|
||||
- streaming config blocks;
|
||||
- stream test-runs;
|
||||
- tool family preview.
|
||||
- streaming config blocks, stream test-runs и tool family preview только там, где это разрешает capability model редакции;
|
||||
|
||||
Детальные DTO и response shapes для экранов `Operations` и `Wizard` зафиксированы отдельно в:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user