Pivôs e enriquecimento
Um pivot é um enriquecimento executável: "mostre-me o que mais a fonte tem sobre isso". Você declara, a biblioteca o executa, prepara os resultados para triagem e os compromete no gráfico com a proveniência de cada elemento assim que o analista assim o indicar.
O problema que isso resolve é que o nó interessante geralmente tem muitas conexões. Um único indicador pode estar relacionado a milhares de outros. Portanto, um pivô consiste em duas ligações em vez de uma: uma ligação barata que diz o que está disponível e uma ligação cara que só funciona em um número com o qual o analista concordou.
Vocabulário, usado consistentemente em todos os lugares abaixo
- pivot: o enriquecimento executável
- provider: as duas funções que você fornece
- candidates: resultados obtidos, ainda não no gráfico
- ingest: comprometendo os candidatos escolhidos no gráfico
- rejeitar: uma dispensa explícita, lembrada para a sessão
- fonte: a etiqueta de procedência escrita em tudo que um pivô pousa
- save: escrevendo um resultado ingerido de volta out, no sistema de onde veio
- não salvo: na tela, ainda não escrito — uma contagem exata, não uma estimativa
summarize → fetch → triage → ingest → save
"2,143" the 210 the 12 on canvas in the source systemApenas fetch é necessário. Todo o resto nessa linha é uma etapa pela qual você opta.
Veja funcionando: Pivotar e enriquecer para o fluxo de estreitamento e Rejeitar e tentar novamente para estados de triagem e falha.
Usar um é o que um analista faz, em ordem, e quanto custa cada etapa para um back-end. Tudo, desde Registrar um em diante é a referência para quem conecta um, e O que sua fonte tem a oferecer é o mesmo contrato lido do lado do serviço.
Usando um
1 · Entre no modo Pivot. Pressione P ou clique no slot Pivot no painel esquerdo. O slot só existe enquanto pelo menos um pivô estiver registrado. Nada foi solicitado a nenhum back-end ainda, e a seleção de nós enquanto o modo está fechado também não solicita nada. A seleção de cinquenta nós em caixa para arrastá-los não deve disparar consultas de contagem de cinquenta.
2 · Escolha uma origem. A origem é o conjunto de nós sobre o qual a pergunta se refere. Escolher origem é a ferramenta de descanso, então um simples clique a define. Origem do laço gira em torno de vários. Tudo o que já estava selecionado quando você entrou no modo se torna a origem e Clear a esvazia. Uma origem vazia ainda tem usos: os pivôs declarados origin: 'none' são aqueles que se aplicam quando nada é escolhido.
3 · Leia o que se aplica. Entrar no modo é a intenção que executa summarize, uma vez por pivô aplicável, agrupado em uma única chamada para uma origem de vários nós. Cada entrada mostra o que seu provedor afirma que existe, escrito como ~2,143. O til marca um número que o provedor afirmou, em vez de um que a biblioteca contou. Um pivô sem summarize mostra um Run simples. Se nada se aplicar, o painel diz isso em vez de mostrar uma lista vazia.
4 · Estreite até que o portão se abra. Um pivô que declara maxCandidates mantém Fetch fora de alcance enquanto a contagem o excede e indica o número, o limite e o caminho a seguir. Marcar um controle de estreitamento pergunta novamente summarize com essa escolha, então 2.143 se torna 210 e Fetch é ativado sozinho. Limpar estreitamento recomeça. Ao lado dele, Fetch & ingest coloca todos os resultados no gráfico sem revisão. É oferecido apenas onde o pivô deixou autoIngest não definido.
5 · Triagem do que voltou. Os resultados não tocam no gráfico. Eles abrem a guia Review no dock inferior, um painel por provedor que você executou, listado ao lado. Clique em qualquer lugar em uma linha para marcá-la, clique com a tecla Shift pressionada para transportar a marca através de um intervalo e Selecionar todas as n correspondências respeita o filtro atual; Pesquisar linhas… filtros (regex opcional) e classificação das colunas. Rejeitar selecionados e Rejeitar todos os restantes descartam as linhas explicitamente e são lembradas para a sessão, enquanto o × apenas fecha um painel e não rejeita nada. Re-run pergunta novamente com o mesmo estreitamento. No final da faixa, Close failed elimina todas as buscas que não trouxeram nada de volta e Close all esvazia a fila; também não rejeita nada. Os candidatos não são o gráfico tem o resto.
6 · Ingerir e desfazer se estiver errado. Ingerir selecionado (12) confirma exatamente aqueles doze, colocados ao redor do nó de onde você girou e marcados com o pivô como sua origem. Ingerir todos os 213 ao lado dele ignora a marcação e confirma tudo o que o provedor ainda está oferecendo, seja qual for a tabela filtrada. O brinde diz Ingested 12 nodes, 14 edges. Para recuperá-lo está o controle de desfazer na barra superior, que fica lá permanentemente, e não enquanto durar o brinde, e inverte toda a execução - suas bordas e quaisquer filhos nos quais ele foi mesclado estão incluídos.
7 · Salve-o de volta, se o pivô puder. Ingest coloca os doze na tela; não os escreve em lugar nenhum. A parte inferior do painel Pivot indica quantos elementos esta sessão extraiu e não escreveu de volta, e Salvar os grava. Um pivô que declara não save nunca contribui para esse número — consulte Salvando os resultados do pivô.
Uma ingestão que não deixa nada a critério fecha o painel e a faixa passa para o próximo provedor em espera. As sobras o mantêm aberto, assim como uma repetição esperando nele. Assim que o último painel vai, um encaixe que a revisão abriu dobra de volta para onde estava, então um pivô deixa o layout que encontrou.
O pivô de um clique
As etapas 2 a 4 são para uma questão que vale a pena ler primeiro. A maioria não é. Clique com o botão direito em um nó e Pivot ▸ lista cada pivô que se aplica a ele com o que cada um está anunciando. Escolher uma linha é executado ali mesmo: sem painel, nada para preencher. A origem é a seleção quando o nó clicado pertence a ela e esse nó sozinho, caso contrário, um pivô em massa pode ser acessado sem sair do menu.
Abrir o submenu também é o que pede. Cada pivô com summarize é colocada a pergunta do próprio painel, esta origem sem nada estreitado, e sua linha mantém um espaço reservado até que a contagem chegue: ~2,143, consultivo como todo número que um provedor anuncia. Um pivô que não anuncia nada não mostra nada e funciona da mesma forma. As perguntas desaparecem apenas quando o submenu é mantido pressionado por um momento, então um ponteiro cruzando Pivot ▸ em seu caminho para outro lugar não pergunta nada, e elas são canceladas se fechar primeiro. As respostas são armazenadas em cache nos dois sentidos: uma segunda olhada mostra de uma só vez e o painel reutiliza o que o menu aprendeu. Um registro mais longo que o menu pode conter rolagens, corte no meio da linha para que pareça continuar.
O destino dos resultados depende de quantos deles são novos na tela:
- Até
pivotQuickIngestLimitnovos candidatos (50 por padrão) chegam diretamente, marcados e desfazíveis como qualquer outra ingestão. - Mais do que isso abra a aba Review, exatamente como faria uma execução de painel.
- Uma recusa de limite acontece antes de qualquer coisa ser buscada, então não há nada para revisar. O painel Pivot abre nesse pivô com o número e o estreitamento que o levanta.
Uma linha corre sem nada estreitado. Quando um pivô tem várias execuções, o analista escolhe entre, como enriquecer um objeto por meio de um de seus atributos, declara menuChoices: a linha então abre um submenu com uma linha por escolha, cada uma executando o pivô com seu próprio estreitamento, e uma última linha abrindo o painel nesse pivô. É síncrono e é executado sempre que o menu é aberto, como appliesTo. A recusa do limite de uma escolha abre o painel sobre o estreitamento da escolha. Retorne [] para uma linha simples.
menuChoices: ([object]) => attributesOf(object).map((a) => ({
label: `${a.type}: ${a.value}`,
narrowing: { attribute: [a.id] },
})),Os candidatos que já estão na tela não são novos e nunca preenchem o limite, portanto, executar novamente um pivô sobre o terreno que você percorreu continua sendo um gesto de um clique. Uma aresta entre dois nós já na tela conta, porque é uma linha sobre a qual você teria sido questionado. Um pivô que declarou autoIngest já respondeu à pergunta e sua resposta é válida: true sempre pousa, false sempre revisa. O limite decide apenas para os pivôs que o deixaram sem definição e apenas para este gesto; um painel Fetch sempre está em estágios.
Até que a resposta seja conhecida, a execução não ofereceu nada; portanto, não é necessária nenhuma tela para dizer que está funcionando. Uma guia Review junta-se à leitura da tira do dock Review… enquanto a busca está fora, sem desdobrar o dock ou trazer um painel para frente; clique nele para assistir à corrida ou cancelá-la. Somente um resultado que precisa ser decidido aparece, e uma corrida que dá certo leva consigo sua conta. As duas respostas sem nada para decidir são do notificador: uma failed fetch diz isso com uma nova tentativa, e um resultado acima do candidateCeiling absoluto diz o número e o limite.
Três maneiras de entrar, então: o modo ferroviário, o submenu Pivot ▸ do menu de contexto de um nó (ausente, nunca desabilitado, quando nada se aplica) e, com pivotRimBadgeVisible: 'always', um emblema de aro, que abre o modo com escopo para seu próprio pivô, ou, onde o aro carrega um emblema para todos eles, para o nó. Um emblema para um nó com exatamente um pivô que não tem nada para estreitar o executa diretamente, como uma linha de menu.
Registrando um
Três portas, um registro, na ordem em que você as alcançaria:
// 1. Options, at construction. Lands before the UI is built, so the rail button is
// already right on the first paint.
const graph = new Pivotick(el, data, { pivots: [correlations] })
// 2. Imperatively, at any point. Returns a disposer.
const remove = graph.pivots.register(correlations)
// 3. From a plugin's install, beside addPanel / addDockTab / addRailMode.
ctx.addPivot(correlations)graph.pivots é o registro e o tempo de execução. for(), run(), undo(), invalidate() e o modelo candidato estão todos pendurados.
Um fornecedor completo
Tudo o que um pivô funcional precisa, sem deixar nada de fora. O restante desta página é a referência de cada peça; esta é a coisa a ser copiada primeiro e cortada.
post abaixo está o seu próprio ajudante JSON - ele lança um não-2xx e passa signal, então o pivô é lido como o contrato e não como HTTP.
const correlations = {
id: 'correlations', // the provenance tag written on everything it lands
label: 'Correlations', // shown verbatim, so translate it yourself
maxCandidates: 2000, // refuse to fetch while the count is above this
// Which nodes this offers itself for — return the ones you accept, and a mixed
// selection runs it against just those. Omit it and it applies to everything.
appliesTo: nodes => nodes.filter(node => node.getData()?.type !== 'case'),
// The cheap call: what is out there, and what you could narrow by.
summarize: async (nodes, narrowing, { signal }) => {
const { total, byType } = await post('/api/correlations/count', {
ids: nodes.map(node => node.id),
types: narrowing.type ?? [], // pass the narrowing on so the count tracks it
}, signal)
return {
total, // judged against maxCandidates
facets: [{
key: 'type', // the key you read back out of `narrowing`
label: 'Type',
type: 'multiselect',
options: byType.map(row => ({ label: row.label, value: row.value, count: row.count })),
}],
}
},
// The expensive call: a flat fragment, run only on a number the analyst agreed to.
fetch: async (nodes, narrowing, { signal }) => {
const { items } = await post('/api/correlations', {
ids: nodes.map(node => node.id),
types: narrowing.type ?? [],
}, signal)
return {
nodes: items.map(item => ({
id: item.uuid, // an id already on canvas is skipped, never overwritten
data: { label: item.value, type: item.type, seen: item.first_seen },
})),
edges: items.map(item => ({
from: item.origin_uuid, // both endpoints must land for the edge to land
to: item.uuid,
data: { type: 'correlation' },
})),
}
},
// The write half, and the only optional one here. Omit it and this pivot's results
// are *not savable* — never counted unsaved, and no Save offered. See below.
async save({ origin, nodes, edges }, ctx) {
const written = await post('/api/correlations/save', {
event: origin[0]?.id,
nodes: nodes.map((node) => node.getData()),
}, ctx.signal)
return {
savedNodeIds: written.ok,
savedEdgeIds: edges.map((edge) => edge.id),
message: written.refused ? `${written.refused} refused by the server` : undefined,
}
},
}
new Pivotick(document.querySelector('#graph'), data, { pivots: [correlations] })Quatro coisas que são fáceis de errar, todas visíveis acima:
narrowingé sua honra. Ele chega como{ [facetKey]: value }, usando as chaves que suas próprias facetas declararam. Ignorá-lo é legal, mas a contagem nunca se move e um pivô sobre o limite nunca pode ser desbloqueado.signalnão é cortesia opcional. Sair do modo Pivot ou alterar a origem aborta as perguntas em andamento. Passe-o para todas as solicitações ou você pagará por respostas que ninguém espera.ctx.isStale()é a mesma verificação para transportes que não recebem sinal.- Throw para uma falha, retorne vazio para um vazio. Um erro lançado surge na entrada pivô com um Retry.
{ total: 0 }é um "nada lá fora" honesto, e os dois são lidos de maneira completamente diferente para um analista. - Diga quando você não sabe. Uma fonte questionada ao vivo pode não contar antes de responder. Retorno
{ total: null, facets }: nenhuma contagem é sorteada,maxCandidatesnão recusa ecandidateCeilingainda limita o quefetchretorna.0alegaria que não há nada. - Ids são a chave de desduplicação. Retorna o ID estável do sistema de origem, não um por resposta, ou cada nova execução direciona os mesmos nós novamente como novos candidatos.
O contrato do fornecedor
interface PivotDefinition {
id: string // provenance tag, menu key, triage key
label: string // used verbatim, so it can be translated
icon?: string // trusted SVG, injected as-is
origin?: 'selection' | 'none'
appliesTo?: (nodes: Node[]) => boolean | Node[]
summarize?: (nodes: Node[], narrowing: PivotNarrowing, ctx: PivotContext) => PivotSummary | Promise<…>
fetch: (nodes: Node[], narrowing: PivotNarrowing, ctx: PivotContext) => PivotResult | Promise<…>
autoIngest?: boolean
maxCandidates?: number
save?: (payload: PivotSavePayload, ctx: PivotSaveContext) => PivotSaveOutcome | Promise<…>
autoSave?: boolean
}Ambas as chamadas recebem um array de nós, portanto, um pivô sobre cinquenta deles é uma solicitação de back-end em vez de cinquenta. Ambos adotam o estreitamento atual. Ambos recebem um PivotContext carregando signal e isStale(), o mesmo padrão de cancelamento que todo gancho assíncrono na biblioteca usa.
appliesTo: toda a origem, ou parte dela
Retorne um booleano para uma regra sobre a origem como um todo, e os nós que você aceita para uma regra que lê um nó por vez. Uma matriz vazia significa a mesma coisa que false.
appliesTo: nodes => nodes.length >= 2 // whole origin
appliesTo: nodes => nodes.filter(n => n.getData()?.type === 'domain') // per nodeO formulário por nó é importante assim que uma seleção é misturada: um domínio e um IP escolhidos juntos mantêm seus próprios provedores, em vez de deixar apenas aqueles que aceitam ambos, e o painel diz isso na entrada - Aplica-se a 3 dos 5 escolhidos.
O que quer que ele mantenha é a origem com a qual o provedor é chamado: summarize e fetch nunca veem um nó que foi desativado, e o resumo em cache é codificado por essa origem restrita. Ele é relido a cada mudança de origem, portanto, mantenha-o síncrono e barato.
graph.pivots.for(nodes) // everything that applies, partially included
graph.pivots.originFor('whois', nodes) // the share this one would run onChamar run com uma origem que um pivô rejeita inteiramente resolve 'failed' em vez de buscar uma origem vazia - uma execução que parecesse boa e não respondesse nada seria pior.
summarize: o mais barato
Exibido na íntegra em Um provedor completo. Seu total é o número pelo qual o limite é julgado, e seu facets se torna os controles de restrição: você declara a forma, a biblioteca desenha os widgets. Passe o estreitamento para o seu back-end para que a contagem rastreie o que o analista escolheu, caso contrário, o portão nunca poderá ser levantado.
Uma faceta pode declarar default: os valores de opção a multiselect começam com marcado, ou o valor a select ou boolean começa em. O painel o desenha e Limpar estreitamento retorna para ele, mas a chave permanece fora do estreitamento enquanto é mantida, portanto, seu provedor deve ler uma chave ausente como padrão. Um multiselect vazio com padrão é enviado como [], o que significa none.
{ key: 'element', label: 'Element', type: 'multiselect', default: ['attribute', 'object'],
options: [/* attribute, object, tag, … */] }Omita summarize totalmente e o pivô não oferece contagem nem estreitamento, apenas um botão Run. Isso é legal, e então fetch deve ser seguro chamar cego, porque nada pode bloqueá-lo.
As contagens são consultivas. Sempre.
summarize retorna uma reivindicação, não um contrato, e difere legitimamente do que é ingerido: primeiro a desduplicação, depois as permissões, a desatualização e os provedores que se aproximam deliberadamente. A UI marca a diferença com um til — ~2,143 correlations é o número do provedor, enquanto 210 fetched e 12 selected são os da própria biblioteca. Uma redução nunca é um erro e uma contagem nunca suporta o layout.
fetch: o verdadeiro
Retorna um fragmento simples: { nodes: RawNode[], edges: RawEdge[] }. Nenhum nó retornado nomeia um pai existente na tela. O aninhamento viaja em RawNode.children, então um contêiner chega como um novo nó carregando sua própria subárvore. Um resultado edges-only é válido: arestas cujas extremidades já estão todas na tela tornam-se suas próprias linhas de triagem.
Tipos de facetas
O estreitamento usa todos os tipos de facetas que o mecanismo de consulta conhece, exceto regex, que nenhum backend pode ser solicitado a avaliar: text, select, multiselect, numberRange, boolean. Regex reside nos próprios filtros do lado do cliente do painel de triagem, onde as linhas já estão em mãos.
Como o painel os desenha e de onde vem a linha de detalhamento
Um multiselect é desenhado como uma lista de caixas de seleção com cada opção e seu count na tela, para que os números pelos quais você está restringindo permaneçam visíveis enquanto você restringe. Uma escolha única select permanece em um menu suspenso e carrega suas contagens nos rótulos de opções.
Abaixo do total, o painel imprime do que é feito, retirado da primeira faceta multiselect: essas opções particionam o resultado. Depois que essa faceta tiver uma escolha, enviada ou por padrão, apenas as opções escolhidas serão listadas. As contagens de uma faceta de escolha única são alternativas e não partes, portanto não são somadas ali.
Gating: a tampa e o teto
maxCandidates: 2000 // per pivot: refuse to fetch while the count exceeds it
new Pivotick(el, data, { pivotCandidateCeiling: 10_000 }) // absolute, all pivotsAmbos recusam e nem truncam nem mostram. Uma recusa sempre traz três coisas em ordem: o número, o limite e o caminho a seguir. “Muitos resultados” sem número não é algo em que um analista possa agir.
O limite é julgado pela contagem mais recente do estreitamento atual, que é o que o permite levantar. Reduza de 2.143 para 210 e Fetch é ativado sozinho. O teto diz respeito à memória para objetos candidatos, e não à mesa, que é virtualizada.
Os candidatos não são o gráfico
Nada que um pivô retorna está no gráfico, na tabela de dados ou em qualquer contagem de facetas até ser ingerido. Os resultados preparados para triagem abrem a guia Review do dock - um painel por pivô, listado como guias verticais na lateral, cada uma com sua própria contagem e sua própria saída - com as próprias colunas do provedor, os filtros da tabela de dados, classificação, paginação e uma ação de ingestão.
- A rejeição é explícita e lembrada para a sessão, digitada por pivô. A próxima corrida não oferece aquele candidato novamente, e a linha do cabeçalho indica quantos foram suprimidos. Uma linha rejeitada é riscada no lugar em vez de movida e permanece reversível.
- O que um pivô está retendo está listado em sua entrada no painel Pivot, nomeado como a tabela o nomeou, com restore em cada linha e Restore all abaixo delas. Ele permanece lá quando o painel é fechado, portanto, uma rejeição pode ser reconsiderada sem executar o pivô novamente para localizá-la.
- Nós e arestas são blocos separados, cada um nomeado e contado quando uma execução retorna ambos. Um resultado somente de bordas é uma tabela própria, não um painel vazio.
- Uma linha de contêiner é aberta. Ela carrega uma contagem Children do que contém diretamente, e um cursor lista o conteúdo, com os atributos compartilhados de cada filho e uma nota onde um filho é ele próprio um contêiner. Abrir uma linha não significa marcá-la e nada dentro dela pode ser coletado sozinho – a ingestão da linha ocupa todo o contêiner.
- Fechar um provedor não rejeita nada. As sobras não triadas voltam na próxima execução. Rejeitar todos os restantes é o único gesto que os elimina.
- Uma nova execução substitui o painel desse provedor. Se você tiver linhas marcadas, isso é dito e oferece Mostrar novo / Manter triagem, em vez de descartar sua triagem sem perguntar.
- Ingerir em um grupo coloca as linhas selecionadas dobradas, um grupo por tipo, fora da origem. Consulte Aterrissagens de pivô. É oferecido a menos que
UI.simplifyesteja desligado. CompivotIngestGrouped: true, cada pouso é agrupado e o botão diz Ingest loose.
Tudo o que o painel faz também pode ser acessado programaticamente:
graph.pivots.candidates('correlations') // the staged set
graph.pivots.mark('correlations', id) // …markAll / reject / rejectRemaining
await graph.pivots.ingest('correlations') // commit the marked ones
await graph.pivots.ingest('correlations', 'triage', { group: true }) // …folded, one group per type
graph.pivots.discard('correlations') // close it; rejects nothing
graph.pivots.rejectedRows('correlations') // what the session is holding back
graph.pivots.unreject('correlations', id) // …unrejectAll for every one of themautoIngest
Para um resultado pequeno e confiável que ninguém deseja analisar, como expandir um contêiner em seus doze filhos, declare autoIngest: true. Os resultados chegam diretamente e o desfazer da barra superior os leva de volta como qualquer outra ingestão. O provedor decide isso, porque só ele sabe se seus próprios resultados são pequenos e confiáveis o suficiente para pular a triagem.
Deixá-lo sem definição não é o mesmo que false. Diz que o pivô não tem visualização, o que deixa um pivô de um clique livre para decidir apenas o tamanho; false diz que os resultados sempre precisam de atenção e está honrado com a forma como a corrida foi iniciada.
autoIngest: true // always lands
autoIngest: false // always reviewed
// unset: a one-click run decides on size
new Pivotick(el, data, { pivotQuickIngestLimit: 50 }) // that size, all pivotsQuaisquer terras, daqui ou da Revisão, podem chegar dobradas em grupos de desembarque em vez de soltas:
new Pivotick(el, data, { pivotIngestGrouped: true }) // every landing, one group per typeIngerir e o que é permitido fazer
Um gesto, um lote, um dataBatchChanged, nesta ordem:
- Dedup — um id que já está na tela é ignorado, nunca substituído, quer estivesse lá quando os candidatos foram apresentados ou outra corrida o pousou enquanto esperavam.
- União de crianças por id.
- Posicionamento próximo ao nó de origem, instável. O próprio
x/ydo provedor vence e uma corrida sem origem é gerada no centro da janela de visualização. - Proveniência, então o lote chega.
A ingestão é puramente aditiva. As únicas coisas que podem ser removidas são removeBySource e executar desfazer.
Um portão, chamado uma vez com todo o conjunto:
callbacks: {
onBeforeIngest: async ({ pivotId, origin, candidates, trigger, confirm }) => {
if (candidates.nodes.length < 50) return true
return await confirm({ title: `Add ${candidates.nodes.length} nodes?` })
},
}Retorne false para vetar ou { accept: true, nodes, edges } para obter um subconjunto. Uma chave omitida significa "conforme solicitado", exatamente como onBeforeDelete lê.
Proveniência
Cada nó e borda carrega o set de fontes que atestam isso. Um conjunto em vez de um escalar, porque dois pivôs sobrepostos em um nó são o caso normal. Com um escalar, o segundo a chegar apaga a reivindicação do primeiro e a remoção de qualquer um deles leva o nó consigo.
node.getSources() // ['seed', 'correlations'], where 'seed' is the loaded data
node.hasSource('correlations')
edge.getSources() // edges carry it too, which is the load-bearing half
graph.removeBySource('correlations') // drop the tag; delete only when the set emptiesDando uma volta correndo
Uma ingestão é uma entrada em graph.history, juntamente com outras coisas que alteram o que a tela contém: exclusões, ocultações duráveis e elementos desenhados à mão.
graph.history.entries() // newest first; a pivot entry's `id` is its runId
graph.history.undo() // the newest entry
graph.history.undo(runId) // that ingest, and contiguously anything done since
graph.history.redo() // re-lands the recorded delta; no refetch, no re-gatingDesfazer é contíguo: mirar em uma entrada mais antiga também reverte todas as entradas acima dela, como um lote. Reverter uma execução antiga por si só é graph.removeBySource(pivotId), que descarta a garantia daquela fonte e exclui apenas o que nada mais garante - uma operação direta, registrada como uma.
Uma ingestão parcial de um conjunto preparado recebe seu próprio runId, portanto, cada lote é uma entrada separada. E desfazer uma ingestão que ainda é a entrada mais recente coloca seus candidatos de volta no painel Revisão em vez de solicitá-los novamente ao provedor. Veja Desfazer e histórico para ver tudo.
Salvando resultados
O Ingest coloca os candidatos na tela; não os escreve em lugar nenhum. Um pivô que declara save pode enviar seus próprios resultados de volta ao sistema de onde vieram, e a biblioteca mantém o registro: quais elementos uma execução criou, se eles foram gravados, quais falharam e o que uma nova tentativa deve carregar.
Omita save e os resultados do pivô serão não salváveis. Eles nunca entram no razão, nunca são contados como não salvos e nenhum Salvar aparece para eles. Essa é a declaração correta para um pivô sobre dados derivados - a saída de um mecanismo de correlação não é sua para escrever de volta - e é o que impede um "210 não salvo" permanente sem solução.
Tudo isso, incluindo gravações parciais, novas tentativas, os ids que um sistema de origem emite e o que o desfazer alcançará ou não: Salvando os resultados do pivô.
O modo trilho pivô
Usando um caminha pela superfície. O que importa quando você está configurando em vez de conduzi-lo: entrar no modo é a intenção que chama um provedor - assim como manter Pivot ▸ aberto no menu de contexto - deixá-lo interrompe todas as perguntas em andamento e a seleção por si só não custa nada. A seleção também é o gesto para arrastar, estilizar e editar em massa, para que a biblioteca nunca ligue para um provedor especulativamente.
O modo é ** limitado no registro**:
UI: { pivotMode: 'auto' } // default: the button exists only while a pivot is registered
UI: { pivotMode: true } // force it, for pivots that arrive asynchronously
UI: { pivotMode: false } // neverUm consumidor que não registra nenhum pivô, portanto, não vê nenhum vestígio do recurso. Nunca é montado no modo viewer ou static.
Pivôs sem origem
Alguns enriquecimentos não possuem nenhum nó para execução, como pesquisar, importar ou colar uma lista de indicadores. Declare origin: 'none' e o pivô recebe [], aplica-se quando a origem está vazia e se dobra em um grupo Sem origem assim que um é escolhido. Os resultados passam pelo mesmo painel de triagem, pelo mesmo portão e pelo mesmo modelo de proveniência, e chegam ao centro da janela de visualização.
appliesTo não tem sentido para um deles e não é consultado.
Potencial declarado
Uma dica na borda do nó, dizendo "há 2.100 correlações aqui", colocada por quem carregou os dados.
node.setPotential('correlations', 2100) // 0 clears it
node.getPotential('correlations')
node.setPotential(2199) // the total across every pivot
node.getPotential()É declarado, nunca consultado. A biblioteca não chamará um fornecedor para sortear um crachá, pois uma contagem que se materializasse em um nó no momento em que fosse questionada deixaria o canvas contando duas histórias diferentes. Clicar no emblema abre o modo Pivot com escopo para esse pivô.
Como qualquer outro setter em Node, ele marca o nó como sujo: chame graph.renderer.update() se nada mais estiver para ser renderizado.
O que o aro desenha
Por padrão, os emblemas do aro são exibidos apenas enquanto o modo Pivot está aberto, onde ajudam a escolher uma origem; clicar em um deles direciona o painel para esse nó e gira. pivotRimBadgeVisible muda quando mostram:
new Pivotick(el, data, { pivots, pivotRimBadgeVisible: 'always' })
graph.pivots.rimBadgeVisible = 'pivot-mode' // or later; it dirties every node'pivot-mode' (padrão) | Somente enquanto o modo Pivot rail estiver aberto. |
'always' | Qualquer que seja o modo, um emblema também é uma forma de entrar no modo Pivot. |
'never' | De jeito nenhum. |
Uma vez mostrado, seu formato é a próxima escolha.
Um nó tem quatro cantos de aro e apenas dois quando tem filhos - a capacidade de expansão reserva o lado Leste. Portanto, um crachá por provedor é legível com poucos e impossível com cem. pivotRimBadge escolhe a forma:
new Pivotick(el, data, { pivots, pivotRimBadge: 'summary' })
graph.pivots.rimBadge = 'per-pivot' // or later; it dirties every node'per-pivot' (padrão) | Um emblema por pivô que declarou um potencial, cada um abrindo esse pivô. Vários deles entram em colapso em +n no início de um nó com filhos. |
'summary' | Um crachá, qualquer que seja a contagem do provedor. Mostra o total declarado sem ID de pivô ou - sem nada declarado - quantos pivôs se aplicam. |
'off' | Nada. NodeStyle.badges ainda empata. |
O que um emblema summary faz quando clicado e de onde vem sua contagem
Em summary o emblema é um gesto: ele abre o painel Pivot naquele nó, porque em qualquer contagem real de provedor o painel é onde residem as contagens e a escolha. A exceção é um nó onde exatamente um pivô se aplica * e * ele declara não summarize — nada para ler primeiro e nada para restringir — onde o clique o executa. O resultado é a chamada do próprio provedor: autoIngest decide entre a tela e o painel de triagem, como em qualquer outro lugar.
A contagem de aplicação de pivôs é derivada, não declarada, portanto não custa nenhuma ligação ao provedor. Ele é mantido entre as alterações do gráfico e atualizado quando o registro ou os nós do gráfico são movidos; um appliesTo lendo outra coisa pode ficar atrasado, e o painel é sempre a resposta exata.
Cache
A biblioteca armazena em cache uma coisa: summarize resultados, codificados por (pivô, origem, estreitamento), porque um resumo de seleção múltipla é um agregado que nada pode decompor. Todo o resto é seu:
graph.pivots.invalidate() // all of it
graph.pivots.invalidate('correlations') // one pivot's
graph.pivots.invalidate('correlations', nodes) // only what was asked about theseAutenticação, nova tentativa, limitação de taxa e qualquer cache próprio pertencem às funções do seu provedor, que são funções simples que você escreveu.
O que sua fonte tem a oferecer
As páginas acima são para quem escreve ao provedor. Este é o mesmo contrato lido do outro lado, para quem possui o serviço que chama.
- Uma chamada de busca, retornando os objetos e suas relações. O único obrigatório. Duas coisas são importantes: são necessários muitos IDs em uma solicitação, portanto, um pivô com mais de cinquenta nós custa uma chamada e os ids que ele retorna são estáveis em execuções, porque é nisso que as chaves de desduplicação são ativadas. Uma identificação por resposta mostra os resultados de ontem novamente como novos candidatos.
- Uma chamada de contagem, pegando os mesmos ids e os mesmos filtros, retornando um total e as contagens por valor para tudo o que pode ser restringido. É o que faz o portão e o estreitamento funcionarem, e é solicitado novamente a cada mudança, então tem que ser barato. Aproximado é bom: contagens são consultivas. Sem um, um pivô ainda funciona, com um Run simples e nada para bloqueá-lo.
- Os mesmos filtros respeitados por ambos. Uma contagem que os ignora nunca se move, portanto, um pivô sobre seu limite nunca pode ser desbloqueado.
- Uma forma de cancelar. As perguntas são abandonadas quando o analista segue em frente, e o
signalque o provedor recebe só vale a pena repassar se o transporte agir sobre ele. - Uma chamada por escrito, se os resultados forem seus, escreva de volta. Ele deve retornar os ids atribuídos, para que
canonicalIdspossa impedir a execução de amanhã de duplicar o que foi salvo e poder relatar um sucesso parcial.
Ambas as chamadas de leitura devem diferenciar uma resposta vazia de uma falha: nenhum resultado é um fato sobre os dados, um erro é um fato sobre o serviço e eles são lidos de maneira completamente diferente para um analista.
Nada aqui precisa de paginação, cursor ou transporte de streaming. O estreitamento é o que os substitui.
O que isso não é
- Sem streaming e sem cursor de servidor. O estreitamento os substitui: em vez de buscar duas mil linhas, pergunte o que há e deixe o analista escolher. O contrato nunca exige um back-end de streaming, porque muitas fontes não possuem um.
- Sem re-parentalidade. Um nó retornado nunca nomeia um pai existente. A união de filhos por id é a única maneira pela qual a ingestão toca os filhos de um nó existente.
- Não é possível desfazer a edição de propriedades.
graph.historycobre o que a tela contém e mostra — o que entrou, o que saiu, o que está oculto. Os dados de um nó estão no estado de back-end que a biblioteca não criou, portanto, reverter um campo localmente é trabalho deonBeforeNodeEditCommit, não nosso. - Sem política de transporte.
saveescreve de volta, mas credenciais, novas tentativas, limitação de taxa e detecção de conflitos são todas de responsabilidade do consumidor. A única nova tentativa da biblioteca é o analista pressionando Retry. - Sem memória de rejeição durante uma recarga. Uma rejeição é um veredicto no contexto de uma sessão, e o mesmo candidato pode valer uma resposta diferente em outro gráfico. As rejeições são realizadas para a sessão e nada mais.
- Não há atualização do que já está aqui. Um candidato com id correspondente deixa o nó existente intacto; reconciliar um nó com uma cópia mais recente precisa de uma política de conflito que a biblioteca não possui.