Объясняя, как я собрал Графическое приложение всего за несколько дней, используя компоненты с открытым исходным кодом.

В данном случае я хотел создать приложение Graph, которое помогло бы обучать пользователей Неосемантике или n10s. Neosemantics - это проект Neo4j Labs, который позволяет вам хранить RDF / связанные данные в Neo4j, а затем экспортировать данные обратно без потерь.

Идея приложения Graph заключалась в том, чтобы предоставить образовательный инструмент, который позволил бы вам предварительно просмотреть, как ваши тройки будут выглядеть в Neo4j без необходимости много знать о базовой технологии, а также научить более опытных пользователей, как на самом деле API процедур библиотеки работает.

Если вы хотите установить приложение в Neo4j Desktop, протестируйте его, пока мы смотрим на код, просто откройте Graph App Gallery или перейдите на install.graphapp.io и нажмите кнопку установки.

Если вас интересует только код, переходите к TL; DR внизу сообщения…

Да, но что такое Graph Apps?

Приложения Graph - это одностраничные приложения (построенные с использованием HTML / Javascript и, при желании, внешней среды по вашему выбору), которые устанавливаются на Neo4j Desktop и размещаются на нем.

Галерея приложений Graph содержит исчерпывающий список приложений, созданных Neo4j, нашими партнерами и даже участниками сообщества. Два приложения Graph, которые я использую на регулярной основе, - это Halin, инструмент для мониторинга и управления, и Query Log Analyzer, который дает вам более удобный просмотр ваших журналов запросов.

Neo4j Desktop предоставляет вам API, который дает вам доступ к проектам и графикам пользователя, а также к файлам и ключам активации. Отсюда вы можете запустить сокращение / фильтр для проектов и графиков, чтобы найти активный график.

const context = await neo4jDesktopApi.getContext()
const activeGraph = context.projects
    .reduce((acc, project) => acc.concat(project.graphs), [])
    .filter(graph => graph.status === 'ACTIVE')

Оттуда вы можете найти URL-адрес сервера и учетные данные, а также создать экземпляр драйвера и запустить операторы шифрования для графа.

const { url, username, password } = activeGraph.connection.configuration.protocols.bolt
// Create a driver instance
const driver = new neo4j.driver(
    url, // Neo4j URL - bolt://…
    neo4j.auth.basic(username, password) // Auth
)

Использование компонентов с открытым исходным кодом

Как я уже говорил ранее, пока ваш код сводится к html-файлу и некоторым javascript-файлам, выбор фреймворка остается спорным. Почти все, что создается командой инженеров Neo4j, построено с использованием React, но лично я больше похож на парня из Vue.js.

Vue.js поставляется с инструментом CLI, который позволяет создавать проекты из готовых шаблонов, поэтому с помощью одной команды вы можете сгенерировать структуру проекта:

vue create neosemantics

Vue и Neo4j

Некоторое время назад, когда я создавал эти приложения для клиентов в качестве своей повседневной работы, я собрал плагин Vue, чтобы упростить взаимодействие с Neo4j. Плагин vue-neo4j добавляет объект $neo4j ко всем компонентам, который содержит вспомогательные функции для подключения к базе данных и выполнения запросов.

// Something.vue
export default {
    name: 'something',
    // ...
    data: () => ({
        driver: false,
        protocol: 'bolt',
        host: 'localhost',
        port: 7687,
        username: 'neo4j',
        password: 'trustno1'
    }),
    methods: {
        connect() {
            this.$neo4j.connect(
                this.protocol, 
                this.host, 
                this.port, 
                this.username, 
                this.password
            )
        }
    }, 
    computed: {
        driver() {
            return this.$neo4j.getDriver()
        }
    },
}

Если плагин обнаруживает Neo4j Desktop API, он также предоставит вам драйвер, уже созданный с деталями подключения активного графа.

Один полезный компонент, который включает этот плагин, - это компонент vue-neo4j-connect. Большинству людей потребуется форма входа в свое приложение, поэтому этот компонент предоставляет вам все необходимое.

Когда приложение открывается в Neo4j Desktop, пользователю будет предоставлен список проектов и графиков для подключения, а также кнопка для подключения к текущему активному графику. Если пользователь нажимает Подключиться к другому графику или приложение открывается в браузере, появляется общая форма входа, в которой пользователь вручную заполняет учетные данные Neo4j.

