Ir al contenido

Pivotes y enriquecimiento ​

Un pivot es un enriquecimiento ejecutable: "muéstrame qué más tiene la fuente sobre esto". tu lo declara, la biblioteca lo ejecuta, organiza los resultados para la clasificación y los envía al gráfico con procedencia de cada elemento una vez que el analista lo diga.

El problema que resuelve es que el nodo interesante suele tener demasiadas conexiones. un Un solo indicador puede relacionarse con miles de otros. Entonces un pivote son dos llamadas en lugar de uno: una llamada barata que dice lo que hay ahí fuera, y una llamada cara que sólo se ejecuta en un número que el analista aceptó.

Vocabulario, usado consistentemente en todas partes a continuación.

  • pivot: el enriquecimiento ejecutable
  • proveedor: las dos funciones que proporcionas
  • candidatos: resultados obtenidos, aún no en el gráfico
  • ingest: comprometer a los candidatos elegidos en el gráfico
  • rechazar: un despido explícito, recordado para la sesión
  • fuente: la etiqueta de procedencia escrita en todo lo que aterriza un pivote
  • guardar: escribir un resultado ingerido de nuevo out, en el sistema del que procede
  • unsaved: en el lienzo, aún no escrito: un recuento exacto, no una estimación
summarize  →  fetch  →  triage  →  ingest  →  save
  "2,143"     the 210    the 12    on canvas   in the source system

Solo se requiere fetch. Todo lo demás en esa línea es un paso al que usted opta.

Véalo funcionando: Pivotar y enriquecer para el estrechar el flujo y Rechazar y reintentar para clasificación y estados de falla.

Usar uno es lo que hace un analista, en orden, y lo que cuesta cada paso back-end. Todo desde Registrar uno en adelante es la referencia para quien conecte uno, y Lo que su fuente tiene para ofrecer es el mismo contrato leído desde el lado del servicio.

Usando uno ​

1 · Ingrese al modo Pivote. Presione P o haga clic en la ranura Pivot en el riel izquierdo. La ranura sólo existe mientras al menos un pivote está registrado. No se ha pedido nada a ningún backend todavía, y seleccionar nodos mientras el modo está cerrado tampoco pregunta nada. Selección de cuadro cincuenta nodos para arrastrarlos no deberían disparar cincuenta consultas de recuento.

2 · Elige un origen. El origen es el conjunto de nodos sobre el que trata la pregunta. Elegir origen es la herramienta de descanso, por lo que un simple clic la configura. Origen del lazo dibuja alrededor de varios. lo que sea ya estaba seleccionado cuando ingresó al modo se convierte en el origen y Clear lo vacía. Un origen vacío todavía tiene usos: los pivotes declarados origin: 'none' son los que aplican cuando no se recoge nada.

3 · Lea lo que corresponde. Al ingresar al modo, el intent se ejecuta summarize, una vez por pivote aplicable, agrupado en una sola llamada para un origen de múltiples nodos. Cada entrada muestra lo que su proveedor afirma que está disponible, escrito como ~2,143. La tilde marca un número del proveedor. afirmó en lugar de uno que la biblioteca contó. Un pivote sin summarize muestra un desnudo Ejecutar en su lugar. Si no se aplica nada, el panel lo dice en lugar de mostrar una lista vacía.

4 · Estrecho hasta que se abra la puerta. Un pivote que declara maxCandidates mantiene Fetch fuera de su alcance mientras la cuenta lo supere, y establece el número, el límite y la forma adelante. Al marcar un control de reducción se vuelve a preguntar summarize con esa opción, por lo que 2,143 se convierte en 210 y Fetch se enciende solo. El estrechamiento claro comienza de nuevo. Al lado, Buscar y ingest coloca cada resultado en el gráfico sin revisión. Se ofrece sólo donde el pivote dejó autoIngest sin configurar.

