Giter VIP home page Giter VIP logo

arxiv-search's Introduction

arxiv-search

Development quickstart

Running Elasticsearch in Docker

You can start up ES on its own. Be sure to map port 9200 to the host machine, so that arXiv search can find it.

docker build -t "arxiv/elasticsearch" -f ./Dockerfile-elasticsearch .
docker run -it \
    -e "http.host=0.0.0.0" \
    -e "transport.host=127.0.0.1" \
    -p 9200:9200 -p 9300:9300 arxiv/elasticsearch

echo $ES_BASE_URL; # http://127.0.0.1:9200
curl "${ES_BASE_URL}/_cat/health?v";

Create & populate the index

A couple of helper scripts are included to create and populate the search index. Note that you will need to have access to the /docmeta endpoint, which is only accessible from the CUL network.

pipenv install --dev
FLASK_APP=app.py FLASK_DEBUG=1 ELASTICSEARCH_SERVICE_HOST=127.0.0.1 pipenv run python create_index.py
FLASK_APP=app.py FLASK_DEBUG=1 ELASTICSEARCH_SERVICE_HOST=127.0.0.1 pipenv run python bulk_index.py

bulk_index.py without parameters populate the index with the list of papers defined in tests/data/sample.json. It take several minutes to run. Individual paper IDs may be specified with the --paper_id parameter.

Flask dev server

You can spin up the search app directly.

FLASK_APP=app.py FLASK_DEBUG=1 ELASTICSEARCH_SERVICE_HOST=127.0.0.1 pipenv run flask run

This will monitor any of the Python bits for changes and restart the server. Unfortunately static files and templates are not monitored, so you'll have to manually restart to see those changes take effect.

If all goes well... http://127.0.0.1:5000/ should render the basic search page.

You can run the new metadata API in dev mode by changing FLASK_APP to point to wsgi-api.py, i.e.:

FLASK_APP=api.py FLASK_DEBUG=1 ELASTICSEARCH_SERVICE_HOST=127.0.0.1 pipenv run flask run

To run the classic API in dev mode, use wsgi-classic-api.py:

FLASK_APP=classic-api.py FLASK_DEBUG=1 ELASTICSEARCH_SERVICE_HOST=127.0.0.1 pipenv run flask run

Running Elasticsearch + Kibana with docker-compose

The easiest way to spin up ES and Kibana is using the included docker-compose.yml file. This will start ES and Kibana on a custom network. The ES service ports 9200 and 9300, and Kibana service port 5601, will be mapped to localhost.

docker-compose up

Kibana will be available at http://127.0.0.1:5601/. The containers started by docker-compose can be stopped with docker-compose down from the same directory.

Make sure that you have a recent version of docker-compose; this is confirmed to work with version 1.18.

Note that connection configuration variables for the search service are set in search/config.py, where they are read from the environment. The arXiv search service expects the ES service to be available at http://localhost:9200 by default. Hence, you should be able to start ES using docker-compose as above and make no configuration changes to the arXiv search service.

Running the indexing agent.

The indexing agent is responsible for updating the search index as new papers are published. By default, docker-compose will also start the search index and a service called Localstack that provides a local Kinesis stream for testing/development purposes.

To disable the agent and localstack, just comment out those services in docker-compose.yml.

The agent takes a little longer than the other services to start. Early in the startup, you'll see something like:

agent            | application 12/Apr/2018:15:43:13 +0000 - search.agent.base - None - [arxiv:null] - INFO: "New consumer for MetadataIsAvailable (0)"
agent            | application 12/Apr/2018:15:43:13 +0000 - search.agent.base - None - [arxiv:null] - INFO: "Getting a new connection to Kinesis at https://localstack:4568 in region us-east-1, with SSL verification=False"
agent            | application 12/Apr/2018:15:43:13 +0000 - search.agent.base - None - [arxiv:null] - INFO: "Waiting for MetadataIsAvailable to be available"
agent            | application 12/Apr/2018:15:43:13 +0000 - search.agent.base - None - [arxiv:null] - ERROR: "Waiting for stream MetadataIsAvailable"

A little while later, when localstack and the indexing agent are running, you should see something like:

agent            | application 12/Apr/2018:15:44:14 +0000 - search.agent.base - None - [arxiv:null] - ERROR: "Failed to get stream while waiting"
agent            | application 12/Apr/2018:15:44:14 +0000 - search.agent.base - None - [arxiv:null] - INFO: "Could not connect to stream; attempting to create"
agent            | application 12/Apr/2018:15:44:14 +0000 - search.agent.base - None - [arxiv:null] - INFO: "Created; waiting for MetadataIsAvailable again"
agent            | application 12/Apr/2018:15:44:14 +0000 - search.agent.base - None - [arxiv:null] - ERROR: "Waiting for stream MetadataIsAvailable"
localstack       | Ready.
agent            | application 12/Apr/2018:15:44:24 +0000 - search.agent.base - None - [arxiv:null] - INFO: "Ready to start"
agent            | application 12/Apr/2018:15:44:24 +0000 - search.agent.base - None - [arxiv:null] - INFO: "Starting processing from position 49583482132750299344823207796409748205413425533752967170 on stream MetadataIsAvailable and shard 0"

Note that Kinesis will be mounted locally on port 5586. It will be using SSL, but with an invalid certificate. You can connect to this local Kinesis using:

import boto3

client = boto3.client(
    'kinesis',
    region_name='us-east-1',
    endpoint_url="https://localhost:5568",
    aws_access_key_id='foo',
    aws_secret_access_key='bar',
    verify=False
)

To verify that the agent is working correctly, try adding some records to the stream.

import json

to_index = [
    "1712.04442",
    "1511.07473",
    "1604.04228",
    "1403.6219",
    "1404.3450",
    "1703.09067",
    "1408.6682",
    "1607.05107",
    "1509.08727",
    "1710.01597",
    "1708.07156",
    "1401.1012",
]

for document_id in to_index:
    data = bytes(json.dumps({'document_id': document_id}), encoding='utf-8')
    client.put_record(
        StreamName='MetadataIsAvailable',
        Data=data,
        PartitionKey='0'
    )

You should see these records being processed in the agent log output almost immediately. For example:

agent            | application 12/Apr/2018:15:49:18 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447528512983060659815298629634"
agent            | application 12/Apr/2018:15:49:19 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447529721908880274444473335810"
agent            | application 12/Apr/2018:15:49:20 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447530930834699889073648041986"
agent            | application 12/Apr/2018:15:49:20 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447532139760519503702822748162"
agent            | application 12/Apr/2018:15:49:21 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447533348686339118400716931074"
agent            | application 12/Apr/2018:15:49:22 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447534557612158733029891637250"
agent            | application 12/Apr/2018:15:49:23 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447535766537978347659066343426"
agent            | application 12/Apr/2018:15:49:24 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447536975463797962288241049602"
agent            | application 12/Apr/2018:15:49:24 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447538184389617576917415755778"
agent            | application 12/Apr/2018:15:49:25 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447539393315437191546590461954"
agent            | application 12/Apr/2018:15:49:25 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447540602241256806175765168130"
agent            | application 12/Apr/2018:15:49:25 +0000 - search.agent.consumer - None - [arxiv:null] - INFO: "Processing record 49583482484923667520018808447541811167076420804939874306"

Other Operations

Audit

To check for missing records, put the IDs in a file like list_of_papers.txt, use audit.py:

ELASTICSEARCH_SERVICE_HOST=127.0.0.1 ELASTICSEARCH_INDEX=arxiv pipenv run python audit.py -l list_of_papers.txt -o missing.txt

Reindexing

ElasticSearch can perform reindexing by copying documents from one index to another index with a different mapping. reindex.py will initiate the reindexing process, and poll for completion until all of the documents are processed. If the destination index does not already exist, it will be created using the current configured mapping.

FLASK_APP=app.py ELASTICSEARCH_SERVICE_HOST=127.0.0.1 pipenv run python reindex.py OLD_INDEX NEW_INDEX

Deploying static assets to S3

Assets in search/static can be deployed to S3 using the included upload_static_assets.py script that leverages the Flask-S3 plugin https://flask-s3.readthedocs.io/en/latest/`_. Note that this requires AWS credentials that have appropriate permissions for the specified bucket.

To enable the S3-based URLs for the static assets in the templates, simply set FLASKS3_ACTIVE=1 when starting the Flask dev server.

Testing & quality

Install testing tools with...

pipenv install --dev

Test suite

Run the main test suite with...

pipenv run nose2 --with-coverage

To include integration tests, the environment variable WITH_INTEGRATION. E.g.