Смотри и чувствуй

Любой, у кого есть зоркий глаз, также заметит, что целый ряд продуктов Neo4j, включая Neo4j Desktop, Neo4j Browser и Neo4j Bloom, используют какие-то вариации поверх семантического пользовательского интерфейса, поэтому, чтобы приложение n10s Graph выглядело чем-то похожим, это сделало бы смысл использовать и это тоже.

Существует также плагин Vue для Semantic UI под названием semantic-ui-vue. Каждый компонент имеет префикс sui-, и с помощью быстрого поиска в документации вы быстро найдете то, что ищете. Вот сокращенная версия формы конфигурации:

<sui-form>
    <sui-form-field>
        <label>handleVocabUris</label>
        <sui-dropdown
            fluid
            :options="handleVocabUriOptions"
            placeholder="handleVocabUris"
            search
            selection
            v-model="handleVocabUris"
        />
    </sui-form-field>
    <sui-button primary 
        :loading="loading" 
        @click.prevent="runQuery"
    >
        {{ buttonText }}
    </sui-button>
</sui-form>

Это также ускорило процесс разработки, предоставив набор готовых компонентов для всего, от базового макета до элементов формы. Это также помогло мне избавиться от большинства дизайнерских решений.

Таким образом, с помощью @ vue / cli и двух компонентов с открытым исходным кодом я смог относительно быстро создать большую часть приложения Graph. О внешнем виде позаботился Semantic UI, а взаимодействие с базой данных Neo4j будет происходить через vue-neo4j.

Общие компоненты

Многие страницы пользовательского интерфейса также используют общие компоненты. Например, все таблицы результатов Cypher используют один и тот же компонент. Компонент просто берет результат, полученный от neo4j-driver, и отображает результаты в <sui-table> компоненте, используя цикл v-for.

<template>
    <div class="n10s-result">
        <sui-table compact striped v-if="!noResults && isTable">
            <sui-table-header>
                <sui-table-header-cell v-for="key in headers" 
                                              :key="key">
                {{ key }}</sui-table-header-cell>
            </sui-table-header>
            <sui-table-body>
                <sui-table-row v-for="(record, index) in  
                                      result.records" :key="index">
                    <sui-table-cell v-for="key in headers" 
                                           :key="index+key">
                        {{ record.get(key) }}
                    </sui-table-cell>
                </sui-table-row>
            </sui-table-body>
        </sui-table>
        <graph-result v-if="!noResults && !isTable"
            :result="result"
        />
        <p class="ui tiny" v-if="summary" v-html="summary" />
    </div>
</template>

Сам компонент принимает результат как опору, а затем использует свойство keys в первой строке для создания строки заголовка как вычисляемого свойства, затем выполняет итерацию через result.records для отображения фактических результатов запроса.

export default {
    name: 'n10s-result',
    props: {
        result: Object,
        displayAs: {
            type: String,
            default: 'table',
        },
    },
    components: {
        GraphResult,
    },
    computed: {
        isTable() {
            return this.displayAs === 'table'
        },
        noResults() {
            return this.result && !this.result.records.length
        },
        headers() {
            return this.result.records[0].keys
        },
        summary() {
            const consumed = 
                this.result.summary.resultConsumedAfter.toNumber()
            const available =            
                this.result.summary.resultAvailableAfter.toNumber()
            return `
                Started streaming ${this.result.records.length} 
                record${this.result.records.length === 1 ? '' : 's'}
                after ${available}ms 
                and completed after ${consumed}ms
            `
        },
    },
}

Затем этот компонент способен обрабатывать все, что ему брошено ...

Если требуется макет Forced Graph, я отключил компонент Result для другого компонента, который является оболочкой для библиотеки Vis.js network.

Многие компоненты также запускают инструкцию Cypher для базы данных. Вместо того, чтобы дублировать этот код много раз, вы можете создать Миксин. Примеси позволяют определять функциональные возможности, которые могут быть унаследованы, путем импорта объекта и включения его в массив mixins компонента.