**5 · Clasifique lo que regresó. ** Los resultados no tocan el gráfico. Abren la pestaña Revisar en el panel inferior, un panel por proveedor que haya ejecutado, enumerado en su costado. Haga clic en cualquier lugar en una fila para marcarla, presione Mayús y haga clic para llevar la marca a través de un rango y Seleccionar todas las n coincidencias respeta el filtro actual; Buscar filas… filtros (expresión regular opcional) y ordenación de columnas. Rechazar seleccionados y Rechazar todos los restantes eliminan las filas explícitamente y se recuerdan para la sesión, mientras que × simplemente cierra un panel y no rechaza nada. Volver a ejecutar pregunta de nuevo con el mismo estrechamiento. Al pie de la franja, Cerrar fallido deja caer cada recuperación que no devolvió nada y Cerrar todo vacía la cola; Tampoco rechaza nada. Los candidatos no son el gráfico tiene el resto.

6 · Ingerir y deshacer si fue incorrecto. Ingerir seleccionado (12) confirma exactamente esos doce, colocados alrededor del nodo desde el que pivotaste y etiquetados con el pivote como fuente. Ingesta todo el 213 al lado omite la marca y confirma todo lo que el proveedor todavía tiene oferta, cualquiera que sea el filtro al que se filtre la tabla. El brindis dice Ingested 12 nodes, 14 edges. Recuperarlo es el control de deshacer en la barra superior, que está ahí permanentemente en lugar de por mientras dure un brindis, e invierte todo el recorrido: sus bordes y los elementos secundarios que fusionó en incluido.

**7 · Guárdalo, si el pivote puede. ** Ingest pone los doce en la lona; no lo hace escríbalos en cualquier lugar. El pie del panel Pivot dice cuántos elementos tiene esta sesión. extraídos y no escritos, y Guardar los escribe. Un pivote que declara no save nunca contribuye a ese número; consulte Guardar resultados dinámicos.

Una ingesta que no deja nada que decidir cierra el panel y la tira pasa al siguiente. proveedor esperando. Las sobras lo mantienen abierto, al igual que una repetición que espera en él. Una vez el último Cuando el panel desaparece, el muelle que abrió la revisión se pliega de nuevo a donde estaba, por lo que un pivote abandona el diseño. encontró.

El pivote con un solo clic ​

Los pasos 2 a 4 son para una pregunta sobre la que vale la pena leer primero. La mayoría no lo es. Haga clic derecho en un nodo, y Pivote ▸ enumera cada pivote que se le aplica con lo que cada uno anuncia. Al seleccionar una fila, se ejecuta allí mismo: no hay panel, no hay nada que completar. El origen es el selección cuando el nodo en el que se hizo clic le pertenece y ese nodo solo en caso contrario, por lo que un pivote masivo es accesible sin salir del menú.

Abrir el submenú es también lo que pide. Cada pivote con un summarize es la pregunta del propio panel, este origen con nada se redujo y su fila mantiene un marcador de posición hasta que llega el conteo: ~2,143, aviso como cada número que anuncia un proveedor. Un pivote que no anuncia nada no muestra nada y corre igual Las preguntas salen sólo una vez que se ha mantenido presionado el submenú por un momento, por lo que un puntero que cruza Pivote ▸ en su camino a otra parte no pregunta nada y se cancelan si se cierra primero. Las respuestas se almacenan en caché en ambos sentidos: una segunda mirada se pinta de inmediato y el panel reutiliza lo que el menú aprendió. Un registro más largo de lo que el menú puede contener pergaminos, cortado a mitad de fila por lo que se lee como continua.

El destino de los resultados depende de cuántos de ellos sean nuevos en el lienzo:

  • Hasta pivotQuickIngestLimit nuevos candidatos (50 por defecto) aterrizan directamente, etiquetados y Deshacer como cualquier otra ingesta.
  • Más que eso abra la pestaña Revisar, exactamente como lo haría una ejecución de panel.
  • Se rechaza el límite antes de que se obtenga algo, por lo que no hay nada que revisar. el El panel de pivote se abre sobre ese pivote con el número y el estrechamiento que lo levanta.

