Git Product home page Git Product logo

eloquent-viewable's Introduction

Eloquent Viewable

Packagist run-tests StyleCI Codecov branch Total Downloads license

This Laravel >= 6.0 package allows you to associate views with Eloquent models.

Once installed you can do stuff like this:

// Return total views count
views($post)->count();

// Return total views count that have been made since 20 February 2017
views($post)->period(Period::since('2017-02-20'))->count();

// Return total views count that have been made between 2014 and 2016
views($post)->period(Period::create('2014', '2016'))->count();

// Return total unique views count (based on visitor cookie)
views($post)->unique()->count();

// Record a view
views($post)->record();

// Record a view with a cooldown
views($post)->cooldown(now()->addHours(2))->record();

Overview

Sometimes you don't want to pull in a third-party service like Google Analytics to track your application's page views. Then this package comes in handy. Eloquent Viewable allows you to easiliy associate views with Eloquent models. It's designed with simplicity in mind.

This package stores each view record individually in the database. The advantage of this is that it allows us to make very specific counts. For example, if we want to know how many people has viewed a specific post between January 10 and February 17 in 2018, we can do the following: views($post)->period(Period::create('10-01-2018', '17-02-2018'))->count();. The disadvantage of this is that your database can grow rapidly in size depending on the amount of visitors your application has.

Features

Here are some of the main features:

  • Associate views with Eloquent models
  • Get total views count
  • Get views count of a specific period
  • Get unique views count
  • Get views count of a viewable type (Eloquent model class)
  • Order viewables by views
  • Set a cooldown between views
  • Elegant cache wrapper built-in
  • Ignore views from crawlers, ignored IP addresses or requests with DNT header

Documentation

In this documentation, you will find some helpful information about the use of this Laravel package.

Table of contents

  1. Getting Started
  2. Usage
  3. Optimizing
  4. Extending

Getting Started

Requirements

This package requires PHP 7.4+ and Laravel 6+.

Support for Lumen is not maintained.

Version information

Version Illuminate Status PHP Version
^7.0 6.x.x - 11.x.x Active support by contributions >= 7.4.0
^6.0 6.x.x - 8.x.x End of life >= 7.4.0
^5.0 6.x.x - 8.x.x End of life >= 7.2.0
^4.0 5.5.x - 5.8.x End of life >= 7.1.0
^3.0 5.5.x - 5.8.x End of life >= 7.1.0
^2.0 5.5.x - 5.7.x End of life >= 7.0.0
^1.0 5.5.x - 5.6.x End of life >= 7.0.0

Installation

First, you need to install the package via Composer:

composer require cyrildewit/eloquent-viewable

Secondly, you can publish the migrations with:

php artisan vendor:publish --provider="CyrildeWit\EloquentViewable\EloquentViewableServiceProvider" --tag="migrations"

Finally, you need to run the migrate command:

php artisan migrate

You can optionally publish the config file with:

php artisan vendor:publish --provider="CyrildeWit\EloquentViewable\EloquentViewableServiceProvider" --tag="config"

Register service provider manually

If you prefer to register packages manually, you can add the following provider to your application's providers list.

// config/app.php

'providers' => [
    // ...
    CyrildeWit\EloquentViewable\EloquentViewableServiceProvider::class,
];

Usage

Preparing your model

To associate views with a model, the model must implement the following interface and trait:

  • Interface: CyrildeWit\EloquentViewable\Contracts\Viewable
  • Trait: CyrildeWit\EloquentViewable\InteractsWithViews

Example:

use Illuminate\Database\Eloquent\Model;
use CyrildeWit\EloquentViewable\InteractsWithViews;
use CyrildeWit\EloquentViewable\Contracts\Viewable;

class Post extends Model implements Viewable
{
    use InteractsWithViews;

    // ...
}

Recording views

To make a view record, you can call the record method on the fluent Views instance.

views($post)->record();

The best place where you should record a visitors's view would be inside your controller. For example:

// PostController.php
public function show(Post $post)
{
    views($post)->record();

    return view('post.show', compact('post'));
}

Note: This package filters out crawlers by default. Be aware of this when testing, because Postman is for example also a crawler.

Setting a cooldown

You may use the cooldown method on the Views instance to add a cooldown between view records. When you set a cooldown, you need to specify the number of minutes.

views($post)
    ->cooldown($minutes)
    ->record();

Instead of passing the number of minutes as an integer, you can also pass a DateTimeInterface instance.

$expiresAt = now()->addHours(3);

views($post)
    ->cooldown($expiresAt)
    ->record();

How it works

When recording a view with a session delay, this package will also save a snapshot of the view in the visitor's session with an expiration datetime. Whenever the visitor views the item again, this package will checks his session and decide if the view should be saved in the database or not.

Retrieving views counts

Get total views count

views($post)->count();

Get views count of a specific period

use CyrildeWit\EloquentViewable\Support\Period;

// Example: get views count from 2017 upto 2018
views($post)
    ->period(Period::create('2017', '2018'))
    ->count();

The Period class that comes with this package provides many handy features. The API of the Period class looks as follows:

Between two datetimes
$startDateTime = Carbon::createFromDate(2017, 4, 12);
$endDateTime = '2017-06-12';

Period::create($startDateTime, $endDateTime);
Since a datetime
Period::since(Carbon::create(2017));
Upto a datetime
Period::upto(Carbon::createFromDate(2018, 6, 1));
Since past

Uses Carbon::today() as start datetime minus the given unit.

Period::pastDays(int $days);
Period::pastWeeks(int $weeks);
Period::pastMonths(int $months);
Period::pastYears(int $years);
Since sub

Uses Carbon::now() as start datetime minus the given unit.

Period::subSeconds(int $seconds);
Period::subMinutes(int $minutes);
Period::subHours(int $hours);
Period::subDays(int $days);
Period::subWeeks(int $weeks);
Period::subMonths(int $months);
Period::subYears(int $years);

Get total unique views count

If you only want to retrieve the unique views count, you can simply add the unique method to the chain.

views($post)
    ->unique()
    ->count();

Order models by views count

