Pular para o conteúdo

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 system

Apenas 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é pivotQuickIngestLimit novos 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.

js
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:

js
// 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.

js
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.
  • signal nã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, maxCandidates não recusa e candidateCeiling ainda limita o que fetch retorna. 0 alegaria 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 ​

ts
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.

ts
appliesTo: nodes => nodes.length >= 2                          // whole origin
appliesTo: nodes => nodes.filter(n => n.getData()?.type === 'domain') // per node

O 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.

js
graph.pivots.for(nodes)                 // everything that applies, partially included
graph.pivots.originFor('whois', nodes)  // the share this one would run on

Chamar 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.

js
{ 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 ​

js
maxCandidates: 2000              // per pivot: refuse to fetch while the count exceeds it
new Pivotick(el, data, { pivotCandidateCeiling: 10_000 })   // absolute, all pivots

Ambos 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.simplify esteja desligado. Com pivotIngestGrouped: true, cada pouso é agrupado e o botão diz Ingest loose.

Tudo o que o painel faz também pode ser acessado programaticamente:

js
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 them

autoIngest ​

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.

js
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 pivots

Quaisquer terras, daqui ou da Revisão, podem chegar dobradas em grupos de desembarque em vez de soltas:

js
new Pivotick(el, data, { pivotIngestGrouped: true })     // every landing, one group per type

Ingerir e o que é permitido fazer ​

Um gesto, um lote, um dataBatchChanged, nesta ordem:

  1. 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.
  2. União de crianças por id.
  3. Posicionamento próximo ao nó de origem, instável. O próprio x/y do provedor vence e uma corrida sem origem é gerada no centro da janela de visualização.
  4. 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:

js
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.

js
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 empties

Dando 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.

js
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-gating

Desfazer é 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**:

js
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 }    // never

Um 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.

js
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:

js
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:

js
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:

js
graph.pivots.invalidate()                       // all of it
graph.pivots.invalidate('correlations')         // one pivot's
graph.pivots.invalidate('correlations', nodes)  // only what was asked about these

Autenticaçã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 signal que 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 canonicalIds possa 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.history cobre 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 de onBeforeNodeEditCommit, não nosso.
  • Sem política de transporte. save escreve 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.