Una fila corre sin nada estrechado. Cuando un pivote es en realidad varias ejecuciones, el analista elige entre, como enriquecer un objeto a través de uno de sus atributos, declara menuChoices: la fila luego abre un submenú con una fila por elección, cada una ejecutando el pivote con su propia estrechándose, y una última fila abriendo el panel sobre ese pivote. Es sincrónico y se ejecuta cada momento en que se abre el menú, como appliesTo. Un rechazo de límite de una elección abre el panel en el La elección se está reduciendo. Regrese [] para una fila simple.

js
menuChoices: ([object]) => attributesOf(object).map((a) => ({
    label: `${a.type}: ${a.value}`,
    narrowing: { attribute: [a.id] },
})),

Los candidatos que ya están en el lienzo no son nuevos y nunca llenan el límite, por lo que volver a ejecutar un pivote sobre El terreno que has cubierto sigue siendo un gesto de un solo clic. Un borde entre dos nodos que ya están en la pantalla. cuenta, porque es una fila sobre la que, de otro modo, le habrían preguntado. Un pivote que declarado autoIngest ya respondió la pregunta y su respuesta es válida: true siempre aterriza, false siempre revisa. El límite se decide sólo para los pivotes que lo abandonaron desarmado, y sólo por este gesto; un panel Fetch siempre presenta etapas.

Hasta que se conozca la respuesta, la ejecución no ha ofrecido nada, por lo que no se necesita nada de la pantalla para decir está funcionando. Una pestaña Review se une a la lectura de tira del muelle Review… mientras la recuperación está hacia fuera, sin desplegar el muelle ni adelantar ningún cristal; haz clic en él para ver la carrera o cancelarlo. Sólo se presenta un resultado que hay que decidir, y una carrera que aterriza tiene su efecto. tabulador con él. Las dos respuestas sin nada que decidir son las del notificador: una búsqueda fallida lo dice con un reintento y un resultado sobre el absoluto candidateCeiling dice el número y el límite.

Entonces, hay tres formas de entrar: el modo ferroviario, el menú contextual de un nodo Pivote ▸ submenú (ausente, nunca deshabilitado, cuando no aplica nada), y, con pivotRimBadgeVisible: 'always', una insignia de llanta, que abre el modo con alcance a su propio pivote, o, cuando la llanta lleva una insignia para todos ellos, al nodo. Una insignia para un nodo con exactamente un pivote que no tiene nada que estrechar lo ejecuta directamente, como una fila de menú.

Registrar uno ​

Tres puertas, un registro, en el orden en que las alcanzaría:

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 es tanto el registro como el tiempo de ejecución. for(), run(), undo(), invalidate() y el modelo candidato cuelgan de él.

Un proveedor completo ​

Todo lo que necesita un pivote funcional, sin dejar nada fuera. El resto de esta página es el referencia para cada pieza; esto es lo que hay que copiar primero y recortar.

post a continuación es su propio ayudante JSON: lanza un no-2xx y pasa signal, por lo que el pivote se lee como el contrato en lugar de 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] })

Cuatro cosas en las que es fácil equivocarse, todas ellas visibles arriba:

  • narrowing es tuyo para honrar. Llega como { [facetKey]: value }, usando las claves tus propias facetas declaradas. Ignorarlo es legal, pero entonces el conteo nunca se mueve y un pivote sobre la tapa nunca podrá desbloquearse.
  • signal no es cortesía opcional. Salir del modo Pivote o cambiar el origen cancela preguntas en vuelo. Páselo a cada solicitud o pagará por las respuestas que nadie espera para. ctx.isStale() es el mismo control para los transportes que no reciben señal.
  • Tirar en caso de fallo, devolver vacío en caso de vacío. Aparece un error arrojado en el pivote entrada con un Reintentar. { total: 0 } es un honesto "no hay nada ahí fuera", y los dos leen completamente diferente a un analista.
  • Diga cuando no lo sepa. Es posible que una fuente preguntada en vivo no cuente antes de responder. Devolución { total: null, facets }: no se saca ningún conteo, maxCandidates no lo rechaza, y candidateCeiling todavía limita lo que fetch devuelve. 0 diría que no hay nada.
  • Los identificadores son la clave de deduplicación. Devuelve el identificador estable del sistema fuente, no uno por respuesta. o cada repetición vuelve a generar los mismos nodos como nuevos candidatos.