The Viewable trait adds two scopes to your model: orderByViews and orderByUniqueViews.

Order by views count

Post::orderByViews()->get(); // descending
Post::orderByViews('asc')->get(); // ascending

Order by unique views count

Post::orderByUniqueViews()->get(); // descending
Post::orderByUniqueViews('asc')->get(); // ascending

Order by views count within the specified period

Post::orderByViews('asc', Period::pastDays(3))->get();  // descending
Post::orderByViews('desc', Period::pastDays(3))->get(); // ascending

And of course, it's also possible with the unique views variant:

Post::orderByUniqueViews('asc', Period::pastDays(3))->get();  // descending
Post::orderByUniqueViews('desc', Period::pastDays(3))->get(); // ascending

Order by views count within the specified collection

Post::orderByViews('asc', null, 'custom-collection')->get();  // descending
Post::orderByViews('desc', null, 'custom-collection')->get(); // ascending

Post::orderByUniqueViews('asc', null, 'custom-collection')->get();  // descending
Post::orderByUniqueViews('desc', null, 'custom-collection')->get(); // ascending

Get views count of viewable type

If you want to know how many views a specific viewable type has, you need to pass an empty Eloquent model to the views() helper like so:

views(new Post())->count();

You can also pass a fully qualified class name. The package will then resolve an instance from the application container.

views(Post::class)->count();
views('App\Post')->count();

View collections

If you have different types of views for the same viewable type, you may want to store them in their own collection.

views($post)
    ->collection('customCollection')
    ->record();

To retrieve the views count in a specific collection, you can reuse the same collection() method.

views($post)
    ->collection('customCollection')
    ->count();

Remove views on delete

To automatically delete all views of an viewable Eloquent model on delete, you can enable it by setting the removeViewsOnDelete property to true in your model definition.

protected $removeViewsOnDelete = true;

Caching view counts

Caching the views count can be challenging in some scenarios. The period can be for example dynamic which makes caching not possible. That's why you can make use of the in-built caching functionality.

To cache the views count, simply add the remember() method to the chain. The default lifetime is forever.

Examples:

views($post)->remember()->count();
views($post)->period(Period::create('2018-01-24', '2018-05-22'))->remember()->count();
views($post)->period(Period::upto('2018-11-10'))->unique()->remember()->count();
views($post)->period(Period::pastMonths(2))->remember()->count();
views($post)->period(Period::subHours(6))->remember()->count();
// Cache for 3600 seconds
views($post)->remember(3600)->count();

// Cache until the defined DateTime
views($post)->remember(now()->addWeeks(2))->count();

// Cache forever
views($post)->remember()->count();

Optimizing

Benchmarks

Database indexes

The default views table migration file has already two indexes for viewable_id and viewable_type.

If you have enough storage available, you can add another index for the visitor column. Depending on the amount of views, this may speed up your queries in some cases.

Caching

Caching views counts can have a big impact on the performance of your application. You can read the documentation about caching the views count here

Using the remember() method will only cache view counts made by the count() method. The orderByViews and orderByUnique query scopes aren't using these values because they only add something to the query builder. To optimize these queries, you can add an extra column or multiple columns to your viewable database table with these counts.

Example: we want to order our blog posts by unique views count. The first thing that may come to your mind is to use the orderByUniqueViews query scope.

$posts = Post::latest()->orderByUniqueViews()->paginate(20);

This query is quite slow when you have a lot of views stored. To speed things up, you can add for example a unique_views_count column to your posts table. We will have to update this column periodically with the unique views count. This can easily be achieved using a schedued Laravel command.

There may be a faster way to do this, but such command can be like:

$posts = Post::all();

foreach($posts as $post) {
    $post->unique_views_count = views($post)->unique()->count();
}

To be updated! Laravel has a nice chunk and cursor feature what may come in handy.

Extending

If you want to extend or replace one of the core classes with your own implementations, you can override them:

  • CyrildeWit\EloquentViewable\Views
  • CyrildeWit\EloquentViewable\View
  • CyrildeWit\EloquentViewable\Visitor
  • CyrildeWit\EloquentViewable\CrawlerDetectAdapter

Note: Don't forget that all custom classes must implement their original interfaces

Custom information about visitor

The Visitor class is responsible for providing the Views builder information about the current visitor. The following information is provided:

  • a unique identifier (stored in a cookie)
  • ip address
  • check for Do No Track header
  • check for crawler

The default Visitor class gets its information from the request. Therefore, you may experience some issues when using the Views builder via a RESTful API. To solve this, you will need to provide your own data about the visitor.

You can override the Visitor class globally or locally.

Create your own Visitor class

Create you own Visitor class in your Laravel application and implement the CyrildeWit\EloquentViewable\Contracts\Visitor interface. Create the required methods by the interface.

Alternatively, you can extend the default Visitor class that comes with this package.

Globally

Simply bind your custom Visitor implementation to the CyrildeWit\EloquentViewable\Contracts\Visitor contract.

$this->app->bind(
    \CyrildeWit\EloquentViewable\Contracts\Visitor::class,
    \App\Services\Views\Visitor::class
);

Locally

You can also set the visitor instance using the useVisitor setter method on the Views builder.

use App\Services\Views\Visitor;

views($post)
    ->useVisitor(new Visitor()) // or app(Visitor::class)
    ->record();

Using your own Views Eloquent model

Bind your custom Views implementation to the \CyrildeWit\EloquentViewable\Contracts\Views.

Change the following code snippet and place it in the register method in a service provider (for example AppServiceProvider).

$this->app->bind(
    \CyrildeWit\EloquentViewable\Contracts\Views::class,
    \App\Services\Views\Views::class
);

Using your own View Eloquent model

Bind your custom View implementation to the \CyrildeWit\EloquentViewable\Contracts\View.

Change the following code snippet and place it in the register method in a service provider (for example AppServiceProvider).

$this->app->bind(
    \CyrildeWit\EloquentViewable\Contracts\View::class,
    \App\Models\View::class
);

Using a custom crawler detector

Bind your custom CrawlerDetector implementation to the \CyrildeWit\EloquentViewable\Contracts\CrawlerDetector.