WITH_INTEGRATION=1 pipenv run nose2 --with-coverage

You can also run to avoid all intentional error messages or kinesis print statements created during the tests

pytest --disable-warnings search

Static checking

Goal: zero errors/warnings.

Use # type: ignore to disable mypy messages that do not reveal actual programming errors, and that are impractical to fix. If ignoring without verifying, insert a # TODO: recheck.

If there is an active mypy GitHub issue (i.e. it's a bug/limitation in mypy) relevant to missed check, link that for later follow-up.

pipenv run mypy -p search | grep -v "test.*" | grep -v "defined here"

Note that we filter out messages about test modules, and messages about a known limitation of mypy related to dataclasses support.

Documentation style

Goal: zero errors/warnings.

pipenv run pydocstyle --convention=numpy --add-ignore=D401 search

Linting

Goal: 9/10 or better.

pipenv run pylint search

Documentation

The latest version of the documentation is available at https://arxiv.github.io/arxiv-search.

The source files for the arXiv search service documentation is located in docs/.

To build the service documentation locally:

cd docs
pip install -r requirements.txt
make [format]

where [format] can be html, latexpdf. See the ``Sphinx documentation http://www.sphinx-doc.org/en/master/`_.

Pre commit hooks

To run pre commit hooks install the dev dependencies:

pipenv install --dev

After that you'll need to install the pre commit hooks:

pipenv run pre-commit install

Git will run all the pre-commit hooks on all changed files before you are allowed to commit. You will be allowed to commit only if all checks pass.

You can also run the pre commit hooks manually with:

pipenv run pre-commit run

arxiv-search's People

Contributors

alefnula avatar bdc34 avatar bmaltzan avatar dependabot[bot] avatar eawoods avatar erickpeirson avatar f380cedric avatar jaimiemurdock avatar kyokukou avatar mhl10 avatar sbbcornell avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

arxiv-search's Issues

Pagination display tuning

UI nitpicking:

  • The "1" button is slightly smaller vertically than all other buttons
  • Balance vertical margins between sort/page controls, paginator and results
  • Put "Advanced/Authors" and "Simple/Authors" into a paragraph with similar bottom margin so that it harmonizes with the beautiful level thing that Erick set up :)
  • Also, "Simple | Authors" and "Simple | Advanced" bounce (due to underlines turning on/off) on hover, but "Advanced | Authors" on the simple search page does not. We want it to be like the latter.

Will it be possible to retrofit mirrors to use new search interface.

History:

At one time arXiv mirrors either ran a home-brew search engine or full blown Lucene server (LANL). When the home-brew search started to fall over due to the immense size of the collection we switched to using the main site to service search requests for mirrors.

Current Implementation:

When a user submits a search on a mirror the search is amended with a 'client_host' argument containing the client host and then passed (redirected) to the find request on the main site.

The main site executes the search and then formats the results page such that all links on the search results page point the client host. The user has no idea the search was handle remotely and the links keep the user on the mirror site.

Implementation for new search:

This might be possible with two modifications: 1) replace the current search page on the mirror such that it reflects the new search page / advanced search page 2) the main search api updates all links to direct to the optional host specified in the request. We currently check that client_host is mirror before doing this modification. 

Supporting proceedings index page links to report numbers

For conference proceedings users can create html pages with links to individual articles. If the html submission is before the individual articles then we have a special markup for users to enter the report numbers. Then we have an html parser that converts the report number markup to search based links.

In general I would like to green field how to handle proceedings for NG, but that doesn't need to happen now.

For now some options could be:

  • modify the html parser to work with the new search
  • modify the new search to support the existing links
  • modify the parser to just search the db for report number and then establish a direct link to the arXivID (if that fails then generate a link)
  • other options???

Current documentation:
https://arxiv.org/help/submit_index

Example html page
https://arxiv.org/html/1108.3558v1

Should there be feedback for no exact match

Ran into this by mistake.

If you do a strict search "reimann hypothesis" that contains a typo the search is smart enough to return related results. But there is no warning that there were no exact matches.

I don't know that this actually needs to be changed so this is more of a question...

Can we imagine use cases of searching for a specific term or phrase and wanting to see only exact matches?

Should there be a warning that the exact search was not found and so we are displaying related results?

