simonhamp / laravel-nova-csv-import

The best CSV import component for Laravel Nova
https://novapackages.com/packages/simonhamp/laravel-nova-csv-import
MIT License
168 stars 76 forks source link

404 in dev, not in production #69

Closed beiaduo closed 6 months ago

beiaduo commented 1 year ago

1 composer require simonhamp/laravel-nova-csv-import --with-all-dependencies

2 new LaravelNovaCsvImport,

beiaduo commented 1 year ago

Do you have any documents ?Thank you!

mberatsanli commented 1 year ago

just adjust the $canImportResource in nova resources

public static $canImportResource = false;
simonhamp commented 1 year ago

@BeiAduo have you customised your Nova menu by any chance? If so, you will need to manually add the tool link to your menu

beiaduo commented 1 year ago

Just default ,I can see import menu, when I click It ,it show me 404,but when I uploaded to server ,it is working, I don't know why,Just local 404, I will clear cache to retry, Thank you

simonhamp commented 1 year ago

@BeiAduo that's strange. There's nothing in the package that differentiates between local and production (iirc) so I suggest there's something peculiar happening in your local environment

Hope you can get to the bottom of it. Please share what you find here

mihaileu commented 1 year ago

same as @BeiAduo

simonhamp commented 1 year ago

@mihaileu You mean it works fine on your production server but not on your local dev environment?

kovpynets commented 1 year ago

I acknowledge the error. The situation is similar

Brissan commented 1 year ago

Change in ImportController.php getAvailableFieldsForImport function

protected function getAvailableFieldsForImport(string $resource, NovaRequest $request): array  
{  
    try {
        $novaResource = new $resource(new $resource::$model);  
        $fieldsCollection = collect($novaResource->creationFields($request));  

        if (method_exists($novaResource, 'excludeAttributesFromImport')) {  
            $fieldsCollection = $fieldsCollection->filter(function(Field $field) use ($novaResource, $request) {  
                return !in_array($field->attribute, $novaResource::excludeAttributesFromImport($request));  
            });  
        }  

        $fields = $fieldsCollection->map(function (Field $field) use ($novaResource, $request) {  
            return [  
                'name' => $field->name,  
                'attribute' => $field->attribute,  
                'rules' => $this->extractValidationRules($novaResource, $request)->get($field->attribute),  
            ];  
        });  
    } catch (\Throwable $th) {  
        return [];  
    }  

    // Note: ->values() is used here to avoid this array being turned into an object due to   
    // non-sequential keys (which might happen due to the filtering above.  
    return [  
        $novaResource->uriKey() => $fields->values(),  
    ];  
}  
beshoo commented 1 year ago

Same here. once I import a tiny CSV file, I get redirected to a 404 page.. the URL I have been redirected to:

http://127.0.0.1:8000/dashboard/csv-import/configure/43937a4ef0d4743f485d25156ff5eb0d.csv

Brissan commented 1 year ago

То же самое здесь. как только я импортирую крошечный CSV-файл, я перенаправляюсь на страницу 404 .. URL, на который я был перенаправлен:

http://127.0.0.1:8000/dashboard/csv-import/configure/43937a4ef0d4743f485d25156ff5eb0d.csv

ChatGPT just told me how to fix the error, threw off the option above, it worked for me immediately as soon as I changed the path where I changed \vendor\simonhamp\laravel-nova-csv-import\src\Http\Controllers

image

beshoo commented 1 year ago

Show me the full php resource code please...

On Mon, Jul 3, 2023, 5:45 PM Brissan @.***> wrote:

То же самое здесь. как только я импортирую крошечный CSV-файл, я перенаправляюсь на страницу 404 .. URL, на который я был перенаправлен:

http://127.0.0.1:8000/dashboard/csv-import/configure/43937a4ef0d4743f485d25156ff5eb0d.csv http://127.0.0.1:8000/dashboard/csv-import/configure/43937a4ef0d4743f485d25156ff5eb0d.csv

ChatGPT just told me how to fix the error, threw off the option above, it worked for me immediately as soon as I changed the path where I changed \vendor\simonhamp\laravel-nova-csv-import\src\Http\Controllers

— Reply to this email directly, view it on GitHub https://github.com/simonhamp/laravel-nova-csv-import/issues/69#issuecomment-1618501286, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABDLT27AZZBQIU3HEKQWGFLXOLLKBANCNFSM6AAAAAAZIWMO3M . You are receiving this because you commented.Message ID: @.***>

Brissan commented 1 year ago
<?php

namespace SimonHamp\LaravelNovaCsvImport\Http\Controllers;

use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Collection;
use Inertia\Response;
use Laravel\Nova\Actions\ActionResource;
use Laravel\Nova\Fields\Field;
use Laravel\Nova\Http\Requests\NovaRequest;
use Laravel\Nova\Nova;
use Laravel\Nova\Resource;
use Laravel\Nova\Rules\Relatable;
use Maatwebsite\Excel\Concerns\ToModel as ModelImporter;