Change the following code snippet and place it in the register method in a service provider (for example AppServiceProvider).

$this->app->singleton(
    \CyrildeWit\EloquentViewable\Contracts\CrawlerDetector::class,
    \App\Services\Views\CustomCrawlerDetectorAdapter::class
);

Adding macros to the Views class

use CyrildeWit\EloquentViewable\Views;

Views::macro('countAndRemember', function () {
    return $this->remember()->count();
});

Now you're able to use this shorthand like this:

views($post)->countAndRemember();

Views::forViewable($post)->countAndRemember();

Upgrading

Please see UPGRADING for detailed upgrade guide.

Changelog

Please see CHANGELOG for more information on what has changed recently.

Contributing

Please see CONTRIBUTING for details.

Credits

See also the list of contributors who participated in this project.

Helpful Resources:

Alternatives

Feel free to add more alternatives!

License

This project is licensed under the MIT License - see the LICENSE file for details.

eloquent-viewable's People

Contributors

aren1989 avatar atmonshi avatar blackjak231 avatar cstriuli avatar cyrildewit avatar dmyers avatar janyksteenbeek avatar kudashevs avatar livijn avatar ludo237 avatar mahmoud217tr avatar mohamed-hendawy avatar mohamedsabil83 avatar nhalstead avatar p3yman avatar reliq avatar stylecibot 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  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

eloquent-viewable's Issues

$sortedPosts = Post::orderByViewsCount()->get();

  • Eloquent Viewable: 2.1.0
  • Laravel Version: 5.5.28
  • PHP Version: 7.1.0
  • Database Driver & Version:

How use Period?

Class 'App\Http\Controllers\Period' not found

Can I use orderByViewsCount like getViews

$sortedPosts = Post::orderByViewsCount(Period::subDays(7))->get();

I need a POST weekly click list...

PS:My English is very poor, sorry

Column 'visitor' cannot be null

This happens when I clear my Cache and Cookies from the browser and refresh the browser.
I get the error Integrity Violation Column 'visitor' cannot be null.
But if I refresh again, It works.

Could it be that the cookie is not being initiated? How can I fix this?

[2.0] Current status of development

v2.0.0

Main Goals

The main goals are:

  • Cache retrieved views count
  • API refactoring
  • Ignore bots
  • Cover the package with tests
  • Rename package to Eloquent Viewable

Development

Branch: 2.0
Migration possible: yes

Tasks statuses

  • Cache retrieved views count.
  • API refactoring & Package renaming.
  • Ignore bots.
  • Add option to ignore visitors with HTTP_DNT header.
  • Instead of trusting IP addresses, move on to cookies.
  • Improve countViews() in ViewableService.php.
  • Make the View model extendable via bindings, instead of using the config.
  • Save the cookie via routes (improvement).
  • Add scopes to the trait to allow ordering by most views.
  • Finish the contract interfaces.
  • Be able to skip views of IP addresses

Cache retrieved views count & API refactoring

Why?

Since every page view is stored as a seperate entity in the database, counting the views can be a time consuming task. So instead, we could cache views counts in the cache to reduce the amount of queries.

How?

While the whole idea of caching sounds quite simple, it is harder than I thought. The way I would cache views counts is saving the number of views under a unique key that's created based upon the since date, upto date and if the views should be unique. The structure of this key is: <package prefix>.counts.<model type>.<model id>.<request type: normal | unique>.<request period: <since?>|<upto?>>.

Segment name Description
package prefix Default: 'cyrildewit.eloquent-viewable'. This can be changed in the config file.
model type The class namespace of the viewable model
model id The primary id of the viewable model
request type Default: 'normal'. Unique views: 'unique'
request period The structure of this part is: '<since?>|<upto?>'.
If the given date is reactive (like past 3 days or past 2 weeks) they will not be stored as a date string

API refactoring & Package renaming

Viewable trait

Old

$article->getPageViews();
$article->getUniquePageViews();
$article->getPageViewsFrom($sinceDate);
$article->getUniquePageViewsFrom($sinceDate);
$article->getPageViewsBefore($uptoDate);
$article->getUniquePageViewsBefore($uptoDate);
$article->getPageViewsBetween($sinceDate, $uptoDate);
$article->getUniquePageViewsBetween($sinceDate, $uptoDate);

New

$article->getViews();
$article->getUniqueViewsSince();
$article->getViewsSince($sinceDate);
$article->getUniqueViewsSince($sinceDate);
$article->getViewsUpto($uptoDate);
$article->getUniqueViewsUpto($uptoDate);
$article->getViewsBetween($sinceDate, $uptoDate);
$article->getUniqueViewsBetween($sinceDate, $uptoDate);

// New
$article->getViewsOfPastSeconds();
$article->getViewsOfPastMinutes();
$article->getViewsOfPastDays();
$article->getViewsOfPastWeeks();
$article->getViewsOfPastMonths();
$article->getViewsOfPastYears();

Tests

Unit Tests

  • Viewable trait (CyrildeWit\EloquentViewable\Traits\Viewable)
  • ViewableService class (CyrildeWit\EloquentViewable\Services\ViewableService)
  • View class (CyrildeWit\EloquentViewable\Models\View)
  • ViewableObserver class (CyrildeWit\EloquentViewable\Observers\ViewableObserver)
  • CrawlerDetector interface (CyrildeWit\EloquentViewable\Contracts\CrawlerDetector)
  • Period class (CyrildeWit\EloquentViewable\Support\Period)
  • ViewTeacker class (CyrildeWit\EloquentViewable\ViewTracker)

ip_address get null with Laravel 5.5

  • Laravel Page View Counter Version: 1.0.3
  • Laravel Version: 5.5.#
  • PHP Version: 7.1
  • Database Driver & Version: MySQL 5.7

Description:

Thank you for a great package. I installed it and everything seems works well, but I got ip_address is null in DB. Tried to check in HasPageViewCounter Trait and it's using $newView->ip_address = (new Request)->ip();, I think it has problem with Laravel 5.5, could you check for help, please?

Thank you.