https://beta.arxiv.org/search/advanced?terms-0-operator=AND&terms-0-term=%22reimann+hypothesis%22&terms-0-field=title&terms-1-operator=AND&terms-1-term=proof&terms-1-field=title&classification-physics_archives=all&date-year=&date-from_date=&date-to_date=&size=25

Consider surname/forename approach from internationalization and multiple surname angles

I recognize that this is a brand new and quickly implemented function! Props on getting it working in part. Some observations on names and the breakup into sur/fore in particular, to start a list of things to work out in the author search functionality.

Examples:

Chaimae El Aisati is broken into surname= Chaimae El forename=Aisati. Searching for any fragment of these doesn't work (C / Aisati, Chaimae El / A) but searching for forename Chaimae El and surname Aisati does. Other examples: P. Vasquez Regueiro, M. Vieites Diaz.

Double initials: R/Appleby does not return results, but R. B./Appleby does.

Organizations: some break on the last word (example: for the IceCube / Collaboration), some don't have a forename at all (example: / LHCb collaboration)

International: for Asian names that have forename/surname flipped, how do we know which is which? Can/should we search both sur- and forename fields for a string given as a name or name stem? We can then filter by relevance (and later facet by whole name, possibly). Also relevant for other cultures, or for the treatment of organization names in the author field.

Using a partial surname doesn't work, nor does using a wildcard.

Should "Subject" field search secondary as well as primary classifications?

