Giter VIP home page Giter VIP logo

jobs-caen-camp's People

Contributors

albertlanne avatar alexisjanvier avatar chroq avatar gaelreyrol avatar keksoj avatar nexoid avatar quen2404 avatar tmaziere avatar

Stargazers

 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

jobs-caen-camp's Issues

Svelte router

Bonjour à tous,
j'ai une petite question concernant l'un des choix pour la partie frontend: le router de svelte.
pourquoi etre parti sur svelte-routing et non sur la solution officiel Sapper ?

Cypress cannot write to the cache directory due to file permissions

Je rencontre un soucis de droits d'écriture lors du make install, et plus particulièrement au moment d'écrire dans le cache de Cypress (install Docker).

Si j'en crois cypress-io/cypress#1281 on aurait peut-être intérêt à préciser CYPRESS_CACHE_FOLDER (env).

error /jobboard/node_modules/cypress: Command failed.
Exit code: 1
Command: node index.js --exec install
Arguments: 
Directory: /jobboard/node_modules/cypress
Output:
Cypress cannot write to the cache directory due to file permissions

See discussion and possible solutions at
https://github.com/cypress-io/cypress/issues/1281

----------

Failed to access /.cache/Cypress:

EACCES: permission denied, mkdir '/.cache'

(MacOS 10.14.6)

Avis bienvenus !

Amélioration du modèle des offres d’emplois

Notre modèle d’offre d’emploi est encore très minimal, et se calque sur le modèle décrit dans schema.org

{
  "title": "Developpeur Javascript",
  "url": "https://jobs.caen.camp",
  "datePosted": "2010-03-02",
  "employerOverview": "Au sein d'une équipe DSI composée de juniors et de séniors",
  "employmentType": "CDD",
  "experienceRequirements": "3 ans d'experience sur un projet Javascript",
  "jobStartDate": "2020-05-02",
  "skills": "JavaScript, Devops, Php, ...",
  "validThrough": "2010-05-05",
  "hiringOrganization": {
    "identifier": "a122edec-5580-4a93-aff7-fc18b41e4c57",
    "name": "Incaya",
    "image": "https://www.incaya.fr/static/logo-incaya.svg",
    "url": "https://www.incaya.fr/",
    "address": {
      "addressCountry": "France",
      "addressLocality": "Caen",
      "postalCode": "14000"
    }
  }
}

Mais cela ne répond pas forcement à vos/nos attentes en tant que développeurs !

Que souhaiteriez-vous ajouter à ce modèle ? Quelles sont les informations qui vous intéresse vraiment lorsque vous regardez une offre d’emploi ? Et celle dont finalement on se moque ? Quelles sont les informations que vous aimeriez avoir et que vous ne trouvez jamais ?

Refonte de la rédaction de la requête côté react-admin

Après avoir refondu la gestion de la pagination, du tri et des filtres, il serait chouette de mettre à jour le front (en tout cas React-admin) pour qu'il ne tente plus d'appeler le back ainsi :

job-postings?filter=%7B%22jobStartDate_before%22%3A%222020-05-05%22%7D&order=ASC&page=1&perPage=10&sort=name

mais ainsi : 

job-postings?jobStartDate:lt=2020-05-05&sortBy=title&orderBy=ASC&currentPage=1&perPage=10

Le contrat OpenApi contient les specs nécessaires, d'ailleurs consultables avec Swagger sur l'endpoint /documentation de l'API.

Je vais me pencher dessus, même si je ne connais pas encore Svelte ou react.
Je pense que ça doit être une bonne porte d'entrée dans le code du front, aussi je mets le label good first issue.

Mise en place du modèle de données

Comment allons nous structurer les données ?
Lors de cette mise en place, nous avons convenu de créer le modèle sous forme d'objets json afin de les implémenter rapidement au sein du service "api-mocked".

Amélioration de l'environnement de développement sans Docker

Pour le moment, le Makefile du projet s'appuie sur un environnement de developpement local utilisant Docker.
Mais c'est un parti pris contestable et nous ne devrions pas imposer l'utilisation de Docker et Docker Compose.