class ImportController
{
    protected $importer;

    protected $filesystem;

    public function __construct(ModelImporter $importer, Filesystem $filesystem)
    {
        $this->importer = $importer;

        $this->filesystem = $filesystem;
    }

    public function configure(NovaRequest $request, string $file): Response
    {
        $file_name = pathinfo($file, PATHINFO_FILENAME);

        $import = $this->importer
            ->toCollection($this->getFilePath($file), $this->getDisk())
            ->first();

        $headings = $import->first()->keys();

        $total_rows = $import->count();

        $config = $this->getConfigForFile($file);

        $rows = $import->take(10)->all();

        $resources = $this->getAvailableResourcesForImport($request);

        $fields = $resources->mapWithKeys(function ($resource) use ($request) {
            return $this->getAvailableFieldsForImport($resource, $request);
        });

        $resources = $resources->mapWithKeys(function ($resource) {
            return [
                $resource::uriKey() => $resource::label(),
            ];
        });

        $mods = $this->importer->getAvailableModifiers();

        return inertia(
            'CsvImport/Configure',
            compact('file', 'file_name', 'resources', 'fields', 'rows', 'total_rows', 'headings', 'config', 'mods')
        );
    }

    /**
     * @throws \Symfony\Component\HttpKernel\Exception\HttpException
     * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
     */
    public function storeConfig(NovaRequest $request)
    {
        $file = $request->input('file');

        // TODO: Add some validation
        $config = json_encode(
            array_merge(
                $this->getConfigForFile($file),
                [
                    'resource' => $request->input('resource'),
                    'mappings' => $request->input('mappings'),
                    'values' => $request->input('values'),
                    'modifiers' => collect($request->input('modifiers'))
                        ->map(function ($modifiers) {
                            return collect($modifiers)
                                ->reject(function ($modifier) {
                                    return empty($modifier['name']);
                                });
                        }),
                ],
            ),
            JSON_PRETTY_PRINT
        );

        $path = $this->getConfigFilePath($file);

        $this->filesystem->delete($path);

        if (! $this->filesystem->put($path, $config)) {
            return abort(500);
        }
    }

    public function preview(NovaRequest $request, string $file): Response
    {
        $config = $this->getConfigForFile($file);

        $resource = $config['resource'];

        $import = $this->importer
            ->setAttributeMap($columns = $config['mappings'])
            ->setCustomValues($config['values'])
            ->setMeta($config['meta'])
            ->setModifiers($config['modifiers'])
            ->toCollection($this->getFilePath($file), $this->getDisk())
            ->first();

        $total_rows = $import->count();

        $mapped_columns = array_values(array_filter($columns));

        $rows = $import->take(100)->all();

        return inertia(
            'CsvImport/Preview',
            compact('config', 'rows', 'total_rows', 'columns', 'mapped_columns', 'resource', 'file')
        );
    }

    public function import(NovaRequest $request)
    {
        $file = $request->input('file');

        $path = $this->getFilePath($file);

        if (! $config = $this->getConfigForFile($file)) {
            return redirect()->route('csv-import.configure', ['file' => $file]);
        }

        $resource_name = $config['resource'];

        $resource = Nova::resourceInstanceForKey($resource_name);
        $rules = $this->extractValidationRules($resource, $request)->toArray();
        $model_class = $resource->resource::class;

        $import = $this->importer
            ->toCollection($path, $this->getDisk())
            ->first();

        $total_rows = $import->count();

        $this->importer
            ->setResource($resource)
            ->setAttributeMap($config['mappings'])
            ->setRules($rules)
            ->setModelClass($model_class)
            ->setMeta($config['meta'])
            ->setCustomValues($config['values'])
            ->setModifiers($config['modifiers'])
            ->import($path, $this->getDisk());

        $failures = $this->importer->failures();
        $errors = $this->importer->errors();

        $results = $this->getResultsFilePath($file);

        $this->filesystem->delete($results);

        $this->filesystem->put($results, json_encode([
            'total_rows' => $total_rows,
            'imported' => $total_rows - $failures->count() - $errors->count(),
            'failures' => $failures,
            'errors' => $errors,
        ], JSON_PRETTY_PRINT));

        return response()->json(['review' => "/csv-import/review/{$file}"]);
    }

    public function review(NovaRequest $request, string $file): Response
    {
        if (! $results = $this->getLastResultsForFile($file)) {
            return redirect()->route('csv-import.preview', ['file' => $file]);
        }

        $imported = $results['imported'];
        $total_rows = $results['total_rows'];
        $failures = collect($results['failures'])->groupBy('row');
        $errors = collect($results['errors'])->groupBy('row');

        $config = $this->getConfigForFile($file);

        return inertia(
            'CsvImport/Review',
            compact('file', 'failures', 'errors', 'total_rows', 'config', 'imported')
        );
    }