El contrato de proveedor ​

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 llamadas toman una array de nodos, por lo que un pivote sobre cincuenta de ellos es una solicitud de backend en lugar de cincuenta. Ambos toman el actual estrechamiento. Ambos obtienen un PivotContext de carga signal y isStale(), el mismo patrón de cancelación que utiliza cada gancho asíncrono de la biblioteca.

appliesTo: todo el origen, o parte de él ​

Devuelve un booleano para una regla sobre el origen en su conjunto, y los nodos que aceptas para una regla que lee un nodo a la vez. Una matriz vacía significa lo mismo que false.

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

La forma por nodo importa cuando se combina una selección: un dominio y una IP seleccionados juntos cada uno mantiene sus propios proveedores en lugar de dejar solo los que aceptan ambos, y el el panel lo dice en la entrada: Se aplica a 3 de los 5 elegidos.

Lo que mantiene es el origen con el que se llama al proveedor: summarize y fetch nunca ven un nodo que rechazó, y el resumen almacenado en caché está codificado por ese origen restringido. se vuelve a leer en cada cambio de origen, así que manténgalo sincrónico y económico.

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

Llamar a run con un origen que un pivote rechaza por completo resuelve 'failed' en lugar de buscar en un origen vacío: una ejecución que parecía estar bien y respondió que nada sería peor.

summarize: el barato ​

Se muestra completo en Un proveedor completo. Su total es el número Se juzga la tapa, y sus facets se convierten en controles de estrechamiento: usted declara la forma, la biblioteca dibuja los widgets. Pase el estrechamiento a su backend para que el recuento realice un seguimiento de lo que eligió el analista, de lo contrario la puerta nunca podrá abrirse.

Una faceta puede declarar un default: la opción valora un multiselect comienza con marcado, o el el valor a select o boolean comienza en. El panel lo dibuja y Borrar estrechamiento vuelve a pero la llave permanece fuera del estrechamiento mientras se mantiene, por lo que su proveedor debe leer un clave faltante como valor predeterminado. Un multiselect vacío con un valor predeterminado se envía como [], que significa ninguno.

js
{ key: 'element', label: 'Element', type: 'multiselect', default: ['attribute', 'object'],
  options: [/* attribute, object, tag, … */] }

Omita summarize por completo y el pivote no ofrecerá ningún conteo ni reducción, solo un Ejecutar botón. Eso es legal, y luego fetch debe ser seguro llamar a ciegas, porque nada puede bloquear eso.

Los conteos son consultivos. Siempre. ​

summarize devuelve un reclamo, no un contrato, y se diferencia legítimamente de lo que ingerimos tierras: dedup primero, luego permisos, obsolescencia y proveedores que se aproximan deliberadamente. La interfaz de usuario marca la diferencia con una tilde: ~2,143 correlations es el número del proveedor, mientras que 210 fetched y 12 selected son propios de la biblioteca. Un psiquiatra nunca es un error, y un count nunca soporta carga para el diseño.

fetch: el verdadero ​

Devuelve un fragmento plano: { nodes: RawNode[], edges: RawEdge[] }. No se devuelven nombres de nodos padre existente en el lienzo. El anidamiento viaja en RawNode.children, por lo que un contenedor llega como Nuevo nodo que lleva su propio subárbol. Un resultado solo bordes es legal: bordes cuyos puntos finales Todos los que ya están sobre el lienzo se convierten en sus propias filas de clasificación.

Tipos de facetas ​