Steps To Reproduce:

getting viewable_id null on $model->addView();

  • Eloquent Viewable: "cyrildewit/eloquent-viewable": "^2.1",
  • Laravel Version: "laravel/framework": "5.6.*",
  • PHP Version: "php": "^7.1.3",
  • Database Driver & Version: mysql

Description:

On my local machine I have installed it and migration with the view table is done successfully but calling the model on my post details controller function as below.

public function getPostDetail(Post $post){
$post->addView();
}
giving me error of viewable_id null so not able to store the views in the database. please check below refer for the error.
Refer: http://prntscr.com/k4pjcn

[2.0] Add caching functionality and improve the way of retrieving visits count by using method chaining

Description

Caching visits counts is harder than I thought. I created this issue to discuss my ideas with the package users.

Caching visits counts is getting done by storing the count under a unique key based upon the model, since date, upto date and type (not unique = normal, unique = unique).

The current problem right now is, that we don't know if the since and upto dates are reactive. By that I mean if they are like: past xx days, past xx weeks, next xx days etc. This is because the transformation is done before the package retrieves the date.

For example: now()->subDays(4) is always different, because it's based upon the current time. If we could somehow now about these transformations, we can cache them differently.

Solution (at this moment theory only)

To make this solution more elegant, I have chosen to use method chaining. This way, our models aren't getting filled with all kinds of methods.

// 1 Since and Upto date options
// 1.1 Static dates
->since(<Carbon instance>)
->upto(<Carbon instance>)

// 1.2 Reactive dates
-><since|upto><Sub|Add><Seconds|Minutes|Days|Weeks|Months|Years>() // 2 * 2 * 6 = 24 methods
e.g. ->sinceSubDays(3),
->sinceSubYears(2),
->uptoSubWeeks(10)

// 2 Only unique visits?
->unique()

// 3 Get the visits count
->count(); // or get() ?

Examples:

// Example 1 (static dates)
$post->getVisitsCount()
     ->since(Carbon::parse('01-03-2007'))
     ->upto(Carbon::parse('01-03-2013'))
     ->count();

// Example 1 (reactive date)
$post->getVisitsCount()
     ->sinceSubDays(4)
     ->count();

$post->getVisitsCount()
     ->since(Carbon::parse('01-01-2018')) // static
     ->uptoSubDays(5) // reactive
     ->count();

// Number of unique visits in the past two weeks
$post->getVisitsCount()
     ->sinceSubWeeks(2)
     ->unique()
     ->count();
<p>Total number of visits in the past two days: {{ $post->getVisitsCount()->sinceSubDays(2)->count() }}</p>

Cache key structure

prefix: a unique prefix like: 'eloquent-visitable'
modelType: the class name with namespace
modelId: just the id
type: normal or unique
period: structured like this: '<sinceDate>|<uptoDate>'.
    These dates are static or reactive.

<prefix>.<modelType>.<modelId>.<type>.<period>

Have you removed the viewThatExpiresAt trait from this?

// Store a new page view into the database with an expiry date
$article->addPageViewThatExpiresAt(Carbon::now()->addHours(3));

Have you removed this? or Is there a different way of doing it?

  • Eloquent Viewable: #.#.#
  • Laravel Version: #.#.#
  • PHP Version: #.#.#
  • Database Driver & Version:

Description:

Steps To Reproduce:

  1. [First Step]
  2. [Second Step]
  3. [and so on...]

Expected behavior:

[What you expected to happen]

Actual behavior:

[What actually happened]

Issue with composer dump-autoload: Class 'CyrildeWit\PageViewCounter\PageViewCounterServiceProvider' not found

  • Laravel Page View Counter Version: 1.0.4
  • Laravel Version: 5.5
  • PHP Version: 7.1

Description:

Hi,

I have an issue with your module. Whenever a composer dump-autoload is triggered, I have an error as composer ignored your project.

Symfony\Component\Debug\Exception\FatalThrowableError thrown with message "Class 'CyrildeWit\PageViewCounter\PageViewCounterServiceProvider' not found"

Stacktrace:
#1 Symfony\Component\Debug\Exception\FatalThrowableError in /laravel/vendor/laravel/framework/src/Illuminate/Foundation/ProviderRepository.php:208
#0 {main} in /laravel/public/index.php:0

I can fix it with a

composer dump-autoload -o

In autoload_psr4.php, I have:

'CyrildeWit\\PageViewCounter\\' => array($vendorDir . '/cyrildewit/laravel-page-view-counter/src'),

in installed.json:

 {
        "name": "cyrildewit/laravel-page-view-counter",
        "version": "v1.0.4",
        "version_normalized": "1.0.4.0",
        "source": {
            "type": "git",
            "url": "https://github.com/cyrildewit/laravel-page-view-counter.git",
            "reference": "ae4ec30fae16c9ea567e68897d528a59221adcc2"
        },
        "dist": {
            "type": "zip",
            "url": "https://api.github.com/repos/cyrildewit/laravel-page-view-counter/zipball/ae4ec30fae16c9ea567e68897d528a59221adcc2",
            "reference": "ae4ec30fae16c9ea567e68897d528a59221adcc2",
            "shasum": ""
        },
        "require": {
            "nesbot/carbon": "^1.22"
        },
        "require-dev": {
            "orchestra/testbench": "^3.4",
            "phpunit/phpunit": "~5.7.0"
        },
        "time": "2018-02-20T15:56:47+00:00",
        "type": "library",
        "extra": {
            "laravel": {
                "providers": [
                    "CyrildeWit\\PageViewCounter\\PageViewCounterServiceProvider"
                ]
            }
        },
        "installation-source": "dist",
        "autoload": {
            "psr-4": {
                "CyrildeWit\\PageViewCounter\\": "src/"
            }
        },
        "notification-url": "https://packagist.org/downloads/",
        "license": [
            "MIT"
        ],
        "authors": [
            {
                "name": "Cyril de Wit",
                "email": "[email protected]",
                "homepage": "http://cyrildewit.nl",
                "role": "Developer"
            }
        ],
        "description": "Simple package for storing page views of Eloquent models into the database",
        "homepage": "https://github.com/cyrildewit/laravel-page-view-counter",
        "keywords": [
            "cyrildewit",
            "hits",
            "laravel",
            "model",
            "pageviews",
            "visits"
        ]
    },