    protected function getAvailableFieldsForImport(string $resource, NovaRequest $request): array
    {
        try {
            $novaResource = new $resource(new $resource::$model);
            $fieldsCollection = collect($novaResource->creationFields($request));

            if (method_exists($novaResource, 'excludeAttributesFromImport')) {
                $fieldsCollection = $fieldsCollection->filter(function(Field $field) use ($novaResource, $request) {
                    return !in_array($field->attribute, $novaResource::excludeAttributesFromImport($request));
                });
            }

            $fields = $fieldsCollection->map(function (Field $field) use ($novaResource, $request) {
                return [
                    'name' => $field->name,
                    'attribute' => $field->attribute,
                    'rules' => $this->extractValidationRules($novaResource, $request)->get($field->attribute),
                ];
            });
        } catch (\Throwable $th) {
            return [];
        }

        // Note: ->values() is used here to avoid this array being turned into an object due to
        // non-sequential keys (which might happen due to the filtering above.
        return [
            $novaResource->uriKey() => $fields->values(),
        ];
    }

    protected function getAvailableResourcesForImport(NovaRequest $request): Collection
    {
        $novaResources = collect(Nova::authorizedResources($request));

        return $novaResources->filter(function ($resource) use ($request) {
            if ($resource === ActionResource::class) {
                return false;
            }

            if (! isset($resource::$model)) {
                return false;
            }

            $resourceReflection = (new \ReflectionClass((string) $resource));

            if ($resourceReflection->hasMethod('canImportResource')) {
                return $resource::canImportResource($request);
            }

            $static_vars = $resourceReflection->getStaticProperties();

            if (! isset($static_vars['canImportResource'])) {
                return true;
            }

            return isset($static_vars['canImportResource']) && $static_vars['canImportResource'];
        });
    }

    protected function extractValidationRules(Resource $resource, NovaRequest $request): Collection
    {
        return collect($resource::rulesForCreation($request))->mapWithKeys(function ($rule, $key) {
            foreach ($rule as $i => $r) {
                if (! is_object($r)) {
                    continue;
                }

                // Make sure relation checks start out with a clean query
                if (is_a($r, Relatable::class)) {
                    $rule[$i] = function () use ($r) {
                        $r->query = $r->query->newQuery();

                        return $r;
                    };
                }
            }

            return [$key => $rule];
        });
    }

    protected function getConfigForFile(string $file): array
    {
        $config = $this->getDataFromJsonFile($this->getConfigFilePath($file));

        $config['values'] = $config['values'] ?? [];
        $config['modifiers'] = $config['modifiers'] ?? new \stdClass;

        $original_filename = $config['original_filename'] ?? '';

        $config['meta'] = [
            'file' => $file,
            'file_name' => pathinfo($file, PATHINFO_FILENAME),
            'original_file' => $original_filename,
            'original_file_name' => pathinfo($original_filename, PATHINFO_FILENAME),
        ];

        return $config;
    }

    protected function getLastResultsForFile(string $file): array
    {
        return $this->getDataFromJsonFile($this->getResultsFilePath($file));
    }

    protected function getFilePath(string $file): string
    {
        return "csv-import/{$file}";
    }

    protected function getConfigFilePath(string $file): string
    {
        return $this->getFilePath("{$file}.config.json");
    }

    protected function getResultsFilePath(string $file): string
    {
        return $this->getFilePath("{$file}.results.json");
    }

    protected function getDataFromJsonFile(string $file): array
    {
        if ($this->filesystem->exists($file)) {
            return @json_decode($this->filesystem->get($file), true) ?? [];
        }

        return [];
    }

    protected function getDisk(): ?string
    {
        return config('csv-import.disk');
    }
}
beshoo commented 1 year ago

То же самое здесь. как только я импортирую крошечный CSV-файл, я перенаправляюсь на страницу 404 .. URL, на который я был перенаправлен: http://127.0.0.1:8000/dashboard/csv-import/configure/43937a4ef0d4743f485d25156ff5eb0d.csv

ChatGPT just told me how to fix the error, threw off the option above, it worked for me immediately as soon as I changed the path where I changed \vendor\simonhamp\laravel-nova-csv-import\src\Http\Controllers

image

I dont understand you , your file is exactly as my file. where is the path you are talking about :\vendor\simonhamp\laravel-nova-csv-import\src\Http\Controllers

What the file you cahnged@

simonhamp commented 1 year ago

@Brissan @beshoo I'm not seeing anything in your replies that are going to help me debug your issue. So I'm not sure whether you're experiencing what @BeiAduo originally raised...

It seems you may be better off checking out and adding to #44 instead

beiaduo commented 1 year ago

Sorry sir, I have been working in other places recently and have not taken time to check. I will definitely check when I get home, and then report back all the information. Thank you for your reply