I recognize that implementing secondaries may not yet be in the plan (we don't display them in the results yet either), but generally I think a subject search would turn up both primaries and secondaries, and we could facet on the position (or use as a relevance indicator).

Example: https://arxiv.org/abs/physics/0001066 "The Genetic Code as a Periodic Table: Algebraic Aspects" is classified as physics.bio-ph and secondarily as q-bio, but only findable by searching physics.

Margins on results page

Wider margins required on the results page to use best design practices for scanning/reading of text.

Error in sorting boxes under some conditions

To reproduce:

  1. Authors form: enter "cat*" in surname field (get list of 4 results)
  2. Sort by: select Submitted date - descending, click Go
  3. Get sent to Advanced Search page (instead of reordering results)

Possibly: pages with < 25 results don't reorder? Testing this hypothesis now.

Secondary categories and nesting

Looking at the ES documentation on nested objects. I guess I'm still not clear about why we would use separate fields for secondary_archives, secondary_groups, and secondary_categories (here rather than a single nested property, e.g. secondary_classification.

What we have now:

        "secondary_categories": {
          "properties": {
            "id": {
              "type": "keyword"
            },
            "name": {
              "type": "keyword"
            }
          }
        },
        "secondary_archives": {
          "properties": {
            "id": {
              "type": "keyword"
            },
            "name": {
              "type": "keyword"
            }
          }
        },
        "secondary_groups": {
          "properties": {
            "id": {
              "type": "keyword"
            },
            "name": {
              "type": "keyword"
            }
          }
        },

A proposed alternative:

        "secondary_classification": {
            "type": "nested",
            "properties": {
                "group": {
                    "type": "nested",
                    "properties": {
                        "id": {"type": "keyword"},
                        "name": {"type": "keyword"}
                    }
                },
                "archive": {
                    "type": "nested",
                    "properties": {
                        "id": {"type": "keyword"},
                        "name": {"type": "keyword"}
                    }
                },
                "category": {
                    "type": "nested",
                    "properties": {
                        "id": {"type": "keyword"},
                        "name": {"type": "keyword"}
                    }
                }
            }
        }

It seems like in the former case, we would need to apply some knowledge about the arXiv taxonomy (external to the search document) in order to generate a sensical faceted search (e.g. which category is allowable with which archive). In the latter case, however, it seems like we wouldn't need to know anything about the taxonomy: we could generate aggregations based entirely on what is present in the search index.

@mhl10 @JaimieMurdock @eawoods thoughts?

Author search page - validation error does not clear

When a validation error has occurred (example: forename without surname), error conditions persist after user attempts to correct error.

Also, my browser (OS X El Capitan, Chrome 63.0) puts up an odd little icon to the right of forename once an error has occurred. No tooltips to say what this is, might be LastPass related? Or my individual browser addon settings? See screenshots. Error condition also propagates to additional field.

screen shot 2018-02-05 at 1 54 44 pm

screen shot 2018-02-05 at 2 00 23 pm

Implement "is-monospace" class for button text

If we use a monospace font (Lucida Grande), button widths for single characters like + and - will be more consistent. I already have a class that specifies LG for tiny type, due to slightly better readability -- make this more globally applicable (and apply to buttons).

Pagination beyond 10,000th result needs attention

Broad searches will already jump way over max_results_window (defaults to 10,000).

We can support pagination beyond that point, but the two mechanisms to support that in ES are focused on actually paging through progressively (not jumping to an arbitrary page). Thus we may need to make some adjustments to UI components for pagination.

For reference:

Sort by and number of results dropdowns do not function

On the search results page, build error happens when to change the sort order or number of pages when the controls at the top are changed.

Page size (number of results) does work when changed on the Advanced Search page.

Error encountered:

builtins.OSError
OSError: Problem communicating with ES: TransportError(400, 'search_phase_execution_exception', 'No mapping found for [submitted_date] in order to sort on')

Keyboard use of advanced search form

When one selects the button to create a new field in the advanced search interface, it's annoying that the tabindex has flowed past the field. Thus, in order to fill out the newly created fields, one must tab all the way back through all the controls on the page to fill out the search term that was newly created (and not filling it out will produce an error on search).

UI: No results message for simple form is easy to miss

When you type a single word into the simple form, if there are no results the title changes but the position of every element on the page stays the same -- making it look like nothing happened. This is a design issue, functionality is working as expected.

Advanced search for two terms in different fields breaks build

Already confirmed: single-term search for "data" and "broad" successful on both simple search and advanced search pages.

On the advanced search form, I entered "data" and selected "Abstract", clicked the "+", and added a field. Entered "broad" and selected "Title". Resulting search brought up the following errors:

builtins.AttributeError
AttributeError: 'str' object has no attribute 'field'

Traceback (most recent call last)
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1997, in call
return self.wsgi_app(environ, start_response)
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1985, in wsgi_app
response = self.handle_exception(e)
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1540, in handle_exception
reraise(exc_type, exc_value, tb)
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1982, in wsgi_app
response = self.full_dispatch_request()
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1614, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1517, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/_compat.py", line 33, in reraise
raise value
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1612, in full_dispatch_request
rv = self.dispatch_request()
File "/Users/eaw82/anaconda3/lib/python3.6/site-packages/flask/app.py", line 1598, in dispatch_request
return self.view_functionsrule.endpoint
File "/Users/eaw82/Documents/arXiv/arxiv-search/search/routes/ui.py", line 34, in advanced_search
response, code, headers = advanced.search(request.args)
File "/Users/eaw82/Documents/arXiv/arxiv-search/search/controllers/advanced/init.py", line 59, in search
response_data.update(index.search(q))
File "/Users/eaw82/Documents/arXiv/arxiv-search/search/services/index.py", line 398, in search
return current_session().search(query)
File "/Users/eaw82/Documents/arXiv/arxiv-search/search/services/index.py", line 325, in search
search = self._prepare(query)
File "/Users/eaw82/Documents/arXiv/arxiv-search/search/services/index.py", line 173, in _prepare
& self._classifications_to_q(query)
File "/Users/eaw82/Documents/arXiv/arxiv-search/search/services/index.py", line 141, in _fielded_terms_to_q
return cls._grouped_terms_to_q(terms)
File "/Users/eaw82/Documents/arXiv/arxiv-search/search/services/index.py", line 98, in _grouped_terms_to_q
term_b = SearchSession._grouped_terms_to_q(term_b)
File "/Users/eaw82/Documents/arXiv/arxiv-search/search/services/index.py", line 94, in _grouped_terms_to_q
term_a = SearchSession._field_term_to_q(term_a)
File "/Users/eaw82/Documents/arXiv/arxiv-search/search/services/index.py", line 75, in _field_term_to_q
if term.field in ['title', 'abstract']:
AttributeError: 'str' object has no attribute 'field'

Expand metadata field options

I frequently search by other metadata fields than Title, Author, Abstract.
Can we include all the searchable fields in the advanced search?

image

Does new search have most current info?

It seems the old search has more current version information to display. Note the search-beta result for arXiv:1710.10131 in the attached screenshot shows v1 info, while the screenshot of the old search result
shows v2 info.

Or maybe there is something else going on here.

screenshot from 2018-03-30 11-59-49
screenshot from 2018-03-30 11-59-40

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.