Il faudrait donc trouver un moyen de rendre le Makefile utilisable, en utilisant Docker, ou pas !

Je n'ai pas trop d'idées pour le moment sur le comment faire ... Ajout d'une variable d'environnement à la charge du développeur ? Detection du l'existance de Docker Compose en début de Makefile ?

A voir.

Toujours est-il que ce fix devrait être documenter dans le guide de contribution.

Et idéallement être expliqué/documenté hors du context du jobBoard dans le coding-caen-camp afin d'en faire profiter d'autre projets

Mise en place d’un flux RSS

Le contenu de site est prévu pour s’afficher sur une application web. Mais ce front n’est pas nécessairement le seul consommateur de l’API. Nous pourrions mettre un place un flux RSS

Refonte des filtres de l'API

Une mise à jour récente de React-Admin a fait apparaitre un problème au niveau de l'implémentation actuelle du tri (issue #56) et de la pagination (issue #55) des listes de l'API.

L'objet filtre utilisé actuellement n'est lui pas impacté par le problème de JSON.stringify du sort et du pagination. Pour autant, l'implémentation des filtres est-elle satisfaisante ?

Pour rappel, voici comment est déclarée la pagination dans la contrat OpenAPI :

// https://github.com/CaenCamp/jobs-caen-camp/blob/master/apps/api/openapi/openapi.yaml#L1052

    Filter:
      name: filters
      in: query
      description: "Filtres appliquables à la requête. C'est un objet json stringifié, avec les filtres en clé/valeur séparé par des virgules."
      required: false
      explode: true
      schema:
        type: string

Nous avons donc un objet serialisé en json, portant les clé/valeur des filtres à appliquer.
Une de problème évidant est la difficulté à documenter proprement cet objet filtre générique, en détaillant les clés applicable en fonction des objets filtrés.

Et regardons ce que disent les Bests practices concernant les filtres d'API :

Il semble donc qu'une première bonne pratique consiste tout d'abord à extraire chaque filtre en tant que paramètre de requête à part entière.

Ce qui transformerait une ancienne requête /api/organizations?filters=%7B%22title%22%3A%22toto%22%2C%22postalCode%22%3A1400%7D en api/organizations?title=toto&postalCode=14000

L'autre point / bonne pratique concerne la manière dont sera appliqué le filtre. En effet, pour le moment, nous avons de filtre qui ont des comportements différents et des nommages ne correspondant plus au nom de la propriété que l'on filtre. Par exemple, pour l'objet jobPosting, nous avons des filtres :

  • employmentType qui filtre sur l'integralité de la valeur passée (en sql : WHERE employment_type = 'value'),
  • title qui filtre sur un LIKE de la valeur passée (en sql : WHERE title LIKE '%value'),
  • jobStartDate_after qui filtre sur une date de début d'emploi supérieure à la date passée en paramètre (en sql : WHERE jobStartDate > dateValue)

Et encore une fois, tout ceci est en l'état difficilement documentable via OpenAPI.

Pour répondre à ce probléme, je propose l'adoption des RHS Colon en les adaptant légèrement, c'est à dire : key=value(:operator)
L'opérateur devient alors optionnel, avec une valeur par défault eq (=). Ceci semble en effet plus facile à utiliser pour l'utilisateur final.

Voici une liste d'opérateur à implémenter :

operateur applicable à explication
:eq string, number, date Is equal to
:gt number, date Is greater than
:lt number, date Is less than
:gte number, date Is greater than or equal to
:lte number, date Is less than or equal to

Ce qui transformerait une ancienne requête /api/organizations?filters={ employmentType: 'CDD', jobStartDate_after='2020/09/01' } en api/organizations?employmentType=CDD&jobStartDate=2020/09/01:gte

Concernant les recherche de %LIKE, je propose de regrouper toutes ces requêtes sur un paramètre unique q qui effectura toujours une recherche de type %LIKE en base de données, quand bien même ce LIKE devrait être appliqué sur plusieurs champs en même temps.

Ce qui transformerait une ancienne requête /api/organizations?filters={ title: 'Javasc' } en api/organizations?q=Javasc