in autoload_static.php:

'C' => 
        array (
            'CyrildeWit\\PageViewCounter\\' => 27,
            'Cron\\' => 5,
            'Carbon\\' => 7,
        ),
'CyrildeWit\\PageViewCounter\\' => 
        array (
            0 => __DIR__ . '/..' . '/cyrildewit/laravel-page-view-counter/src',
        ),

With the optimized autoload, I have new entries in autoload_classmap.php

'CyrildeWit\\PageViewCounter\\Contracts\\PageView' => $vendorDir . '/cyrildewit/laravel-page-view-counter/src/PageViewCounter/Contracts/PageView.php',
    'CyrildeWit\\PageViewCounter\\Helpers\\DateTransformer' => $vendorDir . '/cyrildewit/laravel-page-view-counter/src/PageViewCounter/Helpers/DateTransformer.php',
    'CyrildeWit\\PageViewCounter\\Helpers\\SessionHistory' => $vendorDir . '/cyrildewit/laravel-page-view-counter/src/PageViewCounter/Helpers/SessionHistory.php',
    'CyrildeWit\\PageViewCounter\\Models\\PageView' => $vendorDir . '/cyrildewit/laravel-page-view-counter/src/PageViewCounter/Models/PageView.php',
    'CyrildeWit\\PageViewCounter\\PageViewCounterServiceProvider' => $vendorDir . '/cyrildewit/laravel-page-view-counter/src/PageViewCounter/PageViewCounterServiceProvider.php',
    'CyrildeWit\\PageViewCounter\\Traits\\HasPageViewCounter' => $vendorDir . '/cyrildewit/laravel-page-view-counter/src/PageViewCounter/Traits/HasPageViewCounter.php',
    

Thanks!

This does not happen with any other dependencies I have installed.

Steps To Reproduce:

composer dump-autoload

Error inserting into Model with trait

When using the package on my Post model, I am getting an error when inserting/creating a post.
One of the fields i am inserting is 'user_id' it's value is Auth::user()->id.
I get an error telling me 'user_id' has no default value, not sure how or why this is happening.
It's not an issue with my Model because if i remove the trait from the model my insert/create works just fine no issues.

I should also mention this seems to be an issue with the trait and using Auth::user()->id.
If i hard code a value e.g. $user_id = "1"; the insert works.
if i use $user_id = Auth::user()->id; I get the error
Also if i dd(Auth::user()->id) within my create method, I return the correct value.

Issue seems to happen as soon as the create (database action) is hit.

Any help would be very much appreciated.

Some weird Phenomenon happening with my relation when dealing with adding a page view

  • Laravel Page View Counter Version: 2.0.0
  • Laravel Version: 5.5.x-dev
  • PHP Version: 7.2
  • Database Driver & Version:

Description:

I have no idea why this is happening, but I'm currently using OctoberCMS it has a relation called "attachOne" where it allows me to attach an image to the model via a relation (its basically a morph). What happens is that if it has no background and you visit the page the view will be counted twice. However if it does have a background, the view will be counted once. I'm not sure why this is happening.

Forbid Export Data

  • Laravel Page View Counter Version: 0.1.7
  • Laravel Version: 5.5.39
  • PHP Version: 7.1.12
  • Database Driver & Version: MySQL 5.7.19

Description:

Your application works like charm, thank you!

The issue i'm facing is when I export my data as CSV it is included page views info (if i'm not mistake 5 columns will add to my CSV file)

Question:

How can I ban (forbid) your package of having access to my database export?

How to make it work in API's

  • Eloquent Viewable: latest
  • Laravel Version: 5.6
  • PHP Version: 7.0
  • Database Driver & Version:MySQL

Description:

I have an API that returns a post and I want to implement the eloquent-viewable package but it seems it's not working on the API side whatever I do even when I try to return the API from browser, anyway when I'm testing it on browser on a web routes it's working great but the same web route when hit from postman doesn't count anything, I have disabled cache in the config file too.

Steps To Reproduce:

  1. [First Step]
    make API roue from routes->api.php page in laravel that routes to the postcontroller.php->show method.
  2. [Second Step]
    hit that route from browser and postman bothe not working
  3. [and so on...]

Expected behavior:

[What you expected to happen]

Actual behavior:

[What actually happened]

Guide needed

Hi,

  1. Do I have need any controller method in order to save data or no?

  2. these codes

// Return total visits of the article
$article->page_visits
$article->page_visits_formatted

// Return total visits of last 24 hours
$article->page_visits_24h

// Store a new visit into the database
$article->addVisit();

// Store a new visit into the database with an expiry date
$article->addVisitThatExpiresAt(Carbon::now()->addHours(3));

are just to show visits numbers or what exactly? (i mean frontend such as this post visited 1.000 times like that?)

Thanks.

Sorry can't delete this issue. Please mark it closed

  • Laravel Page View Counter Version: 1.0
  • Laravel Version: 5.4
  • PHP Version: 7.1
  • Database Driver & Version: 10.1.22-MariaDB

I am unable to install the package. I get this error when I run php artisan migrate after doing the first two steps:
In Macroable.php line 74:
Method varchar does not exist.

Steps To Reproduce:

I can not install it

I'm following the tutorial.

But in cmd I get the following answer:

Nothing to publish for tag [migrations].

C:\Users\projectl>composer require cyrildewit/laravel-page-view-counter
Using version ^1.0 for cyrildewit/laravel-page-view-counter
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing cyrildewit/laravel-page-view-counter (v1.0.5): Downloading (100%)
Writing lock file
Generating autoload files
> Illuminate\Foundation\ComposerScripts::postUpdate
> php artisan optimize
Generating optimized class loader
The compiled class file has been removed.

C:\Users\projectl>php artisan vendor:publish --provider="CyrildeWit\PageViewCounter\PageViewCounterServiceProvider" --tag="migrations"
Nothing to publish for tag [migrations].