export default {
    data: () => ({
        loading: false, error: false, result: false, tab: 0
    }),
    computed: {
        cypher: () => 'MATCH (n) RETURN count(n) AS count',
        params: () => {},
    },
    methods: {
        runQuery() {
            this.loading = true
            this.error = false
            this.$neo4j.run(this.cypher, this.params)
                .then(res => this.result = res)
                .catch(e => this.error = e)
                .finally(() => {
                    this.tab = 1
                    this.loading = false
                })
        },
    },
}

В этом случае я определил cypher как вычисляемую переменную, но определение ее в компоненте, который использует этот миксин, который содержит свою собственную переменную cypher, перезапишет это. Таким образом, если я забуду перезаписать переменную, у меня не будет ошибок в консоли.

import CypherComponent from './mixins/CypherComponent'
export default { 
    name: 'import', 
    mixins: [ CypherComponent ], 
    // ... 
    computed() {
        cypher() {
            return `CALL n10s.rdf.import.fetch(
                "${this.url}",
                "${this.format}",
                ${JSON.stringify(this.parameters)}
            )`
        },
    },
}

Процесс TL; DR

Используйте @ vue / cli для создания нового проекта.

vue create neosemantics

Установите vue-router для навигации и vue-neo4j и semantic-ui-vue как зависимости.

npm install --save vue-router vue-neo4j semantic-ui-vue

Импортируйте и зарегистрируйте плагины в main.js:

import Vue from 'vue'
import VueNeo4j from 'vue-neo4j'
import SuiVue from 'semantic-ui-vue'
import App from './App.vue'
import router from './router'
import 'semantic-ui-css/semantic.min.css'
// Tell Vue to use these plugins
Vue.use(VueNeo4j)
Vue.use(SuiVue)
new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

Настройте каждый из отдельных маршрутов в Vue-Router:

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './routes/Home'
import Config from './routes/Config'
import Import from './routes/Import'
import Preview from './routes/Preview'
import Delete from './routes/Delete'
import Export from './routes/Export'
Vue.use(VueRouter)
export default new VueRouter({
    routes: [
        { name: 'config',  path: '/config', component: Config, },
        { name: 'import',  path: '/import', component: Import, },
        { name: 'preview',  path: '/preview', component: Preview, },
        { name: 'delete',  path: '/delete', component: Delete, },
        { name: 'export',  path: '/export', component: Export, },
        // Redirect to Home
        { name: 'home', path: '/', component: Home, },
        { path: '*', redirect: '/', },
    ]
})

Создайте миксин vue для предоставления методов для выполнения запросов к базе данных:

export default {
    data: () => ({
        loading: false, error: false, result: false, tab: 0,
    }),
    computed: {
        cypher: () => 'MATCH (n) RETURN count(n) AS count',
        params: () => {},
    },
    methods: {
        runQuery() {
            this.loading = true
            this.error = false
            this.$neo4j.run(this.cypher, this.params)
                .then(res => this.result = res)
                .catch(e => this.error = e)
                .finally(() => {
                    this.tab = 1
                    this.loading = false
                })
        },
    },
}

Затем соберите все вместе в компонент маршрута, используя метод runQuery из миксина для выполнения вычисленной переменной cypher.

import CypherComponent from './CypherComponent'
export default {
    name: 'config',
    mixins: [ CypherComponent, ],
    // ...
    computed: {
        cypher() {
            let params = []
            const keys = ['handleVocabUris', 'handleMultival', 
                          'handleRDFTypes']
            keys.map(key => {
                if ( this[ key ] !== undefined && this[key] !== '' )
                    params.push(`${key}: '${this[key]}'`)
            })
            if ( params.length ) {
                params = `{\n\t${params.join(',\n\t')}\n}`
            }
            return `CALL n10s.graphconfig.init(${params})`
        },
    },
}

Вы можете просмотреть весь исходный код приложения n10s на Github по адресу https://github.com/neo4j-apps/n10s

Признание

Моя работа как разработчика опыта разработчиков в Neo4j - создавать подобные приложения. Но я также здесь, чтобы облегчить вам жизнь как разработчика. Если я могу что-то сделать, чтобы улучшить ваш опыт разработки приложений Graph; будь то улучшенная документация, лучшие учебники и руководства или фреймворки и повторно используемые компоненты для вашего любимого языка / фреймворка, я все слышу.

Не стесняйтесь обращаться ко мне в Twitter или оставлять сообщение на сайте сообщества Neo4j.