La reducción utiliza todos los tipos de facetas que el motor de consultas conoce excepto regex, que ningún backend puede hacer. se le pedirá que evalúe: text, select, multiselect, numberRange, boolean. La expresión regular vive en los filtros del lado del cliente del panel de clasificación, donde las filas ya están disponibles.

Cómo los dibuja el panel y de dónde viene la línea de ruptura

Se dibuja un multiselect como una lista de casillas de verificación con cada opción y su count en la pantalla, de modo que los números que está reduciendo permanecen visibles mientras reduce. Una única opción select sigue siendo un menú desplegable y lleva sus recuentos en las etiquetas de opciones.

Debajo del total, el panel imprime de qué está hecho, tomado del primer multiselect faceta: esas opciones dividen el resultado. Una vez que esa faceta tenga una selección, enviada o por defecto, sólo se enumeran las opciones seleccionadas. Los recuentos de una faceta de opción única son alternativas. en lugar de partes, por lo que no se suman allí.

Puerta: la tapa y el techo. ​

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

Ambos se niegan, y ninguno se trunca ni se muele. Una negativa siempre conlleva tres cosas orden: el número, el límite y el camino a seguir. "Demasiados resultados" sin número no es algo sobre lo que un analista puede actuar.

El límite se juzga según el recuento más reciente para la reducción actual, que es lo que le permite ascensor. Se reduce de 2143 a 210 y Fetch se activa solo. El techo es sobre la memoria. para objetos candidatos en lugar de sobre la tabla, que virtualiza.

Los candidatos no son el gráfico. ​

Nada de lo que devuelve un pivote está en el gráfico, en la tabla de datos o en cualquier recuento de facetas hasta que es ingerido. Los resultados preparados para la clasificación abren la pestaña Review del muelle: un panel por pivote, listado como vertical pestañas a un lado, cada una con su propio conteo y su propia salida, con el propio proveedor columnas, los filtros de la tabla de datos, clasificación, paginación y una acción de ingesta.

  • El rechazo es explícito y recordado para la sesión, ingresado por pivote. la siguiente carrera no vuelve a ofrecer ese candidato y la línea del encabezado dice cuántos suprimió. un La fila rechazada se tacha en su lugar en lugar de moverse y permanece reversible.
  • Lo que un pivote está reteniendo aparece en su entrada en el panel Pivote, denominado como la tabla lo nombró, con restore en cada fila y Restore all debajo de ellas. se queda ahí una vez que el panel está cerrado, por lo que se puede reconsiderar un rechazo sin ejecutar el pivote otra vez para encontrarlo.
  • Los nodos y los bordes son bloques separados, cada uno de ellos nombrado y contado cuando una ejecución devuelve ambos. Un resultado de sólo bordes es una tabla propia, no un panel vacío.
  • Se abre una fila de contenedores. Lleva un recuento Niños de lo que contiene directamente, y un El cursor enumera el contenido, con los atributos compartidos de cada niño y una nota donde se encuentra el niño. en sí mismo un contenedor. Abrir una fila no es marcarla y no se puede coger nada de su interior. propio: al ingerir la fila se necesita todo el contenedor.
  • Cerrar un proveedor no rechaza nada. Los restos no clasificados regresan en la siguiente ejecución. Rechazar todos los restantes es el único gesto que dispone de ellos.
  • Una nueva ejecución reemplaza el panel de ese proveedor. Si tiene filas marcadas, lo dice y ofrece Mostrar nuevo / Continuar clasificando, en lugar de desechar la clasificación sin que se lo soliciten.
  • Ingerir en un grupo coloca las filas seleccionadas plegadas, un grupo por tipo, fuera del origen. Consulte Aterrizajes de pivote. Se ofrece a menos que UI.simplify sea apagado. Con pivotIngestGrouped: true se agrupan todos los rellanos y el botón dice Ingerir suelto en su lugar.

Todo lo que hace el panel también es accesible mediante programación:

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 un resultado pequeño y confiable que nadie quiere revisar, como expandir un contenedor en su docena de hijos, declara autoIngest: true. Los resultados llegan directamente y la barra superior deshacer los recupera como cualquier otra ingesta. El proveedor decide esto, porque sólo él sabe si sus propios resultados son lo suficientemente pequeños y confiables como para saltarse la clasificación.