In config/app I already inserted the provider

CyrildeWit\PageViewCounter\PageViewCounterServiceProvider::class,

[2.1] Add ability to add a delay between views from the same session

Summmay of Feature

The first version (1.0) of this package provided a method called addPageViewThatExpiresAt. It accepted an expiry date as first argument. This method checked if the model was stored in the session with an expiry date. If not, the view would be stored in the database.

This feature was removed when I created the second version (2.0), but since it was useful to some users I decided to bring an improved version of it back.

Detailed Description

I've got some ideas in my mind, but I'm interested in some feedback!

// 1. (old way)
// @param  DateTime  $expiryDateTime
$post->addViewThatExpiresAt(Carbon::now()->addHours(3)); // 3 hours after now

// 2.
// @param  int  $minutes
$post->addViewWithDelay(3 * 60); // 3 hours after now

// 3. (same as 2 but with two different behaviours)
// @param  DateTime|int  $delay

// 3.1
$post->addViewWithDelay(2 * 60); // 2 hours after now

// 3.2
$post->addViewWithDelay(Carbon::parse('2018-06-10 12:00:00')); // expires at 2018-06-10 12:00:00

Delay is probably not the right word for this behaviour.


Fixes #69

not work if use Laravel Scout MySQL Driver

I get the error as below when I combine it with Laravel Scout MySQL Driver
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'page_visits' in 'where clause' (SQL: select count(*) as aggregate from artikels where (idLIKE %a% ORjenisLIKE %a% ORjudulLIKE %a% ORslugLIKE %a% ORthumbnailLIKE %a% ORdeskripsiLIKE %a% ORcategories_idLIKE %a% ORcontentLIKE %a% ORsourceLIKE %a% ORstatusLIKE %a% ORusers_idLIKE %a% ORlockedLIKE %a% ORcreated_atLIKE %a% ORupdated_atLIKE %a% ORpage_visitsLIKE %a% ORpage_visits_24hLIKE %a% ORpage_visits_7dLIKE %a% ORpage_visits_14dLIKE %a% ORpage_visits_formattedLIKE %a% ORpage_visits_24h_formattedLIKE %a% ORpage_visits_7d_formattedLIKE %a% ORpage_visits_14d_formatted LIKE %a%))

Is anyone using this at scale?

I'm about to add this package to my project, but wondered how this would scale with large numbers of views? Do queries become very slow when thousands of records have been saved to the database?

Anyone have experience of using this with MySQL?

[Feature Request] total number of pageviews for a model

Is it possible that you can add a method like Article::getTotalViews(), that adds the views of all the articles together.
for example, i have a site that has a section with news-items, blog-posts, scheduled-events, it would be nice to see wich section of my site attracts the most pageviews.
I know i can het the total myself by looping through all the articles, but it would be nice to have a built-in method for it.

sortBy with paginate

  • Laravel Page View Counter Version: ^1.0
  • Laravel Version: 5.6.*
  • PHP Version:7.1.9
  • Database Driver & Version:mysql

Description:

It seems that sortBy not working properly with paginate

when trying this

        $scholarships  = Scholarship::with(
            'city.country'
        )->paginate(12)->sortBy('page_views');     

it throwing method not exist exception

ErrorException Method Illuminate\Database\Eloquent\Collection::links does not exist. 

[Feature Request] Putting the attributes into seperated traits

The idea is:
The main trait: HasPageViewCounter will contain all the main functionality. Things like relationships and core functions to retrieve the page view count will be placed here.

If the developer wants to use the available attributes, he or she can add the needed traits to his model.
This avoids unnecessary attributes in a model.

unable to use page_views attribute

  • Laravel Page View Counter Version: 1.0
  • Laravel Version: 5.6
  • PHP Version: 7.2.2
  • Database Driver & Version: mysql

Description:

Hi, first thank you for sharing this awesome package!

I tried to sort my model (Activity) by page_views by getting this error:

Symfony \ Component \ Debug \ Exception \ FatalThrowableError (E_ERROR)
Call to a member function addPageViewThatExpiresAt() on null

Steps To Reproduce:

    public function sortByPageView()
    {
        // $acts = Activity::all()->sortByDesc(function ($act){
        //     return $act->getPageViews();
        // });
        $acts = Activity::all()->sortBy('page_views');

        return view('activities.index')->with('activities', $acts);
    }

And the addPageViewThatExpiresAt() is called at

    public function show($id)
    {
        $act = Activity::find($id);
        $act->addPageViewThatExpiresAt(Carbon::now()->addHours(2));
        return view('activities.show')->with('act', $act);
    }

edit: The show function works without any problem, but I cannot sort by the page_views.

And I have did at Activity

    use HasPageViewCounter;

    protected $appends = ['page_views'];

    public function getPageViewsAttribute()
    {
        return $this->getPageViews();
    }

Any helps? Thank you!

[2.x] Add ability to configure custom cache lifetimes of different views counts

Summary of Feature

Make it available to easily configure custom cache lifetimes for various views counts.

Detailed Description

Currently, every views count will be cached for a fixed number of minutes that's defined in the eloquent-viewable config file.

Simplified version:

'cache' => [
    'cache_views_count' => [
        'lifetime_in_minutes' => 60, // this line
    ],
],

It would be great to be able to define custom cache lifetimes for various views counts. This is very useful when you want to cache for example the 'total number of views' for one day instead of one hour.

Solution(s)

Creating a method in your viewable model

Define the different cache lifetimes in a viewable model class within a public method, so the ViewableService can read it and use it when needed.

Example: Post model

public function registerViewsCountsCacheLifetimes($registrar)
{
    $registrar->add(Period::pastDays(5), false, 30) // past5days, normal, 30 minutes

    $registrar->add(Period::upto(Carbon::now()), true, 5 * 60) // 2018-04-12 14:58:55 , unique, 5 days
}

Configuring it inside the config file

Works exactly like the above one, but then you configure it inside your config file.