Todo list

  • Rédiger un ADR concernant cette nouvelle implémentation (pour l'historique. Ne pas hésiter à reprendre la présente issue)
  • Reprendre la gestion du filtres dans les router.s de job-posting et organization
  • Reprendre la gestion du filtres dans les repository de job-posting et organization
  • Mettre à jour le contrat OpenAPI
  • Mettre à jour les tests
  • Mettre à jour le dataProvider de react-admin

Bref, c'est une très bonne occasion de rentrer dans le code du projet !

Réparer le mapping de l'utilisateur courant sur les conteneurs Docker

Dans le Makefile et les docker-compose.yml, nous mappons les UID et GUID de l'utilisateur de l'hôte sur l'utilisateur du conteneur. Cela permet par exemple que cet utilisateur soit propriétaire de tous les fichiers créés dans le conteneur (comme les node_modules/) sur les volumes montés entre l'hote et le containeur.

Pour faire cela, nous utilisions ceci en début de Makefile

export CURRENT_UID ?= $(shell id -u):$(shell id -g)

Mais certains environnement (mac ?, windows ?) ne supportaient pas l'utilisation du shell. Cette commande est alors devenu :

export CURRENT_UID ?= $(id -u):$(id -g)

Mais cela pose des problème au niveau de la CI (Github actions).

Du coup, ce patch a été mis en place

CURRENT_UID=$(id -u):$(id -g)
export CURRENT_UID ?= $(shell id -u):$(shell id -g)

Si cela semble avoir réglé le provlème sur les environnement prédédement cité, maintenant, cela ne fonction plus sous Linux (Ubuntu)

Il faudrait trouver une solution fiable pour régler ce problème.

Permettre le formatage des contenus en markdown.

Certain type de contenu (la description d’une entreprise, la description des offre d’emploi) ne permet pas pour le moment de faire une mise en forme du contenu. Il faudrait permettre d’appliquer un balisage markdown à ces contenus.

Publication des annonces sur Slack

Le contenu de site est prévu pour s’afficher sur une application web. Mais ce front n’est pas nécessairement le seul consommateur de l’API. Nous pourrions mettre un place un bot pour publier les offres d’emplois sur Slack

Mise en place de yarn workspace avec CRA

Avec la mise en place de react-admin, il s'impose la nécessité d'utiliser les workspaces yarn pour mieux gérer les différents "packages".

CF cette PR : #36

J'ai réussi à mettre en place, sur un autre projet, les workspaces avec plusieurs CRA en utilisant craco, un petit tool qui permet d'override la config webpack pour passer les composants reacts des packages sur babel-loader :

https://github.com/gsoft-inc/craco

le fichier craco.config.js en question :

const path = require('path');
const { getLoader, loaderByName } = require('@craco/craco');

// Transform into an array if you want to include more babelized packages

const absolutePath = path.join(__dirname, 'packages/core/src');
module.exports = {
  webpack: {
    alias: {},
    plugins: [],
    configure: webpackConfig => {
      const { isFound, match } = getLoader(webpackConfig, loaderByName('babel-loader'));
      if (isFound) {
        const include = Array.isArray(match.loader.include)
          ? match.loader.include
          : [match.loader.include];
        include.push(absolutePath); // Add core packages to the babel-loader list. Use concat in case of array.
        match.loader.include = include; // Push it to the config

      }

      return webpackConfig;
    },
  },
};

Cette snippet permet dans un package, de consommer des composants d'un autre package en les passant via babel-loader.
ll suffit ensuite d'utiliser les commandes craco à la place de react-scripts dans le package.json en question.

Que pensez-vous de ce système ?

Je l'ai mis en place dans ma boîte, ça fait 3 semaines que ça tourne bien.
Nous utilisions avant https://github.com/react-workspaces/create-react-app mais il ne semble plus activement maintenu et c'est un remplaçant à CRA tandis que Craco n'est qu'une couche d'override.

Sécurisation de l’administration

Nous avons mis en place une administration basée sur react-admin. Avant de pouvoir mettre en ligne le site et son administration, il faut mettre en place une authentification sur react-admin.

Bootstrap du projet

Afin de chacun puisse commencer à participer au projet :

  • Mise en place du Monorepo
  • Ajout du package api
  • Ajout du package api-mock
  • Mise en place de Docker Compose
  • Ajout d'un makefile

Refonte du tri de l'API

Tout comme le problème de pagination décrit dans l'issue #55, une mise à jour récente de React-Admin a fait apparaitre un problème au niveau de l'implémentation actuelle du tri des listes de l'API.

Pour rappel, voici comment est déclarée la pagination de la contrat OpenAPI :

// https://github.com/CaenCamp/jobs-caen-camp/blob/master/apps/api/openapi/openapi.yaml#L1060

    Sort:
      name: sort
      in: query
      description: "Le tri applicable à la liste. C'est un tableau stringifié de la forme [sortProp, sortDirection]"
      required: false
      explode: false
      schema:
        type: array
        maxItems: 2
        minItems: 2
        items:
          type: string

Même cause, même effet, le problème vient donc de l'utilisation d'un tableau stringifié pour gérer le tri. (Encore une fois, la lecture de l'issue #55 vous renseignera sur le principe du problème à adresser dans cette issue).

Revenons à quelques Best Practices :

Je propose donc comme dans le cas de la pagination d'extraire les deux paramètres de tri utilisés dans la tableau sort en deux paramètres : sortBy et orderBy

Ce qui transformerait une ancienne requête /api/organizations?sort=%5B%22title%22%2C%22DESC%22%5D en api/organizations?sortBy=title&orderBy=DESC

On pourrait débattre du camelCase utilisé sur le nom de ces paramètres. Une url n'est pas sensible à la casse. En effet, le W3C dit:

There may be URLs, or parts of URLs, where case doesn't matter, but identifying these may not be easy. Users should always consider that URLs are case-sensitive.

Du coup, pourquoi ne pas utiliser du kebab-case ou du snake_case ?

Et bien parce que tout les paramètres d'un objet implémenté dans notre API sont en camelCase ! Entre autre pour être compatible avec schema.org. Du coup, nous devrons transmettre les filtres par propriété (voir l'issue #XX) en camelCase. Il semble donc préférable de rester cohérent.

Par contre, il faudra bien considérer lors du traitement de ces paramètres de requête au niveau de l'API qu'ils ne doivent pas être sensibles à la casse !

Todo list

  • Rédiger un ADR concernant cette nouvelle implémentation (pour l'historique. Ne pas hésiter à reprendre la présente issue)
  • Reprendre la gestion du tri dans les router.s de job-posting et organization
  • Reprendre la gestion du tri dans les repository de job-posting et organization
  • Mettre à jour le contrat OpenAPI
  • Mettre à jour les tests
  • Mettre à jour le dataProvider de react-admin

Bref, c'est une très bonne occasion de rentrer dans le code du projet !

Erreur lors d'un make install

Le make file declare 2 variables d'environnement au début du fichier:

export UID = $(shell id -u)
export GID = $(shell id -g)

Si la commande shell n'existe pas, aucune erreur n'est remonté mais le yarn install va échouer car il n'aura pas les bonnes permissions étant donné que le UID & le GID ne seront pas valides ...

Partage des offres d’emploi par activityPub/Activity Streams

Le contenu de site est prévu pour s’afficher sur une application web. Mais ce front n’est pas nécessairement le seul consommateur de l’API. Nous pourrions mettre un place un mécanisme de diffusion des offres d’emploi via un flux Activity Streams

Besoin de règles de formatage pour openapi.yaml

Le fichier openapi est en yaml, avec des tabulations larges de deux espaces. VScode me propose deux linters différents, mais comment les configurer ? J'ai peur de tout casser.

Ce qui serait super, ce serait d'avoir un fichier du genre .ymllinterrc avec les règles de formatages pour le yaml, dans le fichier racine. Qu'en pensez-vous ? Connaissez-vous des solutions ?

Refonte de la pagination de l'API

Une mise à jour récente de React-Admin a fait apparaitre un problème au niveau de l'implémentation actuelle de la pagination des listes de l'API.

Pour rappel, voici comment est déclarée la pagination de la contrat OpenAPI :

// dans jobs-caen-camp/apps/api/openapi/openapi.yaml, ligne 1072
// https://github.com/CaenCamp/jobs-caen-camp/blob/master/apps/api/openapi/openapi.yaml#L1072

Pagination:
    name: pagination
    in: query
    description: "Les paramètres pour la pagination. C'est un tableau stringifié de la forme [perPage, currentPage]"
    required: false
    explode: false
    schema:
    type: array
    maxItems: 2
    minItems: 2
    items:
        type: string

Soit un tableau de type [perPage, currentPage].
Alors tout d'abord, l'utilisation d'un tableau à deux valeurs n'est pas forcement très parlant. On se pose un peu toujours le question de savoir à quoi correspond le premier et le second, ces paramètres n'étant pas nommés.
Mais un second problème provient du fait que c'est un tableau qui est déclaré dans OpenAPI, et non un string.
Hors, voici ce que faisait jusqu'à présent le dataProvider de react-admin

// https://github.com/CaenCamp/jobs-caen-camp/blob/master/apps/admin/src/jobBoardDataProvider.js#L24
const query = {
    sort: JSON.stringify([field, order]),
    filters: JSON.stringify(params.filter),
    pagination: JSON.stringify([perPage, page])
};

C'est à dire que l'on transforme le tableau en string. C'est d'ailleurs ce qui est implémenter dans tous les tests, par exemple :

// https://github.com/CaenCamp/jobs-caen-camp/blob/master/tests-e2e/api/job-postings.spec.js#L139
await frisby
    .get(
        `http://api:3001/api/job-postings?pagination=${JSON.stringify(
            [2, 1]
        )}`
    )
    ...

Or depuis la mise à jours de react-admin, voici ce que retourne le serveur :

RequestValidationError: Schema validation error (/pagination: minItems should NOT have fewer than 2 items)

Le premier patch envisagé à consister à ne plus stringifier pagination depuis la dataProvider. Cela a effectivement réglé le problème de l'erreur, mais du coup, la pagination ne fonctionnait plus. Ce qui est logique, puisque si l'on regarde ce que fait l'API :

// https://github.com/CaenCamp/jobs-caen-camp/blob/master/apps/api/src/job-posting/router.js#L20
const { jobPostings, contentRange } = await getJobPostingPaginatedList({
    client: ctx.db,
    filters: parseJsonQueryParameter(ctx.query.filters),
    sort: parseJsonQueryParameter(ctx.query.sort),
    pagination: parseJsonQueryParameter(ctx.query.pagination),
});

// https://github.com/CaenCamp/jobs-caen-camp/blob/f474868ba4d81456c7a8a97ce256cd27f3acbc51/apps/api/src/toolbox/sanitizers.js#L96
const parseJsonQueryParameter = parameter => {
    if (parameter === undefined) {
        return false;
    }
    try {
        return JSON.parse(parameter);
    } catch (e) {
        return false;
    }
};

C'est à dire que l'on s'attend au niveau de l'API à avoir une pagination stringifiée, qu'on essaye de la parser, et que l'on renvoi false si on a une erreur. C'est ce qui se passe si l'on ne fait plus pagination: parseJsonQueryParameter(ctx.query.pagination) dans le dataProvider.

Du coup, le seconde solution de résolution de problème a été de modifier le contrat OpenAPI en déclarant une string pour la pagination plutôt qu'un array (comme pour les filters, ce qui est un autre problème, voir l'issue #). Si cela resolvait la problème côté react-admin, cela a provoqué beaucoup d'echec dans les tests déjà implémenté ...

Et c'est là qu'on peut commencer à se dire sérieusement : "cette manière de gérer la pagination est-elle vraiment la bonne ?"

En se renseignant un peu sur les Best Practices en la matière, il semblerait bien que non ^^

Voici donc la nouvelle implémentation proposée pour la pagination :

Utilisation des paramètres de requête page et perPage

Ce qui consiste simplement à renvoyer les paramètres qui étaient jusqu'à présent envoyés dans le tableau pagination en deux paramètre explicitement nommés.

Ce qui transformerait une ancienne requête /api/organizations?pagination=%22%5B10%2C1%5D%22 en api/organizations?page=1&perPage=10

On pourrait discuter de méthode plus efficiente de pagination (Fast pagination on PostgreSQL ou Five ways to paginate in Postgres, from the basic to the exotic), mais pour autant, et vu nos besoins et nos volumes de données les plus optimistes, il semble que page et perPage soit très compréhensibles et ils conviennent parfaitement à l'implémentation de la pagination déjà en place.

Suppression du Content-Range comme support d'information sur la pagination

Jusqu'à présent, nous utilisons le header http Content-Range pour renvoyer les informations concernant la pagination (information sur la page en cours, le nombre de résultats retournés et le nombre total de page). Cela nous permet entre autre de ne pas utiliser un formatage du retour d'API qui inclurait les informations de pagination (problématique de HATEOAS)

// https://github.com/CaenCamp/jobs-caen-camp/blob/f474868ba4d81456c7a8a97ce256cd27f3acbc51/apps/api/src/toolbox/sanitizers.js#L85

/**
 * Transforms the Knex paging object into a string compatible with the "content-Range" header.
 * https://developer.mozilla.org/fr/docs/Web/HTTP/Headers/Content-Range
 *
 * @param {string} objectType - type of object returned in the paginated collection
 * @param {object} pagination - Knex pagination object (https://github.com/felixmosh/knex-paginate#pagination-object)
 * @returns {string} string ready to be set has "Content-Range" http header
 * @example Content-Range: posts 0-24/319
 */
const formatPaginationContentRange = (objectType, pagination) =>
`${objectType.toLowerCase()} ${pagination.from}-${pagination.to}/${
    pagination.total
}`;

Or, si on lit bien la spécification de Content-Range on se rend vite compte que l'utilisation de ce header pour la pagination n'est pas la bonne manière de faire, car ce header a une autre signification.

The Content-Range response HTTP header indicates where in a full body message a partial message belongs.

Ce qui correspond donc à un retour http de type 206 Partial Content.

Ajout d'un header http X-Total-Count

Donc, pour ne pas avoir à reformater le corps de notre réponse pour y inclure la pagination, mais retourner un paramètre indispensable à la pagination, c'est à dire le nombre total de résultats possible, nous allons implémenter une header http spécifique : le X-Total-Count. C'est un header certes spécifique, mais assez largement utilisé.

Ajout d'une balise Link dans le header concernant la pagination

Toujours pour ne pas à avoir à implémenter une structure particulière au body de la réponse de l'API, nous allons rajouter une ou des balises Link dans l'en-tête de la réponse afin de respecter les bonnes pratiques. L'utilisation de cette balise est décrite dans ce post Link header introduced by RFC 5988 et prendrait la forme :

Link: <https://job.caen.camp/api/organization?page=2&perPage=10>; rel="next", <https://job.caen.camp/api/organization?page=6&perPage=10>; rel="last"

On pourra s'inspirer de l'implémentation de Github.

Todo list

  • Rédiger un ADR concernant cette nouvelle implémentation (pour l'historique. Ne pas hésiter à reprendre la présente issue)
  • Reprendre la gestion de la pagination dans les router.s de job-posting et organization
  • Reprendre la gestion de la pagination dans les repository de job-posting et organization
  • Mettre à jour le contrat OpenAPI
  • Mettre à jour les tests
  • Mettre à jour le dataProvider de react-admin

Bref, c'est une très bonne occasion de rentrer dans le code du projet !

OpenAPI : rajouter les requêtes de type json-ld

Le contrat OpenAPI décrit les requêtes de type json (application/json). Le format des données respectant la description des entreprises et des offres d’emploi décrite par schema.org, il pourrait être intéressant de rajouter la description des requêtes sur le type application/json-ld

OpenAPI : rajouter des exemples de paramètres de requête

Dans la documentation Swagger génére depuis le contrat openAPI, il n’y a pas d’exemple pour les paramètres de requêtes pour les listes paginées (filters, sort et pagination).

Ce serait un plus que de rajouter ces exemples au contrat

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.