Dejarlo sin configurar no es lo mismo que false. Dice que el pivote no tiene vista, lo que deja un pivote con un clic libre de decidir solo el tamaño; false dice resultados Siempre necesito estar atento a ellos y es un honor, independientemente de cómo se haya iniciado la carrera.

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

Cualesquiera que sean las tierras, de aquí o de Review, pueden llegar plegadas en grupos de aterrizaje en lugar de sueltos:

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

Ingerir y qué está permitido hacer. ​

Un gesto, un lote, un dataBatchChanged, en este orden:

  1. Dedup: una identificación que ya está en el lienzo se omite y nunca se sobrescribe, independientemente de si estaba allí cuando los candidatos fueron presentados u otra carrera lo consiguió mientras esperaban.
  2. Unión infantil por id.
  3. Colocación cerca del nodo de origen, nervioso. El x/y de un proveedor gana, y un Semillas sin origen en el centro de la ventana gráfica.
  4. Procedencia, luego aterriza el lote.

La ingesta es puramente aditiva. Las únicas cosas que alguna vez se eliminan son removeBySource y ejecutan deshacer.

Una puerta, llamada una vez con todo el 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?` })
    },
}

Regrese false para vetar, o { accept: true, nodes, edges } para conseguir un subconjunto. Una clave omitida significa "según lo solicitado", exactamente como se lee onBeforeDelete.

Procedencia ​

Cada nodo y borde lleva el conjunto de fuentes que lo avalan. Un conjunto en lugar de un escalar, porque dos pivotes superpuestos en un nodo es el caso normal. Con un escalar, el El segundo en llegar borra el reclamo del primero, y al eliminar cualquiera de ellos se toma el nodo con eso.

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

Tomando una carrera de regreso ​

Una ingesta es una entrada en graph.history, junto con otras cosas que cambian lo que Retenciones de lienzos: tachaduras, pieles duraderas y elementos dibujados a mano.

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

Deshacer es contiguo: apuntar a una entrada anterior también invierte todas las entradas que se encuentran encima de ella, así como un lote. Revertir una ejecución anterior por sí sola es graph.removeBySource(pivotId), que cae esa fuente da fe y elimina sólo lo que nada más da fe: una operación directa, grabado como uno solo.

Una ingesta parcial de un conjunto preparado tiene su propio runId, por lo que cada lote es independiente entrada. Y deshacer una ingesta que sigue siendo la entrada más reciente ** vuelve a colocar a sus candidatos el panel Revisar** en lugar de pedírselos nuevamente al proveedor. Ver Deshacer e historial para todo.

Guardar resultados ​

Ingest pone a los candidatos en la lona; no los escribe por ningún lado. Un pivote que declara save puede enviar sus propios resultados al sistema del que provienen, y la biblioteca conserva los Libro mayor: qué elementos creó una ejecución, si se han escrito, cuáles fallaron y lo que debería llevar un reintento.

Omita save y los resultados del pivote son no se pueden guardar. Nunca ingresan al libro mayor, son nunca se cuentan como no guardados y no aparece ningún Guardar para ellos. Esa es la declaración correcta para un pivote. sobre datos derivados (la salida de un motor de correlación no es suya para escribirla) y es lo que detiene un "210 no guardado" permanente sin remedio.

Todo esto, incluidas las escrituras parciales, los reintentos, los identificadores que un sistema fuente crea y lo que Deshacer alcanzará y no alcanzará: Guardando resultados de pivote.

El modo de carril pivote ​

Usando uno recorre la superficie. ¿Qué importa cuando estás configurando en lugar de conducirlo: ingresar al modo es la intención que llama a un proveedor, al igual que mantener Pivotar ▸ abrir en el menú contextual; dejarlo detiene todas las preguntas en curso y La selección por sí sola no cuesta nada. La selección es también el gesto para arrastrar, aplicar estilo y generar volumen. editar, por lo que la biblioteca nunca llama a un proveedor de forma especulativa.

El modo está ** cerrado en el 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

Por lo tanto, un consumidor que no registra ningún giro no ve ningún rastro de la característica. nunca es montado en modo viewer o static.

Pivotes sin origen ​

Algunos enriquecimientos no tienen un nodo en el que ejecutarse, como buscar, importar o pegar una lista de indicadores. Declara origin: 'none' y el pivot recibe [], aplica cuando el origen es vacío y se pliega en un grupo Sin origen una vez que se elige uno. Ruta de resultados a través el mismo panel de clasificación, la misma puerta y el mismo modelo de procedencia, y aterrizan en el centro de la ventana gráfica.

appliesTo no tiene sentido para uno de ellos y no se consulta.

Potencial declarado ​

Una pista en el borde del nodo, que dice "aquí hay 2.100 correlaciones", puesta allí por quienquiera que sea. cargó los datos.

js
node.setPotential('correlations', 2100)   // 0 clears it
node.getPotential('correlations')

node.setPotential(2199)                   // the total across every pivot
node.getPotential()

Es declarado, nunca consultado. La biblioteca no llamará a un proveedor para sacar una credencial, porque un recuento que se materializó en un nodo en el momento en que se preguntó dejaría el lienzo que cuenta dos historias diferentes. Al hacer clic en la insignia, se abre el modo Pivote con alcance para eso. pivote.

Como cualquier otro configurador en Node, marca el nodo como sucio: llame a graph.renderer.update() si no hay nada más a punto de rendirse.

Lo que dibuja el borde ​

De forma predeterminada, las insignias de llanta se muestran solo mientras el modo Pivote está abierto, donde ayudan a elegir una origen; Al hacer clic en uno, el panel se dirige a ese nodo y gira. pivotRimBadgeVisible cambia cuando muestran:

js
new Pivotick(el, data, { pivots, pivotRimBadgeVisible: 'always' })
graph.pivots.rimBadgeVisible = 'pivot-mode'   // or later; it dirties every node
'pivot-mode' (predeterminado)Solo mientras el modo Pivot Rail esté abierto.
'always'Cualquiera que sea el modo, una insignia también es una forma de acceder al modo Pivote.
'never'En absoluto.

Una vez mostrada, su forma es la siguiente opción.

Un nodo tiene cuatro esquinas de borde y solo dos una vez que tiene hijos: la capacidad de expansión reserva el lado este. Por lo tanto, una credencial por proveedor es legible para unos pocos e imposible para cien. pivotRimBadge elige la forma:

js
new Pivotick(el, data, { pivots, pivotRimBadge: 'summary' })
graph.pivots.rimBadge = 'per-pivot'   // or later; it dirties every node
'per-pivot' (predeterminado)Una insignia por pivote que declaró un potencial, cada apertura de ese pivote. Varios de ellos colapsan en un +n al principio de un nodo con niños.
'summary'Una insignia, cualquiera que sea el número de proveedores. Muestra el total declarado sin ID de pivote o, sin nada declarado, cuántos pivotes se aplican.
'off'Nada. NodeStyle.badges todavía empata.
Qué hace una insignia summary cuando se hace clic y de dónde proviene su recuento

En summary la insignia es un gesto: abre el panel Pivote en ese nodo, porque en cualquier recuento real de proveedores, el panel es donde residen los recuentos y las opciones. La excepción es un nodo. donde se aplica exactamente un pivote y declara no summarize: nada que leer primero y nada que limitar: donde se ejecuta el clic. Donde llega el resultado es la propia llamada del proveedor: autoIngest decide entre el lienzo y el panel de clasificación, como en cualquier otro lugar.

El recuento de la aplicación de pivotes se deriva, no se declara, por lo que no cuesta ninguna llamada al proveedor. se lleva a cabo entre cambios de gráfico y se actualiza cuando se mueven el registro o los nodos del gráfico; un appliesTo leer algo más puede retrasarse en el borde y el panel siempre es la respuesta exacta.

Almacenamiento en caché ​

La biblioteca almacena en caché una cosa: summarize resultados, codificados por (pivote, origen, estrechamiento), porque un resumen de selección múltiple es un agregado que nada puede descomponer. todo lo demás es tuyo:

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

La autenticación, el reintento, la limitación de velocidad y cualquier caché propio pertenecen a las funciones de su proveedor. que son funciones simples que escribiste.

Lo que tu fuente tiene para ofrecer ​

Las páginas anteriores son para quien escribe al proveedor. Este es el mismo contrato leído en el del otro lado, para quien sea dueño del servicio al que llama.

  • Una llamada de recuperación, devolviendo los objetos y sus relaciones. El único requerido. dos Las cosas al respecto importan: se necesitan muchos identificadores en una solicitud, por lo que un pivote sobre cincuenta nodos cuesta una llamada y los identificadores que devuelve son estables en todas las ejecuciones, porque eso es lo que teclas de desduplicación activadas. Una identificación por respuesta muestra los resultados de ayer nuevamente como nuevos candidatos.
  • Una llamada de conteo, tomando los mismos identificadores y los mismos filtros, devolviendo un total y el El valor por valor cuenta para cualquier cosa que pueda reducirse. Es lo que hace que la puerta y el El trabajo se reduce y se les vuelve a solicitar en cada cambio, por lo que tiene que ser barato. El aproximado está bien: los recuentos son orientativos. Sin uno un El pivote todavía se ejecuta, con un simple Run y nada que lo bloquee.
  • Los mismos filtros respetados por ambos. Un conteo que los ignora nunca se mueve, por lo que un pivote sobre su tapa nunca podrá ser desbloqueado.
  • Una forma de cancelar. Las preguntas se abandonan cuando el analista continúa y el signal el proveedor sólo vale la pena transmitirlo si el transporte actúa sobre él.
  • Una llamada por escrito, si los resultados son suyos, puede respondernos. Debería devolver los identificadores. asignado, por lo que canonicalIds puede detener La ejecución de mañana duplicará lo que se guardó y podrá informar un éxito parcial.

Ambas llamadas de lectura deberían diferenciar una respuesta vacía de un error: la ausencia de resultados es un hecho los datos, un error es un hecho sobre el servicio, y se leen de manera completamente diferente a un analista.

Aquí nada necesita paginación, un cursor o un transporte de streaming. Estrechar es lo que reemplaza ellos.

que esto no es ​

  • Sin transmisión ni cursor de servidor. La reducción los reemplaza: en lugar de buscar dos mil filas, pregunta qué hay y deja que el analista elija. El contrato nunca requiere una backend de streaming, porque muchas fuentes no tienen uno.
  • Sin re-parenting. Un nodo devuelto nunca nombra a un padre existente. Unión de niños por identificación. es la única forma en que la ingesta toca a los hijos de un nodo existente.
  • Sin deshacer edición de propiedad. graph.history cubre lo que contiene y muestra el lienzo: lo que lo que entró, lo que salió, lo que se esconde. Los datos de un nodo están en un estado de backend que la biblioteca no tenía autor, por lo que revertir un campo localmente es trabajo de onBeforeNodeEditCommit, no nuestro.
  • Sin política de transporte. save responde, pero las credenciales, los reintentos, la limitación de velocidad y la detección de conflictos son todos del consumidor. El único reintento de la biblioteca es que el analista presione Reintentar.
  • No hay memoria de rechazo durante una recarga. Un rechazo es un veredicto en el contexto de una sesión, y el mismo candidato puede merecer una respuesta diferente en otro gráfico. Los rechazos son celebrada para la sesión y no más.
  • No se actualiza lo que ya está aquí. Un candidato con ID coincidente abandona el nodo existente intacto; reconciliar un nodo con una copia más reciente necesita una política de conflicto, la biblioteca no tiene.