'views_counts_cache_lifetimes' => [

    Post::class => [
        [
            'period' => Period::pastDays(5),
            'lifetime' => 30,
        ],
        [
            'period' => Period::upto(Carbon::now()),
            'unique' => true,
            'lifetime' => 5 * 60,
        ],
    ],

],

Accept second/third optional argument in ->getViews() and ->getUniqueViews() method

Easily change the lifetime in minutes through an optional parameter in ->getViews() and ->getUniqueViews().

$post->getViews(null, 5 * 60); // 5 days
$post->getViews(Period::pastDays(5), 3 * 60); // 3 days
$post->getUniqueViews(Period::since(Carbon::create(2014)), 20 * 60); // 20 days

I don't think this is a clean solution, because ->getViews(null, 5 * 60) requires null as first argument to retrieve the total number of views. But could be a possibility.

addViewWithExpiryDate() does not exists (anymore)

  • Eloquent Viewable: 2.1
  • Laravel Version: 5.6
  • PHP Version: 7.2
  • Database Driver & Version: MySQL 8

Description:

Has been removed? Only addView() seems to be available.

Steps To Reproduce:

Add steps of README

Expected behavior:

Is addView() doing the same and is addViewWithExpiryDate() not needed anymore?

Actual behavior:

Doesn't exists.

Typehint errors

  • Laravel Page View Counter Version: 2.0
  • Laravel Version: 5.5.dev
  • PHP Version: 7.2.5
  • Database Driver & Version: MariaDB 10.2

Description:

Im not sure why but changing my cache to redis seems to be cause some typehinting errors:

Symfony\Component\Debug\Exception\FatalThrowableError: Type error: Return value of Tera\Comic\Models\Comic::getViews() must be of the type integer, string returned in /home/caligula/public_html/vendor/cyrildewit/laravel-page-view-counter/src/Viewable.php:56
public function getViews($period = null): int

to

public function getViews($period = null)

when i remove the typehint from "all" the functions then everything works fine otherwise i just use memcached again. It also works if i disable caching in the viewable config

Steps To Reproduce:

Change cache to redis

Help with a function regarding ordering

i have this function:

    public function onTopComics()
    {
        //Get the comic with order by time
        $comics = Comic::with(['volumes' => function($query) { $query->has('cover'); }])->has('chapters')->orderByViewsCount()->get();
        //ajax time post
        $time = post('time');
        //sort by views
        $sortedComics = $comics->sortByDesc(function ($comic) use ($time) {
            switch($time) {      
                case '12hour':
                    return $comic->getViews(Period::subHours(12));
                break;                         
                case 'day':
                    return $comic->getViews(Period::pastDays(1));
                break;
                case 'week':
                    return $comic->getViews(Period::pastWeeks(1));
                break;
                case 'month':
                    return $comic->getViews(Period::pastMonths(1));
                break;
            }
        });

        return ['#rank-data' => $this->renderPartial('sidebar/top-comics', ['topComics' => $sortedComics, 'time' => $time])];  
    }

would this be the proper way to order the comics by views, I have tried this however im getting some mismatched ordering. Im not sure if its the ordering that's incorrect or its the view count retrieval that incorrect. (im using twig and made a function to use the period facade) so getting the views would looks something like this:

{{ comic.getViews(period.subHours(12)) }}

but when i check its incorrect, here is a screenshot of the ordering where its showing me an incorrect ordering:

Method Illuminate\Database\Query\Builder::addViewWithExpiryDate does not exist.

  • Eloquent Viewable: 2.1.0
  • Laravel Version: 5.6.*
  • PHP Version: 7.1.3
  • Database Driver & Version:

Description:

I followed the instructions provided in the README file. However, when i want to add view with expiry date, laravel throws BadMethodCallException

Steps To Reproduce:

  1. Run
    composer require cyrildewit/eloquent-viewable

  2. Append below to providers array
    CyrildeWit\EloquentViewable\EloquentViewableServiceProvider::class,

  3. Publish Migrations:

php artisan vendor:publish --provider="CyrildeWit\EloquentViewable\EloquentViewableServiceProvider" --tag="migrations"
  1. Run migration:
    php artisan migrate

  2. Publish config file.

php artisan vendor:publish --provider="CyrildeWit\EloquentViewable\EloquentViewableServiceProvider" --tag="config"
  1. Prepare model:
...
use CyrildeWit\EloquentViewable\Viewable;

class Project extends Model implements HasMedia, LikeableContract
{
    use Sluggable;
    use Taggable;
    use HasMediaTrait;
    use Likeable;
    use Commentable;
    use Viewable;
    ...
}
  1. Save Views in show method of the controller:
public function show($project_slug){
        if ($project_slug === 'rp'){
            $project = Project::inRandomOrder()->first();
        }else{
            $project = Project::where('slug', '=', $project_slug)->first();
        }

        $project->addViewWithExpiryDate(Carbon::now()->addHours(2));

        return view('frontend.projects.show')
            ->with('page_title', 'Projeler - ' . $project->title)
            ->with('project', $project);
}

Expected behavior:

Expecting increment on the first page view and page view should not increase in case of refresh for 2 hours on the same session.

Actual behavior:

BadMethodCallException is thrown for addViewWithExpiryDate method. By the way addView() method works as expected.

BadMethodCallException
Method Illuminate\Database\Query\Builder::addViewWithExpiryDate does not exist.

Any plans to allow order models by visit count?

Great package, easy to integrate. I'm wondering if you have any plans to allow ordering model items by visit count?

being able to order by total visits as well as 24h, 7d and 14d would be great.

visitable_id issue

  • Laravel Page View Counter Version: ^1.0
  • Laravel Version: 5.5
  • PHP Version: 7.0.0
  • Database Driver & Version:

Description:

I dont know What visitable_id is
Integrity constraint violation: 1048 Column 'visitable_id' cannot be null (SQL: insert into page-views (visitable_id, visitable_type, ip_address, updated_at, created_at) values (, App\Doctor, 139.190.64.77, 2018-01-11 09:01:32, 2018-01-11 09:01:32))
see here in the query above, it is taking visitable_id as empty value
please tell me what the issue is

Steps To Reproduce:

Does this package not use cookies?

I know it does use cookies but every time i refresh the post page and go back, it is added as a view to the db. The expected behavior is that the user should only be able to view one single post once. Unless the cookie is deleted and browser is refreshed. In that case, a new view will be added. Or am I doing something wrong here?

How can I count multiple views setup

First off, love the package and what you’ve done with it so far.
The issue for me is that I have 2 variables which handles 2 kinds of views. One of the route is as follows:

Route:get(‘post/{category}/{id}’, ‘SomeController@Display’);

And here is the second route:

Route:get(‘post/{category}/{subcategory}/{id}’, ‘SomeOth rController@Display’)

As you can see, I have one controller and route which displays post with category. The other route and controller is for sub category. They both come from the same model. They also have different where clauses.

So my question is, in this case, should I use the same method as in the docs? I mean, should I use the $post->addView(); on both the controller display method?

And if so, how can I get the count of these two separately? One is with category. The other one with sub category.
Lastly, how can I order these two by whats the most popular posts. The issue for me is that I am using a category and subcategory separately. So if I want to even count the views, I would want to count it for category and subcategory. Those two are having 2 routes and two methods. That’s my confusion.

Bug with connection

  • Eloquent Viewable: 2.0.x
  • Laravel Version: 5.5.*@dev
  • PHP Version: 7.2.5
  • Database Driver & Version: MySQL 5.5.59

Description:

when I put in a remote connection for mysql, the queuing works and doesn't work at the same time. What I mean is that the connection is made, and the view gets sent to the remote database after the delay, so everything works there. It's just that after it gets sent Horizon shows

InvalidArgumentException: Database [mysql_offload] not configured.

but that connection "is" configured and everything works.

[Suggestion] Increment Visit Rather than creating a new entry

Rather than flooding the database with new visit entries why not create a new column that gets incremented, every time someone with the same IP visits the site, the total pageviews would just count sum of all the increments all ips have for that model and the unique views would count just for that row for each visited IP. This way you wont end up with a completely flooded database filled with records.

[Feature Request] Tracking unique page visitors

This package contains at this moment functionality for storing the page visits of an Eloquent model item. It also provides a special method for giving a page visit an expiry date. So the page views that the user has made before that time range will not be stored into the database.

But this allows users to make multiple page views of an Eloquent model. This means that they are not unique.

Concept

I'm thinking about tracking down the IP address of the visitors. So by using a simple filter, we're able to filter all the unique page views.
To give you access to this data, I'm maybe going to add new attributes. But I'm a little bit worried about the amount of attributes that this package adds to your Eloquent models. So to solve this problem, I got an idea.

Instead of one trait with all functionalities, we can split them up into multiple traits. This will give you the flexibility of choosing which features you need.

REST API

  • Laravel Page View Counter Version: 1.0.3
  • Laravel Version: 5.5
  • PHP Version: 7.1
  • Database Driver & Version:

Description:

I need to know how can I use this package for API as well.
Because there are web services in my project and they are also showing total views for each item and I have to store views from that service as well.
can I use this package for storing and retrieving page views as well for JSON response?

Error after removing package

i removed your package, but i need to keep view.php in config, otherwise i got this error 👍
Type error: Argument 2 passed to Illuminate\View\FileViewFinder::__construct() must be of the type array, null given, called in .../vendor/laravel/framework/src/Illuminate/View/ViewServiceProvider.php on line 64.

How can i delete this file safely?

create new article throwing up category_id doesn't have a default value

It is not taking lots of other fields like title,body and others. It is working fine if i comment out the below
use HasPageVisitsCounter;

error:

SQLSTATE[HY000]: General error: 1364 Field 'category_id' doesn't have a default value (SQL: insert into articles (updated_at, created_at) values (2017-08-16 12:05:23, 2017-08-16 12:05:23))

controller:

public function store(Request $request)
   {
    $input = $request->all();
    $input['slug'] = str_slug($request->input('title'), '-');
    $article = \App\Article::create($input);
    return redirect('master/articles');
   }

Article Model:

 use HasPageVisitsCounter;
 protected $fillable = [
    'category_id','author','title','body', 'slug','excerpt'
];
public function category() {
    return $this->belongsTo('App\Category');
}

Do not know where the issue is.

[Proposel] Improving the speed and core functionality [v2]

Overview

This issue is reserved for optimizing the speed of retrieving and storing page views.

I think that caching everything could improve the speed very much. This would be a nice addition, but the next thing to discuss is how should the page views be stored. Using a simple counter or storing every page view as a record (it currently works like this). But instead of counting all these page views on every request, the total page views could be stored in dedicated attribute on the model (or different table) as a JSON object.

Draft ideas

  • Caching all the page views and use them to count (can by quite slow if there are 2 million views)
  • Caching the page view count only
  • Storing the page view count for various periods (configured in the config file or per model) in a table collumn that will be updated every with an artisan command.

Draft one [branch: v2-testing]

There are two tables needed:

  1. page-views This table is only needed for users who wants to keep track of the history
  2. page-views-counter

The page views table consists of every maked page view.
The page views counter table contains the same data as the main page-views table, but is counted every day, week etc.

If page views are requested for a time range that's not available in the counters table, it will be counted and stored. This happens only the first time (unless these values are resetted or recounted).

Caching the values in the page-views-counter table could improve the speed for a few milliseconds.

Ideas are very welcome!

Counter is no longer working?

Counter is no longer working? i have had no problems until the past couple of days when the view count wont exceed 1 ?

visitable_id issue

  • Laravel Page View Counter Version: #.#.#
  • Laravel Version: #.#.#
  • PHP Version:
  • Database Driver & Version:

Description:

I dont know What visitable_id is
Integrity constraint violation: 1048 Column 'visitable_id' cannot be null (SQL: insert into page-views (visitable_id, visitable_type, ip_address, updated_at, created_at) values (, App\Doctor, 139.190.64.77, 2018-01-11 09:01:32, 2018-01-11 09:01:32))
see here in the query above, it is taking visitable_id as empty value
please tell me what the issue is

Steps To Reproduce:

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.